U2F安全协议分析

阅读量310830

|评论1

|

发布时间 : 2018-11-24 10:00:48

U2F,Universal 2nd Factor,通用第二因素,属于2FA的一种,意即在用户名和密码的基础上再增加一种验证手段,以增强用户验证的安全性。

出于研究的用途,笔者买了一支yubico 的usb key,并对其进行了测试和研究。以下是笔者的研究笔记,供对U2F感兴趣的朋友参考。

方法

对于想要测试U2F朋友,yubico已经提供了测试用的网站,网址是:

https://demo.yubico.com/u2f

在测试网址上可以测试注册和登录功能。

U2F可以理解为一种协议标准,目前该标准由FIDO联盟进行维护和设备认证,也就是说FIDO联盟制定了U2F协议标准并对该标准进行维护,同时对厂商生产的U2F设备进行认证。

U2F标准规定了U2F设备可以使用USB接口,蓝牙,NFC的方式与PC机进行通信。通信过程需要三方的支持:网站,U2F客户端,U2F设备。目前常用的U2F客户端为浏览器。

U2F的业务流程描述如下:U2F主要支持两种业务,注册和登录。既然是双因素认证,首先,用户使用浏览器(U2F客户端)输入自己的用户名和密码在网站注册。当注册成功后,网站向浏览器发起U2F设备注册请求,浏览器收到网站的请求后,将请求打包发给U2F设备,设备进行处理,在设备内针对每个不同的网站都生成一对ECC曲线的公私钥,并将用户公钥传给网站。

接着是登录过程,登录过程必须先使用用户名和密码登录,用户名和密码验证正确后,网站使用之前注册的信息向浏览器发出登录验证请求,浏览器向U2F设备转发。U2F使用私钥对协议数据进行签名,网站使用注册时留下的公钥进行验签,以鉴别用户身份。验签成功,完成2FA过程。

目前,chrome浏览器已经实现了对U2F协议和U2F设备的支持。

笔者的测试方法就是使用chrome浏览器访问https://demo.yubico.com/u2f

执行网页上的注册和登录操作,同时,使用Wireshark监听USB通信(是的,Wireshark不仅能监听网络包,还能监听USB包,安装Wireshark记得把USBPcap的选项选上),同时参考U2F协议的文档,对协议进行分析。

因为笔者手上仅有USB接口的U2F设备,因此以下分析仅针对U2F-HID协议进行分析。

 

协议分析

U2F-HID协议由底至顶可以分为三层,USB层,U2F-HID层,U2F-Raw Message层。因为我手上的yubikey使用USB接口与PC机进行通信。因此,首先必须实现USB协议,接下来是U2F-HID层。USB层,U2F-HID层都与接口有关。最后一层是与接口无关的。不管使用的USB,蓝牙还是NFC都是一样的。

首先是USB层的实现,一款USB-HID设备想要和PC进行通信,首先必须实现最基本的USB协议,响应主机的USB配置请求。

如上图所示,主机向yubikey发送设备描述符请求和接口描述符请求,yubikey响应这些请求,告诉主机自己是一款USB-HID设备及使用哪一个USB端口。因为在这里USB协议仅仅是U2F协议的一个载体,不影响U2F协议的分析,而且在网上有很多针对USB协议的分析文章,这一部分我们就略过不表。

接下来是U2F-HID层。我们先来介绍下U2F的协议。

根据U2F协议要求,每个U2F-HID包的最大大小是64个字节,当一个包的数据长度超过64个字节时,需要将包进行拆分。拆分出来的包格式也不一样,第一个包的格式如下:

偏移 长度 缩写 描述
0 4 CID 通道ID
4 1 CMD 命令类型
5 1 BCNTH 长度高字节
6 1 BCNTL 长度底字节
7 N DATA 数据

从上表我们可以看出,第一个包的头7个字节已经被用掉了,还剩下64-7=57个字节用于存放数据。当包头的BCNTH*256+BCNTL>57的时候,就说明还有数据剩余,在这一个包里面装不下。接下来剩下的包都必须按如下的格式封装:

偏移 长度 缩写 描述
0 4 CID 通道ID(0~127)
4 1 SEQ 包序号
5 N DATA 剩余数据

从上表可以看出,在一段数据中剩下的包序列从0开始,最大可以达到127,所有一共有128个可用序列。而可用数据长度为64-5=59个字节。
所以,一段数据的最大长度为57+128*59=7609。

