Onehdy jsem narazil na službu, která ověřovala podpis požadavků na straně serveru. Jednalo se o malé online kasino, které u každého požadavku zkontrolovalo nějakou hodnotu zaslanou uživatelem z prohlížeče. Bez ohledu na to, co jste v kasinu dělali: uzavírali sázky nebo vkládali vklad, dalším parametrem v každé žádosti byla hodnota „sign“, skládající se ze sady zdánlivě náhodných znaků. Bez toho nebylo možné odeslat požadavek - stránka vrátila chybu a zabránila mi odeslat vlastní požadavky.
Nebýt této hodnoty, v tu chvíli bych stránky opustil a už by mě to nenapadlo. Ale navzdory všem předpokladům to nebyl pocit rychlého zisku, co mě vzrušovalo, ale spíše zájem o výzkum a výzva, kterou mi kasino přinášelo svou hloupostí.
Bez ohledu na účel, který měli vývojáři na mysli, když tento parametr přidali, mi připadá, že to byla ztráta času. Koneckonců, samotný podpis je generován na straně klienta a jakákoli akce na straně klienta může být podrobena zpětnému inženýrství.
V tomto článku budu diskutovat o tom, jak se mi podařilo:
Tento článek vás naučí, jak ušetřit svůj drahocenný čas a odmítnout zbytečná řešení, pokud jste vývojář, který má zájem dělat bezpečné projekty. A pokud jste pentester, po přečtení tohoto článku se můžete naučit několik užitečných lekcí o ladění a také programování vlastních rozšíření pro Swiss Knife of security. Zkrátka všichni jsou v plusu.
Pojďme konečně k věci.
Služba je tedy online kasino se sadou klasických her:
Interakce se serverem funguje výhradně na základě HTTP požadavků. Bez ohledu na hru, kterou si vyberete, každý požadavek POST na server musí být podepsán - jinak server vygeneruje chybu. Podepisování žádostí v každé z těchto her funguje na stejném principu – vezmu si k prošetření pouze jednu hru, abych nemusel dělat stejnou práci dvakrát.
A já si vezmu hru jménem Dragon Dungeon.
Podstatou této hry je v roli rytíře postupně vybírat dveře na hradě. Za každými dveřmi se skrývá buď poklad, nebo drak. Pokud hráč narazí za dveřmi na draka, hra se zastaví a on prohraje peníze. Pokud je poklad nalezen - výše počáteční sázky se zvyšuje a hra pokračuje, dokud hráč nezíská výhru, neprohraje nebo neprojde všemi úrovněmi.
Před zahájením hry musí hráč určit výši sázky a počet draků.
Jako součet zadám číslo 10, nechám jednoho draka a podívám se na žádost, která bude odeslána. To lze provést z nástrojů pro vývojáře v libovolném prohlížeči, v prohlížeči Chromium je za to zodpovědná karta Síť.
Zde také můžete vidět, že požadavek byl odeslán na koncový bod /srv/api/v1/dungeon
.
Karta Payload zobrazuje samotné tělo požadavku ve formátu JSON
První dva parametry jsou zřejmé – vybral jsem je z uživatelského rozhraní; poslední, jak asi tušíte, je timestamp
neboli čas, který uplynul od 1. ledna 1970, s typickou Javascriptovou přesností milisekund.
Tím zůstane jeden nevyřešený parametr a - a to je samotný podpis. Abych pochopil, jak se tvoří, přejdu na kartu Zdroje - toto místo obsahuje všechny zdroje služby, které prohlížeč načetl. Včetně Javascriptu, který zodpovídá za veškerou logiku klientské části webu.
Porozumět tomuto kódu není tak snadné - je minifikován. Můžete to zkusit všechno deobfuskovat - ale je to dlouhý a únavný proces, který zabere hodně času (s ohledem na množství zdrojového kódu), nejsem na to připraven.
Druhou a jednodušší možností je jednoduše najít potřebnou část kódu pomocí klíčového slova a použít debugger. Tak to udělám, protože nepotřebuji vědět, jak celý web funguje, stačí mi vědět, jak se podpis generuje.
Chcete-li tedy najít část kódu, která je zodpovědná za generování kódu, můžete otevřít prohledávání všech zdrojů pomocí kombinace kláves CTRL+SHIFT+F
a hledat přiřazení hodnoty odesílanému sign
klíči. v žádosti.
Naštěstí je tam jen jeden zápas, což znamená, že jsem na dobré cestě.
Pokud kliknete na shodu, dostanete se do sekce kódu, kde se vygeneruje samotný podpis. Kód je zatemněn jako předtím, takže je stále obtížně čitelný.
Naproti řádku kódu vložím bod přerušení, obnovím stránku a udělám novou nabídku v „dracích“ - skript nyní přestal pracovat přesně v okamžiku tvorby podpisu a vidíte stav některých proměnných.
Volaná funkce se skládá z jednoho písmene, proměnné také - ale žádný problém. Můžete přejít do konzole a zobrazit hodnoty každého z nich. Situace se začíná vyjasňovat.
První výstup hodnoty I je hodnota proměnné H
, což je funkce. Můžete na něj kliknout z konzole a přesunout se na místo, kde je deklarován v kódu, níže je výpis.
Toto je docela velký úryvek kódu, kde jsem viděl vodítko - SHA256. Toto je hashovací algoritmus. Můžete také vidět, že funkci jsou předány dva parametry, což naznačuje, že to nemusí být jen SHA256, ale HMAC SHA256 s tajemstvím.
Pravděpodobně proměnné, které jsou zde předány (také výstup do konzoly):
10;1;6693a87bbd94061678473bfb;1732817300080;gRdVWfmU-YR_RCuSkWFLCUTly_GZfDx3KEM8
- přímo hodnota, na kterou se aplikuje operace HMAC SHA256.31754cff-be0f-446f-9067-4cd827ba8707
je statická konstanta, která funguje jako tajemství
Abych se o tom ujistil, zavolám funkci a získám předpokládaný podpis
Nyní jdu na web, který počítá HMAC SHA256 a předávám do něj hodnoty.
A porovnání s tím, který byl zaslán v žádosti, když jsem podával nabídku.
Výsledek je identický, což znamená, že mé odhady byly správné - skutečně používá HMAC SHA256 se statickým tajemstvím, kterému se předává speciálně vytvořený řetězec s rychlostí, počtem draků a některými dalšími parametry, o kterých vám povím dále v průběhu článku.
Algoritmus je poměrně jednoduchý a přímočarý. Ale pořád to nestačí – pokud by bylo cílem v rámci pracovního projektu pro pentest najít zranitelnosti, musel bych se naučit posílat vlastní dotazy pomocí Burp Suite.
A to rozhodně potřebuje automatizaci, o které teď budu mluvit.
Přišel jsem na algoritmus generování podpisu. Nyní je čas naučit se, jak jej automaticky generovat, abyste odstranili všechny nepotřebné věci při odesílání požadavků.
Požadavky můžete posílat pomocí ZAP, Caido, Burp Suite a dalších nástrojů pentest. Tento článek se zaměří na Burp Suite, protože to považuji za uživatelsky nejpřívětivější a téměř dokonalé. Community Edition si můžete zdarma stáhnout z oficiálních stránek, stačí na všechny experimenty.
Burp Suite po vybalení neví, jak vygenerovat HMAC SHA256. Za tímto účelem můžete použít rozšíření, která doplňují funkčnost sady Burp Suite.
Rozšíření jsou vytvářena jak členy komunity, tak samotnými vývojáři. Jsou distribuovány buď prostřednictvím vestavěného bezplatného obchodu BApp Store, Github nebo jiných úložišť zdrojového kódu.
Jsou dvě cesty, kterými se můžete vydat:
Každá z těchto cest má své pro a proti, ukážu vám obě.
Metoda s hotovým nástavcem je nejjednodušší. Je to stáhnout si jej z obchodu BApp Store a použít jeho funkce ke generování hodnoty pro parametr sign
.
Rozšíření, které jsem použil, se nazývá Hackvertor . Umožňuje vám používat XML jako syntaxi, takže můžete dynamicky kódovat/dekódovat, šifrovat/dešifrovat, hashovat různá data.
Chcete-li jej nainstalovat, Burp vyžaduje:
Přejděte na kartu Rozšíření
Do vyhledávání zadejte Hackvertor
Vyberte nalezené rozšíření v seznamu
Klepněte na tlačítko Instalovat
Po instalaci se v Burp objeví karta se stejným názvem. Můžete na něj přejít a vyhodnotit možnosti rozšíření a počet dostupných značek, z nichž každý lze vzájemně kombinovat.
Abychom uvedli příklad, můžete něco zašifrovat pomocí symetrického AES pomocí značky <@aes_encrypt('supersecret12356','AES/ECB/PKCS5PADDING')>MySuperSecretText<@/aes_encrypt>
.
Tajemství a algoritmus jsou v závorkách a mezi značkami je samotný text, který má být zašifrován. Jakékoli značky lze použít v Repeater, Intruder a dalších vestavěných nástrojích Burp Suite.
S pomocí rozšíření Hackvertor můžete popsat, jak by měl být podpis generován na úrovni tagu. Udělám to na příkladu skutečné žádosti.
Takže vsadím v Dragon Dungeon, zachytím stejný požadavek, který jsem zachytil na začátku tohoto článku pomocí Intercept Proxy, a zdůrazňuji ho do Repeateru, abych ho mohl upravit a znovu odeslat.
Nyní místo hodnoty ae04afe621864f569022347f1d1adcaa3f11bebec2116d49c4539ae1d2c825fc
musíme nahradit algoritmus pro generování HMAC SHA256 pomocí značek poskytovaných Hackvertorem.
Формула генерации у меня получилась следующая <@hmac_sha256('31754cff-be0f-446f-9067-4cd827ba8707')>10;1;6693a87bbd94061678473bfb;<@timestamp/>000;MDWpmNV9-j8tKbk-evbVLtwMsMjKwQy5YEs4<@/hmac_sha256>
.
Zvažte všechny parametry:
10
- vsazená částka1
- počet draků6693a87bbd94061678473bfb
- unikátní ID uživatele z databáze MongoDB, viděl jsem to při analýze podpisu z prohlížeče, ale tehdy jsem o tom nepsal. Dokázal jsem to najít prohledáním obsahu dotazů v Burp Suite, vrací se z dotazu na koncový bod /srv/api/v1/profile/me
.
<@timestamp/>000
- generování časového razítka, poslední tři nuly zpřesňují čas na milisekundyMDWpmNV9-j8tKbk-evbVLtwMsMjKwQy5YEs4
– token CSRF, který je vrácen z koncového bodu /srv/api/v1/csrf
a nahrazen v každém požadavku v záhlaví X-Xsrf-Token
.<@hmac_sha256('31754cff-be0f-446f-9067-4cd827ba8707')>
a <@/hmac_sha256>
- otevírací a uzavírací značky pro generování HMAC SHA256 ze substituované hodnoty s tajným klíčem jako konstantou 31754cff-be0f-446f-9067-4cd827ba8707
.
Důležité upozornění: parametry musí být vzájemně propojeny přes ;
v přísném pořadí, - jinak bude podpis vygenerován nesprávně - jako na tomto snímku obrazovky, kde jsem prohodil rychlost a počet draků
V tom spočívá veškeré kouzlo.
Nyní udělám správný dotaz, kde specifikuji parametry ve správném pořadí a získám informaci, že vše proběhlo úspěšně a hra se spustila - to znamená, že Hackvertor místo vzorce vygeneroval podpis, dosadil jej do dotazu a vše funguje .
Tento způsob má však značnou nevýhodu – ruční práce se úplně nezbavíte. Pokaždé, když změníte rychlost nebo počet draků v JSON, musíte to změnit v samotném podpisu, aby se shodovaly.
Také pokud odešlete nový požadavek z karty Proxy do Intruder nebo Repeater, musíte přepsat vzorec, což je velmi, velmi nepohodlné, když potřebujete mnoho karet pro různé testovací případy.
Tento vzorec také selže v jiných dotazech, kde se používají jiné parametry.
Rozhodl jsem se tedy napsat vlastní rozšíření, abych tyto nevýhody překonal.
Rozšíření pro Burp Suite můžete psát v Javě a Pythonu. Použiji druhý programovací jazyk, protože je jednodušší a vizuálnější. Musíte se ale předem připravit: nejprve si musíte stáhnout Jython Standalone z oficiálních stránek a poté cestu ke staženému souboru v nastavení Burp Suite.
Poté je potřeba vytvořit soubor se samotným zdrojovým kódem a příponou *.py
.
Již mám předvalek, který definuje základní logiku, zde je jeho obsah:
Vše je intuitivně jednoduché a přímočaré:
getActionName
– tato metoda vrací název akce, kterou má rozšíření provést. Samotné rozšíření přidává pravidlo pro zpracování relace, které lze flexibilně použít na jakýkoli z požadavků, ale o tom později. Je důležité vědět, že tento název se může lišit od názvu rozšíření a že jej bude možné vybrat z rozhraní.performAction
- zde bude upřesněna logika samotného pravidla, které bude aplikováno na vybrané požadavky
Obě metody jsou deklarovány podle rozhraní ISessionHandlingAction .
Nyní k rozhraní IBurpExtender . Deklaruje jedinou nezbytnou metodu registerExtenderCallbacks
, která se provede ihned po načtení rozšíření a je potřeba, aby vůbec fungovalo.
Zde se provádí základní konfigurace:
callbacks.setExtensionName(EXTENSION_NAME)
– zaregistruje aktuální rozšíření jako akci pro zpracování relacísys.stdout = callbacks.getStdout()
- přesměruje standardní výstup (stdout) do výstupního okna Burp Suite (panel „Rozšíření“)self.stderr = PrintWriter(callbacks.getStdout(), True)
– vytvoří proud pro chyby výstupuself.stdout.println(EXTENSION_NAME)
– vytiskne název rozšíření v Burp Suiteself.callbacks = callbacks
- uloží objekt zpětných volání jako atribut self. To je potřeba pro pozdější použití Burp Suite API v jiných částech kódu rozšíření.self.helpers = callbacks.getHelpers()
– také získává užitečné metody, které budou potřeba při spuštění rozšíření
Po předběžných přípravách je to hotovo. Nyní můžete rozšíření načíst a ujistit se, že vůbec funguje. Chcete-li to provést, přejděte na kartu Rozšíření a klikněte na Přidat.
V okně, které se zobrazí, zadejte
A klikněte na Další.
Pokud byl soubor zdrojového kódu správně naformátován, nemělo by dojít k žádným chybám a na kartě Výstup se zobrazí název přípony. To znamená, že vše funguje dobře.
Rozšíření se načte a funguje - ale vše, co bylo načteno, byl obal bez jakékoli logiky, nyní potřebuji kód přímo k podepsání požadavku. Už jsem to napsal a je to zobrazeno na obrázku níže.
Celé rozšíření funguje tak, že než je požadavek odeslán na server, bude mým rozšířením upraven.
Nejprve vezmu požadavek, který rozšíření zachytilo, a z jeho těla získám rychlost a počet draků
json_body = json.loads(message_body) amount_currency = json_body["amountCurrency"] dragons = json_body["dragons"]
Dále jsem si přečetl aktuální časové razítko a získal CSRF token z odpovídající hlavičky
currentTime = str(time.time()).split('.')[0]+'100' xcsrf_token = None for header in headers: if header.startswith("X-Xsrf-Token"): xcsrf_token = header.split(":")[1].strip()
Dále je samotný požadavek podepsán pomocí HMAC SHA256
hmac_sign = hmac_sha256(key, message=";".join([str(amount_currency), str(dragons), user_id, currentTime, xcsrf_token]))
Samotná funkce a konstanty označující tajemství a ID uživatele byly předem deklarovány nahoře
def hmac_sha256(key, message): return hmac.new( key.encode("utf-8"), message.encode("utf-8"), hashlib.sha256 ).hexdigest() key = "434528cb-662f-484d-bda9-1f080b861392" user_id = "zex2q6cyc4ba3gvkyex5f80m"
Poté jsou hodnoty zapsány do těla požadavku a převedeny na JSON
json_body["sign"] = hmac_sign json_body["t"] = currentTime message_body = json.dumps(json_body)
Posledním krokem je vygenerování podepsaného a upraveného požadavku a jeho odeslání
httpRequest = self.helpers.buildHttpMessage(get_final_headers, message_body) baseRequestResponse.setRequest(httpRequest)
To je vše, zdrojový kód je napsán. Nyní můžete rozšíření znovu načíst v Burp Suite (mělo by to být provedeno po každé úpravě skriptu) a ujistit se, že vše funguje.
Nejprve je ale potřeba přidat nové pravidlo pro zpracování požadavků. Chcete-li to provést, přejděte do Nastavení v části Relace. Zde najdete všechna různá pravidla, která se spouštějí při odesílání požadavků.
Kliknutím na Přidat přidáte rozšíření, které se spouští u určitých typů požadavků.
V okně, které se objeví, nechám vše tak, jak je, a zvolím Přidat v akcích pravidel
Zobrazí se rozevírací seznam. V něm vyberte Invoke a Burp extension.
A zadejte pro něj pobočku, která bude volána při odesílání požadavků. Jeden mám a je to Burp Extension.
Po výběru rozšíření kliknu na OK. A přejdu na kartu Rozsah, kde specifikuji:
Rozsah nástrojů – opakovač (rozšíření by se mělo spustit, když ručně odesílám požadavky přes opakovač)
Rozsah URL – Zahrnout všechny adresy URL (aby fungoval na všechny odeslané požadavky).
Mělo by to fungovat jako na obrázku níže.
Po kliknutí na OK se pravidlo rozšíření objevilo v obecném seznamu.
Konečně si můžete vše vyzkoušet v akci! Nyní můžete změnit některé dotazy a zjistit, jak se bude podpis dynamicky aktualizovat. A i když se dotaz nezdaří, bude to proto, že jsem zvolil zápornou sazbu, ne proto, že by bylo něco špatně s podpisem (jen nechci vyhazovat peníze 😀). Samotné rozšíření funguje a podpis je generován správně.
Všechno je skvělé, ale jsou tu tři problémy:
Abychom to vyřešili, musíme místo requests
přidat dva další požadavky, které lze provést pomocí vestavěné knihovny Burp Suite namísto jakýchkoli třetích stran.
Abych to udělal, zabalil jsem nějakou standardní logiku, aby byly dotazy pohodlnější. Prostřednictvím standardních metod Burpa se interakce s dotazy provádí v pleintextu.
def makeRequest(self, method="GET", path="/", headers=None, body=None): first_line = method + " " + path + " HTTP/1.1" headers[0] = first_line if body is None: body = "{}" http_message = self.helpers.buildHttpMessage(headers, body) return self.callbacks.makeHttpRequest(self.request_host, self.request_port, True, http_message)
A přidal dvě funkce extrahující data, která potřebuji, token CSRF a ID uživatele.
def get_csrf_token(self, headers): response = self.makeRequest("GET", "/srv/api/v1/csrf", headers) message = self.helpers.analyzeRequest(response) raw_headers = str(message.getHeaders()) match = re.search(r'XSRF-TOKEN=([a-zA-Z0-9_-]+)', raw_headers) return match.group(1) def get_user_id(self, headers): raw_response = self.makeRequest("POST", "/srv/api/v1/profile/me", headers) response = self.helpers.bytesToString(raw_response) match = re.search(r'"_id":"([a-f0-9]{24})"', response) return match.group(1)
A to aktualizací samotného tokenu v odeslaných hlavičkách
def update_csrf(self, headers, token): for i, header in enumerate(headers): if header.startswith("X-Xsrf-Token:"): headers[i] = "X-Xsrf-Token: " + token return headers
Funkce podpisu vypadá takto. Zde je důležité poznamenat, že vezmu všechny vlastní parametry, které jsou odeslány v požadavku, na jejich konec přidám standardní user_id
, currentTime
, csrf_token
a všechny je podepíšu pomocí ;
jako oddělovač.
def sign_body(self, json_body, user_id, currentTime, csrf_token): values = [] for key, value in json_body.items(): if key == "sign": break values.append(str(value)) values.extend([str(user_id), str(currentTime), str(csrf_token)]) return hmac_sha256(hmac_secret, message=";".join(values))
Hlavní letax se zredukoval na několik řádků:
OrderedDict
, který generuje slovník v pevném pořadí, protože je důležité jej zachovat při podepisování. csrf_token = self.get_csrf_token(headers) final_headers = self.update_csrf(final_headers, csrf_token) user_id = self.get_user_id(headers) currentTime = str(time.time()).split('.')[0]+'100' json_body = json.loads(message_body, object_pairs_hook=OrderedDict) sign = self.sign_body(json_body, user_id, currentTime, csrf_token) json_body["sign"] = sign json_body["t"] = currentTime message_body = json.dumps(json_body) httpRequest = self.helpers.buildHttpMessage(final_headers, message_body) baseRequestResponse.setRequest(httpRequest)
Snímek obrazovky, jen pro jistotu
Pokud nyní přejdete do jiné hry, kde jsou vlastní parametry již 3 místo 2, a pošlete žádost, můžete vidět, že bude úspěšně odeslána. To znamená, že moje rozšíření je nyní univerzální a funguje pro všechny požadavky.
Příklad odeslání žádosti o doplnění účtu
Rozšíření jsou nedílnou součástí Burp Suite. Často služby implementují vlastní funkcionalitu, kterou předem nenapíše nikdo jiný než vy. Proto je důležité nejen stahovat hotová rozšíření, ale také si psát vlastní, což jsem se vás snažil naučit v tomto článku.
To je zatím vše, zdokonalujte se a nebuďte nemocní.
Odkaz na zdrojový kód rozšíření: *klikněte* .