STH-Mini-Web-CTF-2025 — Write Up

🎯 Target
https://web1.ctf.p7z.pw
⚡ เป้าหมาย
- ทำการโจมตีเว็บโจทย์การแข่งขัน เพื่อหาข้อความลับ ที่เรียกว่า Flag โดย Flag จะมีรูปแบบ เช่น
STH1{cff940beed74db5e1c7c63007223a6e6}
- เข้าสู่ระบบเป็นสิทธิ์ผู้ดูแลระบบ (Flag 1)
- ทำการพิมพ์เงินออกจากระบบ (Flag 2)
🚩 Flag 1: เข้าสู่ระบบเป็นสิทธิ์ผู้ดูแลระบบ

หลังจากเข้าเว็บมาก็จะเห็นหน้า login
ปัญหา: ซึ่งเราไม่มี username, password แล้วเราจะ login ได้ยังไง?
📊 ขั้นตอนการแก้ไข
1️⃣ รวบรวมข้อมูล (Information Gathering)
เริ่มจากลอง inspect หน้าเว็บดูเผื่อจะเจออะไร

เมื่อลองดูใน Source code เราจะเห็น comment ของ credentials อยู่:
- username
- password
ลองเอา username และ password ที่ได้มา login ดู และ ติ๊ก Remember Me ด้วย

หลังจาก login แล้ว เราจะเห็นว่ามีข้อมูลเกี่ยวกับการ login ของเราขึ้นมา:
- Username:
"test"
- Role:
"user"

แต่ใน Flag นี้เราจะต้อง login ด้วย Admin user แล้วเราจะสามารถ login ด้วย Admin ได้ยังไง?
2️⃣ ค้นหาข้อมูลเพิ่มเติม
ลอง inspect หน้าเว็บดูอีกรอบเผื่อจะมีอะไรซ่อนอยู่อีก

เมื่อลองส่องๆดูเราจะเห็นไฟล์ javascript "script.js" และเราเห็นว่าในไฟล์นี้มี function อยู่ 2 function:
✅ debugFetchUserTest()

function นี้จะทำการ fetch data จาก:
api.php?action=get_userinfo&user=test
ลองยิง API ไปที่ endpoint นี้ดู:
https://web1.ctf.p7z.pw/api.php?action=get_userinfo&user=test
ผลลัพธ์ที่ได้:

แสดงว่า function นี้เป็น function ที่ทำหน้าที่ดึงข้อมูลของ user นั้นๆ
✅ debugFetchAllUsers()

function นี้จะทำการ fetch data จาก:
api.php?action=get_alluser
ลองยิง API ไปที่ endpoint นี้ดู:
https://web1.ctf.p7z.pw/api.php?action=get_alluser
ผลลัพธ์ที่ได้:

แสดงว่า function นี้เป็น function ที่ทำหน้าที่ fetch user ทั้งหมดในระบบ
3️⃣ ค้นหาข้อมูล Admin
จากผลลัพธ์ของ function debugFetchAllUsers()
เราจะเห็นว่า มี user อยู่อีก 1 user ซึ่งอาจจะเป็น admin user
เราจะลองเอา username นี้ไปยิง API เพื่อขอข้อมูลของ user ดู:
https://web1.ctf.p7z.pw/api.php?action=get_userinfo&user=admin-uat
ผลลัพธ์ที่ได้:

ดูจากข้อมูลนี้เราจะเห็น remember_me_token
ซึ่งอาจจะเป็น token ที่ใช้ sign JWT token ของ admin-uat
แสดงว่าเราอาจจะใช้ token นี้มา sign token เพื่อ login เป็น admin-uat ได้
4️⃣ หา JWT Secret Key
แต่เราไม่มี JWT secret key สำหรับใช้ sign token แล้วเราจะหามันได้ยังไง?
คำตอบก็คือ bruteforce ยังไงหล่ะ!
ก่อนอื่นเราต้องไปเอา JWT token ของเรามาก่อน ซึ่งจะอยู่ใน cookies:

และเราจะใช้ hashcat สำหรับ brutefoce เพื่อหา JWT secret key:
hashcat -a 0 -m 16500 "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbiI6ImI4MTk0M2JhLWQxYzUtNDk1YS04NDI3LTQ3MTFjMzkyNTZiZiJ9.Rlk_a69lx16hNhwn4nBfRxhiMGmEDoPIcxfr1_7JdH8" /usr/share/wordlists/rockyou.txt

