惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Jina AI
Jina AI
酷 壳 – CoolShell
酷 壳 – CoolShell
小众软件
小众软件
S
Schneier on Security
人人都是产品经理
人人都是产品经理
博客园_首页
L
LangChain Blog
D
Docker
B
Blog
阮一峰的网络日志
阮一峰的网络日志
D
DataBreaches.Net
C
Check Point Blog
WordPress大学
WordPress大学
博客园 - 聂微东
P
Palo Alto Networks Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Tailwind CSS Blog
腾讯CDC
Cisco Talos Blog
Cisco Talos Blog
A
Arctic Wolf
C
Cybersecurity and Infrastructure Security Agency CISA
Help Net Security
Help Net Security
The Last Watchdog
The Last Watchdog
有赞技术团队
有赞技术团队
美团技术团队
aimingoo的专栏
aimingoo的专栏
博客园 - 叶小钗
爱范儿
爱范儿
S
Security @ Cisco Blogs
MyScale Blog
MyScale Blog
C
Cisco Blogs
P
Proofpoint News Feed
I
Intezer
Last Week in AI
Last Week in AI
The Register - Security
The Register - Security
IT之家
IT之家
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
H
Help Net Security
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Latest news
Latest news
M
MIT News - Artificial intelligence
N
News | PayPal Newsroom
G
Google Developers Blog
Cloudbric
Cloudbric
T
Troy Hunt's Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Recorded Future
Recorded Future
Hugging Face - Blog
Hugging Face - Blog
AWS News Blog
AWS News Blog

离别歌

Codex Security代码审计初体验 | 离别歌 Apifox CDN 供应链投毒事件简单复盘 | 离别歌 React2Shell攻防笔记:原理挖掘与价值15万美元的WAF绕过思路 | 离别歌 本地多语言AI字幕组:whisper实战教程 | 离别歌 扒一扒h2database远程代码执行 | 离别歌 ClassPathXmlApplicationContext的不出网利用 | 离别歌 从HertzBeat聊聊SnakeYAML反序列化 | 离别歌 如何巧妙构建“LDAPS”服务器利用JNDI注入 | 离别歌 使用eUICC卡片将手机变成eSIM手机 | 离别歌 当Nashorn失去括号:非典型Java命令执行绕过 | 离别歌
UTF-8 Overlong Encoding导致的安全问题 | 离别歌
2024-02-23 · via 离别歌

「代码审计」知识星球中@1ue 发表了一篇有趣的文章《探索Java反序列化绕WAF新姿势》,深入研究了一下其中的原理,我发现这是一个对我来说很“新”,但实际上年纪已经很大的Trick。

0x01 UTF-8编码原理

UTF-8是现在最流行的编码方式,它可以将unicode码表里的所有字符,用某种计算方式转换成长度是1到4位字节的字符。

参考这个表格,我们就可以很轻松地将unicode码转换成UTF-8编码:

First code point Last code point Byte 1 Byte 2 Byte 3 Byte 4
U+0000 U+007F 0xxxxxxx
U+0080 U+07FF 110xxxxx 10xxxxxx
U+0800 U+FFFF 1110xxxx 10xxxxxx 10xxxxxx
U+10000 U+10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

举个例子,欧元符号€的unicode编码是U+20AC,按照如下方法将其转换成UTF-8编码:

  • 首先,因为U+20AC位于U+0800和U+FFFF之间,所以按照上表可知其UTF-8编码长度是3
  • 0x20AC的二进制是10 0000 1010 1100,将所有位数从左至右按照4、6、6分成三组,第一组长度不满4前面补0:0010000010101100
  • 分别给这三组增加前缀11101010,结果是111000101000001010101100,对应的就是\xE2\x82\xAC
  • \xE2\x82\xAC即为欧元符号€的UTF-8编码

image.png

那么,了解了UTF-8的编码过程,我们就可以很容易理解Overlong Encoding是什么问题了。

Overlong Encoding就是将1个字节的字符,按照UTF-8编码方式强行编码成2位以上UTF-8字符的方法。

仍然举例说明,比如点号.,其unicode编码和ascii编码一致,均为0x2E。按照上表,它只能被编码成单字节的UTF-8字符,但我按照下面的方法进行转换:

  • 0x2E的二进制是10 1110,我给其前面补5个0,变成00000101110
  • 将其分成5位、6位两组:00000101110
  • 分别给这两组增加前缀11010,结果是1100000010101110,对应的是\xC0\xAE

0xC0AE并不是一个合法的UTF-8字符,但我们确实是按照UTF-8编码方式将其转换出来的,这就是UTF-8设计中的一个缺陷。

按照UTF-8的规范来说,我们应该使用字符可以对应的最小字节数来表示这个字符。那么对于点号来说,就应该是0x2e。但UTF-8编码转换的过程中,并没有限制往前补0,导致转换出了非法的UTF-8字符。

这种攻击方式就叫“Overlong Encoding”。

Overlong Encoding实际上很早就被提出了,早到那时候我还没开始学安全。很多语言在实现UTF-8的转换时,会对这个攻击方式做一定检查。比如,Python中如果你想将0xC0AE转换成点号,就会抛出异常:

b'\xC0\xAE'.decode()

image.png

但我们质朴刚健的Java生态,在很多地方是没有对其进行防御的,这就导致了一些安全问题。

0x03 GlassFish 任意文件读取漏洞

如果对安全熟悉的读者,看到前面的0xC0AE,其实应该很快想起来一个经典漏洞——GlassFish 任意文件读取漏洞

这个漏洞就是在URL中使用%C0%AE来代替点号.,绕过目录穿越的限制,导致任意文件读取漏洞:

image.png

其原理就是GlassFish在路径解码时使用UTF-8编码,很典型的Overlong Encoding利用。

0x04 利用Overlong Encoding绕过WAF

回到本文开头的文章,其实@1ue 是完全在分析反序列化代码的时候发现了这个问题,换句话说,就等于把Overlong Encoding攻击重新发现了一遍,还是挺厉害的。

Java在反序列化时使用ObjectInputStream类,这个类实现了DataInput接口,这个接口定义了读取字符串的方法readUTF。在解码中,Java实际实现的是一个魔改过的UTF-8编码,名为“Modified UTF-8”。

参考其文档可以发现,“Modified UTF-8”类似于MySQL中的UTF8,只使用三个字节来表示:

image.png

但其三字节以内的转换过程是和UTF-8相同的,所以仍然继承了“Overlong Encoding”缺陷。

攻击者可以将反序列化字节流里一些字符按照“Overlong Encoding”的方法转换成非法UTF-8字符,用来绕过一些基于流量的防御方法。

我写了一个简单的Python函数,用于将一个ASCII字符串转换成Overlong Encoding的UTF-8编码:

def convert_int(i: int) -> bytes:
    b1 = ((i >> 6) & 0b11111) | 0b11000000
    b2 = (i & 0b111111) | 0b10000000
    return bytes([b1, b2])


def convert_str(s: str) -> bytes:
    bs = b''
    for ch in s.encode():
        bs += convert_int(ch)

    return bs


if __name__ == '__main__':
    print(convert_str('.')) # b'\xc0\xae'
    print(convert_str('org.example.Evil')) # b'\xc1\xaf\xc1\xb2\xc1\xa7\xc0\xae\xc1\xa5\xc1\xb8\xc1\xa1\xc1\xad\xc1\xb0\xc1\xac\xc1\xa5\xc0\xae\xc1\x85\xc1\xb6\xc1\xa9\xc1\xac'

参考链接: