Vulnerable API writeup

Most of the applications I see these days heavily depend on APIs. Pentesting them is a bit different than that of web applications. In this writeup I will show you how I discovered the vulnerabilities in the “Vulnerable API” project.

According to the documentation, vulnerable API (vAPI) is a set of API endpoints written specifically to illustrate common API vulnerabilities. vAPI is implemented using the Bottle Python Framework and consists of a user database and a token database.

This project mimics the real world scenario and is not a blind Capture the Flag type challenge. The project is more of a practice ground to budding pentesters and security consultants. vAPI comes with the following vulnerabilities:

  1. Transport Layer Security
  2. User enumeration
  3. Information exposure through server headers
  4. Authentication bypass
  5. User input validation
  6. SQL injection
  7. Error handling
  8. Session management
  9. Encryption
  10. AuthN bypass
  11. Command Injection
  12. Regex DDoS

The different API endpoints, requests to them and their responses, user credentials, etc are all documented in the project’s README.

The setup is quite simple:

  1. Install Docker
  2. Run the vAPI docker container: docker run -tid -p 8081:8081 --name api mkam/vulnerable-api-demo

If the above is successfully setup, you would see the following when you hit http://localhost:8081 on your browser:

vAPI running

Usually if you have an API server and you can’t find the endpoints (say from mobile apps, JS files fetched from archive.org, Google dorks, etc) it’s almost a dead end. You can be lucky if the API server is in itself vulnerable to some unauthenticated RCE. In this challenge, we have a documented list of API endpoints along with requests and expected responses.

Before we check the API endpoints mentioned, let’s see if there is any interesting information in the response.

vAPI response

The server discloses the header Server: WSGIServer/0.1 Python/2.7.11 in the response. A quick Google search doesn’t show any known vulnerabilities for this server version.

Next let’s look at the docs to get the list of endpoints. The endpoints are:

  1. POST /tokens : to login
  2. GET /user/USER_ID : to get user information (USER_ID is a number)
  3. POST /user : to create users
  4. GET /uptime and GET /uptime/FLAG : to get uptime (FLAG is s as per the docs)

Let’s check how the server responds to these endpoints for different HTTP verbs.

vAPI endpoint responses

Apart from the HTTP method and endpoint combinations mentioned in the docs, we find that GET /tokens is also available. This endpoint discloses all user credentials in the database. This is our first bug.

Now let’s test the endpoints one by one.

Login : POST /tokens

JSON Body

Login with JSON

1
2
3
4
curl -i -s -k -X 'POST' \
    -H 'Content-Type: application/json' \
    --data-binary '{"auth": {"passwordCredentials": {"username": "user1", "password":"pass1"} } }' \
    'http://localhost:8081/tokens'

XML Body

Login with XML

1
2
3
4
curl -i -s -k -X 'POST' \
    -H $'Content-Type: application/xml' \
    --data-binary '<?xml version="1.0" encoding="UTF-8"?><auth><passwordCredentials><username>user1</username><password>pass1</password></passwordCredentials></auth>' \
    'http://localhost:8081/tokens'

Following observations were made:

  1. Takes user credentials and provides auth token with an expiry time.
  2. The auth token is the same till the expiry time. So if there are two or more login attempts with the same user credentials in a given short interval, you get same auth token. However, expired tokens can be used to fetch data from the endpoints.
  3. The API endpoint only responds when the request contains the content type as application/json and application/xml. This means that the endpoint is expecting JSON / XML which contains the user credentials.
  4. There is no difference in the response when logged in as a user or admin.

Login functionality always calls you to check few bugs: SQLi and rate limiting.

This endpoint doesn’t have any kind of ratelimit. Adding ' at the end of username gives an error which is likely to be a SQLi.

Login SQLi 1

With the help of SQLi, you can login to any user account just by appending '-- at the end. This doesn’t require the user’s password.

Login SQLi 2

Login using XML always calls to look for XXE. Appending DOCTYPE ENTITY responds with "password does not match" instead of an error. This might be because it’s ignoring the payload or successfully replacing it.

Basic XXE test

I was unable to confirm if it was vulnerable to XXE.

Get User Details

1
2
3
curl -i -s -k \
    -H 'X-Auth-Token: YOUR_AUTH_TOKEN' \
    'http://localhost:8081/user/1'

Following observations were made:

  1. This endpoint requires X-Auth-Token header with a token to work
  2. Works only when the user attempts to access details of his account. Trying to access other account will give the error: the token and user do not match!. If the account doesn’t exist, it gives an error user id XX not found where XX is the user ID.

Adding a ' at the end of user ID showed it to be vulnerable to SQLi. Running sqlmap produced the following:

Sqlmap Output 1

Dumping the DB (sqlmap -r req.txt --delay 5 --dump):

Sqlmap Output 2

Create user : POST /user

Create User

1
2
3
4
curl -i -s -k -X 'POST' \
    -H 'X-Auth-Token: YOUR_ADMIN_AUTH_TOKEN' -H $'Content-Type: application/json' \
    --data-binary '{"user":{"username": "USERNAME", "password": "PASSWORD"}}' \
    'http://localhost:8081/user'

Observations:

  1. Admin token is required to create users
  2. The user is created when username has atleast one character appended with a digit at the end

Since this API requires Admin token to create users, let’s check if we can bypass this. Adding ' at the end of X-Auth-Token creates an error.

Auth token error

Looks like a SQL query again. The backend SQL statement verifies both the auth token and the userid. Finally, with the help of SQL injection, one can create users even without admin token using the payload ' OR userid = 10--

User creation bypass

Using this anyone can create a user, but still created username should be a string ending with a number. Most probably regex is used in the backend to validate the usernames. If it’s regex, we need to give Regex DoS (ReDoS) a try. Passing a long string that doesn’t match a regex is a simple test to check ReDoS.

Creating user aaaaa takes 2 ms.

ReDoS Check 1

Creating user aaaaaaaaaaaaaaaaaaaaaaaaaaa takes 14,927 ms. Also the time taken to create almost doubles each time an alphabet is appended.

ReDoS Check 2

So the server is vulnerable to ReDoS. Combining both issues (SQLi + ReDoS), any unauthorized user can bring down the server.

Uptime 1

1
curl -i -s -k 'http://localhost:8081/uptime'

Uptime 2

1
curl -i -s -k 'http://localhost:8081/uptime/s'

Observations:

  1. It’s an unauthenticated API endpoint
  2. This endpoint seems to execute the command uptime along with it’s flags (like -s) in the backend
  3. The endpoint gives 404 when tried to append / after /uptime/s

This is a classic command injection. To exploit this endpoint, append ; or && after the flag.

Example:

Command injection 1

There is an issue when you try to access file paths or execute commands that have / in it. The webserver parses it as URL inspite of URL encoding.

Command Injection error

Double encoding doesn’t work as well. To bypass this, I hosted a static payload file on a webserver and piped it to bash shell. It worked.

Command injection 2

  1. The connection to vAPI server is in plain HTTP and is obviously vulnerable to MitM attack. Theres no transport layer security.
  2. The endpoint GET /user/USER_ID responds with username and password. This means the password is not hashed.
  3. Verbose error message is disclosed along with code.