Skip to content

Commit c288630

Browse files
committed
[2026-02-21]: Update notes
1 parent 54d3e14 commit c288630

File tree

1 file changed

+80
-138
lines changed

1 file changed

+80
-138
lines changed

content/posts/STH Mini Web CTF 2025.md

Lines changed: 80 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -4,181 +4,123 @@ tags:
44
- fruit
55
created: 2025-10-05
66
---
7-
![](https://cdn-images-1.medium.com/max/800/0*quSIvw2fwHBmbBJa)
8-
9-
Target : [https://web1.ctf.p7z.pw](https://web1.ctf.p7z.pw)
10-
11-
- ทำการโจมตีเว็บโจทย์การแข่งขัน เพื่อหาข้อความลับ ที่เรียกว่า Flag โดย Flag จะมีรูปแบบ เช่น STH1{cff940beed74db5e1c7c63007223a6e6}
12-
- เข้าสู่ระบบเป็นสิทธิ์ผู้ดูแลระบบ (Flag 1)
13-
- ทำการพิมพ์เงินออกจากระบบ (Flag 2)
7+
> [!abstract] **Target Information**
8+
>
9+
> - **Target URL:** [https://web1.ctf.p7z.pw](https://web1.ctf.p7z.pw/)
10+
>
11+
> - **Objective:** ค้นหา Flag ทั้งหมด 2 ตัว (Admin Access & Logic Bypass)
12+
>
13+
> - **Flag Format:** `STH1{...}`
14+
>
1415
1516
---
1617

17-
## มาเริ่มกันที่ Flag 1 : เข้าสู่ระบบเป็นสิทธิ์ผู้ดูแลระบบ
18-
19-
![](https://cdn-images-1.medium.com/max/800/1*MIA-GGdTygl9TsJOnUpJFg.png)
20-
21-
หลังจากเข้าเว็ปมาก็จะเห็นหน้า login ซึ่งเราไม่มี username, password แล้วเราจะ login ได้ยังไง ?
22-
23-
ขั้นตอนแรกเรามารวบรวมหาข้อมูลให้มากที่สุดก่อน (Information Gathering) เริ่มจากลอง inspect หน้าเว็ปดูเผื่อจะเจออะไร
24-
25-
![](https://cdn-images-1.medium.com/max/800/1*dqASwHKhA-0YkurFc0i-1w.png)
26-
27-
เมื่อลองดูใน Source code เราจะเห็น comment ของ credentials อยู่ คือ username และ password ลองเอา username และ password ที่ได้มา login ดู และ ติ๊ก Remember Me ด้วย
28-
29-
![](https://cdn-images-1.medium.com/max/800/1*qBCMf8ulCnAjzEl_uYdSWg.png)
30-
31-
หลังจาก login แล้ว เราจะเห็นว่ามีข้อมูลเกี่ยวกับการ login ของเราขึ้นมา คือ username “test”, Role “user”
32-
33-
![](https://cdn-images-1.medium.com/max/800/1*V9pCf388lfTMN3p6CzXecA.png)
34-
35-
แต่ใน Flag นี้เราจะต้อง login ด้วย Admin user แล้วเราจะสามารถ login ด้วย Admin ได้ยังไง ? ลอง inspect หน้าเว็บดูอีกรอบเผื่อจะมีอะไรซ่อนอยู่อีก
36-
37-
![](https://cdn-images-1.medium.com/max/800/1*tPAnEqOqZCeNaFMxgSg_BQ.png)
38-
39-
เมื่อลองส่องๆดูเราจะเห็นไฟล์ javascript “script.js” และเราเห็นว่าในไฟล์นี้มี function อยู่ 2 function คือ
40-
41-
**debugFetchUserTest()** - function นี้จะทำการ fetch data จาก **api.php?action=get_userinfo&user=test**
42-
43-
![](https://cdn-images-1.medium.com/max/800/1*XEHSmAeddFaafO57Y1d9Ng.png)
18+
## Flag 1: Privilege Escalation (เข้าสู่ระบบสิทธิ์ Admin)
4419

20+
### 1. Information Gathering & Login
4521

46-
ลองยิง api ไปที่ endpoint นี้ดู `https://web1.ctf.p7z.pw/api.php?action=get_userinfo&user=test`
22+
จากการสำรวจหน้าแรก เราพบช่องโหว่พื้นฐานจากการตรวจสอบ Source Code (Inspect Element)
4723

48-
ผลลัพธ์ที่ได้ :
24+
- **พบ Credentials หลุด:** มี Username/Password ของ User ทดสอบถูก Comment ไว้
25+
26+
- **Action:** ทำการ Login ด้วยสิทธิ์ `test` และติ๊ก **"Remember Me"** เพื่อสร้าง JWT Cookie
27+
28+
### 2. API Enumeration (การไล่หาข้อมูลผ่าน API)
4929

50-
![](https://cdn-images-1.medium.com/max/800/1*RjiFslSA3qieWE09l66fAQ.png)
30+
เมื่อตรวจสอบไฟล์ `script.js` พบฟังก์ชัน Debug ที่น่าสนใจ 2 ตัว:
5131

52-
แสดงว่า function นี้เป็น function ที่ทำหน้าที่ดึงข้อมูลของ user นั้นๆ ทีนี้ลองไปดูอีก function ว่ามันทำอะไรได้บ้าง ?
32+
1. `debugFetchUserTest()` → ดึงข้อมูล User `test`
33+
34+
2. `debugFetchAllUsers()` → ดึงรายชื่อ User ทั้งหมดในระบบ
35+
5336

54-
**debugFetchAllUsers()** - function นี้จะทำการ fetch data จาก **api.php?action=get_alluser**
37+
> [!bug] **Exploit Point**
38+
>
39+
> จากการเรียก `api.php?action=get_alluser` พบ User ชื่อ `admin-uat`
40+
>
41+
> เมื่อเจาะจงไปที่ `api.php?action=get_userinfo&user=admin-uat` เราได้รับ **remember_me_token** ของ Admin มา!
5542
56-
![](https://cdn-images-1.medium.com/max/800/1*ztm59x3xqb5ipCb0fzgnHA.png)
43+
### 3. JWT Secret Brute-forcing
5744

58-
ลองยิง api ไปที่ endpoint นี้ดู `https://web1.ctf.p7z.pw/api.php?action=get_alluser`
45+
เรามี Token แต่ไม่สามารถปลอมแปลงสิทธิ์ได้ถ้าไม่มี **Secret Key** เราจึงต้องใช้ `Hashcat` เพื่อถอดรหัสหา Key จาก JWT เดิมของเรา:
5946

60-
ผลลัพธ์ที่ได้ :
61-
62-
![](https://cdn-images-1.medium.com/max/800/1*2nzCK4WD1WhV1tRgjVCayQ.png)
63-
64-
แสดงว่า function นี้เป็น function ที่ทำหน้าที่ fetch user ทั้งหมดในระบบ
65-
66-
จากผลลัพธ์ของ function **debugFetchAllUsers()** เราจะเห็นว่า มี user อยู่อีก 1 user ซึ่งอาจจะเป็น admin user เราจะลองเอา username นี้ไปยิง api เพื่อขอข้อมูลของ user ดู
67-
68-
`https://web1.ctf.p7z.pw/api.php?action=get_userinfo&user=admin-uat`
69-
70-
ผลลัพธ์ที่ได้ :
71-
72-
![](https://cdn-images-1.medium.com/max/800/1*iKnZgCthfsCiMGCq0L4vuw.png)
73-
74-
ดูจากข้อมูลนี้เราจะเห็น remember_me_token ซึ่งอาจจะเป็น token ที่ใช้ sign jwt token ของ admin-uat แสดงว่าเราอาจจะใช้ token นี้มา sign token เพื่อ login เป็น admin-uat ได้
75-
76-
แต่เราไม่มี jwt secret key สำหรับใช้ sign token แล้วเราจะหามันได้ยังไง ? คำตอบก็คือ **bruteforce** ยังไงหล่ะ
77-
ก่อนอื่นเราต้องไปเอา Jwt token ของเรามาก่อน ซึ่งจะอยู่ใน cookies
78-
79-
![](https://cdn-images-1.medium.com/max/800/1*pFCpW3nPwieQsrVl37GpHw.png)
80-
81-
และเราจะใช้ hashcat สำหรับ brutefoce เพื่อหา Jwt secret key
47+
Bash
8248

8349
```
84-
hashcat -a 0 -m 16500 "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbiI6ImI4MTk0M2JhLWQxYzUtNDk1YS04NDI3LTQ3MTFjMzkyNTZiZiJ9.Rlk_a69lx16hNhwn4nBfRxhiMGmEDoPIcxfr1_7JdH8" /usr/share/wordlists/rockyou.txt
50+
# ใช้ Mode 16500 สำหรับ JWT (HS256)
51+
hashcat -a 0 -m 16500 "<YOUR_JWT_HERE>" /usr/share/wordlists/rockyou.txt
8552
```
8653

87-
![](https://cdn-images-1.medium.com/max/800/1*iEY26e5EwWmYuby84foSmQ.png)
88-
89-
หลังจาก brutefoce เสร็จ เราก็จะได้คำตอบว่า jwt secret key คือ
54+
- **Result Found:** Secret Key คือ `"bobcats"`
55+
9056

91-
![](https://cdn-images-1.medium.com/max/800/1*VzCLOwQjpO5cNY5NcnFChQ.png)
57+
### 4. Forging Admin Token
9258

93-
ตอนนี้เราได้ทั้ง jwt secret key และ token แล้ว
59+
ใช้ Python ในการสร้าง JWT ใหม่โดยใช้ Token ของ `admin-uat` ที่เราแอบดูมาจาก API:
9460

95-
ต่อไปคือการเอาทั้ง 2 อย่างมารวมกัน ก็คือ การ sign token นั่นเอง
96-
97-
เราจะใช้ python สำหรับ sign token
61+
Python
9862

63+
```
9964
import jwt
100-
print(jwt.encode({ "token": "73eb7063-f8c3-4e50-bea2-07c05681aa92"}, '"bobcats"', algorithm="HS256"))
101-
102-
![](https://cdn-images-1.medium.com/max/800/1*woGgxRc0ghPoRvdEphHaYw.png)
103-
104-
หลังจาก ได้ jwt token มาแล้ว เราจะเอา token ไปแก้ ใน cookies ของเรา
105-
106-
![](https://cdn-images-1.medium.com/max/800/1*7rK6Jf0o0ti3dL4g8D4RaQ.png)
107-
108-
แล้วลอง refresh browser ดู ถ้า user ยังไม่เปลี่ยนให้ ปิด browser แล้วเปิดใหม่ มันอาจจะติด cache อยู่
109-
110-
เราก็จะสามารถ login ด้วย admin-uat ได้แล้ว
111-
112-
![](https://cdn-images-1.medium.com/max/800/1*9_H-AIoIbyZnD0UjLBhKMA.png)
113-
114-
เราจะลองเข้าไปที่หน้า admin.php ตามที่ถูก comment ไว้ใน script.js
115-
116-
![](https://cdn-images-1.medium.com/max/800/1*foch7j9mazC6tgDgHq_6cw.png)
117-
118-
![](https://cdn-images-1.medium.com/max/800/1*kzY9YBqNPKoSNH8dzf-VpQ.png)
119-
120-
หลังจากเข้ามาที่หน้า admin.php แล้ว ก็ลอง inspect ดูอีกรอบ
121-
122-
![](https://cdn-images-1.medium.com/max/800/1*tNyPQK5p_8Gh_X5UFiGHZg.png)
65+
# Sign token ใหม่ด้วย Secret ที่ Brute-force ได้
66+
payload = { "token": "73eb7063-f8c3-4e50-bea2-07c05681aa92" }
67+
print(jwt.encode(payload, "bobcats", algorithm="HS256"))
68+
```
12369

124-
จะเห็น Flag ที่ 1 ถูก comment อยู่ใน source code
70+
**Step สุดท้าย:** นำ JWT ที่ได้ไปแทนที่ใน Cookies ของ Browser แล้ว Refresh หน้าเว็บเพื่อเข้าสู่สิทธิ์ Admin และไปที่หน้า `admin.php` เพื่อรับ Flag ที่ 1 ใน Source Code
12571

12672
---
12773

128-
## มาต่อกันที่ Flag ที่ 2
129-
130-
Flag 2 : ทำการพิมพ์เงินออกจากระบบ
131-
132-
จากการ inspect หน้า admin.php เราจะเห็น โค้ดอะไรบางอย่างถูก comment อยู่
133-
134-
![](https://cdn-images-1.medium.com/max/800/1*lTNt3qPI3dQjNBTJtgbTBw.png)
74+
## Flag 2: Logic Bypass (พิมพ์เงินออกจากระบบ)
13575

136-
ลองแกะๆการทำงานดูจะเห็นว่า
76+
### 1. Vulnerability Analysis
13777

138-
โค้ดนี้คือโค้ดสำหรับ validate input และแสดง Flag ออกมา
78+
ในหน้า `admin.php` มีโค้ดตรวจสอบความปลอดภัย (Validation) ที่ถูก Comment ไว้ ซึ่งมีช่องโหว่ที่ตัว Regex:
13979

140-
logic ของการทำงาน คือ
80+
PHP
14181

142-
**validateNumber()**
143-
144-
- Regular Expression `/^[0-9]+$/m`เช็คว่า input เป็นตัวเลขหรือไม่
145-
- ข้อสังเกตคือ Regx นี้ใช้ /m ซึ่งจะตรวจสอบแค่บรรทัดเดียวเท่านั้น
146-
147-
**strpos($amount, ‘STH’)**
148-
149-
- เช็คว่า Input มีคำว่า STH อยู่หรือไม่
150-
151-
การที่เราจะ bypass validator นี้ไปได้ คือ input ต้องเป็นตัวเลขทั้งหมด และ มี STH อยู่ด้วย ระบบถึงจะ return Flag มาให้เรา
152-
153-
แล้วเราจะทำยังไงหล่ะ ??
154-
155-
ถ้าจำได้ Regx ที่ validate input มันตรวจสอบแค่บรรทัดเดียว ดังนั้นเราก็สามารถ input ข้อมูล 2 บรรทัดได้สิ
156-
157-
เช่นแบบนี้
158-
159-
12345
160-
STH
161-
162-
เพราะบรรทัดแรกจะถูกตรวจสอบว่าถูกต้องด้วย Regx แต่บรรทัดที่ 2 มีคำว่า STH ทำให้ logic ของระบบเป็น True && True และเราก็จะได้ Flag
82+
```
83+
// Regex: /^[0-9]+$/m <-- มีจุดอ่อนที่ตัวแก้ไข /m (Multiline)
84+
if (preg_match('/^[0-9]+$/m', $amount) && strpos($amount, 'STH')) {
85+
echo $flag2;
86+
}
87+
```
16388

164-
เราจะใช้ burpsuit สำหรับยิง API แทนการพิมพ์ข้อมูลใน form ของหน้าเว็ป
89+
> [!warning] **The Flaw**
90+
>
91+
> - `^...$ /m` จะตรวจสอบเงื่อนไขแบบ **บรรทัดต่อบรรทัด**
92+
>
93+
> - ถ้าบรรทัดแรกเป็นตัวเลข (ผ่าน Regex) และมีบรรทัดอื่นที่มีคำว่า "STH" (ผ่าน strpos) เงื่อนไขจะเป็นจริงทันที
94+
>
16595
166-
![](https://cdn-images-1.medium.com/max/800/1*KXxreu45N0Z6cwvrVBpF8g.png)
96+
### 2. Exploitation (Newline Injection)
16797

168-
เพราะหน้าเว็ปใช้ input type number ทำให้เราไม่สามารถใส่ ข้อความใน input ได้
98+
เราไม่สามารถใส่ข้อความผ่านหน้าเว็บได้เพราะ Input ถูก Lock เป็น `type="number"` จึงต้องใช้ **Burp Suite** ในการยิง Request โดยตรง
16999

170-
ลองยิง API ด้วย burpsuit โดยส่ง payload **amount=123%0ASTH&denomination=USD**
100+
**Payload ที่ใช้:**
171101

172-
`12345` คือ ตัวเลขที่เราต้องการให้ผ่าน regx
102+
HTTP
173103

174-
`%0A` คือ \n แบบเข้ารหัส (url encoding)
104+
```
105+
POST /api.php HTTP/1.1
106+
...
107+
amount=123%0ASTH&denomination=USD
108+
```
175109

176-
`STH` คือ สิ่งที่เราต้องการแทรกเข้าไปใน payload เพื่อให้ได้ flag
110+
- `123` : ผ่านเงื่อนไขบรรทัดแรกว่าเป็นตัวเลข
111+
112+
- `%0A` : คือ Newline (`\n`) เพื่อขึ้นบรรทัดใหม่
113+
114+
- `STH` : บรรทัดที่สองทำให้ฟังก์ชัน `strpos` ตรวจเจอคำที่ต้องการ
115+
177116

178-
![](https://cdn-images-1.medium.com/max/800/1*YwS2Ho3DH7ueHwzh5CyXyg.png)
117+
**Result:** ระบบส่ง Flag ที่ 2 กลับมาให้ใน Response Body ทันที!
179118

180-
หลังจากยิง API ไป เราก็จะเห็น Flag ที่ 2 ใน Response
119+
---
181120

182-
![](https://cdn-images-1.medium.com/max/800/1*kg2kjMK5Hs_AQ_PgV1FwnA.png)
121+
## Summary
183122

184-
ทีนี้เราก็จะได้ Flag ครบทั้ง 2 Flag แล้ว เย้ๆๆๆ
123+
|**Flag**|**Vulnerability Type**|**Tool Used**|
124+
|---|---|---|
125+
|**Flag 1**|Information Disclosure & Weak JWT Secret|Hashcat, Python, DevTools|
126+
|**Flag 2**|Regex Multiline Bypass (Newline Injection)|Burp Suite|

0 commit comments

Comments
 (0)