在上面两张表中,通道ID就是通信双方进行通信的通道ID,一整个通信过程使用一个通道ID。当通信信道还没有建立的时候,使用通道ID ffffffff,表示此时使用的是广播用的通道ID。

CMD命令类型是如下四种中的一个:

命令类型 描述
U2FHID_MSG 传递数据
U2FHID_INIT 初始化
U2FHID_PING 状态监测
U2FHID_ERROR 告知错误

BCNTH,BCNTL,两个字节用于告知数据长度,这个很好理解。

那么DATA段里面是什么内容呢?

DATA的内容根据不同的CMD有不同的内容。

当CMD为U2FHID_MSG时,DATA段里面的数据根据U2F-Message层进行封装。不管你采用哪种通信接口,蓝牙,NFC,还是USB。U2F-Message层的封装都是一致的。U2F-Message层的封装主要参考ISO7816国际标准,采用APDU格式进行封装。主要包括CLA,INS,P1,P2,长度及数据6个部分。

CLA是CLASS的缩写,用于表示类别。INS用于表示指令,P1,P2用于表示参数。

U2F-Message支持三种命令:U2F_REGISTER, U2F_AUTHENTICATE和U2F_VERSION,这三种命令根据INS字节进行区别,三种命令的字段填充如下:

类型 INS P1 P2
U2F_REGISTER 01 00 00
U2F_AUTHENTICATE 02 03/07/08 00
U2F_VERSION 03 00 00

CLS字段在当前版本的协议中统一填充0;

而命令的返回值也参考ISO7816协议,使用0x9000表示命令处理成功,6XXX表明命令处理失败。

好了,上面对协议的介绍可能不太直观,不太好理解,下面我们针对具体的数据包进行分析。

 

数据包分析


第一个包是23号包,从方向上看,是主机发给yubikey的。包的头64个字节,即0x40之前的部分是USB帧的内容。从0x40开始是U2F-HID包的内容,即截图下部蓝色加深的部分。

头四个字节FFFFFFFF表示通信信道还没有建立。

0x86 字节是CMD,那么这个0x86是什么类型呢?笔者在协议的文档中没有找到,找了很久才在如下链接中找到:

https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/inc/u2f_hid.h

部分内容摘录如下:

#define TYPE_INIT            0x80
#define U2FHID_PING      (TYPE_INIT | 0x01)  // Echo data through local processor only
#define U2FHID_MSG        TYPE_INIT | 0x03)  // Send U2F message frame
#define U2FHID_LOCK         (TYPE_INIT | 0x04)  // Send lock channel command
#define U2FHID_INIT         (TYPE_INIT | 0x06)  // Channel initialization
#define U2FHID_WINK         (TYPE_INIT | 0x08)  // Send device identification wink

所以0x86表示的是U2FHID_INIT。

接下来的两个字节 0x00 0x08表示的是长度为8个字节。

接下来的8个字节数据是nonce数据,用随机数填充。

上图的数据是yubikey返回的给主机的。

通道号为ffffffff,CMD为U2FHID_INIT,长度为17个字节。

这17个字节意义如下:

分别是,之前传来的8字节nonce随机数,4字节新建的通信信道ID-000c0001,表示key与主机的通信信道已经建立,接下来的所有通信都会使用这个通道ID。

然后是设备版本号,和设备属性字节。这有点类似于HTTPS协议中互say hello。

第三个包截图如下:

通信信道建立后,浏览器开始使用信道ID-000c0001。

CMD类型为0x83,U2FHID_MSG,0007是长度,表明信息长度为7个字节。

剩下的7个字节00 03 00 00 00 00 00 是U2F raw message。参考APDU的格式,CLA是00,INS是03,表示U2F_VERSION,表明这个包是浏览器在询问设备的U2F协议版本号。

上图是U2F设备给浏览器的回复。

信道ID 000c0001,以后浏览器和U2F设备通信都是用这个ID,下面的分析里面,不会再啰啰嗦嗦重复提这个信道ID。

CMD是U2FHID_MSG,长度为8个字节,55 32 46 5f 56 32,看右边的字符提示,这是ascii 码的U2F_V2,表示U2F设备告诉浏览器,我是用U2F_V2协议。最后的9000,表示命令执行成功。

