Skip to content
Field Detail
Platform PortSwigger Web Security Academy
Type GraphQL API Vulnerabilities
Difficulty Practitioner
Objective Locate a hidden GraphQL endpoint, bypass its introspection filter using a newline injection, discover the deleteOrganizationUser mutation, and delete carlos

Finding a Hidden GraphQL Endpoint

Browsing the app generated no GraphQL traffic — the endpoint wasn't linked from any page. Probing common paths, navigating to /api returned:

"Query not present"
Screenshot

"Query not present" is the standard GraphQL response to a non-GraphQL request — the confirmation we needed. Testing with a parameter:

/api?query=teto
Screenshot
"Invalid syntax with offending token 'teto' at line 1 column 1"

A GraphQL parse error confirms both the endpoint location and that queries are being processed. A hidden endpoint is not a secure endpoint — it was still accessible to anyone probing common paths, and the mutations it exposed had no access control.

Loading Burp's full introspection query via the GraphQL tab:

Screenshot
{
    "errors": [
        {
            "message": "GraphQL introspection is not allowed, but the query contained __schema or __type"
        }
    ]
}

The server uses a regex or string match blocking __schema in the query. Inserting a URL-encoded newline (%0a) between __schema and the opening brace breaks the regex while remaining valid GraphQL syntax — the parser handles __schema{ and __schema\n{ identically. Any regex-based keyword filter can be defeated by whitespace or newlines that GraphQL ignores; the only reliable way to disable introspection is at the library level:

GET /api?query=query+IntrospectionQuery+%7b%0a++++__schema%0a...
Screenshot

Introspection succeeded. Saving the response to the site map for structured browsing:

Screenshot

The site map revealed two relevant operations: a getUser query and a deleteOrganizationUser mutation. Sending getUser with incrementing IDs to find carlos:

Screenshot

id: 3"username": "carlos". Sending the mutation:

mutation($input: DeleteOrganizationUserInput) {
    deleteOrganizationUser(input: $input) {
        user {
            id
            username
        }
    }
}
{"input": {"id": 3}}
Screenshot
Screenshot

Lab solved...

Resources