羅嬌燕,左黎明,陳藝琳,郝 恬
(華東交通大學(xué) 理學(xué)院,江西 南昌 330013)
隨著互聯(lián)網(wǎng)應(yīng)用的普及和數(shù)據(jù)交換的增加,安全問題已成為互聯(lián)網(wǎng)應(yīng)用開發(fā)中不可忽視的挑戰(zhàn)[1]。傳統(tǒng)的基于session-cookie模式的有狀態(tài)身份認證方式不僅面臨著CSRF等安全問題,而且在可擴展性和跨域方面也存在缺陷[2]。針對上述問題,基于Token身份認證機制提出了一種無狀態(tài)、輕量級、可擴展和支持跨域的身份認證方案。根據(jù)這些優(yōu)點,Token身份認證機制在API接口中得到了廣泛應(yīng)用,其中以JWT技術(shù)為代表[3]。
JWT技術(shù)是由RFC 7519[4]定義的一套開放標(biāo)準(zhǔn),具有緊湊且安全的特點,可用于執(zhí)行身份認證和信息交換。但由于JWT標(biāo)準(zhǔn)的復(fù)雜性以及開發(fā)者對技術(shù)細節(jié)和加密算法的理解不夠透徹,為開發(fā)帶來便利的同時也為應(yīng)用程序的安全性埋下了隱患。2015年Tim McLean[5]發(fā)現(xiàn),有些類庫在實現(xiàn)JWT規(guī)范的過程中會產(chǎn)生一些漏洞;例如利用“none”算法或者直接在交互過程中刪除簽名均可以繞過簽名驗證過程。2016年,安全研究人員Sjoerd Langkemper[6]提出一種針對簽名算法的攻擊,將RSA和HMAC算法混淆使用來繞過服務(wù)器的身份認證。2021年,Apache發(fā)布的安全公告中,公開了Apache ShenYu中的身份驗證繞過漏洞(編號:CVE-2021-37580);由于產(chǎn)品對客戶端提交的token進行解析的同時沒有進行校驗,導(dǎo)致攻擊者可繞過認證,直接進入系統(tǒng)后臺。2022年Khaled Nassar[7]在github上披露了Oracle JavaSE數(shù)字簽名驗證機制的漏洞利用代碼(編號:CVE-2022-21449),該漏洞是由于開發(fā)人員在實現(xiàn)橢圓曲線數(shù)字簽名算法(ECDSA)時未充分考慮簽名驗證機制所導(dǎo)致的,攻擊者可以偽造簽名從而繞過身份驗證。同年,nodejs的開源基礎(chǔ)庫JsonWebToken被曝存在遠程代碼執(zhí)行漏洞(編號:CVE-2022-23529),攻擊者可以通過該漏洞遠程執(zhí)行惡意代碼,導(dǎo)致系統(tǒng)崩潰、數(shù)據(jù)泄露、賬戶被劫持等風(fēng)險,從而給用戶的隱私和財產(chǎn)帶來巨大威脅。此外,由于該庫每月在NPM上下載量超過3 600萬次,應(yīng)用于超過22 000個開源項目,其中包括微軟、Twilio、Salesforce、Intuit、Box、IBM、Docusign、Slack、SAP等知名公司創(chuàng)建的開源項目,這也意味著該漏洞的影響范圍極其廣泛。
JWT中存在的安全問題,例如“none”算法繞過(漏洞編號:CVE-2018-1000531、CVE-2022-23540)、敏感信息泄露(漏洞編號:CVE-2019-7644、CVE-2022-23541)、密鑰窮舉攻擊、算法混淆攻擊(漏洞編號:CVE-2016-10555)等,主要是由于開發(fā)人員對于JWT技術(shù)理解程度的不夠深入而引發(fā)的[8]。基于上述安全問題,該文提出了一種基于國密SM9的JWT強身份認證方案,利用國密SM9算法對認證參數(shù)進行數(shù)字簽名,實現(xiàn)對用戶身份的安全認證。
消息保護技術(shù)在應(yīng)用層中很少單獨使用,通常融合在整個安全技術(shù)體系之中,而JWT就是代表性的消息保護技術(shù)[9]。JWT可有效保證多方通信中的信息安全,在無需頻繁調(diào)用資源服務(wù)器或者數(shù)據(jù)庫的情況下,JWT就可驗證后續(xù)客戶端請求,可適用于分布式站點的單點登錄場景[10-11]。該文主要基于JWT的身份認證功能進行分析和優(yōu)化。圖1展示了基于JWT的身份認證流程[12]。首先,客戶端使用登錄憑證(比如用戶名和密碼)請求授權(quán)服務(wù)器;憑據(jù)驗證通過后,授權(quán)服務(wù)器利用密鑰生成一個JWT令牌返回客戶端。然后,客戶端攜帶此JWT令牌請求資源服務(wù)器上受保護的資源;資源服務(wù)器從JWT中解析用戶請求并進行處理。

