paint-brush
Pentester rozebral kód webové stránky, aby dokázal, že je naprosto nesmyslnýpodle@vognik
1,565 čtení
1,565 čtení

Pentester rozebral kód webové stránky, aby dokázal, že je naprosto nesmyslný

podle Maksim Rogov16m2024/12/06
Read on Terminal Reader

Příliš dlouho; Číst

Tento článek vysvětluje, jak zpětně analyzovat neznámý kód a jak napsat vlastní rozšíření pro Burp Suite.
featured image - Pentester rozebral kód webové stránky, aby dokázal, že je naprosto nesmyslný
Maksim Rogov HackerNoon profile picture
0-item
1-item
2-item

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:

  1. Vyřešte algoritmus generování podpisu požadavku
  2. Napsat vlastní rozšíření pro Burp Suite, které automatizuje veškerou špinavou práci


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.

Ariadnino vlákno: rozluštění podpisového algoritmu


Služba je tedy online kasino se sadou klasických her:

  • Plinko — hra, ve které hráči shazují míček z horní části hrací desky plné kolíčků a sledují, jak se odrazí dolů, aby přistál ve slotu s výhrou nebo ztrátou hodnoty;
  • Tikety — hráči si zakoupí losy se sadou čísel a vyhrají, pokud se jejich čísla shodují s náhodně vylosovanými čísly;
  • LiveDealers — online kasinové hry vedené skutečnými dealery v reálném čase, což hráčům umožňuje sledovat a komunikovat prostřednictvím video streamu.
  • Double — jednoduchá hra, kde hráči sázejí na to, zda bude další karta vyšší nebo nižší než aktuální karta.
  • Crash — hráči sázejí a sledují nárůst multiplikátoru s cílem vydělat peníze dříve, než multiplikátor zkolabuje;
  • Nvuti — hráči sázejí na to, zda číslo klesne pod nebo nad určitý interval;
  • Automaty — kasinové hry, kde hráči roztáčí kotouče se symboly a vyhrávají, pokud se na obrazovce objeví určité kombinace.


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):

  • řetězec 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.

Je vůbec nutné napsat vlastní rozšíření?

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:

  1. Použijte běžně dostupné rozšíření z obchodu BApp Store
  2. Napište si vlastní rozšíření


Každá z těchto cest má své pro a proti, ukážu vám obě.

Seznámení s Hackvertorem

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:

  1. Přejděte na kartu Rozšíření

  2. Do vyhledávání zadejte Hackvertor

  3. Vyberte nalezené rozšíření v seznamu

  4. 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.

Použití Hackvertoru v boji

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á částka
  • 1 - 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 milisekundy
  • MDWpmNV9-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.

Objevte veškeré kouzlo Burp se svým rozšířením

Počáteční nastavení

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ýstupu
  • self.stdout.println(EXTENSION_NAME) – vytiskne název rozšíření v Burp Suite
  • self.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

  • Typ rozšíření - Python nebo programovací jazyk, ve kterém je rozšíření napsáno
  • Soubor přípony – cesta k samotnému souboru přípony.


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.

Zkouška pera

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.

Testování nového pravidla v praxi

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ě.


Dovést to k dokonalosti

Všechno je skvělé, ale jsou tu tři problémy:

  1. Token CSRF je převzat z hlavičky. Obecně by měl být na jedno použití, ale pravděpodobně zde má životnost (nebo ne, což je špatně). V každém případě by bylo správnější vytvořit samostatnou žádost o získání nového a aktualizovat jej. 2- Používá se předdefinované uživatelské ID. Pokud chci zkontrolovat IDOR na této službě, můj předchozí skript se stane neplatným pro jiného uživatele, protože ID je pevně zakódováno.
  2. Různé dotazy mohou mít různé parametry. A schéma, které bylo původně popsáno pro scénář, bude platné pouze pro Dungeon Dragons a žádné jiné. A chtěl bych mít možnost upravit a poslat jakýkoli požadavek.


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ů:

  1. Provede se získání tokenu CSRF a UserID
  2. Vypočítá se časové razítko a na základě všech parametrů se vygeneruje podpis. Zde je důležité poznamenat, že používám OrderedDict , který generuje slovník v pevném pořadí, protože je důležité jej zachovat při podepisování.
  3. Vygeneruje se konečné tělo požadavku a odešle se dále
 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

Závěr

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* .

L O A D I N G
. . . comments & more!

About Author

Maksim Rogov HackerNoon profile picture
Maksim Rogov@vognik
Saved hostages from Goldenface. Also enjoys hockey.

ZAVĚŠIT ZNAČKY

TENTO ČLÁNEK BYL PŘEDSTAVEN V...