Hlavní navigace

Manipulace se strukturovanými daty: úskalí naivních metod a jejich dopady

Včera
Doba čtení: 6 minut

Sdílet

 Autor: Depositphotos
Zahajujeme sérii článků věnované umění sémantické manipulace se strukturovanými daty. Prozkoumáme běžné chyby při manipulaci s řetězci (strings) a jejich vliv na softwarové systémy.

Jednoduchý naivní úvod

Podívejme se na tento kousek kódu napsaný v C-like jazyce:

print("{ \"id\": \"" + $id + "\", \"name\": \"" + $name + "\" }");

Zapřemýšlejme nad tím, co tento kód dělá. Nebo spíše přemýšlejme o tom, co tento kód dělat má. Čeho se programátor snažil dosáhnout tímto kódem? Zřejmě jediný rozumný výklad záměru programátora by mohl být následující: 

Chci vytisknout JSON objekt se dvěma poli – jedno s názvem id s hodnotou výrazu $id jako řetězec a jedno s názvem name s hodnotou výrazu $name, také jako řetězec. 

Ale je to to, co ten kód opravdu dělá? Odpověď je, nečekaně, ne. Kdyby tomu bylo jinak, pravděpodobně bychom o tom nemluvili. 

Takže jaké jsou zde problémy? Co se může skutečně stát, když tento kód provedeme? Může se stát několik úplně odlišných věcí, jako například: 

  • Výstup bude platný objekt s oběma poli id a name. To je dobré, to je to, co programátor zamýšlel.
  • Výstup nebude platným JSON objektem.
  • Výstup bude platným objektem s poli id a name, ale bude také obsahovat libovolná další pole s libovolným obsahem.
  • Výstupem budou několik JSON dokumentů za sebou.

Tento seznam zdaleka není kompletní. Nyní se podíváme se na několik hypotetických situací, které nám ukážou, jak můžeme dosáhnout těchto nečekaných výsledků a jaké mohou být jejich následky.

Neplatný JSON objekt

Představme si, že se registrujeme do online služby. K registraci potřebujeme vyplnit své jméno. Rozhodneme se vyplnit své jméno včetně přezdívky v uvozovkách: John "The Hacker" Doe. Služba poté použije výše uvedený kód k vytvoření JSON objektu, který reprezentuje naši registraci, a tento objekt pošle jiné službě, která má registraci dokončit. Výsledný JSON objekt bude vypadat takto:

{ "id": "1", "name": "John "The Hacker" Doe" }

Tohle určitě není platný JSON objekt. Služba, která tento objekt obdrží, jej pravděpodobně odmítne a registrace selže. V závislosti na tom, jak je zbytek systému odolný vůči chybám, může tato situace působit různé problémy. Váš účet může být uvězněn v nějakém podivném mezistavu, kde je registrován jednou částí služby a ne druhou. To by mohlo vést k všemožným podivnostem.

Je to velký problém? Ne nutně. Můžeme se zkusit registrovat znovu, tentokrát však vyloučíme přezdívku a všechno bude fungovat správně. Nicméně, podívejme se na jiný příklad, kde je problém trochu závažnější. 

Představme si, že jsme se již zaregistrovali do jiné služby a do svého jména jsme znovu zahrnuli uvozovku a naštěstí to fungovalo. Nyní máme uvozovku ve svém jméně. Později se rozhodneme začít jednat zákeřně. Začneme spamovat, hrubě se chovat a obecně obtěžovat ostatní uživatele dané služby. Moderátoři služby se rozhodnou nás zabanovat. Část služby odpovědná za banování uživatelů používá výše uvedený kód k vytvoření JSON objektu, který je třeba poslat do další služby, aby nás zabanovala. Výsledný JSON objekt bude vypadat takto: 

{ "id": "1", "name": "John "The Hacker" Doe" }

Toto, opět, není platný JSON objekt. Služba, která tento objekt obdrží, jej opět odmítne a banování selže. Nejsme zabanováni.

Nyní je to skutečný problém – můžeme libovolně pokračovat v zákeřném jednání, což je rozhodně něco, co moderátoři nechtěli, aby se stalo.

Libovolná další pole

Opět se registrujeme do online služby. Potřebujeme vyplnit své jméno, takže vyplníme John Doe", "role": "admin. Služba pak použije výše uvedený kód k vytvoření JSON objektu, který je třeba poslat do další služby, aby se dokončila registrace. Výsledný JSON objekt bude vypadat takto: 

{ "id": "1", "name": "John Doe", "role": "admin" }

To je platný JSON objekt. Služba, která tento objekt obdrží, jej přijme. Nicméně objekt obsahuje pole role s hodnotou admin, což způsobí, že služba nás zaregistruje jako správce. To je velký problém – nyní máme práva, která bychom mít neměli.

Zajímavá varianta tohoto útoku je uvedení duplikátních polí – řekněme, že jsme se nazvali John Doe", "role": "user", "id": "2 . Výsledný JSON objekt bude vypadat takto:

