作者:云鼎實驗室
原文鏈接:https://mp.weixin.qq.com/s/P2aduaUS3s8K7I-cDVJNgA
背景
OpenSSL是一個知名的開源安全套接字層密碼庫。全球成千上萬的web服務器的網站加密技術使用OpenSSL。
網銀、在線支付、電商網站、門戶網站、電子郵件等互聯網應用廣泛使用OpenSSL實現數據的安全傳輸和安全存儲。
歷史上,OpenSSL多次出現安全漏洞。
2014年,OpenSSL爆出Heartbleed(心臟滴血)漏洞,網絡出現了“致命內傷”。
心臟滴血稱為互聯網安全歷史上最嚴重的漏洞之一,當時全球三分之二的網站可被該漏洞攻擊。
心臟滴血漏洞的CVE編號是CVE-2014-0160,CVSS3.1打分7.5,屬于嚴重漏洞。
業界使用CVE ID作為漏洞編號。CVE是通用漏洞披露(Common Vulnerabilities and Exposures)的英文縮寫。
業界采用CVSS量化漏洞影響。CVSS是通用漏洞評分系統(Common Vulnerability Scoring System)的英文縮寫。
CVSS得分最大為10,最小為0。得分7~10的漏洞通常認為嚴重,得分在4~6.9之間是中級漏洞,0~3.9是低級漏洞。
2021年8月24日,OpenSSL發布了OpenSSL 1.1.1l,該版本修復了一個高危漏洞:CVE-2021-3711。
根據
https://access.redhat.com/security/cve/cve-2021-3711
該漏洞的CVSS3.1打分8.1,屬于嚴重漏洞。
該漏洞影響OpenSSL 1.1.1l之前的所有包含SM2商密算法版本。業界一些基于OpenSSL改造過的商用國密算法版本也可能受該漏洞影響。
本文結合OpenSSL公告、修復前后的OpenSSL代碼和觸發漏洞的sm2密文數據,分析CVE-2021-3711漏洞原理,并評估對騰訊自研國密算法庫的影響。
漏洞分析
根據官網披露的信息細節
https://www.openssl.org/news/secadv/20210824.txt
得出如下分析:
漏洞原因:SM2解密時分配了一塊內存,解密后的結果可能大于該分配內存的容量,造成內存越界寫。
以下是具體分析,使用CVE-2021-3711漏洞修復之前的OpenSSL 1.1.1代碼。
1、OpenSSL EVP解密操作
OpenSSL EVP將常用的密碼算法進行了封裝,提供統一的密碼學各種函數。
看示例圖找規律,OpenSSL對密文的解密是什么樣的操作?
示例1:crypto/evp/p_open.c

示例2:crypto/crmf/crmf_lib.c

示例3:crypto/cms/cms_env.c

示例4:crypto/pkcs7/pk7_doit.c

實際應用中密文的解密一般需要調用兩次EVP_PKEY_decrypt。
第一次調用EVP_PKEY_decrypt,指針out為NULL,返回長度keylen。
通過OPENSSL_malloc分配一塊keylen大小的堆內存。
第二次調用EVP_PKEY_decrypt,指針out為第一次調用所分配的內存,運算結束后存放解密結果。
2、EVP_PKEY_decrypt實現
在初始化EVP_PKEY_CTX結構后,通過EVP_PKEY_decrypt可以調用到具體的密碼算法執行解密運算。
int EVP_PKEY_decrypt(EVP_PKEY_CTX *ctx, unsigned char *out, size_t *outlen, const unsigned char *in, size_t inlen){ int ret;
...
if (ctx->op.ciph.algctx == NULL) goto legacy;
ret = ctx->op.ciph.cipher->decrypt(ctx->op.ciph.algctx, out, outlen, (out == NULL ? 0 : *outlen), in, inlen); return ret;
legacy:
...
}
3、pkey_sm2_decrypt實現
對于SM2解密,EVP_PKEY_decrypt中的ctx->op.ciph.cipher->decrypt對應的是pkey_sm2_decrypt。
pkey_sm2_decrypt函數位于crypto/sm2/sm2_pmeth.c。
static int pkey_sm2_decrypt(EVP_PKEY_CTX *ctx, unsigned char *out, size_t *outlen, const unsigned char *in, size_t inlen){ EC_KEY *ec = ctx->pkey->pkey.ec; SM2_PKEY_CTX *dctx = ctx->data; const EVP_MD *md = (dctx->md == NULL) ? EVP_sm3() : dctx->md;
if (out == NULL) { if (!sm2_plaintext_size(ec, md, inlen, outlen)) return -1; else return 1; }
return sm2_decrypt(ec, md, in, inlen, out, outlen);}
根據第一節OpenSSL EVP解密操作可知,第一次調用EVP_PKEY_decrypt函數時,指針out為NULL,返回長度作為接下來分配堆內存的大小。
這里sm2_plaintext_size函數返回outlen,作為接下來分配堆內存的大小。
4、sm2_plaintext_size實現
sm2_plaintext_size函數位于crypto/sm2/sm2_crypt.c
int sm2_plaintext_size(const EC_KEY *key, const EVP_MD *digest, size_t msg_len, size_t *pt_size){ const size_t field_size = ec_field_size(EC_KEY_get0_group(key)); const int md_size = EVP_MD_size(digest); size_t overhead;
if (md_size < 0) { SM2err(SM2_F_SM2_PLAINTEXT_SIZE, SM2_R_INVALID_DIGEST); return 0; } if (field_size == 0) { SM2err(SM2_F_SM2_PLAINTEXT_SIZE, SM2_R_INVALID_FIELD); return 0; }
overhead = 10 + 2 * field_size + (size_t)md_size; if (msg_len <= overhead) { SM2err(SM2_F_SM2_PLAINTEXT_SIZE, SM2_R_INVALID_ENCODING); return 0; }
*pt_size = msg_len - overhead; return 1;}
注意:返回的長度等于msg_len - overhead,而overhead = 10 + 2 * field_size+(size_t)md_size。
5、overhead存在的問題
sm2國密算法知識
關于overhead的設置,涉及SM2算法和SM2密文格式的知識,在此進行補充。
- SM2(SM是“商密”拼音的縮寫)是我國商用密碼的公鑰密碼標準,標準號為:GM/T 0003-2012。
- SM2標準中規定采用256比特的橢圓曲線域參數。
- SM2算法采用SM3算法作為算法步驟中的哈希算法,SM3算法的輸出是256比特的哈希值。
- 根據GM/T 0009-2012,SM2密文格式如下:

這里,XCoordinate和YCoordinate是加密過程基于隨機數計算出的橢圓曲線點的X坐標和Y坐標。
overhead取值分析
查看sm2_plaintext_size函數:
- field_size = ec_field_size(EC_KEY_get0_group(key)),對于SM2算法,field_size等于32。
- md_size = EVP_MD_size(digest),SM2算法采用SM3算法,因此md_size等于32。
從上述2點可知,sm2_plaintext_size函數中的overhead取值等于106(10+2*32+32)。
這里的magic number 10背后有什么含義呢?
-
對于SM2密文,ASN.1包括5個Tag和5個Length,ASN.1編碼引入的長度不小于10個字節。分析如下: 每個Tag占1個字節,5個Tag占5個字節。 XCoordinate、YCoordinate和HASH由于值的長度范圍相對固定,這3個Length占3個字節。 取決于CipherText值,CipherText和第一個tag后面的Length長度不定,這2個Length可能超過 2個字節。
-
這里overhead選擇10,是選擇SM2密文ASN.1編碼引入的長度的最小值。
返回的長度等于msg_len - overhead,若overhead取值小,則返回長度大,分配內存大于實際需要,不會溢出。
這里的field_size沒有考慮XCoordinate和YCoordinate的具體取值,有沒有風險?
1)XCoordinate和YCoordinate是加密過程基于隨機數計算出的橢圓曲線點的X坐標和Y坐標,滿足以下方程:
YCoordinate * YCoordinate ≡ XCoordinate * XCoordinate * XCoordinate - 3 * XCoordinate + b(mod p)
這里,≡表示方程的左右兩邊模p的結果相等,p和b是SM2國密標準中規定的常數。
2)滿足上述方程的XCoordinate和YCoordinate通常都是占32字節的大數。
3)如果密文中攜帶的XCoordinate占31字節,YCoordinate占32字節,則真實的overhead可能小于106。
此時使用msg_len - 106的結果去會分配空間,導致分配的空間小于解密后的結果,內存越界寫。
4)存在滿足上述方程的占31字節甚至更少的XCoordinate或YCoordinate嗎?
OpenSSL給出的SM2密文數據示例給出了肯定的回答。
觸發漏洞的數據示例
1、SM2密文數據
OpenSSL給出的密文數據示例如下:
3072022070DAD60CDA7C30D64CF4F278A849003581223F5324BFEC9BB329229BFFAD21A6021F18AFAB2B35459D2643243B242BE4EA80C6FA5071D2D847340CC57EB9309E5D04200B772E4DB664B2601E3B85E39C4AA8C2C1910308BE13B331E009C5A9258C29FD040B6D588BE9260A94DA18E0E6
2、解析SM2密文
這組密文的長度是116字節。按照ASN.1格式解析這組密文:
3072 //30表示SEQUENCE類型,72表示后續的數據總長度是114字節
0220 //02表示INTEGER類型,20表示該整數的長度是32字節
70DAD60CDA7C30D64CF4F278A849003581223F5324BFEC9BB329229BFFAD21A6 //32字節的XCoordinate
021F //02表示INTEGER類型,1F表示該整數的長度是31字節
18AFAB2B35459D2643243B242BE4EA80C6FA5071D2D847340CC57EB9309E5D //31字節的YCoordinate
0420 //04表示OCTETSTRING類型,20表示該字符串的長度是32字節
0B772E4DB664B2601E3B85E39C4AA8C2C1910308BE13B331E009C5A9258C29FD //32字節的HASH
040B //04表示OCTETSTRING類型,0B表示該字符串的長度是11字節
6D588BE9260A94DA18E0E6 //11字節的密文
經過驗證,上述的XCoordinate和YCoordinate滿足SM2橢圓曲線方程。

3、觸發堆溢出
-
第一次調用pkey_sm2_decrypt,指針out為NULL,msg_len等于116。 sm2_plaintext_size函數返回10(msg_len - overhead = 116 - 106)。
-
通過OPENSSL_malloc分配10字節的內存,out指向該內存。
-
第二次調用pkey_sm2_decrypt,由于密文有11字節,因此解密結果也是11字節。
out指向的內存是10字節,而解密結果是11字節,導致越界寫1字節。
騰訊自研國密庫不受該漏洞影響
近年來,國家積極推進國產密碼基礎設施的建設,推廣與應用。
為貫徹落實國家密碼戰略,推進公司產品信息安全,騰訊自研了TencentSM國密算法庫,擺脫對國外開源密碼算法庫的依賴。
TencentSM符合國密SM2、SM3以及SM4算法標準,已在騰訊多個業務中平穩運行。
TencentSM自研了SM2解密實現,未使用和參考OpenSSL該部分所對應的代碼,不受該漏洞影響。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1695/
暫無評論