Skip to content

Instantly share code, notes, and snippets.

@Garphy
Created October 23, 2026 11:54
Show Gist options
  • Select an option

  • Save Garphy/0532f362edd44ec3e9704591fda24db1 to your computer and use it in GitHub Desktop.

Select an option

Save Garphy/0532f362edd44ec3e9704591fda24db1 to your computer and use it in GitHub Desktop.
针对 eMudhra CA 证书弱私钥泄露事件的容器批量排查与热升级工具
#!/usr/bin/env bash
# ==============================================================================
# 脚本名称: container_batch_audit_and_upgrade.sh
# 功能描述: 针对 eMudhra CA 证书弱私钥泄露事件的容器批量排查与热升级工具
# 探测端点:
# - 原生链: https://dv-g3b-emudhra.we-have-a-problem.com/
# - 交叉签名链: https://dv-g3b-aaa-emudhra.we-have-a-problem.com/
# 判定标准:
# - 握手失败/抛出证书错误 -> 安全 (SECURE)
# - 握手成功/HTTP 200 -> 存在高危风险 (VULNERABLE)
# ==============================================================================
set -uo pipefail
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
TARGET_URL_DIRECT="https://dv-g3b-emudhra.we-have-a-problem.com/"
TARGET_URL_CROSS="https://dv-g3b-aaa-emudhra.we-have-a-problem.com/"
# 受影响的 eMudhra 关键中间证书 SHA256 指纹列表(大写)
COMPROMISED_FINGERPRINTS=(
"42C4ACCD12C4F59597925E1A08A7A90102608543809308D50EC427E118BA1493" # EM DV TLS CA - G3B (Native)
"45A883F349AEDFD5F3A649B5498FFB121712BA8B448F3F2A91DBC5A766FCDDBE" # EM DV TLS CA - G3B (Sectigo AAA Cross)
"0164D7CEAE5BB909C272AF151FF44B1177AE9B47D9424B94F1D9558C30934D19" # EM DV TLS CA - G3C (Native)
"98B896625C0C230ADEEC3EB2CF6AAD5001ECF9156A68644E82374CE5833BC25B" # EM OV TLS CA - G3B (Native)
"CB6E15165AC802E9B3F993F16C25D41F4E81C21EBDE71AE1B012AC00BF9C7C3F" # EM OV TLS CA - G3C (Native)
"E7568FE6E12331F4FD7DE61DA4C7559D0F8EB192A218D4741A3BA75184333838" # EM EV TLS CA - G3B (Native)
"432EC25F0D28DE9006D97BF752F17BBB2CD41F669933FCB072D9A1148D0261F3" # EM EV TLS CA - G3C (Native)
"9E39DE2881273ACC225A149C72BA3E0F51424963669AB633EE1DC7DF5DB067B4" # EM OV CS CA - G3B (Code Signing Native)
"A496E23AC0CAB8155FC9B117B97EAE368EFCBB9041AEC8804D5A308D2D01C2D8" # EM OV CS CA - G3B (Sectigo Cross)
)
MODE="audit" # 默认模式:audit(仅审计)或 fix(审计并升级)
AUTO_YES=false
print_banner() {
echo -e "${BLUE}========================================================================${NC}"
echo -e "${BLUE} eMudhra CA 证书泄露容器内部批量排查与修复工具 (v1.0) ${NC}"
echo -e "${BLUE} 针对 CVE-2026-111943 弱私钥中间证书信任排查 ${NC}"
echo -e "${BLUE}========================================================================${NC}"
echo ""
}
show_help() {
echo "用法: $0 [选项]"
echo ""
echo "选项:"
echo " -a, --audit 仅执行检测与排查,不修改容器(默认模式)"
echo " -f, --fix 执行排查,并对发现易受攻击的容器尝试在线更新 ca-certificates"
echo " -y, --yes 在 fix 模式下自动确认操作,无需交互提示"
echo " -c, --container 指定单个容器名称或 ID 进行排查处理"
echo " -h, --help 显示本帮助信息"
echo ""
echo "示例:"
echo " $0 --audit # 批量检查当前主机上所有运行中的容器"
echo " $0 --fix -y # 批量检查并自动尝试热升级所有容器的 CA 证书包"
echo " $0 -c my-web-app --fix # 针对指定容器进行排查与修复"
echo ""
}
# 检查 Docker 运行时
check_runtime() {
if ! command -v docker &>/dev/null; then
echo -e "${RED}[ERROR] 未检测到 docker 命令,请确认主机已安装 Docker 且已启动服务。${NC}"
exit 1
fi
if ! docker info &>/dev/null; then
echo -e "${RED}[ERROR] 无法连接 Docker Daemon,请检查当前用户权限或 sudo 权限。${NC}"
exit 1
fi
}
# 探测单个容器内部的连通性与证书信任状态
probe_container() {
local cid="$1"
local cname="$2"
local vulnerable=false
local probe_tool="none"
echo -e "${CYAN}------------------------------------------------------------------------${NC}"
echo -e "${CYAN}[*] 正在扫描容器: ${cname} (${cid:0:12})${NC}"
# 1. 检测容器内可用的 HTTP 工具 (curl, wget, python, busybox)
if docker exec "${cid}" which curl &>/dev/null; then
probe_tool="curl"
elif docker exec "${cid}" which wget &>/dev/null; then
probe_tool="wget"
elif docker exec "${cid}" which python3 &>/dev/null; then
probe_tool="python3"
elif docker exec "${cid}" which python &>/dev/null; then
probe_tool="python"
fi
# 2. 对靶场发起探测
if [ "${probe_tool}" = "curl" ]; then
# 测试原生链
if docker exec "${cid}" curl -s -m 5 -o /dev/null -w "%{http_code}" "${TARGET_URL_DIRECT}" 2>/dev/null | grep -q "200"; then
echo -e " ${RED}[!] 靶场探测 (原生链): 握手成功 (HTTP 200) -> 存在高危信任漏洞!${NC}"
vulnerable=true
else
echo -e " ${GREEN}[✓] 靶场探测 (原生链): 握手被成功阻断 (安全)${NC}"
fi
# 测试交叉签名链
if docker exec "${cid}" curl -s -m 5 -o /dev/null -w "%{http_code}" "${TARGET_URL_CROSS}" 2>/dev/null | grep -q "200"; then
echo -e " ${RED}[!] 靶场探测 (交叉签名链): 握手成功 (HTTP 200) -> 存在高危信任漏洞!${NC}"
vulnerable=true
else
echo -e " ${GREEN}[✓] 靶场探测 (交叉签名链): 握手被成功阻断 (安全)${NC}"
fi
elif [ "${probe_tool}" = "wget" ]; then
if docker exec "${cid}" wget -q -T 5 -O /dev/null "${TARGET_URL_DIRECT}" 2>/dev/null; then
echo -e " ${RED}[!] 靶场探测 (原生链): 握手成功 -> 存在高危信任漏洞!${NC}"
vulnerable=true
else
echo -e " ${GREEN}[✓] 靶场探测 (原生链): 握手被成功阻断 (安全)${NC}"
fi
elif [ "${probe_tool}" = "python3" ] || [ "${probe_tool}" = "python" ]; then
local py_cmd="${probe_tool}"
local py_script="
import urllib.request, sys
try:
urllib.request.urlopen('${TARGET_URL_DIRECT}', timeout=5)
sys.exit(0) # 成功连接,说明存在漏洞
except Exception as e:
sys.exit(1) # 握手失败,说明安全
"
if docker exec "${cid}" "${py_cmd}" -c "${py_script}" &>/dev/null; then
echo -e " ${RED}[!] 靶场探测 (Python urllib): 握手成功 -> 存在高危信任漏洞!${NC}"
vulnerable=true
else
echo -e " ${GREEN}[✓] 靶场探测 (Python urllib): 握手被阻断 (安全)${NC}"
fi
else
echo -e " ${YELLOW}[?] 容器内未找到 curl/wget/python,转入静态证书文件检索...${NC}"
fi
# 3. 静态检索容器内部证书存储文件中是否存在涉事指纹/关键字
local cert_found=false
local cert_check_cmd="
for f in /etc/ssl/certs/ca-certificates.crt /etc/pki/tls/certs/ca-bundle.crt /etc/ssl/cert.pem /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem; do
if [ -f \"\$f\" ]; then
if grep -qi \"emudhra\" \"\$f\" 2>/dev/null || grep -qi \"EM DV TLS CA\" \"\$f\" 2>/dev/null; then
echo \"FOUND_NAME:\$f\"
fi
fi
done
"
local cert_output
cert_output=$(docker exec "${cid}" sh -c "${cert_check_cmd}" 2>/dev/null || true)
if [ -n "${cert_output}" ]; then
echo -e " ${RED}[!] 静态检测: 容器信任库中发现 eMudhra 关键字: ${cert_output}${NC}"
vulnerable=true
cert_found=true
fi
# 4. 根据模式执行修复
if [ "${vulnerable}" = true ]; then
echo -e " ${RED}>>> 综合评估: 容器 ${cname} 判定为 [高危/易受攻击] <<<${NC}"
if [ "${MODE}" = "fix" ]; then
attempt_fix_container "${cid}" "${cname}"
else
echo -e " ${YELLOW}>>> 建议: 请使用 '--fix' 选项或重新基于最新基础镜像构建该容器。<<<${NC}"
fi
else
echo -e " ${GREEN}>>> 综合评估: 容器 ${cname} 判定为 [安全/已阻断] <<<${NC}"
fi
}
# 尝试在容器内部热升级 ca-certificates
attempt_fix_container() {
local cid="$1"
local cname="$2"
echo -e " ${BLUE}[>] 正在尝试在线热升级容器内部 ca-certificates...${NC}"
# 判断包管理器类型
local pkg_manager=""
if docker exec "${cid}" which apk &>/dev/null; then
pkg_manager="apk"
elif docker exec "${cid}" which apt-get &>/dev/null; then
pkg_manager="apt"
elif docker exec "${cid}" which dnf &>/dev/null; then
pkg_manager="dnf"
elif docker exec "${cid}" which yum &>/dev/null; then
pkg_manager="yum"
elif docker exec "${cid}" which microdnf &>/dev/null; then
pkg_manager="microdnf"
fi
case "${pkg_manager}" in
apk)
echo -e " [*] 检测到 Alpine 环境,执行 apk 升级..."
if docker exec "${cid}" sh -c "apk update && apk add --no-cache --upgrade ca-certificates" &>/dev/null; then
echo -e " ${GREEN}[✓] Alpine 容器 ca-certificates 热升级成功!${NC}"
else
echo -e " ${RED}[✗] Alpine 容器升级失败,请检查容器出网网络策略。${NC}"
fi
;;
apt)
echo -e " [*] 检测到 Debian/Ubuntu 环境,执行 apt 升级..."
if docker exec "${cid}" sh -c "DEBIAN_FRONTEND=noninteractive apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y --only-upgrade ca-certificates || apt-get install --no-install-recommends -y ca-certificates" &>/dev/null; then
docker exec "${cid}" update-ca-certificates &>/dev/null || true
echo -e " ${GREEN}[✓] Debian/Ubuntu 容器 ca-certificates 热升级成功!${NC}"
else
echo -e " ${RED}[✗] Debian/Ubuntu 容器升级失败,请检查网络或软件源。${NC}"
fi
;;
dnf|yum)
echo -e " [*] 检测到 RHEL/CentOS 环境,执行 ${pkg_manager} 升级..."
if docker exec "${cid}" "${pkg_manager}" update -y ca-certificates &>/dev/null; then
docker exec "${cid}" update-ca-trust &>/dev/null || true
echo -e " ${GREEN}[✓] RHEL/CentOS 容器 ca-certificates 热升级成功!${NC}"
else
echo -e " ${RED}[✗] RHEL/CentOS 容器升级失败。${NC}"
fi
;;
microdnf)
echo -e " [*] 检测到 UBI/Minimal 环境,执行 microdnf 升级..."
if docker exec "${cid}" microdnf update -y ca-certificates &>/dev/null; then
docker exec "${cid}" update-ca-trust &>/dev/null || true
echo -e " ${GREEN}[✓] UBI 容器 ca-certificates 热升级成功!${NC}"
else
echo -e " ${RED}[✗] microdnf 升级失败。${NC}"
fi
;;
*)
echo -e " ${YELLOW}[!] 未能识别容器内支持的包管理器(可能是 scratch/distroless 或只读文件系统)。${NC}"
echo -e " ${YELLOW}[!] 注意: 只读或极简镜像无法执行热升级,强烈建议在 Dockerfile 中更新并重新部署。${NC}"
;;
esac
}
# 解析命令行参数
SPECIFIC_CONTAINER=""
while [[ $# -gt 0 ]]; do
case "$1" in
-a|--audit)
MODE="audit"
shift
;;
-f|--fix)
MODE="fix"
shift
;;
-y|--yes)
AUTO_YES=true
shift
;;
-c|--container)
SPECIFIC_CONTAINER="$2"
shift 2
;;
-h|--help)
show_help
exit 0
;;
*)
echo "未知参数: $1"
show_help
exit 1
;;
esac
done
print_banner
check_runtime
if [ "${MODE}" = "fix" ] && [ "${AUTO_YES}" = false ]; then
echo -e "${YELLOW}[WARNING] 您已启用 '--fix' 模式,该模式将直接在运行中的容器内执行软件包更新。${NC}"
echo -e "${YELLOW}热升级仅为应急临时措施,容器重新拉取或重建后仍需 Dockerfile 根治。${NC}"
read -r -p "是否继续执行? [y/N]: " confirm
if [[ ! "${confirm}" =~ ^[Yy]$ ]]; then
echo "操作已取消。"
exit 0
fi
fi
if [ -n "${SPECIFIC_CONTAINER}" ]; then
# 单容器扫描
cid=$(docker inspect --format '{{.Id}}' "${SPECIFIC_CONTAINER}" 2>/dev/null || true)
cname=$(docker inspect --format '{{.Name}}' "${SPECIFIC_CONTAINER}" 2>/dev/null || true)
if [ -z "${cid}" ]; then
echo -e "${RED}[ERROR] 未找到容器: ${SPECIFIC_CONTAINER}${NC}"
exit 1
fi
probe_container "${cid}" "${cname#/}"
else
# 批量扫描所有运行中的容器
CONTAINERS=$(docker ps -q)
if [ -z "${CONTAINERS}" ]; then
echo -e "${YELLOW}[INFO] 当前宿主机未发现任何正在运行的 Docker 容器。${NC}"
exit 0
fi
TOTAL_COUNT=$(echo "${CONTAINERS}" | wc -w | tr -d ' ')
echo -e "${BLUE}[*] 发现 ${TOTAL_COUNT} 个正在运行的容器,开始批量执行排查 (模式: ${MODE})...${NC}\n"
CURRENT_INDEX=1
for cid in ${CONTAINERS}; do
cname=$(docker inspect --format '{{.Name}}' "${cid}" 2>/dev/null || echo "unknown")
cname="${cname#/}"
echo -e "${BLUE}[${CURRENT_INDEX}/${TOTAL_COUNT}] 准备检测容器: ${cname}${NC}"
probe_container "${cid}" "${cname}"
echo ""
CURRENT_INDEX=$((CURRENT_INDEX + 1))
done
fi
echo -e "${BLUE}========================================================================${NC}"
echo -e "${GREEN}[✓] 容器批量排查与处置任务执行完毕。${NC}"
echo -e "${BLUE}========================================================================${NC}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment