U2F安全協議分析
U2F,Universal 2nd Factor,通用第二因素,屬於2FA的一種,意即在使用者名稱和密碼的基礎上再增加一種驗證手段,以增強使用者驗證的安全性。
出於研究的用途,筆者買了一支yubico 的usb key,並對其進行了測試和研究。以下是筆者的研究筆記,供對U2F感興趣的朋友參考。
方法
對於想要測試U2F朋友,yubico已經提供了測試用的網站,網址是:
ofollow,noindex" target="_blank">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_INIT0x80 #define U2FHID_PING(TYPE_INIT | 0x01)// Echo data through local processor only #define U2FHID_MSGTYPE_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
找到介紹和利用程式碼。