来源链接:xdxd.love

作者:xdxd

历史上的安全漏洞

spotify的漏洞

相关资料:

https://labs.spotify.com/2013/06/18/creative-usernames/

spotify的漏洞相比于github的漏洞来说是一个真正的高危漏洞,可以修改任意用户的密码。

github的漏洞

相关资料:

https://twitter.com/GitHubSecurity/status/757686530748125184 https://bounty.github.com/researchers/jagracey.html?ljh

github的邮箱名允许unicode字符,导致可以把找回密码的token发送到攻击者的邮箱。从twitter的讨论来以及自己的测试结果来看提供邮箱服务的产品大部分都是白名单了邮箱的用户名,所以都是不可以使用unicode字符的。测试了网易的企业邮箱和腾讯的企业邮箱,都不可以使用unicode字符。所以只能自己搭一个邮件服务器来测试这个问题。

魔法在哪里

编码的基础知识、unicode编码和同形字

参考资料: http://www.freebuf.com/articles/web/25623.html http://www.irongeek.com/homoglyph-attack-generator.php

了解了编码是二进制和字符之间的映射关系之后,可以了解到unicode字符无非也是一种字符而已,只要程序支持,应该不会有任何问题。为什么这个unicode字符在处理过程中变成了另外一个字符,而且是看起来很相似的字符,难道程序也跟人眼看一样,被相同的外表迷惑了。显然程序处理的是二进制的数据,只要二进制不同,无论外表看起来多么一样,程序都应该可以分辨出来。关于unicode同形字的问题,unicode homoglyphs,其实早就有不少讨论。不过大多是利用同形字来迷惑人钓鱼之类。那么机器怎么也会被同形字迷惑呢。

github的漏洞描述中是这么写的:a flaw in the way email addresses were being normalized to standard character sets when used to look up accounts during the password recovery flow. 在邮件地址标准化成标准的字符集的时候出现问题。

而spoity中对有对详细漏洞的代码分析:可以看到也是对字符串的标准化操作导致unicode字符转换成了与他同形的ascii字符。

unicode转ascii

参考资料:

https://www.peterbe.com/plog/unicode-to-ascii http://savanttools.com/unicode-to-ascii.asp

这里才是最有意思的地方,为什么对unicode字符的标准化会导致unicode转变成了对应的同形字。电脑应该是只看二进制,又不是跟人一样,会被同形的字迷惑。通过搜索发现,原来有专门的unicode转ascii函数,对unicode于同形的ascii之间有对应的map。

比如github中提到的例子,转成对应的ascii就刚好对的上。 image

漏洞原理

漏洞的原理比较有意思,主要的条件是用户的id支持unicode。比如两个ID:mike@example.org vs mıke@example.org 然而在一些业务逻辑中会对用户id做标准化的操作,比如重置密码的时候,mıke@example.org标准化之后变成了mike@example.org,从而重置了mike@example.org的密码。

所以关键的条件就是用户识别的ID(包括可以用来登录的邮箱)支持unicode。而在某些业务逻辑中有对unicode字符进行所谓标准化,转换成了对应的ascii。其实假如某些业务逻辑不支持unicode,直接丢掉了那部分unicode,应该也是一样的,目前没有看到类似的案例。导致在这部分业务逻辑中出现了越权的问题。典型的场景就是修改密码,导致可以修改他人的密码。

对微信的测试记录

实际中的场景比较少,大部分产品都会对用户登录名做白名单限制了。经过一番寻找发现微信的绑定邮箱是支持unicode的,而且是邮箱的用户名和域名部分都支持unicode。当然最后的测试结果是不存在这个漏洞。作为一个典型的测试过程记录一下。

搭建支持unicode用户名的邮件服务器

使用iredmail方便快速搭建邮件服务器。然后发现iredmail对用户名也是有过滤的。代码在iRedAdmin-0.6.1/libs/iredutils.py 中的is_email方法。修改该方法直接返回True,不检查邮件用户名。

######################
# Regular expressions.
#
# Email.
reEmail = r'''[\w\-][\w\-\.\+\=]*@[\w\-][\w\-\.]*\.[a-zA-Z0-9\-]{2,15}'''

# Domain.
reDomain = r'''[\w\-][\w\-\.]*\.[a-z0-9\-]{2,15}'''

# End Regular expressions.
####

#####################################
# Pre-defined values of SQL functions.
sqlUnixTimestamp = web.sqlliteral('UNIX_TIMESTAMP()')

#####

##############
# Validators
#
INVALID_EMAIL_CHARS = '~!#$%^&*()\\/\ '  
INVALID_DOMAIN_CHARS = '~!#$%^&*()+\\/\ '

def is_email(s):  
    #return True 直接返回true,不检查email名称合法性。
    s = str(s)
    if len(set(s) & set(INVALID_EMAIL_CHARS)) > 0 \
       or '.' not in s \
       or s.count('@') != 1:
        return False

    reCompEmail = re.compile(reEmail + '/span>, re.IGNORECASE)
    if reCompEmail.match(s):
        return True
    else:
        return False

image

然后发现依然无法收到邮件,发现服务器返回505。拒绝了含有unicode字符的邮箱。

image

采用的解决方式在数据库中插入了一条域名的邮箱,转发到一个正常的邮箱。这样可以接受这个域名所有的邮件。

image

因为iredmail默认开启了SSL,为了调试,查看具体的smtp信息,关闭了SSL,直接明文传。

绑定邮箱以及重置密码

微信邮箱绑定支持unicode字符的邮箱。 image

绑定邮箱后,重置密码。

image

正常重置了自己的密码。链接中的email字段是base64编码的。解码出来是含有unicode字符的邮箱。所以微信的重置密码业务逻辑中没有标准化unicode字符这个处理。不存在unicode同形字引起的漏洞。