☰
无字母数字Webshell绕过:字符过滤与命令执行实战
2026/10/11 19:22:56 网站建设 项目流程

第一次看到这个靶场的提示,我愣了一下。“哦豁,你不能输入字母了”——这个“哦豁”太传神了,像极了输入内容被拦掉时的心情。这不是一个普通的入门靶场,它把“无字母数字webshell”和“命令执行绕过”两个考点合到了一起,对新手来说挺劝退,对有一定基础的人来说则是一个很好的练手环境。

我为什么说它好?因为它的提示足够少,限制足够狠,逼着你去理解字符过滤的本质,而不是靠背payload过关。它适合两类人:一是想搞懂WAF和输入过滤原理的安全爱好者,二是准备面试、想提高代码审计功底的人。下面我把我的完整解题过程、踩过的坑和复盘结论都写出来,希望对你有用。

1. “哦豁”提示背后,先弄清楚这个靶场到底拦住了什么

我在本地环境里搭了一个类似的靶场来复现这个考点,登录进去之后,界面非常简单:一个输入框、一个提交按钮,页面顶部就一句“哦豁,你不能输入字母了”。我试着提交hello,返回的结果是hllo——字母e被吃掉了;提交abc,返回的是空。说明这不是前端校验,而是后端在应用层做了字符过滤。

1.1 三种常见的“禁字母”实现,决定了后续绕法完全不同

这类限制字母的规则,后端实现常见的有三种,虽然页面表现都是“不能输入字母”,但绕法的起点完全不同。

第一种是用正则把字母字符直接删除,比如preg_replace('/[a-z]/i', '', $input)。这种实现最粗暴,它不会报错,只会让你输入的字母消失,其他字符原样保留。第二种是拦截型,检测到字母就直接终止执行或返回403,典型写法是preg_match('/[a-z]/i', $input)一命中就结束。第三种是WAF层过滤,一般位于中间件或网关入口,会对URL、请求头和POST数据做统一检查。前两种属于应用层,第三种属于网络层。

区分这三者,在靶场里有一个非常简单的办法:输入一串同时包含字母、数字、符号的数据,观察返回结果。如果字母被删除但数字和符号保留,就是删除型;如果整个请求直接被掐断,响应码变成403或者直接一片空白,就是拦截型;如果换一个请求头、改一下Content-Type或编码,行为立刻就不一样,那多半是WAF层。这个靶场实测是删除型加拦截型的混合规则,后面我会具体说明混合在哪。

1.2 判断过滤点所在层:前端、后端还是中间层

判断层面这一步,很多人会忽略。前端限制最容易被识破,打开浏览器开发者工具改一下JS逻辑、绕过input标签的限制就能继续,因为它根本没到服务器。后端过滤才是真正要面对的,这时候在输入框里怎么改都没用,必须从HTTP请求层面想办法。中间层过滤则要看流量走向,常见于统一入口的防护组件,它对所有进站请求做检查。

我的习惯是用抓包工具把请求原样重放一遍:先正常提交一个含字母的payload,看返回;再手动改请求头,加上常见的编码类型或分块传输,看过滤是否变弱。这个靶场在抓包工具的Repeater里直接改原始数据包,前端JS根本管不到,但后端过滤依然生效,所以可以确认过滤点在服务端应用层。这一点很重要,如果过滤点只在WAF层,绕过思路就该转向协议层面,比如大小写混淆、双重URL编码、分块传输,而不是去研究PHP语法层面的payload构造。

2. 摸清过滤规则的边界,才有资格谈绕过

很多人在这一关就放弃了,因为他们感觉“所有字母都不能用”,那命令还怎么写?实际上,任何一种过滤规则都有自己的边界。你需要做的不是对着网上的payload盲试,而是先花几分钟把规则的边界测出来。

2.1 手工探测的三招,把规则边界测出来

