CTF này được tổ chức bởi team mà mình đang tham gia (idek), nhưng khi mình vô thì ctf này đã hoàn thiện về các challenge nên mình không đóng góp bất kì challenge nào trong ctf này. Vì vậy mình thử sức chơi một số bài web do teammate mình tạo ra. Những thử thách sau đây cũng khá hay và có nhiều thứ để học. Trong ctf này mình đã không giải 4 challenge client side, mình yếu cái này và do cũng mấy nay cũng bận nên không có thơi gian nghiên cứu. Mình có để tất cả source của all challenge web ở phía dưới nên mọi người có thể download về và test nhé.
Tất cả source code mình để ở đây nhé SOURCE
Bài này được cung cấp source php khi chúng ta truy cập vô challenge
Phân tích source 1 chút xíu nhé:
lib.php
-> chúng ta không biết nó chứa gìextract
để lấy tất cả các tham số mà chúng ta đưa vào bằng GET method. (vuln đầu tiên của bài này cũng ở đây). Giải thích về hàm này thì khi chúng ta truyền các tham số thì sẽ ghi đè được các biến internal.idek
và cookie idek
bằng nhau thì sẽ echo ra chuỗi I love c0000000000000000000000000000000000000kie
.flag
. Nhưng ở đây chỉ sử dụng dấu ==
(loose compare) => vuln thứ 2 của bài này.Dựa vào 2 vuln mình đã nói ở trên thì bây giờ mình chỉ cần viết payload:
extract
truyền vô 2 tham số _COOKIE[idek]
và _SESSION[idek]
khác giá trị nhau thì sẽ nhảy vô được nhánh else
.==
để truyền 2 giá trị khác nhau nhưng kết quả trả về là true
=> có flag
Result:
Bài này vẫn là 1 bài php và được cung cấp source khi truy cập challenge.
Phân tích source:
lib.php
và chúng ta không biết file này chứa gì.check
sẽ được gán bằng giá trị nằm trong array $_SERVER['QUERY_STRING']
(các tham số mình nhập vô GET).best-team
nằm trong biến check
thì sẽ echo ra chuỗi Who is the best team?
.$_GET['best-team']
bằng với idek_is_the_best
thì sẽ in ra flag
(nhưng lừa đấy).Ở đây chúng ta chỉ cần truyền 2 tham số best-team
best-team
thứ nhất với giá trị bất kì, được gán vô check và kiểm tra preg_match
.best-team
thứ hai là của $_GET['best-team']
và chúng tra truyền giá trị idek_is_the_best
=> echo ra flag.Sau khi mình send với request như này thì sẽ nhận được
Vậy tiếp tục ta sẽ access vô /secure-bypass.php
=> get được 1 source mới
Phân tich source:
url
truyền vào bằng GET
method, và độ dài phải ít nhất là 15.redirect
tới cái url mình truyền vô, sẽ echo flag
sau khi redirect đó. Vậy ở đây nếu như echo flag nằm trên header
fuction thì mình có thể dùng burp để bắt lại và có flag.CLRF
hoặc nullbyte
thì có thể bypass đoạn này.Result:
Bày này thì như tên bài thì có một chút guessy. Chúng ta không được cung cấp source, chỉ có mỗi url. Khi truy cập vào thì cũng được cảnh báo rằng không được scan dir
nếu không sẽ bị block IP.
Mình thử truy cập /robots.txt
(những bài guessy mình sẽ thử cái này đầu tiên ) thì nhận được
Có vẻ liên quan đến replit.com
. Tới đây mình nhớ về một bài mình đã đọc qua ở replit
(mình sài cái này khá nhiểu :D). Nội dung bài viết mình để ở đây Tip_Replit
Nếu như thêm __repl
thì nó sẽ redirect tới source repl của url này. Mình đã thử và get được flag thành công.
Result:
Bài này, chúng ta được chung cấp 1 source được viết bằng node js
, có cung cấp docker nên mọi người có thể deploy để debug cho hiểu rõ về code hoạt động như nào hơn nhé. Sau đây, mình chỉ phân tích một đoạn code để dẫn đến việc có thể khai thác ở bài này.
Phân tích source:
/flag
và /diff
./flag
: Check địa chỉ của mình truy cập bằng với ::1
(localhost) thì sẽ in ra flag không thì sẽ trả về Forbidden
./diff
:
url1
và url2
. Kiểm tra phải là string.urls
. Sau đó đưa từng tham số url vào function validifyURL
.ssrfFilter
, hàm này mình không biết nó như nào, chỉ biết được require vô const ssrfFilter = require('ssrf-req-filter');
ở đầu file. Nhưng mình đoán sẽ ra chặn các request từ localhost.urls
vô hàm diffURLs
ssrfFilter
mà chỉ fetch
tới từng url
mà mình đưa vào. Sau đó dùng hàm diffLines
để check sự khác nhau của từng line.Khi mình nhìn vô challenge thì thấy có vẻ nghi nghi DNS Rebinding
. Và sau khi đọc code và phân tích thì mình sure bài này dạng DNS Rebinding
, tương tự như 1 bài mình đã ra đề cho ISITDTU CTF 2021 Quals
. Idea ở đây:
ssrfFilter
ở hàm validifyURL
còn diffURLs
thì sẽ không check, như mình đã phân tích ở trênphp
với content sẽ redirect
tới http://localhost:1337/flag
(1337 vì port đang run của challenge) để get flag. Mình sẽ random
giữa khoảng 0->1, nếu như 0 thì sẽ return về bình thường, còn 1 thì sẽ redirect
tới http://localhost:1337/flag
.validifyURL
check filter thì sẽ là ramdom = 0
=> return về bình thường không phải localhost. Tiếp xuống hàm diffLines
sẽ không check filter và khi fetch tới url
của mình đã host thì có thể khi đó random sẽ bằng 1
(chính là localhost
) => có thể đọc đc flag. Ở đây vì dựa vô độ "may mắn" nữa nên mọi người cần race nhé.File index.php
How to run:
php -S 0.0.0.0:1234
ngrok http localhost:1234
File payload.py
How to run:
python3 payload.py | grep 'idek'
Result:
Lí do mình gộp 2 challenge này thành 1 bài để viết vì mình sử dụng 1 payload để solves cả 2 challenge này. Source của 2 bài cũng giống nhau chỉ khác 1 chỗ.
Giống như các bài có source khác, việc đầu tiên là đi phân tích source để biết chương trình làm gì, như vậy thì sẽ exploit hơn.
Phân tích source:
q
đưa vào với POST method, không được đưa vô nhiều hơn 1 tham số.blacklist
có trong input chúng ta đưa vào hay không.false
thì sẽ render_template_string
ra template error_page
.false
thì vẫn sài render_template_string
(có thể SSTI) nhưng ở template error_page
lại không đưa bất kì input nào của chúng ta vào template này => impossible SSTI ở đây.render_template_string
ra template page
, ở trong template này có đưa input của chúng ta vào là query
(chính là q
). Vừa sử dụng render_template_string
vừa đưa input của chúng ta vô template => có thể SSTI ở đây.blacklist
() [] join | dict ~ cycler attr
..
= |attr()
" '
= dict()|join
Payload đầu tiên của mình thử như sau:
cat flag
hoặc ... flag
(… ở đây có nghĩa là 1 lệnh nào đó), nhưng điểm chung của mấy command này là đều cần có khoảng trắng, nhưng mình không nghĩ ra cách đoạn này. Nếu dùng các kí tự để bypass khoảng trắng thì server sẽ trả về 500.Payload thứ hai:
__builtins__
có attribute open
=> đọc thẳng flag
luôn.Result:
Bài jinjail
thì source vẫn tương tự nhưng chỉ check length của input của chúng ta nhập vào > 256 thì sẽ trả về false. Nhưng payload của mình ở trên < 256 => dùng 1 payload cho 2 bài.
Script:
How to run:
python3 payload.py | grep "idek"