圖1 JWT工作流程
JWT是一個由三部分組成的字符串,分別是頭部(Header)、載荷(Payload)和簽名(Signature),點(“.”)號作為相鄰部分的分隔符。其中頭部和載荷部分本身是JSON格式的數(shù)據(jù),可以利用Base64url算法對內(nèi)容進行解碼,解碼后的內(nèi)容如下所示。
Headers = {
"typ": "JWT",
"alg": "HS256"
}
Payload = {
"iss": "Token Builder",
"iat": 1680163910,
"exp": 1680163970,
"sub": "tom@webgoat.org",
"user name": "Tom",
"Email": "tom@webgoat.org",
"admin": "true"
}
SIGNATURE =HMACSHA256{
base64UrlEncode(header) + "." + base64UrlEncode(payload),
密鑰
}
頭部定義令牌類型以及所采用的加密算法,HS256代表該JWT令牌的簽名算法為HMAC SHA256。載荷部分主要是對實體(通常是用戶實體)和附加數(shù)據(jù)的聲明,聲明通常分為三類,分別是注冊聲明、公共聲明和私有聲明。圖中的iss(發(fā)布者)、iat(發(fā)布時間)、exp(到期時間)、sub(主題)等字段都是屬于注冊聲明,并非強制性使用的;公共聲明和私有聲明開發(fā)者可根據(jù)實際交互情況進行設(shè)置。簽名部分是利用算法對頭部和載荷部分進行簽名,防止數(shù)據(jù)被篡改[4];在頭部聲明的簽名算法不同,簽名的過程也是不同的。
JWT是API技術(shù)中消息傳遞的重要形式,各個API服務(wù)提供方在OAuth 2.0和OpenID Connect使用它在各方之間交換信息[9]。盡管JWT的使用范圍很廣,但常常因為提供方的實現(xiàn)不規(guī)范產(chǎn)生了一些安全問題。JWT由三部分組成,該文針對各部分產(chǎn)生的漏洞進行分析,并對一些典型漏洞進行研究。
(1)針對頭部的攻擊。
“none”算法繞過。
JWT的頭部通過設(shè)置typ參數(shù)和alg參數(shù)分別標(biāo)明了Token類型和簽名算法。在RFC 7518[13]標(biāo)準(zhǔn)中,定義了JWT可以使用一系列算法進行簽名,也可以不使用算法進行簽名,即將alg參數(shù)的值設(shè)置為none。如圖2所示,在JWT中設(shè)置“none”算法時,在簽名部分將不使用任何算法進行簽名,即簽名部分為空字符串,后端服務(wù)器不執(zhí)行簽名驗證,此時前兩部分符合規(guī)范的任何token都是有效的。“none”算法最初的目的是為了方便開發(fā)者在開發(fā)環(huán)境中進行調(diào)試,如果在生產(chǎn)環(huán)境中也使用該功能,攻擊者便可以偽造任意用戶的token進行身份認證[5]。