第五个包是长度为0x49=73个字节的MSG包,APDU的INS为01,表示这是此时网站向设备发出的注册请求。在这里我们可以看到,73>57。因此,这个包需要进行拆分。

P1为03,这个和协议好像不太相符,主机应该是借用了U2F_AUTHENTICATE的P1。P2为0。接下类的三个字节表示剩下的数据长度为0x40=64个字节。

这64个字节的内容按协议规定如下:

第一部分是网站用户身份字符串,使用SHA256算法HASH以后得的32个字节。

第二部分是网站特征字符串,用于标识注册的网站,使用SHA256算法HASH以后得的32个字节。

前面我们提到此数据包长度过长,需要拆分,上图就是拆分以后的第二个包。

CID后面的0x00是SEQ,表示这是拆分后的第一个包,从0x00开始。接下来就是数据了,长度共计14个字节。(b3 55 b7 …… 12)
加上上一个包的50个字节,一共64个字节。

此时,设备给主机发了一个长度为2的MSG, 内容为6985。表明注册请求被拒绝,需要用户参与。此时,yubikey设备中的LED指示灯开始闪烁,需要用户按一下设备中间的LED灯,设备才会允许主机的注册请求。

接下来的数据因为会涉及到一些设备数据,我曾经看到有资料称可以根据设备的序列号和证书信息复原设备私钥,因此,接下来的数据包我这里不再截图,仅仅根据协议讲解。但我相信根据上面的讲解,你一定已经知道应该如何分析U2F的协议了!

闲话少叙,用户在U2F设备闪烁时按下LED灯时,yubikey会向主机返回如下数据:

该数据长度不固定,头一个字节是保留字节,数据固定为0x05。接下来是65个字节的ECC公钥,这一公钥是Key对每一个网站都单独生成的,每一个网站的都不一样。公钥对应的私钥就代表了用户的身份。在下一步的身份鉴别过程,就使用这个公钥进行用户签名的验证以验证用户身份。

接下来是key handle的长度和key handle数据。Key handle是公私钥的句柄,用于标识公私钥。因为每一个网站都生成一对公私钥必然要对U2F设备的存储容量提出要求。因此,设备可以根据这个key handle重新生成公私钥对,这样,公私钥对就不需要存储在key中。

剩下是证书和签名部分,证书是设备初始化时烧录的,可以用于标识设备。

最后的签名是对之前提到application parameter,challenge parameter,key handle, user public key数据进行签名得到的,(上图下半部半透明部分)用于保证数据没有被篡改。签名算法使用的是ECC NIST-P256曲线签名算法。

介绍完注册,我们再看看看认证过程。认证请求是用浏览器向U2F设备发出的。内容如下(当然,前面要有USB头和U2FHID头):

因为是2FA,首先用户会根据用户名和密码登录,登录成功后,支持U2F的网站会从数据库中找到相应的数据,发给浏览器,浏览器经过Hash后发给U2F设备。
Control byte用于说明是否需要用户按下设备中的LED灯进行身份认证,其余的部分我们之前都介绍过。
设备收到请求后,设备会根据control byte来决定是否需要用户按一下设备中间的LED灯,如果不需要,则直接返回。

设备向浏览器返回的数据如上图所示:
User presence表示用户是否按下U2F设备中的LED灯。

Counter用4个字节记录当前的登录次数,这一字段主要是用于提示用户设备是否被复制。因为每次登录时,这一字段就会加1,因此,这一字段必然是递增的。如果设备被复制以后,就会有两个key,这一字段在两个设备中无法同步,就可能造成数据不一致。当登录网站时,如果网站发现,数据包中的这一数据比数据库中的小了,那就说明设备被复制过,就会提示用户。

最后是签名,这段签名代表了用户的身份,网站使用之前收到的公钥和这段签名验证是否是用户上次注册所使用的key,以达到第二因素认证的目的。

 

现状

可惜的是,目前支持U2F协议的网站并不多,仅有Google,GitHub,Facebook等少数几个网址支持U2F协议登录。

而之前,阿里安全专家曾发现了一种叫做U2Fishing的针对U2F的攻击技术,感兴趣的读者可以在

https://github.com/scateu/U2Fishing

找到介绍和利用代码。

本文由prered2原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/164960

安全客 - 有思想的安全新媒体

分享到:微信
+10赞
收藏
prered2
分享到:微信

发表评论

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66