天天看點

記自己發現的—SM2國密算法應用的高危漏洞—CVE-2021-3711

記自己發現的—SM2國密算法應用的高危漏洞—CVE-2021-3711

openssl在8月24日釋出了openssl 1.1.1l的穩定版,其中修複了一個高危漏洞:CVE-2021-3711。該漏洞會影響openssl 1.1.1l 之前的所有包含SM2商密算法版本,其中也包括基于openssl改造過的版本:阿裡巴巴的babassl。

漏洞産生的原因,是解密 SM2公鑰加密後的資料時,有可能配置設定了一個過小的記憶體,導緻解密後的明文長度,大于該記憶體長度,造成記憶體越界,進而導緻整個應用程式崩潰。而這個漏洞,對基于openssl搭建的國密網關服務、WEB服務,有一定機率導緻服務崩潰,進而産生嚴重影響。目前,市面上比較通用的國密密碼套件是使用SM2_SM4_SM3(0xE013),也就是說,該密碼套件是采用SM2加解密的方式進行密鑰協商,是以,這就很有可能觸發該漏洞,導緻程式崩潰。

漏洞分析

解密 SM2公鑰加密後的資料時,應用程式會調用函數EVP_PKEY_decrypt(),該函數定義如下:

int EVP_PKEY_decrypt(EVP_PKEY_CTX *ctx,
                     unsigned char *out, size_t *outlen,
                     const unsigned char *in, size_t inlen);
           

通常應用程式會調用兩次這個函數:第一次,在進入時,“out”參數傳 NULL,在函數傳回時,“outlen”參數會傳回”out”所需的緩沖區大小。然後應用程式配置設定足夠的緩沖區,并再次調用 EVP_PKEY_decrypt(),但這次”out”傳遞的是非NULL。整個流程如下圖所示:

static int pkcs7_decrypt_rinfo(unsigned char **pek, int *peklen,
                               PKCS7_RECIP_INFO *ri, EVP_PKEY *pkey,
                               size_t fixlen)
{
    EVP_PKEY_CTX *pctx = NULL;
    unsigned char *ek = NULL;
    size_t eklen;

    int ret = -1;

    pctx = EVP_PKEY_CTX_new(pkey, NULL);
    if (!pctx)
        return -1;

    if (EVP_PKEY_decrypt_init(pctx) <= 0)
        goto err;

    if (EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_DECRYPT,
                          EVP_PKEY_CTRL_PKCS7_DECRYPT, 0, ri) <= 0) {
        PKCS7err(PKCS7_F_PKCS7_DECRYPT_RINFO, PKCS7_R_CTRL_ERROR);
        goto err;
    }

    if (EVP_PKEY_decrypt(pctx, NULL, &eklen,
                         ri->enc_key->data, ri->enc_key->length) <= 0)
        goto err;

    ek = OPENSSL_malloc(eklen);

    if (ek == NULL) {
        PKCS7err(PKCS7_F_PKCS7_DECRYPT_RINFO, ERR_R_MALLOC_FAILURE);
        goto err;
    }

    if (EVP_PKEY_decrypt(pctx, ek, &eklen,
                         ri->enc_key->data, ri->enc_key->length) <= 0
            || eklen == 0
            || (fixlen != 0 && eklen != fixlen)) {
        ret = 0;
        PKCS7err(PKCS7_F_PKCS7_DECRYPT_RINFO, ERR_R_EVP_LIB);
        goto err;
    }

    ret = 1;

    OPENSSL_clear_free(*pek, *peklen);
    *pek = ek;
    *peklen = eklen;

 err:
    EVP_PKEY_CTX_free(pctx);
    if (!ret)
        OPENSSL_free(ek);

    return ret;
}
           

我們再來看使用SM2算法時,EVP_PKEY_decrypt内部實作:通過函數指針的方式,會調用到pkey_sm2_decrypt函數,從下圖可以看到,當”out”為NULL時,會調用sm2_plaintext_size函數,其中“outlen”參數會傳回”out”所需的緩沖區大小。

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);
}
           

問題就出在sm2_plaintext_size函數裡。首先我們先需要了解一下SM2公鑰加密後的ASN.1資料結構(ASN.1抽象文法标記,是一種資料格式),下圖引用于《GB/T 35276-2017 資訊安全技術 SM2密碼算法使用規範》

記自己發現的—SM2國密算法應用的高危漏洞—CVE-2021-3711

通常情況SM2算法中xy分量的長度是32,但是也有可能小于32,問題就來了,sm2_plaintext_size函數的作用是擷取CipherText密文的長度,計算方式簡單粗暴:

overhead = 10 + 2 * field_size + (size_t)md_size;

overhead:整個加密後的資料中,不含密文後的長度

10: ASN.1格式中,所有标記的長度

2 * field_size:xy分量的長度,field_size是SM2密鑰中的一個值(這個值是固定的32),而x/y分量的實際長度是有可能小于32的!

md_size:雜湊值的長度

*pt_size = msg_len - overhead;

pt_size:計算出的密文長度

msg_len:整個加密後的資料長度

是以計算出的pt_size有可能偏小,結合上面提到的EVP_PKEY_decrypt()調用方式,就可能配置設定一個偏小的緩沖區,進而造成記憶體越界,程式崩潰。

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;
}
           

這個漏洞有可能被惡意攻擊者利用,攻擊者在國密SSL握手時,生成一個x/y分量長度小于32的加密資料,就可以導緻服務端程式崩潰。

漏洞修複

目前openssl已經出了1.1.1l了,已修複這個高危漏洞。

https://github.com/openssl/openssl/commit/36cf45ef3ba71e44a8be06ee81cb31aa02cb0010?branch=36cf45ef3ba71e44a8be06ee81cb31aa02cb0010&diff=unified

https://github.com/openssl/openssl/commit/ad1ca777f9702f355a2f74dc5eed713476825f23?branch=ad1ca777f9702f355a2f74dc5eed713476825f23&diff=split

漏洞溯源

這個漏洞非常的隐蔽,并且發生的機率并不高,是以很難被發現,但是一旦被利用,将會造成嚴重影響。

從openssl的送出記錄中可以看到,在2018年openssl支援SM2算法時,這個漏洞就一直存在了(下圖),同樣的,基于openssl改造的babassl都存在這個漏洞。

記自己發現的—SM2國密算法應用的高危漏洞—CVE-2021-3711
記自己發現的—SM2國密算法應用的高危漏洞—CVE-2021-3711

希望大家盡快更新openssl的版本,或修改相關代碼以修複漏洞。

CVE-2021-3711漏洞連結:

https://www.openssl.org/news/vulnerabilities.html#CVE-2021-3711

能為國密應用的發展貢獻自己的一份力量,這個還是挺高興的!

我在安全客的投稿連結(出處): https://www.anquanke.com/post/id/251504

繼續閱讀