圖2 基于“none”算法生成的JWT
(2)針對載荷的攻擊。
當(dāng)JWT用于身份認證時,通常需要包含用戶的相關(guān)信息,而載荷部分便是用于聲明用戶實體及其要發(fā)送的數(shù)據(jù)信息,是JWT不可缺少的一部分[4]。根據(jù)身份認證所需要的信息生成一個JSON對象,利用Base64url算法對其進行編碼,編碼后的字符串便是JWT的第二部分。按照RFC4648[14]的定義,Base64url算法是基于Base64算法而形成的一種加密方式,對內(nèi)容進行了簡單編碼,無法保證傳輸過程中信息的機密性。因此可以直接利用Base64url算法對JWT中的載荷部分進行解碼,如圖3所示,從解碼后的內(nèi)容可以獲取身份認證過程所包含的敏感信息。通過載荷部分泄露的敏感信息,推斷身份認證的參數(shù)及其含義,再結(jié)合前面提到的“none”算法漏洞,攻擊者可以直接接管賬號或提取權(quán)限[9]。

圖3 基于載荷部分的敏感信息泄露漏洞
(3)針對簽名的攻擊。
①算法混淆攻擊。
簽名部分主要是根據(jù)頭部的alg參數(shù)設(shè)置的算法類型對Base64url算法編碼后的頭部和載荷兩部分內(nèi)容進行簽名,從而保證了JWT在數(shù)據(jù)傳輸過程的不可篡改性。RFC 7518[13]標(biāo)準(zhǔn)聲明了對稱算法和非對稱算法均可以作為JWT的簽名算法,由于有些開發(fā)者的技術(shù)實現(xiàn)不規(guī)范,提供了與算法無關(guān)的簽名驗證方法verify()。算法偽碼如下:該方法只是根據(jù)頭部(Header)的alg參數(shù)值確定簽名的算法類型,而不驗證提供的Token加密算法,將導(dǎo)致身份認證服務(wù)器驗證JWT簽名的算法與開發(fā)人員預(yù)期的算法不相同,從而產(chǎn)生對稱和非對稱算法混淆漏洞。
publicKey =
token = request.getCookie("session");
verify(token, publicKey);
……
function verify(token, secretOrPublicKey){
algorithm = token.getAlgHeader();
if(algorithm == "RS256"){
// Use the provided key as an RSA public key
} else if (algorithm == "HS256"){
// Use the provided key as an HMAC secret key
} }
產(chǎn)生算法混淆漏洞的流程如圖4所示。當(dāng)開發(fā)人員調(diào)用verify()方法但僅使用非對稱算法(如RS256)作為JWT的簽名算法,即在身份認證過程中,認證服務(wù)器使用私鑰對數(shù)據(jù)簽名,使用公鑰進行簽名驗證[15]。由于公鑰對所有人可見,攻擊者獲取RS256公鑰并將其作為HS256算法的簽名密鑰,同時將頭部的alg參數(shù)值設(shè)為“HS256”,然后對修改后頭部和載荷部分結(jié)合HS256算法進行簽名生成JWT令牌。在身份認證過程中,認證服務(wù)器將使用HS256算法和相同的公鑰去驗證簽名。

圖4 基于簽名部分的算法混淆漏洞流程
②密鑰窮舉攻擊。
密鑰窮舉攻擊(Brute Force Attack)是通過暴力破解的方式對所有可能的密鑰值進行嘗試來達到破解加密數(shù)據(jù)的目的的一種攻擊方式。當(dāng)開發(fā)者在使用某些簽名算法(比如HS256)時,可將任意長度的字符串作為簽名密鑰生成簽名,此時若使用一個長度較短的弱密鑰,將會導(dǎo)致密鑰窮舉攻擊。圖5為利用python暴力破解腳本破解弱密鑰簽名的JWT令牌的運行結(jié)果。