หลังจาก brutefoce เสร็จ เราก็จะได้คำตอบว่า JWT secret key คือ:

5️⃣ สร้าง Token เพื่อ Login เป็น Admin
ตอนนี้เราได้ทั้ง JWT secret key และ token แล้ว
ต่อไปคือการเอาทั้ง 2 อย่างมารวมกัน ก็คือ การ sign token นั่นเอง
เราจะใช้ Python สำหรับ sign token:
import jwt
print(jwt.encode({ "token": "73eb7063-f8c3-4e50-bea2-07c05681aa92"}, '"bobcats"', algorithm="HS256"))
ผลลัพธ์ที่ได้:

6️⃣ Login เป็น Admin
หลังจาก ได้ JWT token มาแล้ว เราจะเอา token ไปแก้ ใน cookies ของเรา:

แล้วลอง refresh browser ดู ถ้า user ยังไม่เปลี่ยนให้ ปิด browser แล้วเปิดใหม่ มันอาจจะติด cache อยู่ เราก็จะสามารถ login ด้วย admin-uat ได้แล้ว

เราจะลองเข้าไปที่หน้า admin.php ตามที่ถูก comment ไว้ใน script.js

เราจะลองเข้าไปที่หน้า admin.php ตามที่ถูก comment ไว้ใน script.js

หลังจากเข้ามาที่หน้า admin.php แล้ว ก็ลอง inspect ดูอีกรอบ

จะเห็น Flag ที่ 1 ถูก comment อยู่ใน source code 🎉
🚩 Flag 2: ทำการพิมพ์เงินออกจากระบบ
จากการ inspect หน้า admin.php เราจะเห็น โค้ดอะไรบางอย่างถูก comment อยู่

📊 วิเคราะห์โค้ด
ลองแกะๆการทำงานดูจะเห็นว่า โค้ดนี้คือโค้ดสำหรับ validate input และแสดง Flag ออกมา
Logic ของการทำงาน คือ:
-
validateNumber()
- Regular Expression
/^[0-9]+$/m
เช็คว่า input เป็นตัวเลขหรือไม่ - ⚠️ ข้อสังเกต: Regex นี้ใช้
/m
ซึ่งจะตรวจสอบแค่บรรทัดเดียวเท่านั้น
- Regular Expression
-
strpos($amount, 'STH')
- เช็คว่า Input มีคำว่า STH อยู่หรือไม่
💡 แนวทางการ Bypass
การที่เราจะ bypass validator นี้ไปได้:
- Input ต้องเป็นตัวเลขทั้งหมด และ
- มี STH อยู่ด้วย
ระบบถึงจะ return Flag มาให้เรา
แล้วเราจะทำยังไงหล่ะ??
ถ้าจำได้ Regex ที่ validate input มันตรวจสอบแค่บรรทัดเดียว ดังนั้นเราก็สามารถ input ข้อมูล 2 บรรทัดได้!
เช่นแบบนี้:
12345
STH
- บรรทัดแรกจะถูกตรวจสอบว่าถูกต้องด้วย Regex
- บรรทัดที่ 2 มีคำว่า STH
ทำให้ logic ของระบบเป็น True && True
และเราก็จะได้ Flag
🛠️ เครื่องมือและการโจมตี
เราจะใช้ Burp Suite สำหรับยิง API แทนการพิมพ์ข้อมูลใน form ของหน้าเว็บ

เพราะหน้าเว็บใช้
input type="number"
ทำให้เราไม่สามารถใส่ข้อความใน input ได้
ยิง API ด้วย Burp Suite
ส่ง payload:
amount=123%0ASTH&denomination=USD
โดย:
12345
คือ ตัวเลขที่เราต้องการให้ผ่าน regex%0A
คือ\n
แบบเข้ารหัส (URL encoding)STH
คือ สิ่งที่เราต้องการแทรกเข้าไปใน payload เพื่อให้ได้ flag

🎯 ผลลัพธ์
หลังจากยิง API ไป เราก็จะเห็น Flag ที่ 2 ใน Response:

🏆 สรุป
ทีนี้เราก็จะได้ Flag ครบทั้ง 2 Flag แล้ว! 🎉🎉🎉
- Flag 1: ได้มาจากการปลอมตัวเป็น admin ด้วยการจัดการกับ JWT token
- Flag 2: ได้มาจากการ bypass validation โดยใช้ newline character