php反序列化小记
函数方法
is_a方法
自动触发__ autoload 方法,触发php类得自动加载机制
用在条件语句中就是,判断是不是某个类的实例,
在 PHP 5 中使用 instanceof 运算符
count方法
- php7.2以下可传参类型为int string array object
- php7.2以上产参类型为array object
__wakeup方法
绕过和版本差异
- 在特定的php版本可被绕过(CVE-2016-7124)
- 在php7以下异常类LogicException会对反序列化中的参数进行清空
方法调用
demo
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 51
| <?php class A{ private $a;
public function __construct() { $this->a = "a123"; } public function __call($name, $arguments) { return call_user_func_array(array($this->a,$name),$arguments); } public function test(){ return "this class is A!"; } }
class B{ private $b; public function __construct() { $this->b = $this; } public function __destruct() { $this->close(); } public function aaa(){} public function test(){ return "this class is B!"; } public function close(){ $this->b->aaa(); $c = $this->b->test(); echo $c; } }
class C{ private $c; public function __construct() { $this->c = $this; } public function test(){ return "this class is C!"; } }
$a = $_POST['a']; unserialize($a);
|
当直接调用C类中不存在的方法时,这是致命错误,程序GG

当通过__call方法去调用时,waring,程序继续执行

常量和静态变量
const和define
const是在类里面声明的,可以在类中序列化,通过self::形式会被调用
define定义的常量也可以被序列化,但是不能在类中声明,也就是利用链里面的如果有这种常量座位关键点,那么基本就断了
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
| <?php class A{ private $a; const A = "11111111"; protected static $b = "no no no !";
public function __construct() { $this->a = "a123"; } public function __destruct() { echo $this->a."<br>"; echo self::$b."<br>"; echo self::A; } }
$a = $_POST['a']; unserialize($a);
....
class A{ private $a;
const A = "a"; protected static $b; public function __construct() { $this->a = "a: yes"; self::$b = "yes yes yes!"; } }
$b = new A(); echo str_replace('+', '%20', urlencode(serialize($b)));
|
输出:
1 2 3
| a: yes no no no ! 11111111
|
结论:
- 可操作对象只有动态变量
- 序列化保存的是对象的状态,静态变量数以类的状态,因此序列化并不保存静态变量。
- 这里的不能序列化的意思,是序列化信息中不包含这个静态成员域
资源类型
碰到过几次,这是个不能被序列化的类型,代表的是一种状态,强行序列化会得到int类型
测试代码
1 2
| $fp = @fopen("D:\phpstudy\\1.txt",'rb'); echo serialize($fp);
|

那么,牵扯到资源的类型的怎么做
思考:在用户层面如果有调用,在调用之后的序列化是否可用
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
| class D{ private $fp; private $d; public function __construct() { $this->fp = @fopen("D:\phpstudy\\1.txt",'rb'); $this->d = new B(); }
public function __wakeup() { if(is_resource($this->fp)){ $a = $this->d->test(); echo $a; }else{ $a = $this->test(); echo $a; } } public function test(){ return "this class is D!"; } }
$d = new D(); $a = $_POST['a']; unserialize($a);
|
结论:不可用,这是两个不同的实例,互不干涉

解决了另外的一个问题,如果程序的反序列化利用链牵扯到了,程序运行所必须加载的类,能不能对这个类重新赋值的问题
php的反射调用亦是入此,虽然加载了类最后会触发析构方法,但是因为是不通的实例,只不过是多触发一次析构方法罢了
方法回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| call_user_func_array(["A","test1"],["aaa"]);
call_user_func_array([new A(),"test1"],["aaa"]); ...
call_user_func(["A","test1"],"aaa"); call_user_func("A::test1","aaa");
call_user_func([new A(),"test1"],"aaa"); ...
$a = [new A(),"test"]; $a();
$b = "A::test1"; $b();
|
触发任意类任意方法
- call_user_func\(\$this->[a-z_]{1,},
- cal_user_func_array\(array\(\$this->[a-z_]{1,},\$this->[a-z_]{1,}\),
- \$[a-z_]{1,}\(
- \(\$this->[a-z_]{1,}\)\(
节点的寻找
- 当我们苦苦寻找一个可以调用任意类任意方法时,一个调用固定方法的的点可能会成为突破口,当然也是call_user_func方法调用固定的方法
- 同样,N多方法不可控的情况,一个全新方法的加如,可能就会造成一个漏洞
两次失败的利用链
败在is_resource的利用链
PHPMailer第三方库
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
| public function __destruct() { $this->smtpClose(); } ...
public function smtpClose() { if (is_a($this->smtp, 'SMTP')) { if ($this->smtp->connected()) { $this->smtp->quit(); $this->smtp->close(); } } } ...
public function connected() { if (is_resource($this->smtp_conn)) { $sock_status = stream_get_meta_data($this->smtp_conn); if ($sock_status['eof']) { $this->edebug( 'SMTP NOTICE: EOF caught while checking if connected', self::DEBUG_CLIENT ); $this->close(); return false; } return true; } return false; }
protected function edebug($str, $level = 0) { if ($level > $this->do_debug) { return; } if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) { call_user_func($this->Debugoutput, $str, $level); return; } switch ($this->Debugoutput) {...} } ...
public function preSend() { try { $this->error_count = 0; $this->mailHeader = '';
foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { $params[1] = $this->punyencodeAddress($params[1]); call_user_func_array(array($this, 'addAnAddress'), $params); } ... } }
protected function addAnAddress($kind, $address, $name = '') { if (!in_array($kind, array('to', 'cc', 'bcc', 'Reply-To'))) { $error_message = $this->lang('Invalid recipient kind: ') . $kind; $this->setError($error_message); $this->edebug($error_message); ... } ... }
protected function lang($key) { if (count($this->language) < 1) { $this->setLanguage('en'); }
if (array_key_exists($key, $this->language)) { if ($key == 'smtp_connect_failed') { ... } return $this->language[$key]; } else { return $key; } }
protected function edebug($str) { if ($this->SMTPDebug <= 0) { return; } if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) { call_user_func($this->Debugoutput, $str, $this->SMTPDebug); return; } ... }
|
phpexcle第三方库
没有合适中间节点,不列举了
主要是两点
- 静态变量无法序列化
- call_user_func调用不存在方法,不是致命错误
存在close不存在exec
1 2 3 4 5 6 7 8
| public function __destruct() { if (!is_null($this->DBHandle)) { $this->DBHandle->exec('DROP TABLE kvp_'.$this->TableName); $this->DBHandle->close(); } $this->DBHandle = null; }
|
任意文件删除
1 2 3 4 5 6 7
| public function __destruct() { if ($this->tempFileName != '') { @unlink($this->tempFileName); } }
|