之前在网上也看过一些大佬写的关于如何构造pop的文章,但是对于未接触过pop或代码审计能力较弱的小白们来说,看起来还是蛮吃力的。因此趁今天天气晴朗,便阅读了一篇国外关于pop的paper,并根据自己的理解详细的分析了一个pop的构造与相应poc的构造的例子,若有不对之处,还请大佬们指出。
1.切入漏洞点
某应用使用了版本为6.0.2的guzzlehttp/guzzle代码库,在其代码段中存在反序列化函数unserialize(),并且其入口参数为我们可以控制的参数,那么接下来我们就需要在其内部寻找我们可以利用的类,并且寻找可以利用的反序列时将会触发的__destruct()函数和__wakeup()函数,然后再在这两类方法中找可能存在漏洞的点,我们可以在sublime中使用ctrl+shift+f键来在指定的文件夹下来查找我们指定的函数,如下图为查找的效果
2.构造pop链
我们注意到
vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php中存在__destruct()函数,并且我们猜测filename属于文件型参数,可能与文件操作相关,因此我们便可以定位到FileCookieJar.php文件中进行审计
函数体中存在save方法,因此我们继续跟进到save方法
我们可以看到save方法中存在file_put_contents()函数,我们猜测是不是可以在此进行写shell,其中入口参数$filename为类FileCookieJar的私有成员变量,是我们可以控制的,$json变量来自$cookie变量,而又因为$cookie变量调用的getExpires()函数和getDiscard()函数和toArray()函数均是在SetCookie这个类中进行定义的,所以$cookie变量肯定是SetCookie类实例化后的对象,从而来调用类中的方法。并且此时我们需要满足
1.$cookie>getExpires()返回Ture
2.$cookie>getDiscard()返回False
接下来我们跟进到SetCookie.php中观察类SetCookie是如何定义的:
其中在SetCookie类中的成员变量里定义了私有静态成员变量$defaults和私有成员变量$data:
并且在其构造方法中$defaults的值将被$data数组中的值进行替换,并且将替换的结果赋值给$data变量。
其中getExpires()函数和getDiscard()和toArray()函数的定义如下所示:
从上面三个函数的定义中可以看到,上面两个函数返回$data变量的两个键值,下面的函数直接返回$data变量。那么我们可以让这上面两个键值满足if条件,通过我们实例化SetCookie类并传给其一个$data数组即可在其构造方法中完成赋值操作。
到这里,假设我们已经进入第一个箭头所指示的if条件语句中,那么此时将把$cookie对象所调用的toArray()函数的返回值(也就是我们上面所说的SetCookie类中所定义的$data变量的值)赋给$json变量,接下来就会将$json变量的值写入变量$filename所对应的路径中,到此pop链已经完成。
3.相应poc构造
最后,我们在本地测试我们以上的思路以及构造的pop链是否正确,首先编辑composer.json文件并在其中包含我们所测试的对应版本的代码库
接下来在composer的同级目录中执行
1.curl-sS https://getcomposer.org/installer | php //安装composer
2.phpcomposer.phar install //依赖代码库的安装
简单说下composer,composer是 PHP的一个依赖管理工具。它允许我们声明项目所依赖的代码库,并且会在我们的的项目中安装所依赖的代码库。composer.json中定义的require键值代表的含义是我们将要开发的应用,依赖于guzzlehttp的6.0.2版本,对于库的自动加载信息,composer生成了一个vendor/autoload.php 文件,我们可以通过引入这个文件,从而实现开发中所需要的类的自动加载
接下来就可以开始构造我们的poc了,因为要用到上面说的类,所以我们直接引入对应目录下的autoload.php就可以,这里的两条use语句只是使用了命名空间,具体的加载类交给我们的autoload.php来完成,
接着我们就可以来构造我们最终想要利用的file_put_contents()函数的文件路径名和文件内容,首先进行分析
因为文件路径名$filename参数是类FileCookieJar的私有成员变量,并且在其构造函数中将直接将入口参数赋值给了$filename,并在其析构函数中调用了$filename的值(也就是在反序列化时将会调用$filename的值),因此我们通过实例化FileCookieJar的匿名对象,并将想要写入的路径名作为其入口参数,就可以完成对$filename的赋值。又因为save方法要用到$cookie变量的值,并且$cookie变量的值必须由类SetCookie的实例化后的对象来进行赋值操作,而FileCookieJar的父类是CookieJar,并且在其中存在setCookie()方法,其入口参数为类SetCookie的实例化的对象,因此我们成功地找到了为$Cookie变量赋值的方法,就是通过类FileCookieJar的实例化对象来调用父类中所定义的setCookie()方法,并将其入口参数值设置为类SetCookie的匿名实例化对象,从而实现$cookie变量的赋值。
接下来我们就要构造$cookie变量的数据了,首先我们需要了解setCookie方法,它的入口参数为实例化的类SetCookie的匿名对象,其在函数中调用了validate函数对$cookie变量进行检测,并且定义了设置二次设置$cookie时的判断操作,这里我们构造poc时只需要调用一次setCookie()函数,所以不会进入foreach循环
也就是我们的目标只有一个,那就是是必须使validate()函数返回True
而validate()函数要求我们必须满足以下三个条件才能够返回true:
1.$data[‘Name’]变量非空,并且不能为数字,并且必须为所规定的字符
2.$data[‘Value’]变量非空,并且不能为数字
3.$data[‘Domain’]变量非空,并且不能为数字
并且结合又因为$data[‘Expires’]不能为null,因此我们可以确定我们必须需要设置的$data的键名为以上四个,那么接下来我们就可以继续写我们的poc了,我们在实例化类FileCookieJar时将我们想要写入shell的路径名作为入口参数传递进去,然后将php一句话写到$data变量的Name,Value,Domain中的任何一个值中,接着通过让类对象$tr1ple调用setCookie(),并将其入口参数设置为类SetCookie的匿名实例化(其入口参数为$data),就可以完成对$cookie变量的赋值,因为$cookie值最终会被传递给$json变量并经过json_encode()函数编码以后直接写入到我们指定的路径中。
4.测试结果
我们的poc文件名为poc.php,通过执行phppoc.php将会生成exp文件,其中为我们序列化以后的exp数据
接下来我们可以反序列化exp中包含的数据来测试此exp能否执行成功
通过执行结果我们可以判断出已经成功反序列数据并写入shell了,说明我们之前分析的思路和测试过程是正确的。
如果大家对文章中存在问题或者疑惑,请联系我:919991842@qq.com
渗透PHP代码审计——学习PHP代码审计的基础知识
http://www.hetianlab.com/cour.do?w=1&c=C9d6c0ca797abec2017041916230700001
PHP反序列化漏洞——了解什么是反序列化漏洞,反序列化漏洞的成因及如何挖掘和防御反序列化漏洞
http://www.hetianlab.com/expc.do?ec=ECID172.19.104.182016010714511600001