第一招,测试过滤对数字和符号是否放行。构造一组测试数据:abc123!@#,提交后看返回。这个靶场里返回的是123!@#,说明数字和特殊符号是安全的。第二招,测试双写绕过是否可行。如果过滤是删除型,aabc删除一个a后变成abc,仍然包含字母;如果只删一次,反而可能把aabc变成abc,等于把字母又“复活”了。这个靶场用的是全局删除,双写不行,我实测提交ppaayylnoaad后返回pyload,字母几乎都被剥掉了,连看起来无害的组合也保不住。

第三招很关键,测试过滤是否跟随大小写。提交ABCDEFG看结果。不少正则只写了[a-z]没加i修饰符,大写字母就会全部放行。但这个靶场显然加了i,大写也被删。到这里我基本可以得出结论:想靠大小写混淆和双写绕过在这个靶场里是行不通的,必须走“无字母字符构造”这条路。

2.2 从回显差异反推后端语言和过滤函数

后端语言决定了你能用哪些绕过手段。这个靶场虽然页面提示很简洁,但通过几个特征就能判断是PHP环境:一是报错信息里出现PHP相关的函数名和堆栈格式;二是支持?a=...这种query参数时,变量名和值的解析方式符合PHP习惯;三是当你提交超长参数或者一个非法数组结构,比如a[]=1,看回显是否出现 PHP 典型错误。这个靶场在我提交数组类型参数时直接吐了一段堆栈,后端语言基本就锁定了。

为什么一定要先判断语言?因为不同语言的绕过方式差异非常大。如果是Java的Spring框架,考虑的是SpEL表达式注入和OGNL;如果是Python的Flask,优先考虑Jinja2模板注入和字符串format;如果是PHP,则可以玩的东西就多了:可变变量、字符串异或、取反、自增、数组键名覆盖。这个靶场确认是PHP之后,我的脑子里基本就排出了方案优先级:通配符、取反、异或、自增。

3. 无字母payload的三条构造路线

到这里,游戏才真正开始。下面三条路线,不要求全背下来,建议把它们当成三条思考路径,理解原理之后再去靶场里试。

3.1 路线一:用通配符和shell特性绕过“字母”限制

第一种思路是绕开字母本身,而不是编出字母。如果注入点最终是拼进系统命令里的,那Linux shell的通配符就可以帮上大忙。所谓的“不能输入字母”,限制的是提交上来的字符串,但shell在执行命令时,通配符?和*会用匹配到的真实文件名去替换这些字符。也就是说,我可以提交一个完全不含字母的payload,让shell在匹配阶段把文件路径填进来。

举个例子:想执行cat /flag,输入却可以是cat /???这样的形式。如果担心cat这两个字母也过不去,那就彻底点,写成/?in/c?t /???。这串东西里只有/、?、.和空格,没有字母。空格如果也被过滤了,可以用<重定向符或tab,这个靶场实测tab也能当作参数分隔符。通配符的粒度和匹配结果,跟目标系统里的文件结构强相关,所以提交前最好先通过报错、延时等方式确认系统里存在哪些路径。通配符方案最稳定的一点是:它不涉及语言层面的解析器,只要命令最终能落到shell,就一定能用。

3.2 路线二:用取反运算把函数名“变”出来

如果过滤规则连/、?这两个符号都给干掉,通配符路线就废了。第二种思路是用位运算来“造”字母。PHP里对字符串做~取反运算,会把每个字符的ASCII码按位取反,结果是一串不可读的乱码,但关键来了:这串乱码里通常没有字母。反过来,我把一个函数名字符串取反,再在payload里对它做一次取反,PHP执行时就得到了真正的函数名,而整个payload里没有任何一个字母。

用代码演示一下这个原理:

def neg(s): return ''.join(chr(255 - ord(c)) for c in s) print(neg("phpinfo"))