圖5 python破解弱密鑰簽名的JWT令牌
SM9標(biāo)識密碼算法[16]是利用橢圓曲線對實現(xiàn)的基于標(biāo)識的密碼算法,算法的安全性主要是建立在有關(guān)雙線性對問題難解性的基礎(chǔ)之上。SM9算法主要由三部分組成,分別是數(shù)字簽名算法、密鑰交換協(xié)議、密鑰封裝機制和公鑰加密算法[17]。該文主要使用SM9算法中的數(shù)字簽名部分,主要分為三個步驟:密鑰產(chǎn)生、生成簽名和簽名驗證。
參數(shù)說明見文獻[17],下面主要描述SM9數(shù)字簽名算法的各個步驟。
(1)密鑰產(chǎn)生。
KGC(Key Generation Center,密鑰生成中心)[16]產(chǎn)生隨機數(shù)ks∈[1,N-1]作為簽名主私鑰,Ppub=[ks]P2作為簽名主公鑰,則簽名主密鑰對為(s,Ppub),Ppub公開,ks由KGC保存。而用戶簽名密鑰對為(Ppub-A,dA),其中用戶簽名公鑰(Ppub-A=[H1(IDA‖hid)]P+Ppub,用戶簽名私鑰dA=[s·(H1(IDA‖hid)+s)-1]P1.
(2)生成簽名。
授權(quán)服務(wù)器根據(jù)用戶A的簽名私鑰dA運用SM9數(shù)字簽名算法對消息M進行計算,生成數(shù)字簽名(h,S),簽名生成流程如表1所示。

表1 生成數(shù)字簽名的算法流程
(3)簽名驗證。
資源服務(wù)器根據(jù)SM9數(shù)字簽名算法對用戶A發(fā)送的消息M'和數(shù)字簽名(h',S')進行驗證,簽名驗證過程中需要進行的步驟如表2所示。

表2 驗證數(shù)字簽名的算法流程
基于SM9的JWT強身份認證方案提出了一種與傳統(tǒng)的JWT技術(shù)不同的令牌生成方式,生成過程如圖6所示。頭部(Header)和載荷(Payload)部分內(nèi)容分別為Cheader和Cpayload,從Payload中獲取發(fā)行時間Valiat和用戶Alice的身份標(biāo)識UidAlice,將UidAlice作為國密SM9數(shù)字簽名算法的用戶標(biāo)識,Valiat作為新鮮因子。

圖6 JWT令牌生成過程
(1)通過國密SM3密碼雜湊算法Hsm3將頭部和載荷部分均映射成長度為M的數(shù)字串,即分別計算Sh=Hsm3(Cheader,Valiat)和Sp=Hsm3(Cpayload,Valiat);
(2)計算消息Msg=Sh⊕Sp;
(3)利用國密SM9數(shù)字簽名算法對Msg進行簽名,運算結(jié)果Valsig作為令牌JwtToken的簽名部分;
(4)將Cheader和Cpayload依次作為自變量x代入公式y(tǒng)=fbase64(x)進行計算,其中x={Cheader,Cpayload},運算結(jié)果為Valheader和Valpayload;
(5)最終,將三部分結(jié)合起來得到JWT令牌,即JwtToken=Valheader+Valpayload+Valsig。
基于JWT的身份認證方案可應(yīng)用的場景有Web應(yīng)用[18]、s桌面應(yīng)用[19]、移動應(yīng)用[19]和嵌入式應(yīng)用[20],該文提出的基于SM9的JWT強身份認證方案也可以在這些場景中實現(xiàn)對用戶的身份認證。身份認證流程如圖7所示,主要包括用戶Alice、客戶端C、授權(quán)服務(wù)器Sa和資源服務(wù)器Sr四個節(jié)點;KGC生成Sa的簽名主私鑰ks和簽名主公鑰Ppub,Sa保存主私鑰ks,公開主公鑰Ppub。

圖7 基于JWT的強身份認證方案工作流程
(1)用戶Alice訪問客戶端C。
(2)Alice通過客戶端C向授權(quán)服務(wù)器Sa提交用戶憑據(jù)TAlice。
(3)授權(quán)服務(wù)器Sa驗證憑據(jù)TAlice,驗證通過后,生成成功狀態(tài)碼,并利用主私鑰ks和身份標(biāo)識UidAlice生成簽名私鑰dAlice,然后結(jié)合國密SM9算法生成令牌JwtToken;若驗證不通過,則授權(quán)服務(wù)器Sa將會生成失敗狀態(tài)碼。
(4)授權(quán)服務(wù)器Sa將狀態(tài)碼和JWT令牌返回給客戶端C。
(5)客戶端C攜帶JWT令牌向資源服務(wù)器Sr請求相關(guān)資源。
(6)資源服務(wù)器Sr根據(jù)主公鑰Ppub和身份標(biāo)識UidAlice生成Alice的簽名公鑰Ppub-Alice,利用Ppub-Alice驗證令牌JwtToken。
(7)若資源服務(wù)器Sr驗證通過,則處理請求并返回對應(yīng)的服務(wù)器資源信息;反之,則返回處理失敗狀態(tài)碼。
授權(quán)服務(wù)器對客戶端發(fā)送的用戶憑據(jù)驗證通過后,利用國密SM9數(shù)字簽名算法生成令牌JwtToken的簽名部分。如果在交互過程中,攻擊者截獲數(shù)據(jù)包,并對令牌JwtToken內(nèi)容進行了篡改,那么相對應(yīng)的簽名部分也會發(fā)生變化,從而無法驗證簽名。因此,該方案可以保證數(shù)據(jù)完整性,防止數(shù)據(jù)在傳輸過程中被篡改。
授權(quán)服務(wù)器首先對令牌JwtToken的前兩部分進行運算,然后利用私鑰對運算結(jié)果進行簽名生成第三部分。客戶端可利用授權(quán)服務(wù)器發(fā)布的公鑰對簽名進行驗證,從而確定簽名來源的正確性;同時資源服務(wù)器也可利用公鑰對令牌JwtToken進行驗證,驗證通過后再對用戶請求進行處理。因此,該方案可以實現(xiàn)授權(quán)服務(wù)器的不可抵賴性,從而保證了數(shù)據(jù)的可靠性和真實性。
重放攻擊(Replay Attacks)[21]又稱回放攻擊,指的是攻擊者發(fā)送一個服務(wù)器已經(jīng)接收過的包,從而實現(xiàn)欺騙服務(wù)器的目的。該方案選取時間戳作為新鮮因子嵌入在待簽名的消息中,服務(wù)器在驗證簽名時,首先會驗證令牌JwtToken的新鮮性,如果請求的時間不在有效時間范圍內(nèi),即令牌JwtToken已經(jīng)失效了,將會跳轉(zhuǎn)到登錄頁面,重放數(shù)據(jù)包如圖8所示。加入時間戳作為新鮮因子,有效預(yù)防了重放攻擊,保證了數(shù)據(jù)的唯一性和失效性。

圖8 抗重放攻擊測試數(shù)據(jù)包
該文在前面提到將頭部alg參數(shù)設(shè)置為“none”時,后端服務(wù)器將不執(zhí)行驗證簽名的過程,從而導(dǎo)致客戶端攜帶無簽名的Token也可進行資源訪問。該文提出的認證方案中簽名認證過程是基于代碼中定義的國密SM9算法進行認證,與alg參數(shù)無關(guān);同時當(dāng)alg參數(shù)值不等于SM9時,令牌JwtToken將會失效。圖9為模擬客戶端攜帶“none”算法的JWT向服務(wù)器請求資源的過程。

圖9 可抵抗針對頭部(Header)的“none”算法繞過攻擊測試數(shù)據(jù)包
JWT在載荷(Payload)部分存放于用戶實體的相關(guān)信息進行身份認證[4],如果開發(fā)者在聲明用戶實體的過程中直接明文存儲相關(guān)信息,將會導(dǎo)致敏感信息泄露攻擊。該文提出的認證方案針對載荷(Payload)部分所需要聲明的用戶實體信息均進行了編碼操作,并且不將用戶敏感信息存放在令牌JwtToken中,保證了用戶信息的安全性,防止敏感信息泄露漏洞的出現(xiàn)。圖10為模擬攻擊者截獲令牌JwtToken后解密的內(nèi)容。

圖10 解密令牌JwtToken
該文提出的認證方案中授權(quán)服務(wù)器利用私鑰生成簽名,客戶端和資源服務(wù)器可以利用公鑰進行簽名驗證,并且簽名的生成和驗證過程都是基于國密SM9算法,與alg參數(shù)無關(guān)。不過當(dāng)alg參數(shù)值不等于SM9時,令牌JwtToken將被判定為無效令牌。
該文提出的認證方案中的簽名是國密SM9算法生成的,該算法是用橢圓曲線實現(xiàn)的基于標(biāo)識的數(shù)字簽名算法,具有很高的安全性和抗暴力破解能力。并且用戶私鑰是根據(jù)大整數(shù)類型的主私鑰和用戶標(biāo)識生成的,假設(shè)攻擊者獲得了用戶標(biāo)識,也不可能通過猜測的方式獲取用戶私鑰,從而無法破解加密數(shù)據(jù),預(yù)防了密鑰窮舉攻擊。
選用國內(nèi)外應(yīng)用了JWT技術(shù)實現(xiàn)了身份驗證和授權(quán)的平臺[22-23]進行安全性分析與對比,分析結(jié)果如表3所示。

表3 安全性對比分析
方案1:Microsoft Azure Active Directory;
方案2:百度數(shù)據(jù)開放平臺;
方案3:文中方案。
該文在Windows 10 64位操作系統(tǒng)下,使用基于 JDK 1.8的環(huán)境和IntelliJ IDEA 2021.2.1開發(fā)平臺,實現(xiàn)了一個基于B/S架構(gòu)的身份認證方案,其中服務(wù)器端采用了 SpringMVC和Mybatis框架進行開發(fā),方案的核心代碼如下:
服務(wù)器端根據(jù)用戶Alice身份標(biāo)識生成數(shù)字簽名:
// 用戶標(biāo)識
String id_A = userId;
//初始化Header和Payload部分
String orgHeader = headerJson.to String();
String orgPayload = bodyJson.to String();
// 生成待簽名的消息msg
String msg = getMsg(orgHeader,orgPayload,iatBytes);
// Header和Payload分別進行base64url編碼
String header = base64URLEn(orgHeader);
String payload = base64URLEn(orgPayload);
// 對消息msg進行簽名
String signatrue = sm9JwtUtils.sm9_sign(kgc,sm9,P_pub,ks,id_A,msg);
// 生成最終的JWT
String JwtToken = joinWithDot(header,payload,signatrue);
服務(wù)器端驗證簽名:
// JWT令牌解析
String header = base64URLDe(tokenParts[0]);
String payload = base64URLDe(tokenParts[1]);String signature = tokenParts[2];
// 解析payload部分
JSONObject payloadJson = new JSONObject(payload);
// 獲取用戶標(biāo)識
String id_A = payloadJson.get("useId").to String();
// 獲取 iat 字段的值,并轉(zhuǎn)換為字節(jié)數(shù)組
byte[ ] iatBytes = ByteBuffer.allocate(Long.BYTES).putLong(payloadJson.optLong("iat")).array();
// 生成消息msg
String msg = getMsg(header,payload,iatBytes);
//驗證簽名
boolean flag = sm9JwtUtils.sm9_verify(kgc,sm9,P_pub,ks,id_A,msg,signature);
(1)用戶Alice在客戶端通過登錄頁面向授權(quán)服務(wù)器端提交用戶憑據(jù),驗證通過后,授權(quán)服務(wù)器端根據(jù)用戶標(biāo)識UidAlice和主私鑰ks生成令牌JwtToken,并返回給客戶端,數(shù)據(jù)包如圖11所示。

圖11 服務(wù)器端將JWT令牌給客戶端的數(shù)據(jù)包
(2)客戶端攜帶令牌JwtToken向資源服務(wù)器請求資源,資源服務(wù)器解析傳遞令牌JwtToken,獲取用戶標(biāo)識UidAlice,并計算消息Msg,然后再根據(jù)授權(quán)服務(wù)器公開的主公鑰Ppub和UidAlice驗證令牌JwtToken中的簽名部分Valsig。如果驗證通過,資源服務(wù)器將根據(jù)UidAlice處理客戶端請求,并返回對應(yīng)的資源,資源請求交互過程的數(shù)據(jù)包如圖12所示。

圖12 資源請求交互過程
針對JWT技術(shù)中存在的一些漏洞,該文提出了一種基于國密SM9算法的JWT強身份認證方案。與現(xiàn)有開放平臺所使用的JWT技術(shù)進行安全性分析與比較,可知該認證方案具有數(shù)據(jù)完整性、不可抵賴性和抗重放攻擊等特點,并且可抵抗“none”算法繞過、敏感信息泄露、算法混淆和密鑰窮舉等攻擊。但實驗表明該方案的運行效率不高,有待進一步完善。