本文发表在NDSS22,原文链接ndss2021_3B-4_24212_paper.pdf
本文围绕短信验证码单因素认证的安全性问题,基于Local Attack的攻击模型,针对从短信OTP在被发送到受害者手机中,到最终输入到目标app中的这一过程,提出了多种有趣的攻击方式来窃取短信OTP。
SMS 1FA OTP全称(SMS One-Factor One-Time Password),即短信单因素一次性密码。通俗的讲就是仅使用短信验证码作为验证的一种认证方式,在现代智能手机普及的今天,这种认证方式并不罕见,在各大移动应用中非常普及。这种认证方式极大的为用户带来了便利性,但是同时也存在一些潜在的安全隐患。
以往的研究中,针对短信验证码的攻击五花八门:
在安卓系统版本的迭代过程中,从安卓8开始出现了多个不同版本的短信验证码相关的API,旨在提高用户的使用体验,同时确保安全性。对于这些新的API能够对目前已有的短信验证码的安全体系产生多大的影响,目前尚未有人进行研究,本文旨在弥补这一Gap。
本文的Contribution如下:
本文对于系统完整性的要求也很低,即使攻击者在一个具有正常完整性的系统中即可,这意味着app之间的隔离性良好,进程间通信(IPC)的信道也无法被劫持。此外,本文也不会利用以往已经发现的,基于UI劫持的攻击方式。
短信验证码的工作流程如下图所示:
这套流程是否安全有一定的条件:
本文假设上述条件均满足,而本文的攻击集中于上图中的Step 3,通过Local Attack窃取短信验证码。
合法应用获取短信验证码的方式分为三大类:
通过用户交互获取短信验证码的方式也分为三类:
通过申请短信读取权限来读取短信,具体由两类短信权限:
这类权限被认为是高危权限,因此有着来自系统和应用商店的双重限制
这类权限在app运行时需要动态的请求权限,会弹框询问用户是否同意授权
申请这类权限的app在发布到Google Play前,需要先接受人工审核,只有满足以下条件才能通过审核:
在安卓高版本(安卓8+)中,提出了几个新的API专门用来处理短信验证码,它们的核心思想是:通过服务器在短信中附加标识性字符串来指定仅将该短信转发给特定的app
。通过使用这些API,app可以无需申请其他任何的短信相关API而自动完成短信验证码的填充过程。具体有以下三类短信验证码API:
SMSRetriever工作原理如上图所示,这个api的标识性字符串被成为hashcode,其计算方式如下:
SMS Token 和 SMS Token+的工作流程均如上图所示,两者的区别如图modernAPIs
所示,后者的参数多了一个prefixs
可以对短信内容的前缀做筛选。此外在文档上无任何区别,但是下文发现事情没那么简单。
针对每一种获取短信验证码的方式,作者均提出了特定的攻击方式。
针对通过用户交互来获取验证码的方式,作者提出了Deception attack
,具体步骤如下:
上述攻击无需申请任何短信相关权限,攻击成功的原因是受害者在点击“请求验证码”后通常情况下会收到,且仅会收到一条短信验证码,而此时正好合法App的服务端给他发了一条,那么就符合他的预期,导致他粗心大意没有判断这条短信验证码的来源是否和将要填入的app相匹配。
对于这种攻击能产生多大的效果,作者进行了User Study,通过设计一个虚拟环境来模拟app注册和登录过程供志愿者进行操作,收集其交互结果并分析,其虚拟环境以及结果如下图:
不论是手动输入还是copy验证码,其结果的占比均相差不大,而且比例较高,受骗人占比在45%~71%之间。尽管在弹框中会明确提示哪个app即将读取短信,且同时显示了短信内容,但是还是有这么多人上当,更别说作者还指出,想要篡改弹框中显示的app名称,其实也并非难事。
Weakness 1:缺少可靠的方法来帮助用户鉴别到底是哪个app想要读取短信
作者对如何绕过申请读取短信权限时受到的限制进行了研究
系统层面的限制和上面的Weakness 1
差不多,都是弹框,因此作者又做了一个User Study,设计了问卷来询问志愿者:在弹框后点击“Allow”会发生什么?问卷及其结果如下:
结果显示,很多用户虽然知道点击“Allow”以后App会读取短信,但是不知道它的潜在危害是能够窃取用户账号。
Weakness 2:系统提示的信息没有向用户解释清楚潜在威胁
对于Google Play有人工审核这一限制,能够自然而然的想到:首先提交一个合法的版本能够申请读取短信权限且通过人工审核,然后再发布一个更新版本,能够申请读取短信权限但是不满足通过人工审核的要求。结果显示,更新的版本也能成功发布在Google Play中,说明人工审核不会对更新的版本进行审核。
Weakness 3:在版本更新时,缺少人工审核
除了申请读取短信权限以外,还有一种侧信道的方式能够读取短信内容。接收到短信时,通常都会显示在通知栏,因此,申请读取通知栏权限可以起到同样的效果,以此可以绕过申请读取短信权限时的诸多限制。但是由于读取通知权限在申请时,会要求用户手动的去设置中将该app的权限打开,操作较为复杂,因此该权限在系统层面较难获取,但是却无需受到来自应用市场的人工审核。
Weakness 4:来自应用市场和系统的限制没有对齐
作者还对每个Modern API提出了可行的攻击方式
从上文对SMS Retriever的介绍可知,这个API在理论上是比较安全的,但是实际上,由于该API的说明文档不够清晰,造成了许多开发者对这一API产生了误用。具体来说就是:他们没有将Hashcode硬编码在服务端并每次由服务端发送OTP时附加该Hashcode,相反的,他们在客户端计算或者硬编码Hashcode,然后发送给服务端,在由服务端将接收到的Hashcode附加在OTP中。这一情况导致了如下图所示的攻击:
Weakness 5:SMS Retriever API容易被误用
这个API在根本上就是不安全的,原因是它的标识性字符串(Token)由客户端生成,且每次都随机生成,因此服务端无法区分该Token是否来自合法的App。因此导致了如下图所示的攻击:
Weakness 6:SMS Token在设计上是有缺陷的
理论上,由于文档说明中SMS Token与SMS Token+的唯一区别是后者多了个前缀filter,因此两者的安全性应该完全一致。但是实际上,SMS Token+的文档有误,或者说,没有说明两者最重要的区别:SMS Token+的Token并不是每次随机生成的,而是与SMS Retriever相似,基于包名和签名生成的。因此其正确的使用方式应该与SMS Retriever相同,然而如果开发者按照文档说明来使用这个API,结果就是和SMS Token一样会受到攻击。
Weakness 7:SMS Token+的文档推荐与SMS Token相同的使用方式
作者还列举了两个比较重要的安全问题
短信验证码一旦进入到信箱中,就有可能被第三方app读取到,因此尽量不要让短信验证码进入到信箱中。根据图modernAPIs
可知,SMS Retriever始终会将短信存入信箱,而对于SMS Token和SMS Token+,一条短信只有在满足两个条件的同时才不会被存入信箱:
即便如此,由于很多app的服务端会接收客户端的Token并返回,因此可以通过随意设置一个无用Token发给服务端,使得不满足上述的第二个条件,来使短信存入信箱中。
Weakness 8:短信验证码不应该存入信箱中
回顾以下SMS Retriever的Hashcode计算方式
在第二步中,将生成的SHA256转化为base64字符串后仅截取了结果的11位字符串作为Hashcode,因此将这11位Hashcode解码后,有效的hash位数为66bits。在NIST guidelines中要求一个可靠的hash位数应该不小于224 bits,否则容易产生hash碰撞。作者通过实验证明,两个hash碰撞的app能够顺利的发布在Google Play中。
Weakness 9:Hashcode的有效位数不符合NIST guidelines
最后作者做了一个大规模的实验
第一步是通过静态分析来筛选出可能遭受攻击的app,分为以下几个步骤:
通过静态分析的app会进入动态确认的环节:
最终结果如下:
这个app在韩国很火,93%的智能手机用户都在使用。这个app用的是SMS Retriever这个API但是后端实现逻辑有上文中提到的问题。具体攻击流程如下:
该App开发者在被作者告知漏洞后已修复这个问题。
这个软件大家都熟,Google Play中有1亿下载量,可惜用了SMS Token这个API,该App开发者在被作者告知漏洞后已修复这个问题。
这个是一个专门给开发用于集成短信验证码功能的SDK,其内部错误的使用了SMS Retriever API还明确的教开发者要硬编码在客户端作为参数传递,不仅如此还使用了SMS Token这个API,此外,开发者虽然在被作者告知漏洞后承认了这一问题,但是至今未修复。
最后,作者对现有的Modern API提出改进。作者认为,一个套理想的短信验证码机制一个满足以下条件:
根据以上几点作者提出了SMS Retriever API的修改版(解决Weakness 6和7):
最后附上我演讲时用的ppt:SMS OTP