这段脚本会输出一串乱码,而这串乱码在PHP里拼成~(...)的形式,执行结果就等于调用了phpinfo()。整个payload看起来全是百分号编码和符号,但执行时完全合法。掌握了这个公式,任何函数名都能用同样方式构造出来。唯一的门槛是先把函数名对应的取反字符串算出来,这一步用本地脚本就能解决。

3.3 路线三:用异或和自增,在没有字母的世界里造出“_GET”

第三种思路更灵活,适合规则更严格的场景。PHP里字符串可以异或,两个由特殊字符组成的字符串,异或之后可能得到一个可读的英文字母字符串。经典的构造目标是_GET,因为拿到_GET之后,就能把真正想执行的代码塞进URL或POST参数里,从根本上绕过了“输入内容”的字母检查。

具体操作上,一种常见方式是利用PHP的数组键名变化:提交一个键名为空字符的数组参数,PHP会把它变成空字符串键,再配合花括号和字符串下标访问,可以逐字符取出目标字符串。自增思路则更像“穷举”:PHP里'a'++会变成'b','b'++变成'c',连续自增几十次,理论上能从任何初始字符出发得到任意字母。不过这些手段写出来的payload通常很长,而且依赖PHP版本对字符串递增行为的支持,所以我的建议是:先用通配符,再用取反,最后才考虑异或和自增,尽量用高成功率的手段拿结果。

4. 越过空格、分隔符、回显三道坎,才能拿到最终结果

你可能会觉得,构造出无字母payload已经算通关了。但实际在靶场里,前面只是把“能执行”搞定,后面还有三道常规坎等着你,很多人在第一道坎就卡住了。

4.1 空格被干掉以后,用tab和重定向喂参数

这个靶场把空格也一并禁了,我试过提交ls /tmp,中间那个空格让请求直接返回错误。解决办法有两个,一个是环境变量方式的空白符,另一个是tab字符。环境变量方式在严格字母过滤下会失效,因为变量名本身含有字母;而tab字符用%09表示,走URL解码后不是一个字母,所以能活下来。实测用它替代空格后,命令可以正常执行。

这里存在一个容易混淆的点:为什么%09能过,而环境变量方式不行?因为过滤发生在应用层,拿到的是解码后的字符串,%09经过URL解码后是一个tab字符,不是字母,所以通过;环境变量方式解码后包含三个字母,被正则命中,所以被删掉。如果你在某个靶场发现环境变量能直接用,说明过滤规则没有覆盖变量名区域,或者过滤发生在更早的阶段。遇到这种情况别急着高兴,先拿命令执行结果验证一下规则边界。

4.2 命令拼接位置不同,分隔符的取舍也不一样

注入点通常有几种形态:参数值直接被当作命令执行、参数值拼在命令中间、参数值拼在命令末尾。第三种最常见,也最考验分隔符选择。我一开始直接提交无字母payload,发现系统回显是命令没有正常执行,后来仔细看才发现,注入点后面还跟着一个固定后缀,比如ping -c 1 {input}这种形态,输入直接被当成了目标地址。

这时候你得先闭合前面的结构,再插入自己的命令。常用的分隔符有;、&&、||、|,以及对URL编码敏感的换行符。在这个靶场里,管道符被过滤了,&还在,所以我最终用的是分号加无字母命令。分隔符的选择不仅取决于过滤规则,还取决于shell的解析顺序。如果你发现提交;id返回空,可能不是分隔符的问题,而是id命令的输出没被捕获;这时可以试着重定向到可见路径,再看有没有结果。

4.3 无回显场景的备用方案:延时、外带和写文件

如果靶场不给你回显,前面的所有技巧都会失效一半,因为你根本不知道命令有没有执行成功。这种场景下的核心思路是“让命令的结果自己走出来”。常见方案有三种:延时盲注,在命令里加sleep 5,通过请求耗时判断命令是否执行;外带数据,把命令输出通过请求发到可控监听地址,实测里最常用的就是 curl 和外部记录服务;写文件,把输出重定向到web目录下的静态文件,再直接访问它。这个靶场特意保留了回显,算是给练习者降低了一部分难度,但我在复现的时候还是刻意走了写文件的路线,因为真实环境里无回显往往是常态。

