Lucene search
+L

GraphQL Introspection Abuse

🗓️ 10 Jul 2026 11:31:42Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com👁 4 Views

GraphQL Introspection Abuse confirms introspection and enumerates schema types and fields.

Refs
Code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
GraphQL introspection and lightweight abuse probe.

Runs full introspection when enabled, surfaces sensitive queries (users, admin,
password), and tests batching / alias amplification signals.
"""

from __future__ import annotations

import json
from typing import Any, Dict, List, Optional, Set

from kittysploit import *
from lib.protocols.http.http_client import Http_client
from lib.scanner.http.module_result import finalize_http_scanner_run, target_base_url


class Module(Auxiliary, Http_client):

    __info__ = {
        "name": "GraphQL Introspection Abuse",
        "description": (
            "Confirm GraphQL introspection, enumerate schema types/fields, and flag "
            "sensitive queries (users, admin, secrets) for follow-up exploitation."
        ),
        "author": "KittySploit Team",
        "tags": ["web", "api", "graphql", "introspection", "scanner"],
        "agent": {
            "risk": "active",
            "effects": ["network_probe"],
            "expected_requests": 4,
            "reversible": True,
            "approval_required": False,
            "produces": ["risk_signals", "endpoints", "tech_hints"],
            "chain": {
                "consumes_capabilities": ["graphql_endpoint"],
                "produces_capabilities": [
],
                "option_bindings": {
                    "graphql_path": "graphql_endpoint",
                },
                "suggested_followups": [
                    "auxiliary/scanner/http/sqli_engine",
                    "post/http/gather/authenticated_surface"],
            },
        },
    }

    graphql_path = OptString("/graphql", "GraphQL endpoint path", True)
    max_types = OptInteger(40, "Maximum schema types to list", False)
    test_batching = OptBool(True, "Probe alias/batch amplification", False)

    INTROSPECTION_QUERY = """
    query IntrospectionKitty {
      __schema {
        queryType { name }
        types {
          name
          kind
          fields {
            name
            type { name kind ofType { name kind } }
          }
        }
      }
    }
    """

    SENSITIVE_FIELD_MARKERS = (
        "password", "secret", "token", "admin", "credential",
        "apikey", "api_key", "private", "ssn", "email",
    )

    def _gql_post(self, path: str, query: str, variables: Optional[dict] = None) -> Optional[Any]:
        payload = {"query": query}
        if variables:
            payload["variables"] = variables
        return self.http_request(
            method="POST",
            path=path,
            data=json.dumps(payload),
            headers={"Content-Type": "application/json"},
            allow_redirects=False,
            timeout=15,
        )

    def _parse_types(self, body: str) -> List[Dict[str, Any]]:
        try:
            data = json.loads(body)
            types = data.get("data", {}).get("__schema", {}).get("types") or []
            return [t for t in types if isinstance(t, dict)]
        except Exception:
            return []

    def _sensitive_fields(self, types: List[Dict[str, Any]]) -> List[str]:
        found: List[str] = []
        for ttype in types[: int(self.max_types or 40)]:
            name = str(ttype.get("name") or "")
            if name.startswith("__"):
                continue
            for field in ttype.get("fields") or []:
                if not isinstance(field, dict):
                    continue
                fname = str(field.get("name") or "").lower()
                if any(m in fname for m in self.SENSITIVE_FIELD_MARKERS):
                    found.append(f"{name}.{field.get('name')}")
        return found[:24]

    def run(self):
        path = str(self.graphql_path or "/graphql").strip() or "/graphql"
        print_status(f"GraphQL introspection probe at {path}")

        response = self._gql_post(path, self.INTROSPECTION_QUERY)
        if not response or response.status_code not in (200, 400):
            print_warning("GraphQL endpoint not responsive to introspection")
            return finalize_http_scanner_run(
                self,
                [],
                title="GraphQL introspection",
                severity="info",
                category="api",
                findings_key="graphql_findings",
            )

        body = response.text or ""
        if "__schema" not in body and "data" not in body:
            print_warning("Introspection disabled or blocked")
            return finalize_http_scanner_run(
                self,
                [],
                title="GraphQL introspection",
                severity="low",
                category="api",
                findings_key="graphql_findings",
            )

        types = self._parse_types(body)
        sensitive = self._sensitive_fields(types)
        type_names = [str(t.get("name")) for t in types if t.get("name") and not str(t.get("name")).startswith("__")]

        print_success(f"Introspection enabled — {len(type_names)} types, {len(sensitive)} sensitive fields")
        for item in sensitive[:8]:
            print_info(f"  sensitive: {item}")

        batch_ok = False
        if bool(self.test_batching):
            batch_query = "query B { a1:__typename a2:__typename a3:__typename a4:__typename a5:__typename }"
            batch_resp = self._gql_post(path, batch_query)
            batch_ok = bool(batch_resp and batch_resp.status_code == 200 and "__typename" in (batch_resp.text or ""))

        hits = [{
            "vulnerable": True,
            "path": path,
            "indicator": "graphql_introspection",
            "status_code": response.status_code,
            "type_count": len(type_names),
            "sensitive_fields": sensitive,
            "sensitive_query": sensitive[0] if sensitive else "",
            "graphql_endpoint": path,
            "batching": "yes" if batch_ok else "no",
            "content_preview": body[:800],
        }]

        chain_extra = {
            "graphql_endpoint": path,
            "sensitive_query": sensitive[0] if sensitive else "",
            "sensitive_fields": ",".join(sensitive[:12]),
        }

        return finalize_http_scanner_run(
            self,
            hits,
            title="GraphQL introspection enabled",
            severity="high" if sensitive else "medium",
            category="api",
            findings_key="graphql_findings",
            hit_mapper=lambda hit: {
                "method": "POST",
                "request_url": target_base_url(self, path=str(hit.get("path") or path)),
                "status_code": hit.get("status_code"),
                "evidence_snippet": hit.get("content_preview") or hit.get("indicator"),
                "indicators": [hit.get("indicator")] if hit.get("indicator") else [],
                "graphql_endpoint": hit.get("graphql_endpoint"),
            },
            vulnerability_info_extra=chain_extra,
        )

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

02 Aug 2026 20:51Current
5.8Medium risk
Vulners AI Score5.8
4