JAVA安全网JAVA安全网

生命不息,折腾不止。
--JAVA人自留地。

Ghost Bits详解

转载自微信公众号,作者:珂技知识分享 | 原文:点击查看

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

jsp挑战

还记得这个挑战吗?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安全网 » Ghost Bits详解

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址