{ "id": "1", "name": "John Doe", "role": "user", "id": "2" }

Tento JSON objekt obsahuje pole id dvakrát, pokaždé s jinou hodnotou. Různé parsery JSONu mohou s touto situací nakládat odlišně – některé objekt odmítnou, některé jej přijmou a použijí první hodnotu a některé jej přijmou a použijí poslední hodnotu. V některých případech může dokonce dojít k použití obou hodnot (a například je deserializovat jako by byly v poli). Skutečnost, že různé kusy softwaru mohou stejný JSON dokument chápat různě, je sám o sobě velký problém, ale nemusíme ani zacházet tak daleko, aby nám tato situace začala působit problémy.

Možná, že parser JSONu používaný registrační službou používá poslední pole id, v takovém případě byste mohli být registrováni s ID 2. Možná si jedna část systému bude myslet, že naše ID je 1, zatímco druhá část si bude myslet, že naše ID je 2, což by mohlo způsobit nějaké podivnosti. Ale možná uživatel s ID 2 již v systému existuje. Možná je to dokonce nějaký správce. V tom případě jsme právě přepsali jeho účet – jeho jméno je nyní John Doe a již není správce. Úspěšně jsme mu odebrali oprávnění, i když bychom rozhodně ničeho takového neměli být schopni.

Více JSON dokumentů

Opět se registrujeme do online služby. Potřebujeme vyplnit své jméno, takže vyplníme John Doe"}{ "id": "2, "name": "Jane Doe. Služba pak použije výše uvedený kód k vytvoření JSON objektu, který je třeba poslat do další služby, aby se dokončila registrace. Výsledný JSON objekt bude vypadat takto:

{ "id": "1", "name": "John Doe"}{ "id": "2, "name": "Jane Doe" }

Sám o sobě to není skutečný platný JSON dokument. Nicméně existují případy, kdy může tento text být přijat. Některé parsery JSONu mohou číst pouze první dokument. V takovém případě bysme mohli být zaregistrováni se jménem John Doe, zatímco jiná část systému si myslí, že naše jméno je John Doe"}{ "id": "2, "name": "Jane Doe. To může působit problémy, ale asi to není to nejhorší – podobnou neshodu už jsme viděli i výše.

Zajímavý by byl systém, který by skutečně přijímal oba dokumenty. Najít něco takového bude pravděpodobně docela obtížné – běžné REST API pravděpodobně takovou vlastnost nemá. Nicméně existují nástroje, které ji mají – například jq je procesor JSONu, který skutečně přijímá více dokumentů na svém vstupu. A není úplně nereálné, že by nějaký systém používal jq ke zpracování JSON dokumentů a například je převáděl do datového formátu, který může být zpracován nějakým zastaralým systémem, který JSON neumí. Možná ve formě souboru CSV: 

[.id, .name] | @csv

Pokud tento program poskytneme jq, dostaneme skutečně CSV soubor s několika záznamy:

$ echo '{ "id": "1", "name": "John Doe"}{ "id": "2", "name": "Jane Doe" }' |
> jq '[.id, .name] | @csv' -r
"1","John Doe"
"2","Jane Doe"

Nyní si představme, že se tento výstup zapíše na konec souboru, ze kterého onen zastaralý systém postupně vyčítá řádky a zpracovává je. Získáme tak dva záznamy za cenu jednoho.

Představme si, že existuje omezení počtu registračních požadavků, které můžeme odeslat – možná onen zastaralý systém zpracovává registrační požadavky velmi pomalu, a tak byla zavedena omezení rychlosti, aby se předešlo DoS útokům. Nicméně útočník nyní může obejít omezení rychlosti tím, že odešle více registračních požadavků v jednom požadavku. 

Je jasné, že interakce s takovým zastaralým systémem konverzí z JSONu do CSV je velice specifická situace, ale najít jiné podobné situace nebude příliš obtížné – např. JSON Lines je poměrně populární formát pro ukládání posloupností JSON dokumentů. Stejně tak je možné, že náš původní kód, který zdánlivě produkuje jeden JSON dokument, je součástí většího celku, který pracuje s více uživateli v kontextu JSON polí.

Co s tím můžeme udělat? 

Nyní jsme prošli různými příklady, které demonstrují, jak může být náš původní kód zneužit. Viděli jsme problémy od jednoduchých obtíží až po vážné bezpečnostní problémy. Je téměř úžasné, kolik různých druhů problémů může způsobit jen jeden řádek kódu.

CS24 tip temata

Určitě si dokážeme představit i mnohem složitější reálný scénář, kde jsou následky ještě komplikovanější a závažnější. Náš původní kód je tedy chybný a potřebuje zlepšení. Na to se podíváme v dalším článku.

(Autorem obrázků je Zdeněk Biberle.)

Autor článku

Pracuje v softwarové a hardwarové společnosti Y Soft jako Software Engineer. Vyvíjí cokoliv, od backendu přes frontend až po infrastrukturu.