Největší programátorský evergreen jsou zmatky a nejasnosti kolem escapování. Neznalost způsobuje, že nejtriviálnější metody narušení webových stránek, jako třeba Cross Site Scripting (XSS) nebo SQL injection, patří bohužel mezi nejrozšířenější.
Escapování je náhrada znaků majících v daném kontextu speciální význam za jiné odpovídající sekvence.
Příklad: do řetězce ohraničeného uvozovkami chceme zapsat uvozovky. Jelikož uvozovky mají v kontextu řetězce speciální význam a jejich prosté zapsání by bylo chápáno jako ukončení řetězce, je potřeba je zapsat jinou odpovídající sekvencí. Jakou přesně určují pravidla kontextu.
Předpoklady
Každá escapovací funkce předpokládá, že vstupem je vždy surový řetězec v určitém kódování (znakové sadě).
Ukládat třeba do databáze řetězce již předem escapované pro HTML výstup a podobně je zcela kontraproduktivní.
S jakými kontexty se setkáváme?
Jak bylo řečeno, escapování převádí znaky mající v určitém kontextu speciální význam. Pro každý kontext se používají jiné escapovací funkce. Tato tabulka je pouze orientační, je nutné si přečíst poznámky níže.
kontext | escapovací funkce | reverzní funkce |
---|---|---|
HTML | htmlspecialchars | html_entity_decode |
XML | htmlspecialchars | — |
regulární výraz | preg_quote | — |
PHP řetězce | var_export | — |
MySQL databáze | mysql_real_escape_string | — |
MySQL improved | mysqli_real_escape_string | — |
SQLite databáze | sqlite_escape_string | — |
PostgreSQL databáze | pg_escape_string | — |
PostgreSQL, typ bytea | pg_escape_bytea | pg_unescape_bytea |
JavaScript, JSON | json_encode | json_decode |
CSS | addcslashes | — |
URL | rawurlencode | urldecode |
Vysvětlení k následujícím poznámkám:
- řada kontextů má své podkontexty a v nich se escapování liší. Nebude-li řečeno jinak, je uvedená escapovací funkce použitelná plošně bez dalšího rozlišování podkontextů.
- pod pojmem obvyklá znaková sada se rozumí znaková sada s 1bajtovým nebo UTF-8 kódováním
HTML
V HTML kontextech mají souhrnně speciální význam znaky
< & " '
a odpovídající sekvence jsou
< & " '
. Výjimkou je ovšem HTML
komentář, kde má speciální význam jen dvojice --
.
K escapování se používá:
$s = htmlspecialchars($s, ENT_QUOTES);
Funguje s libovolnou obvyklou znakovou sadou. Ale nezohledňuje podkontext
HTML komentářů (tj. neumí nahradit dvojici --
za něco
jiného).
Reverzní funkce:
$s = html_entity_decode($s, ENT_QUOTES, 'UTF-8');
XML / XHTML
XML 1.0 se od HTML liší v tom, že zakazuje použití kontrolních znaků
C0 (a to včetně zápisu v podobě entity) s výjimkou tabulátoru,
odřádkování a mezery. XML 1.1 tyto zakázané znaky s výjimkou
NUL
v podobě entit naopak povoluje a dále přikazuje kontrolní
znaky C1 s výjimkou NEL
taktéž zapisovat jako entity. Dále
v XML má speciální význam sekvence ]]>
, proto je třeba
jeden z těchto znaků také escapovat.
Pro XML 1.0 a libovolnou obvyklou znakovou sadu tak lze použít:
$s = preg_replace('#[\x00-\x08\x0B\x0C\x0E-\x1F]+#', '', $s);
$s = htmlspecialchars($s, ENT_QUOTES);
Regulární výraz
V Perlových regulárních
výrazech mají souhrnně speciální význam znaky
. \ + * ? [ ^ ] $ ( ) { } = ! < > | : -
a tzv. delimiter,
což je znak ohraničující regulární výraz (např. pro výraz
'#[a-z]+#i'
je to #
). Escapuje se znakem
\
.
$s = preg_quote($s, $delimiter);
V řetězci, kterým se hledaný výraz nahrazuje (tedy například
2. parametr funkce preg_replace
), má speciální význam zpětné
lomítko a dolar:
$s = addcslashes($replacement, '$\\');
Kódování musí být buď 1bajtové nebo UTF-8, podle modifikátoru v regulárním výrazu. Viz také Escapování v regulárních výrazech.
PHP řetězce
PHP rozlišuje tyto typy řetězců:
- v jednoduchých uvozovkách, kde speciální význam mohou mít
znaky
\ '
- ve dvojitých uvozovkách, kde speciální význam mohou mít
znaky
\ " $
- NOWDOC, kde speciální význam nemá žádný znak
- HEREDOC, kde speciální význam mohou mít znaky
\ $
Escapuje se znakem \
. To obvykle provádí programátor při
psaní kódu, pro generátory PHP kódu lze využít funkci var_export.
Poznámka: protože zmíněné regulární výrazy se obvykle zapisují
uvnitř PHP řetězce, je potřeba zkombinovat obě escapování. Např. znak
\
se pro regulární výraz zapíše jako \\
a
v řetězci s uvozovkami je třeba psát \\\\
.
SQL a databáze
Každá databáze má svou vlastní escapovací funkci, viz tabulka výše.
Téměř vždy je ale dostupná jen funkce pro escapování řetězců a tu
nelze použít k ničemu jinému, zejména chybí funkce escapující
zástupné znaky používané v konstrukcích LIKE
(v MySQL jde
o znaky % _
) nebo identifikátory, jako jsou názvy tabulek či
sloupců. Databáze nevyžadují odstraňování escapování na
výstupu! (S výjimkou např. typu bytea.)
Znakové sady s neobvyklým vícebajtovým kódováním je nutné v MySQL nastavit funkcí mysql_set_charset resp. mysqli_set_charset.
Doporučuji používat databázový layer (např. dibi, Nette Database, PDO) nebo parametrické dotazy, které escapování obstarají za vás.
JavaScript, JSON
Jakožto programovací jazyk má řadu velmi odlišných podkontextů. K escapování řetězců lze využít vedlejší efekt funkce
$s = json_encode((string) $s);
která navíc obalí řetězec do uvozovek. Striktně vyžaduje UTF-8.
JavaScript zapsaný uvnitř HTML atributů (např. onclick
) je
nutné ještě escapovat podle HTML pravidel, neplatí
to však pro JavaScript uvnitř značek <script>
, kde musí
být ošetřen pouze případný výskyt koncové značky
</script>
uvnitř řetězce. To ovšem funkce json_encode
zajistí, jelikož JSON escapuje lomítko /
. Neošetří však
konec HTML komentáře -->
(což v HTML nevadí) nebo XML bloku
CDATA ]]>
, do kterého se skript obaluje. Pro XML/XHTML je
řešením
$s = json_encode((string) $s);
$s = str_replace(']]>', ']]\x3E', $s);
Jelikož JSON využívá podmnožinu syntaxe JavaScriptu, je reverzní funkce json_decode plně použitelná jen pro JSON, pro JavaScript omezeně.
CSS
V CSS kontextech je rozsah platných znaků přesně vymezen, pro escapování identifikátorů lze použít například tuto funkci:
$s = addcslashes($s, "\x00..\x2C./:;<=>?@[\\]^`{|}~");
Pro CSS uvnitř HTML kódu platí totéž, co bylo řečeno o JavaScriptu a
jeho escapování uvnitř HTML atributů a značek (zde se jedná o atributy
style
a značky <style>
).
URL
V kontextu URL se escapuje vše kromě písmen anglické abecedy, číslic a
znaků - _ .
nahrazením za %
+ hexadecimálně
vyjádřený bajt.
$s = rawurlencode($s);
Podle RFC 2718 (z roku 1999) nebo RFC 3986 (z roku 2005) je preferován zápis znaků v kódování UTF-8.
Reverzní funkcí je v tomto případě urldecode, která rozeznává i znak
+
s významem mezery.
Pokud se vám zdá celá problematika příliš složitá, nezoufejte. Brzy přijdete na to, že jde vlastně o jednoduché tranformace a celý trik spočívá v uvědomění, v jakém kontextu se nacházím a jakou musím pro něj zvolit funkci. Nebo ještě lépe, zkuste použít inteligentní šablonovací systém, který dokáže kontexty rozeznat sám a použít správné escapování: Latte