Unicode安全

犹豫了挺久要不要把这篇文章放上来,猛然发现距离上一篇文章已经过了一个月了,那就拿这篇文章来凑凑数吧,反正也是一个偏门的小众领域

Unicode是什么

对于unicode是什么,参见 https://zh.wikipedia.org/wiki/Unicode
对于unicode在域名上的规范(即IDN),参见 https://www.unicode.org/faq/idn.html

Unicode的安全隐患

Unicode带来的最大的安全隐患就是欺骗,或者说 spoofing

A common security issue is ‘spoofing’, the deliberate misspelling of a domain or user name to trick unaware users into entering an interaction with a hostile site as if it was a trusted site.

许多不同的unicode字符在字形上十分相似,例如一个正常人无法分别出аррӏе.com和apple.com的差别(当然你在这里会发现有一丝丝的差别,这是因为有对比,而在地址栏中没有对比的情况下,几乎没人可以发觉不同之处,这其实也是一个可以利用的地方,你记忆中的字形和呈现在地址栏中的没有与之对比的相似字形,往往是无法分辨的),而实际上,他们的每一个字符都是截然不同的,第一个аррӏе,是全由西里尔字符组成的。

虽然欺骗是unicode最常出现的问题,但需要注意的是,在某些情况下,unicode可能会造成更大的危害
例如这一个十分有趣的例子:https://labs.spotify.com/2013/06/18/creative-usernames/
问题很简单,一个网站允许用unicode作为用户名,他使用一个python库来判断用户名是否幂等

这个函数会把大写转换为小写,把类似的unicode字符做一个与chrome的地址栏里相似的转换,举个例子
BIG会被转换为big, ƁƗƓ会被转换为ɓɨɠ
他们对用户名是否重复的判断是执行一次这个函数然后进行比对 ,例如AAA会被变为aaa则和之前已经注册过的aaa重复 ,但是这里出现了一个错误,注册一个ᴬᴬᴬ,经过函数处理后变成了AAA,因为与aaa不同所以注册成功,而在用户点击重置密码的连接的时候,这个函数再次被执行了一次,AAA变成了aaa,导致用户aaa的密码被越权修改
文章末尾的一些总结:

  1. 必须了解用户输入的究竟是什么,经过层层剥离后又是什么
  2. 如果要支持unicode,有非常多的陷阱

在这个例子中可以看到的是,网站的开发者对unicode机制的错误处理导致了一个严重逻辑漏洞的产生,但这种例子毕竟是少数,unicode更多的安全危害还是来自于spoofing,而这点在域名中被应用到了极致,试想一个钓鱼网站的域名与真正的网站域名字形完全相同或者相似会带来怎样的危害

IDN spoofing

在对域名unicode欺骗的处理上,Chrome目前较为领先,当然我认为他们有一点过于积极了,导致了一种宁可错杀一千也不放过一个的现状。unicode spoof与传统的url spoof最为显著的区别就是他的不好界定性,因为字体、操作系统的不同,使得一个字符在windows的chrome上可以和mac上的有着完全不同的字形,而到了移动端甚至又会发生变化。为了在一定程度上缓解这样的情况,Chrome维护了一个top_domain_list,把一些有知名度的域名放进去,如果一个域名被认定为与top_domians中的域名相似,则强制其在地址栏中显示为punnycode,而如果能绕过这重重机制,则被认为是一个unicode spoof漏洞,虽然我现在仍然认为,top_domains 还是有一点太多了。
那我们就来看看,Chrome是如何处理这些unicode欺骗的吧。

Chrome的idn_spoof_checker

如果需要探讨Chrome的unicode spoof问题,理解其规则是十分重要的,其中,对unicode spoof的判断大多数在idn_spoof_checker.cc这个文件里,我们把第一个断点断在IDNSpoofChecker::IDNSpoofChecker ,往上回溯调用链:

其中IsIDNComponentSafe函数的注释:

可以注意到的一点是,整个调用链中url都是以punnycode显示的,经过IDNSpoofChecker之后再决定是否以unicode显示,这是非常正确的,因为就算你绕过了整个checker函数,最后呈现的也一定是punnycode
接着进入IDNSpoofChecker函数,查看所有的判断规则
1.

关于字符的混合,只允许拉丁字符与一个CJK(中日韩)字符混合,其余所有字符的混合都不允许
2.

跟进去,这一个函数移除了所有不允许出现在url中的字符,
首先添加了unicode规范中建议移除的字符

移除U+0338,因为他看起来类似/
移除U+2010,因为他容易与-混淆
移除U+2019(’),因为他与其他字符相邻时容易被忽略
移除U+2027(‧),容易与·混淆
移除U+8231,同上
移除U+12448,与=混淆
移除U+699和U+700,与单引号和双引号混淆
然后是移除macos下会渲染成空白的字符:

移除基本不会使用的LGC字符:

allowed_set构建完成
3.
之后将一些特殊字符放在一些集合里
首先处理在IDNA 2003版本和2008版本中定义不同的四个字符,映射U+00DF和U+03C2为b和s,然后移除U+200[cd]因为他们是空白字符

