Temat nie jest nowy, już kilka razy o tym wspominałem, ale zrobię to jeszcze raz. Co się stanie w trakcie wykonania tego zapytania:
SELECT * FROM tabela WHERE x=1 AND 1=1/0
Prawidłowa odpowiedź: to zależy. Głównie od wartości x.
By lepiej to zilustrować ucieknę się do Pythona:
In [53]: 1 == 0 and 1/0 == 0 or 1 == 1
Out[53]: True
In [54]: 1 == 1 and 1/0 == 0 or 1 == 1
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-54-f8c9d167ad4a> in <module>()
----> 1 1 == 1 and 1/0 == 0 or 1 == 1
ZeroDivisionError: integer division or modulo by zero
Co się dzieje? W pierwszym przypadku wartość tego wyrażenia jest wyliczona. W drugim przypadku natomiast występuje dzielenie przez zero. Co do zasady wyrażenia są bardzo podobne, różnią się tylko początkiem. Skąd taka różnica?
Sprawa jest dość prosta i, jeśli się nad tym zastanowić, dość oczywista. By rezultat operatora AND był prawdą (True), obie jego strony muszą być prawdą. Tak więc jeśli lewa strona wyrażenia jest False, wyliczanie prawej nie ma sensu, nie ma ona już wpływu na ostateczny wynik. Oczywiście ostateczny wynik tej części wyrażenia. To, co jest po OR jest już wyliczane.
Dokładnie taka sama optymalizacja zachodzi przy operatorze OR. Taka sama, czyli dokładnie odwrotna:
In [56]: 1 == 1 or 1 == 1/0
Out[56]: True
In [57]: 1 == 0 or 1 == 1/0
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-57-abb1e9823d66> in <module>()
----> 1 1 == 0 or 1 == 1/0
ZeroDivisionError: integer division or modulo by zero
W tym wypadku prawa strona wyrażenia będzie wyliczana tylko wóczas, gdy lewa strona to False. Jeśli lewa strona to True, prawa strona nie ma już żadnego wpływu na ostateczny wynik, nie ma więc sensu jej wyliczać.
Do czego to jest przydatne? Warto o tym pamiętać np. przy konstrukcji payloadów do SQLi. Może się tak nieszczęśliwie zdarzyć, że nasz payload nigdy się nie wykona (choć jest prawidłowy), ponieważ nieszczęśliwie wstrzykujemy się w część zapytania, która nie jest wyliczana.
Z drugiej strony to zachowanie można wykorzystać przy konstruowaniu payloadów do blind SQLi. Przykładowy payload:
OR (tu subquery zwracające True/False) AND 1=1/0
Jeśli z lewej strony AND będzie True, wówczas zapytanie zakonczy się błędem dzielenia przez zero, co zwykle spowoduje zauważalny błąd na poziomie aplikacji webowej (np. inny komunikat błędu). A to już jest wystarczające by wyciągnąć dane.
Dawno temu miałem taki bardzo nie fajny zwyczaj, że zamiast pisać:
if(foo) bar();
pisałem to jako:
foo && bar();
I niestety wiele osób nie rozumiało tego kodu A szkoda, bo IMO to dość wygodny sposób.