写文件有个经典坑:目标web目录可能没有写权限,这时候可以尝试写到/tmp下再想办法读取,或者直接把输出拼到已有文件的尾部。不管哪种方式,都要确保重定向符号>和>>不会被过滤,否则方案白搭。

5. 通关之后复盘:这个靶场练了什么,防守方又能补什么

题目最终被我拿下来了,过程前后大概一个小时。但我复盘之后发现,这个靶场真正有价值的不是那个flag,而是它逼你把“字符串”这件事想清楚。接下来我把自己的理解和防守方的改进建议都列一下。

5.1 攻击者视角:真正有价值的是对字符集边界的敏感度

这一整道题,本质是在训练一件事:当你面对一个字符集被大幅压缩的输入点时,你怎么仍然构造出可以执行的行为。这种能力在真实的代码审计里极其有用。很多时候你拿到一个命令执行漏洞,但因为上游已经做了输入校验,直接塞cat /flag根本进不去,反而是这种“无字母”的构造思路能绕过去。

我的体会是,这种题的思考顺序应该固定下来:先确认过滤点在哪一层,再测规则边界,再根据边界选择构造技术。通配符在这个靶场里足够用,但并不意味着取反、异或、自增这些技术没有价值,它们的存在价值是覆盖更严苛的场景。如果你还在为每一道题背payload,那我建议你至少把取反和通配符的原理背下来,这两种是最高频、最稳定的。

5.2 防守者视角:白名单校验和函数管控优于黑名单堆叠

作为一个写代码写过不少漏的人,我其实很清楚这种过滤规则是怎么被绕过成功的。它在设计时的确想到要限制用户输入,但由于是黑名单思路,又只考虑了字母,结果留下了一大片符号区。防守方如果想真正补上这个洞,光把字母禁掉是不够的,因为只要用户输入最终进入了命令执行函数,限制字符集就是一件很吃力的事情。

更有效率的做法是两层配合。第一层,对输入做白名单校验,允许的字符集尽量小,比如只允许数字和固定格式的IP,其他全部拒绝。第二层,在业务层彻底避免把用户输入拼进系统命令,尽量使用封装好的函数和参数化调用。如果实在要用命令执行,也应该维护一份可调用的命令白名单,而不是在黑名单里不断追加字符。另外,日志和告警也值得补上,命令执行函数一旦被调用,就应记录参数摘要和调用栈,这样才能在绕过发生时第一时间发现异常。

5.3 我自己的两个靶场练习习惯

最后分享两个我在练习这类靶场时的习惯。第一个习惯是不急着出结果,先用最笨的办法把规则边界完整测量一遍,把结果写在一张草稿纸上,再开始构造payload。很多人在这一步图快,直接套网上payload,结果被过滤规则变化卡住后,完全不知道问题出在哪。第二个习惯是每道题通关后,把构造思路倒推成一种“防守建议”,写成一句话,比如“这个靶场的问题在于黑名单只覆盖了字母,应该改为白名单”——积累多了,你再看真实代码时就会比大多数人敏锐得多。

还有一个不太起眼但很有用的小技巧:准备一个本地脚本,把取反、异或、自增这几个运算函数提前写好,需要生成无字母payload的时候,直接在脚本里改函数名和参数就行。实战里这个脚本帮我省了很多时间,相当于把一道题的破解时间从半小时压缩到五分钟。

差不多就是这些了。这个靶场让我印象最深的其实不是最后那个结果,而是它把“不能输入字母”这种听起来像玩笑的限制,做成了一个相当完整的练习环境。如果你也在练这块,建议从通配符方案起步,再逐步过渡到取反和异或,把每条路都亲手跑通一遍。下次再遇到类似题目,你会发现自己考虑的不再是“抄哪条payload”,而是“过滤边界逼我走哪条路”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询