Host/Split Exploitable Antipatterns in Unicode Normalization
Background
Unicode -> ASCII 要经过两个过程
- 格式化(标准化字符)
- 微小的编码(将Unicode转换成ASCII)
格式化(Normalization)
将多个类似的Unicode字符统一格式化成另一个Unicode字符
hostname
中任意的Unicode字符都会转换成Compatibility Composition(KC)
格式。因为Unicode字符的不同组合都会影响到视觉上的一些含义,这一步统一格式化操作是为了保证hostname中的字符能唯一的双向渲染出来。
微小的编码(Punycoding)
将Unicode编码转换成ASCII码
经过上一步的格式化操作后,就可以用Punycode
把Unicode字符变成由ASCII Compatible Encoding Prefix(xn--)
、原始顺序的字符、一连串的ASCII字符组成的重构符(用于给机器重构出原来的non-ascii字符部分)
这种转化其实有点像中文域名在url中的处理方式
此外,ASCII -> Unicode 也只需要将punycoding逆向进行一次即可得到。
三种IDNA标准
IDNA2003 / IDNA2008 / IDNA2008 + UTS46
每一个标准都允许不同格式的Unicode字符和定义了不同的格式化规则
The HostSplit Vulnerability
当Unicode字符能直接格式化成合法的ASCII字符时,就不用经过Punycode
转换了,但是这种情况下可能就会出现漏洞点了。
Java
Python
>>> from urllib.parse import urlsplit, urlunsplit
>>> url = 'http://0akarma.c℀.com/some.txt'
>>> url
'http://0akarma.c℀.com/some.txt'
>>> parts = list(urlsplit(url))
>>> host = parts[1]
>>> host
'0akarma.c℀.com'
>>> newhost = []
>>> for h in host.split('.'):
... newhost.append(h.encode('idna').decode('utf-8'))
...
>>> parts[1] = '.'.join(newhost)
>>> finalUrl = urlunsplit(parts)
>>> finalUrl
'http://0akarma.ca/c.com/some.txt'
>>>
从example可以看到,特定的unicode字符经过.encode('idna').decode('utf-8')
会转换成合法的ASCII字符,而无需经过Punycoding
,这样就
另一个变种就是
>>> from urllib.parse import urlparse
>>> r='http://bing.com'+u'\uFF03'+':password@products.office.com'
>>> o = urlparse(r)
>>> o.hostname
'products.office.com'
>>> a = r.encode("IDNA").decode("ASCII")
>>> a
'http://bing.com#:password@products.office.com'
>>> o = urlparse(a)
>>> o.hostname
'bing.com'
这个点其实Zedd
师傅已经在SUCTF2019率先出了个题了~
The HostBond Vulnerability
IDNA2008
允许Unicode转ASCII时使用\u200D
(Zero-Width- Non-Joiner)和\u200c
(Zero-Width Non-Joiner),所以如果有一个hostname长的像这样'0a' + \u200D + 'karma.com'
,那它经过处理之后就会变成xn--0akarma-469d.com
这样的punnycode,但是如果我们将这样的punnycode经过A-labels到U-labels的转换时,你就会发现hostname变成了0akarma.com
,中间那个\u200D
不可见了(其实他还在,只是看不到)。这样就会导致HostBond
漏洞的出现。
这样可以造成的后果包括但不限于:
用户无法用肉眼去分别url,容易被钓鱼网站利用
如果服务端存在ASCII
->Unicode
->ASCII
的处理逻辑,经过HostBond
可以达到绕过目的
Best Practices
如何发现HostSplit
原作者给了两种方式,一种是在hostname中加入可以跳过Punnycode编码的字符,然后利用dns查询通过判断返回结果来确定是否存在该漏洞,eg:http://canada.c℀.bing.com
,如果dns查询结果为canada.ca
则代表存在该漏洞,反之则不存在。
第二种我认为更简单一些,举例说明:http://a.com/X.b.com
,如果访问该url时返回的结果是会请求a.com
下的X.b.com
文件,就代表存在该漏洞,反之如果访问该url时不会发送请求或发送一个请求到包含Punnycode的域名则代表不存在该漏洞。
如何发现HostBond
HostBond出现的地方大多是在UI里,因为需要将A-labels转换成U-labels,然后渲染出来。
修复建议
- 统一url是通过ASCII解析还是Unicode,不要两个混在一起用
- 在解析url之前先进行(黑名单)检测
- 用
STD3ASCIIRule
- 过滤
加餐列表
这个其实可以根据实际情况自己Fuzz
Summary
虽说这个Host-Series
系列漏洞在某种意义上来讲比较鸡(少)肋(见),但是这也不失为一种拓宽自己思路的方式,反正就是各种膜大佬的骚姿势。
References
Host/Split Exploitable Antipatterns in Unicode Normalization