本文首发于:https://mp.weixin.qq.com/s?__biz=MjM5MTYxNjQxOA==&mid=2652850238&idx=1&sn=6f22d8ab7af687993330a2a1774a5579&chksm=bd5934f38a2ebde507c7964e5a0dcd4775dec3f62162a462d95814132a8cf546a9df1daa51e5&scene=0&xtrack=1#rd
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.php composer.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,通过执行php poc.php将会生成exp文件,其中为我们序列化以后的exp数据
接下来我们可以反序列化exp中包含的数据来测试此exp能否执行成功
通过执行结果我们可以判断出已经成功反序列数据并写入shell了,说明我们之前分析的思路和测试过程是正确的。