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.
About 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:
- Transport Layer Security
- User enumeration
- Information exposure through server headers
- Authentication bypass
- User input validation
- SQL injection
- Error handling
- Session management
- AuthN bypass
- Command Injection
- 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:
- Install Docker
- 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:
Let the hunt begin
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.
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:
POST /tokens: to login
GET /user/USER_ID: to get user information (
USER_IDis a number)
POST /user: to create users
GET /uptime/FLAG: to get uptime (
sas per the docs)
Let’s check how the server responds to these endpoints for different HTTP verbs.
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.
Following observations were made:
- Takes user credentials and provides auth token with an expiry time.
- 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.
- The API endpoint only responds when the request contains the content type as
application/xml. This means that the endpoint is expecting JSON / XML which contains the user credentials.
- 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.
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 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.
I was unable to confirm if it was vulnerable to XXE.
Get user info :
Following observations were made:
- This endpoint requires
X-Auth-Tokenheader with a token to work
- 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 foundwhere XX is the user ID.
' at the end of user ID showed it to be vulnerable to SQLi. Running sqlmap produced the following:
Dumping the DB (
sqlmap -r req.txt --delay 5 --dump):
Create user :
- Admin token is required to create users
- 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.
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--
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.
aaaaa takes 2 ms.
aaaaaaaaaaaaaaaaaaaaaaaaaaa takes 14,927 ms. Also the time taken to create almost doubles each time an alphabet is appended.
So the server is vulnerable to ReDoS. Combining both issues (SQLi + ReDoS), any unauthorized user can bring down the server.
- It’s an unauthenticated API endpoint
- This endpoint seems to execute the command
uptimealong with it’s flags (like
-s) in the backend
- The endpoint gives 404 when tried to append
This is a classic command injection. To exploit this endpoint, append
&& after the flag.
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.
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.
- The connection to vAPI server is in plain HTTP and is obviously vulnerable to MitM attack. Theres no transport layer security.
- The endpoint
GET /user/USER_IDresponds with username and password. This means the password is not hashed.
- Verbose error message is disclosed along with code.