13 lat temu pisałem, że encoding musi być dostosowany do kontekstu, w którym dane mają być użyte: Znaj swój kontekst. Dokładnie to samo można przeczytać w OWASP Cross Site Scripting Prevention Cheat Sheet. I co? I dalej można spotkać się z sytuacjami, gdy ktoś uważa, że jedna magiczna funkcja problem XSS rozwiązuje raz i na zawsze, w każdym możliwym przypadku. I oczywiście w dalszym ciągu przychodzą Źli Ludzie™ i pokazują, jak bardzo się ktoś myli...
Ciągle musisz znać swój kontekst
W ramach inspiracji polecam te dwa artykuły:
- The seventh way to call a JavaScript function without parentheses
- Executing non-alphanumeric JavaScript without parenthesis
Do tego trochę moich staroci:
- Niewłaściwy encoding #1
- Niewłaściwy encoding #2
- Niewłaściwy encoding #3
- Niewłaściwy encoding #4
- Niewłaściwy encoding #5
- Encoding: ESAPI
I jeszcze przykład (ilustracyjny), który wciąż się zdarza (pseudokod):
document.write(<%=htmlEncode($userInput)%>);
Teoretycznie wszystko jest OK, bo przecież htmlEncode zakoduje wszystkie znaki specjalne, więc coś jak
<script>alert(0)</script>
zostanie przekształcone do formy
<script>alert(0)</script>
i wszystko będzie w porządku, prawda? No nie do końca, bo ten payload można przekazać również tak:
\x3c\x73\x63\x72\x69\x70\x74\x3e\x61\x6c\x65\x72\x74\x28\x30\x29\x3c\x2f\x73\x63\x72\x69\x70\x74\x3e
Żaden z tych znaków nie będzie kodowany przez htmlEncode, a document.write wypisze nam dokładnie to, co chcemy. Dlaczego? Bo przecież tak też można reprezentować normalne znaki ASCII w stringu. Tu przykład z Pythona:
In [1]: "\x3c\x73\x63\x72\x69\x70\x74\x3e\x61\x6c\x65\x72\x74\x28\x30\x29\x3c\x2f\x73\x63\x72\x69\x70\x74\x3e" Out[1]: '<script>alert(0)</script>'
No i oczywiście nie zapominajmy o tym, że payload można zakodować kilka razy używając różnych kodowań, które sukcesywnie będą "zdejmowane", na przykład w takiej sekwencji:
[1] %20%5c%78%33%63%5c%78%37%33%5c%78%36%33%5c%78%37%32%5c%78%36%39%5c%78%37%30%5c%78%37%34%5c%78%33%65%5c%78%36%31%5c%78%36%63%5c%78%36%35%5c%78%37%32%5c%78%37%34%5c%78%32%38%5c%78%33%30%5c%78%32%39%5c%78%33%63%5c%78%32%66%5c%78%37%33%5c%78%36%33%5c%78%37%32%5c%78%36%39%5c%78%37%30%5c%78%37%34%5c%78%33%65 [2] \x3c\x73\x63\x72\x69\x70\x74\x3e\x61\x6c\x65\x72\x74\x28\x30\x29\x3c\x2f\x73\x63\x72\x69\x70\x74\x3e [3] <script>alert(0)</script>
A na zakończenie - sam encoding, nawet prawidłowy, niekoniecznie rozwiązuje problem. Walidacja też jest ważna. Ale to już trochę inna historia...