将不是ascii的拉丁字符放在一个集合里:

将“危险部分放入一个集合,分别是日本的kana字符和组合用字符

将西里尔字符中与拉丁字符相似的部分放入一个集合:

接着是一个用来优化的步骤,用来判断是否需要比对top_domains和当前的url,如果存在不在集合内的字符则不需要比对

接着是变音符号

然后是一个比较重要的步骤,包含了所有其余类似字符的映射集合,大多数研究者提交的unicode spoof都会被放进这个mapper

然后idn_spoof的checker构建完成,继续进入url_formatter阶段进行url的格式化,
随后到了真正判断能否以unicode显示一个url的环节:IDNSpoofChecker::SafeToDisplayAsUnicode
首先是这个判断,如果域名中有kana字符则返回false,如果域名全为与拉丁字符相似西里尔字符则直接返回false,如果有变音符则直接返回false,如果这三个判断都为true并且没有字符之间的混合,则直接标记当前输入的url为安全的

如果有字符之间的混合,则进入下一个判断,不允许非ascii的拉丁字符与其他非拉丁字符混合

最后是前面说的“危险部分”的处理,这里的注释十分详细了

在上面的处理都完成后,会比对当前输入的url和top_domians里的url是否有重合,如果有重合则显示punnycode

规则带来的安全隐患

经过上面一步步看过来,看起来chrome给出了一个比较完整的解决方案,但同时在阅读的过程中,其实已经可以发现一些可能出现或是已经出现的unicode漏洞类型了,我这里给出现过的unicode spoof漏洞分一个类,并在后面给出几个案例以便说的更清楚

  1. 常规的对[a-z]的spoof,即找到与之相似的字符进行的欺骗,而其中又有whole-script spoofing和混合字符的spoof,最经典的就是之前提到的,全由西里尔字符组成的аррӏе.com,可以看https://bugs.chromium.org/p/chromium/issues/detail?id=683314
  2. 组合字符带来的欺骗,主要是组合字符与前一个字符配合组成新的字形导致的spoof,例如这一个例子:https://bugs.chromium.org/p/chromium/issues/detail?id=750239,是由i j ı配合组合字符中的上位点U+0307造成的欺骗
  3. 特殊的ascii字符的欺骗,例如-的spoof以及/的spoof,之前说的在mac下渲染为空白的字符,可能还会出现.的spoof和@的spoof
  4. RTL字符造成的欺骗
  5. 顶级域名的欺骗,这个比较特殊,来源于我之前提交的一个unicode spoof,其实不能算是一个正规的顶级域名,是co.com这个伪顶级域名的spoof,参见http://registry.co.com/ ,这个网站旨在让一些无法在.com上注册的域名注册.co.com来代替,例如我想注册qq.com,但是无法注册因为这已经被注册了,那我可以退而求其次去注册qq.co.com,这也造成了如果有一个对co这两个字母的spoof,就可以造成对.co.com下的任意子域的钓鱼攻击,包括其注册网站http://registry.co.com/,我提交的是缅甸字符的ငဝ,但是这个漏洞并不被chrome承认,因为他们还没有对这类“顶级域名”的欺骗的处理机制,并且co.com不在其设置的top_domains中,所以直到现在ငဝ.com都没有被block,当时这个洞是因为其余的一些的不那么相似的缅甸字符拿到了bounty,个人认为有一种钦定的感觉,但是我还是觉得co.com应该加入top_domains里,在我写这篇文章的时候,我发现registry.ငဝ.com这一个域名已经出现了,是一个印度人注册的

思路

在chrome对unicode进行过滤的很多个步骤之中,每一步都有其理由,但同时每一步也明确指出了可能的攻击思路和欺骗方法,接下来我们再按着上面调试走过的流程,一步一步看chrome的规则中每一步会带来什么攻击思路
第一个步骤,允许ascii字符与一个CJK字符混合,CJK字符我们都很熟悉,中日韩三国的文字简直和ascii天差地别,但是这里仍然会有问题,例如-的spoof以及组合字符的spoof
第二个步骤,移除与- / · =相似的字符,这就明明白白的告诉了你,你可以去找其余的,与- /这些字符相似的字符
第三个步骤,移除在mac渲染为空白的字符,有没有在Windows, Linux, iOS, Android上渲染为空白的字符呢?
……
剩下的不写了2333 写不动了

referer

  1. http://www.unicode.org/versions/Unicode11.0.0/UnicodeStandard-11.0.pdf
  2. http://www.unicode.org/reports/tr36/
  3. http://www.unicode.org/reports/tr39/
  4. https://www.unicode.org/faq/security.html
  5. https://www.unicode.org/reports/tr46/
  6. https://www.unicode.org/faq/idn.html
  7. https://unicode-table.com/cn/#control-character
  8. http://unicode.org/cldr/utility/confusables.jsp?a=a&r=None

发表评论