Postřehy z bezpečnosti: zranitelnost v AI asistentovi GitLab Duo

26. 5. 2025
Doba čtení: 10 minut

Sdílet

Robot sedí u počítače a programuje
Autor: Root.cz s využitím Zoner AI
Pravidelný pondělní přehled zajímavých událostí z oblasti počítačové bezpečnosti. Podíváme se na zneužití TikToku pro distribuci malwaru, pokračování operace Endgame nebo novou zranitelnost v AI asistentovi.

Zneužití TikToku k šíření malwaru

Malware známý jako Latrodectus se stal nejnovějším, jenž využívá jako vektor distribuce široce používanou techniku sociálního inženýrství zvanou ClickFix.

Technika ClickFix umožňuje spuštění malwaru v paměti, místo aby byl zapsán na disk – proto odebírá mnoho příležitostí prohlížečů nebo bezpečnostních nástrojů, aby malware odhalily nebo zablokovaly. Latrodectus, považovaný za nástupce IcedID, je název pro malware, jenž funguje jako downloader pro další payloady, například ransomware. Poprvé byl zdokumentován společnostmi Proofpoint a Team Cymru v dubnu 2024.

Tento malware je zároveň jedním z mnoha škodlivých softwarů, které utrpěly porážku v rámci operace Endgame, která mezi 19. a 22. květnem 2025 zlikvidovala 300 serverů po celém světě a neutralizovala 650 domén souvisejících s Bumblebee, Lactrodectus, QakBot, HijackLoader, DanaBot, TrickBot a WARMCOOKIE (viz níže).

V poslední sadě útoků Latrodectus, které společnost Expel zaznamenala v květnu 2025, jsou nic netušící uživatelé navedeni ke zkopírování a spuštění příkazu PowerShell z infikované webové stránky, což je taktika, která se stala rozšířenou metodou distribuce široké škály malwarů. Po spuštění uživatelem se tyto příkazy pokusí nainstalovat soubor umístěný na vzdálené adrese URL pomocí MSIExec a poté jej spustit v paměti, uvedl Expel. Útočník tak nemusí soubor zapsat do počítače a riskovat, že ho odhalí prohlížeč nebo antivir, jenž by jej mohl detekovat na disku.

Instalační soubor MSI obsahuje legitimní aplikaci od společnosti NVIDIA, která je použita k bočnímu nahrání (sideload) škodlivé knihovny DLL, která pak pomocí curl stáhne hlavní payload. Pro zmírnění útoků tohoto typu se doporučuje zakázat funkci Windows Run pomocí objektů zásad skupiny (GPO) nebo vypnout horkou klávesu „Windows + R“ pomocí změny registru systému Windows.

Společnost Trend Micro odkryla podrobnosti o nové kampani sociálního inženýrství, která namísto falešných stránek CAPTCHA využívá videa na platformě TikTok generovaná pravděpodobně pomocí nástrojů umělé inteligence k doručení nástrojů Vidar a StealC pro krádež informací, přičemž uživatele instruuje, aby ve svých systémech spustili škodlivé příkazy pro aktivaci systémů Windows, Microsoft Office, CapCut a Spotify.

Tato videa byla zveřejněna z různých účtů na TikToku, jako jsou @gitallowed, @zane.houghton, @allaivo2, @sysglow.wow, @alexfixpc a @digitaldreams771. Tyto účty již nejsou aktivní. Jedno z videí, které tvrdí, že poskytuje návod, jak „okamžitě zvýšit zážitek ze služby Spotify“, nasbíralo téměř 500 000 zhlédnutí s více než 20 000 lajky a více než 100 komentáři.

Kampaň znamená novou eskalaci ClickFix v tom, že uživatelé hledající způsoby aktivace pirátských aplikací jsou slovně i vizuálně vedeni k tomu, aby stisknutím klávesové zkratky „Windows + R“ otevřeli dialogové okno Spustit systém Windows, spustili PowerShell a příkaz zdůrazněný ve videu, čímž nakonec ohrozí vlastní systém.

Aktéři hrozeb nyní využívají videa na TikToku, která jsou potenciálně generována pomocí nástrojů s umělou inteligencí, k ovlivňování uživatelů, aby provedli příkazy PowerShell pod záminkou, že vedou k aktivaci legitimního softwaru nebo odemknutí prémiových funkcí, uvedl bezpečnostní výzkumník Junestherry Dela Cruz.

Tato kampaň poukazuje na to, že útočníci jsou připraveni využít k šíření malwaru jakoukoliv aktuálně populární platformu sociálních médií.

Europol zabavil 300 serverů a 3,5 milionu eur v ransomwarové síti

V rámci poslední sezóny operace Endgame zlikvidovala koalice donucovacích orgánů přibližně 300 serverů po celém světě, neutralizovala 650 domén a vydala zatykače na 20 aktérů.

Operace Endgame, která byla poprvé spuštěna v květnu 2024, je pokračující operací orgánů činných v trestním řízení zaměřenou na služby a infrastruktury, jež napomáhají nebo přímo poskytují počáteční či konsolidační přístup k ransomwaru. Předchozí část operace se zaměřila na likvidaci rodin malwaru pro počáteční přístup, které byly využívány k poskytování ransomwaru.

Poslední iterace se podle Europolu zaměřila na nové varianty malwaru a nástupnické skupiny, které se znovu objevily po loňském odstranění, jako jsou Bumblebee, Lactrodectus, QakBot, HijackLoader, DanaBot, TrickBot a WARMCOOKIE. Akce byla provedena mezi 19. a 22. květnem 2025.

Kromě toho bylo během akčního týdne zabaveno 3,5 milionu eur v kryptoměnách, čímž celková částka zabavená během operace Endgame přesáhla 21,2 milionu eur, uvedla agentura. Europol upozornil, že varianty malwaru jsou nabízeny jako služba dalším aktérům a jsou využívány k rozsáhlým útokům ransomwaru. Kromě toho byly vydány mezinárodní zatykače na 20 klíčových aktérů, kteří pravděpodobně poskytují nebo provozují služby počátečního přístupu ransomwarovým skupinám.

Tato nová fáze ukazuje schopnost orgánů činných v trestním řízení přizpůsobit se a znovu udeřit, i když se kyberzločinci převlékají a reorganizují, uvedla výkonná ředitelka Europolu Catherine De Bolleová. Narušením služeb, na které se zločinci spoléhají při nasazování ransomwaru, přerušujeme smrtící řetězec u jeho zdroje.

Německý Spolkový kriminální úřad (Bundeskriminalamt, BKA) oznámil, že bylo zahájeno trestní řízení proti 37 identifikovaným aktérům. Některé z osob, které byly zařazeny na seznam nejhledanějších osob EU, jsou uvedeny níže:

  • Roman Michajlovič Prokop (aka carterj), 36 let, člen skupiny QakBot.
  • Danil Raisowitsch Khalitov (aka dancho), 37 let, člen skupiny QakBot
  • Iskander Rifkatovič Šarafetdinov (aka alik, gucci), 32 let, člen skupiny TrickBot
  • Mikhail Mikhailovich Tsarev (aka mango), 36 let, člen skupiny TrickBot
  • Maksim Sergeevich Galochkin (aka bentley, manuel, Max17, volhvb, crypt), 43 let, člen skupiny TrickBot
  • Vitalii Nikolaevich Kovalev (aka stern, ben, Grave, Vincent, Bentley, Bergen, Alex Konor), 36 let, člen skupiny TrickBot

Odhalení přichází v době, kdy Europol ukončil rozsáhlou operaci, jejímž výsledkem bylo 270 zatčených prodejců a kupců na dark webu v 10 zemích: Spojené státy (130), Německo (42), Spojené království (37), Francie (29), Jižní Korea (19), Rakousko (4), Nizozemsko (4), Brazílie (3), Švýcarsko (1) a Španělsko (1).

Europol uvedl, že podezřelí byli identifikováni na základě zpravodajských informací získaných při likvidaci dark webových tržišť Nemesis, Tor2Door, Bohemia a Kingdom Markets. Několik podezřelých údajně uskutečnilo tisíce prodejů na nelegálních tržištích, přičemž často používali šifrovací nástroje a kryptoměny, aby skryli své digitální stopy. Tato mezinárodní razie známá jako operace RapTor rozbila sítě obchodující s drogami, zbraněmi a padělaným zbožím a vyslala jasný signál zločincům skrývajícím se za iluzí anonymity, uvedl Europol.

Spolu se zatýkáním úřady zabavily 184 milionů eur v hotovosti a kryptoměnách, dvě tuny drog, 180 střelných zbraní, 12 500 padělaných výrobků a více než čtyři tuny nelegálního tabáku. Společná akce navazuje na operaci SpecTor z května 2023, jež vedla k zatčení 288 prodejců a kupců z dark webu a zabavení 50,8 milionu eur v hotovosti a kryptoměnách.

CISA varuje před podezřením na rozsáhlejší SaaS útoky

Americká agentura pro kybernetickou bezpečnost a bezpečnost infrastruktury (CISA) ve čtvrtek oznámila, že společnost Commvault monitoruje aktivity kybernetických hrozeb zaměřené na aplikace hostované v jejich cloudovém prostředí Microsoft Azure.

Aktéři hrozeb mohli získat přístup k soukromým informacím klientů zálohovacího řešení softwaru jako služby (SaaS) Microsoft 365 (M365) společnosti Commvault (Metallic), které je hostováno v prostředí Azure, uvedla agentura. Tímto způsobem získali aktéři hrozeb neoprávněný přístup do prostředí M365 zákazníků společnosti Commvault, která mají aplikační tajemství uložená společností Commvault.

CISA dále poznamenala, že tato aktivita může být součástí širší kampaně zaměřené na cloudové infrastruktury různých poskytovatelů softwaru jako služby (SaaS) s výchozími konfiguracemi a zvýšenými oprávněními.

Informace přichází několik týdnů poté, co společnost Commvault oznámila, že ji Microsoft v únoru 2025 informoval o neoprávněné aktivitě státního aktéra hrozby v prostředí Azure. Incident vedl ke zjištění, že aktéři zneužívali zranitelnost nultého dne (CVE-2025–3928), blíže nespecifikovanou chybu ve webovém serveru Commvault, která umožňuje vzdálenému autentizovanému útočníkovi vytvářet a spouštět web shelly.

Na základě informací expertů z oboru tento aktér hrozeb používá propracované techniky, kterými se snaží získat přístup do prostředí zákazníků M365, uvedla společnost Commvault ve svém oznámení. Tento aktér hrozeb mohl získat přístup k podmnožině přihlašovacích údajů aplikací, jež někteří zákazníci společnosti Commvault používají k ověřování svých prostředí M365.

Společnost Commvault uvedla, že přijala několik nápravných opatření, včetně rotace přihlašovacích údajů aplikace M365, ale zdůraznila, že nedošlo k žádnému neoprávněnému přístupu k záložním datům zákazníků.

Pro zmírnění těchto hrozeb doporučuje CISA uživatelům a správcům dodržovat následující pokyny:

  • Sledovat protokoly auditu systému Entra, zda nedošlo k neoprávněným změnám nebo přidání pověření k zadavatelům služeb iniciovaným aplikacemi/zákazníky služeb společnosti Commvault.
  • Prohlížet protokoly společnosti Microsoft (audit Entra, přihlašovací protokoly Entra, jednotné protokoly auditu) a provádět interní vyhledávání hrozeb.
  • U aplikací pro jednoho tenanta implementujte zásady podmíněného přístupu, které omezují ověřování hlavní služby aplikace na schválenou IP adresu, jež je uvedena v seznamu povolených IP adres společnosti Commvault.
  • Přezkoumejte seznam registrací aplikací a příkazů služeb v systému Entra se souhlasem správce pro vyšší oprávnění.
  • Omezte přístup k rozhraním pro správu systému Commvault na důvěryhodné sítě a systémy správy.
  • Zjistěte a zablokujte pokusy o path-traversal a podezřelé nahrávání souborů nasazením brány Web Application Firewall a odebráním externího přístupu k aplikacím Commvault.

CISA, která koncem dubna 2025 přidala CVE-2025–3928 do svého katalogu známých zneužívaných zranitelností, uvedla, že ve spolupráci s partnerskými organizacemi pokračuje ve vyšetřování této škodlivé aktivity.

Zranitelnost GitLab Duo umožnila získat odpovědi AI pomocí skrytých promptů

Výzkumníci v oblasti kybernetické bezpečnosti objevili v asistentovi umělé inteligence Duo společnosti GitLab chybu v nepřímém vkládání promptů, která mohla útočníkům umožnit krádež zdrojového kódu a vložení nedůvěryhodného HTML do jeho odpovědí, které pak mohly být použity k přesměrování obětí na škodlivé webové stránky.

GitLab Duo je kódovací asistent s umělou inteligencí, který uživatelům umožňuje psát, prohlížet a upravovat kód. Služba, vytvořená na základě modelů společnosti Anthropic Claude, byla poprvé spuštěna v červnu 2023.

Jak však zjistila společnost Legit Security, GitLab Duo Chat byl náchylný k chybě v podobě nepřímé výzvy (indirect prompt injection), která útočníkům umožňuje krást zdrojový kód ze soukromých projektů, manipulovat s návrhy kódu zobrazenými ostatním uživatelům a dokonce vynášet důvěrné a nezveřejněné zranitelnosti nultého dne.

Prompt injection označuje třídu zranitelností běžných v systémech umělé inteligence, které umožňují aktérům hrozeb využít velké jazykové modely (LLM) k manipulaci s odpověďmi na podněty uživatelů a vést k nežádoucímu chování. Nepřímé výzvy jsou mnohem složitější, protože namísto přímého zadání vstupu vytvořeného umělou inteligencí jsou podvodné pokyny vloženy do jiného kontextu, například dokumentu nebo webové stránky, které má model zpracovat.

Nedávné studie ukázaly, že LLM jsou také zranitelné vůči technikám útoku typu jailbreak, jež umožňují podvést chatboty řízené umělou inteligencí, aby generovaly škodlivé a nezákonné informace, které nerespektují jejich etické a bezpečnostní zábrany, čímž účinně eliminují potřebu pečlivě připravených promptů. Metody úniku podnětů (PLeak) by navíc mohly být použity k neúmyslnému odhalení přednastavených systémových podnětů nebo pokynů, kterými se má model řídit.

Pro organizace to znamená, že může dojít k úniku soukromých informací, jako jsou interní pravidla, funkce, kritéria filtrování, oprávnění a uživatelské role, uvedla společnost Trend Micro ve zprávě zveřejněné na začátku tohoto měsíce. To by mohlo útočníkům poskytnout příležitost ke zneužití slabých míst systému, což by mohlo vést k únikům dat, odhalení obchodních tajemství, porušení právních předpisů a dalším nepříznivým důsledkům.

Nejnovější zjištění izraelské firmy zabývající se zabezpečením dodavatelského řetězce softwaru ukazují, že k úniku citlivých údajů nebo k injektování HTML do odpovědí aplikace GitLab Duo stačil skrytý komentář umístěný kdekoliv v požadavcích na sloučení, zprávách o revizi, popisech problémů nebo komentářích a zdrojovém kódu.

Tyto výzvy bylo možné dále skrýt pomocí kódovacích triků, jako je kódování Base16, pašování Unicode a vykreslování KaTeXu do bílého textu, aby byly méně odhalitelné. Nedostatek sanitizace vstupu a skutečnost, že GitLab neošetřil žádný z těchto scénářů s větší pečlivostí než zdrojový kód, mohly umožnit škodlivým aktérům podstrčit výzvy napříč webem.

Duo analyzuje celý kontext stránky, včetně komentářů, popisů a zdrojového kódu, takže je zranitelný vůči injektovaným instrukcím skrytým kdekoliv v tomto kontextu, uvedl bezpečnostní výzkumník Omer Mayraz. To také znamená, že útočník by mohl oklamat systém AI a zahrnout do syntetizovaného kódu škodlivý balíček JavaScriptu nebo prezentovat škodlivou adresu URL jako bezpečnou, což by způsobilo přesměrování oběti na falešnou přihlašovací stránku, která by získala její přihlašovací údaje.

Navíc společnost zjistila, že díky využití schopnosti GitLab Duo Chat přistupovat k informacím o konkrétních žádostech o sloučení a změnách kódu, je možné do popisu žádosti o sloučení projektu vložit skrytou výzvu, která po zpracování systémem Duo způsobí exfiltraci soukromého zdrojového kódu na server ovládaný útočníkem. To je zase možné díky použití proudového vykreslování markdown pro interpretaci a vykreslování odpovědí do HTML v průběhu generování výstupu.

Po zodpovědném zveřejnění 12. února 2025 společnost GitLab problémy adresovala:

zabbix_tip

Tato zranitelnost poukazuje na dvojí povahu asistentů s umělou inteligencí, jako je GitLab Duo: když jsou hluboce integrováni do vývojových pracovních postupů, dědí nejen kontext – ale i riziko, uvedl Mayraz. Vložením skrytých instrukcí do zdánlivě neškodného obsahu projektu se nám podařilo manipulovat s chováním systému Duo, exfiltrovat soukromý zdrojový kód a ukázat, jak lze reakce AI využít k nezamýšleným a škodlivým výsledkům.

Ve zkratce

O seriálu

Tento seriál vychází střídavě za pomoci pracovníků Národního bezpečnostního týmu CSIRT.CZ provozovaného sdružením CZ.NIC a bezpečnostního týmu CESNET-CERTS sdružení CESNET, bezpečnostního týmu CDT-CERT provozovaného společností ČD - Telematika a bezpečnostních specialistů Jana Kopřivy ze společnosti Nettles Consulting a Moniky Kutějové ze sdružení TheCyberValkyries. Více o seriálu…

Neutrální ikona do widgetu na odběr článků ze seriálů

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.


Autor článku

Pracuje ve státním sektoru jako specialistka kybernetické bezpečnosti a založila neziskovou organizaci TheCyberValkyries.

'; document.getElementById('preroll-iframe').onload = function () { setupIframe(); } prerollContainer = document.getElementsByClassName('preroll-container-iframe')[0]; } function setupIframe() { prerollDocument = document.getElementById('preroll-iframe').contentWindow.document; let el = prerollDocument.createElement('style'); prerollDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:20px;right:25px}"; videoContent = prerollDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('PREROLL sound allowed'); // setUpIMA(true); videoContent.volume = 1; videoContent.muted = false; setUpIMA(); }).catch(function () { console.log('PREROLL sound forbidden'); videoContent.volume = 0; videoContent.muted = true; setUpIMA(); }); } } function setupDimensions() { prerollWidth = Math.min(iinfoPrerollPosition.offsetWidth, 480); prerollHeight = Math.min(iinfoPrerollPosition.offsetHeight, 320); } function setUpIMA() { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Preroll advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = prerollWidth; // adsRequest.linearAdSlotHeight = prerollHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. prerollDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( prerollDocument.getElementById('adContainer'), videoContent); } function unmutePrerollAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } } function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(prerollWidth, prerollHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } function onAdEvent(adEvent) { const ad = adEvent.getAd(); console.log('Preroll event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: if (!ad.isLinear()) { videoContent.play(); } prerollDocument.getElementById('adContainer').style.width = '100%'; prerollDocument.getElementById('adContainer').style.maxWidth = '640px'; prerollDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); if (ad.isLinear()) { intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } prerollDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (prerollLastError === 303) { playYtVideo(); } break; case google.ima.AdEvent.Type.COMPLETE: if (ad.isLinear()) { clearInterval(intervalTimer); } playYtVideo(); break; } } function onAdError(adErrorEvent) { console.log(adErrorEvent.getError()); prerollLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { playYtVideo(); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoPrerollPosition.remove(); playPrerollAd(); } else { return false; } adVolume = 1; return true; } function onContentPauseRequested() { videoContent.pause(); } function onContentResumeRequested() { videoContent.play(); } function onActiveView() { if (prerollContainer) { const containerOffset = prerollContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (prerollPaused) { adsManager.resume(); prerollPaused = false; } return true; } else { if (!prerollPaused) { adsManager.pause(); prerollPaused = true; } } } return false; } function playYtVideo() { iinfoPrerollPosition.remove(); youtubeIframe.style.display = 'block'; youtubeIframe.src += '&autoplay=1&mute=1'; } }
'; document.getElementById('outstream-iframe').onload = function () { setupIframe(); } replayScreen = document.getElementById('iinfoOutstreamReplay'); iinfoOutstreamPosition = document.getElementById('iinfoOutstreamPosition'); outstreamContainer = document.getElementsByClassName('outstream-container')[0]; setupReplayScreen(); } function setupIframe() { outstreamDocument = document.getElementById('outstream-iframe').contentWindow.document; let el = outstreamDocument.createElement('style'); outstreamDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:-5px;right:25px}"; videoContent = outstreamDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; if ( location.href.indexOf('rejstriky.finance.cz') !== -1 || location.href.indexOf('finance-rejstrik') !== -1 || location.href.indexOf('firmy.euro.cz') !== -1 || location.href.indexOf('euro-rejstrik') !== -1 || location.href.indexOf('/rejstrik/') !== -1 || location.href.indexOf('/rejstrik-firem/') !== -1) { outstreamDirectPlayed = true; soundAllowed = true; iinfoVastUrlIndex = 0; } if (!outstreamDirectPlayed) { console.log('OUTSTREAM direct'); setUpIMA(true); } else { if (soundAllowed) { const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('OUTSTREAM sound allowed'); setUpIMA(false); }).catch(function () { console.log('OUTSTREAM sound forbidden'); renderBanner(); }); } } else { renderBanner(); } } } function getWrapper() { let articleWrapper = document.querySelector('.rs-outstream-placeholder'); // Outstream Placeholder from RedSys manipulation if (articleWrapper && articleWrapper.style.display !== 'block') { articleWrapper.innerHTML = ""; articleWrapper.style.display = 'block'; } // Don't render OutStream on homepages if (articleWrapper === null) { if (document.querySelector('body.p-index')) { return null; } } if (articleWrapper === null) { articleWrapper = document.getElementById('iinfo-outstream'); } if (articleWrapper === null) { articleWrapper = document.querySelector('.layout-main__content .detail__article p:nth-of-type(6)'); } if (articleWrapper === null) { // Euro, Autobible, Zdravi articleWrapper = document.querySelector('.o-article .o-article__text p:nth-of-type(6)'); } if (articleWrapper === null) { articleWrapper = document.getElementById('sidebar'); } if (!articleWrapper) { console.error("Outstream wrapper of article was not found."); } return articleWrapper; } function setupDimensions() { outstreamWidth = Math.min(iinfoOutstreamPosition.offsetWidth, 480); outstreamHeight = Math.min(iinfoOutstreamPosition.offsetHeight, 320); } /** * Sets up IMA ad display container, ads loader, and makes an ad request. */ function setUpIMA(direct) { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); if (direct) { adsRequest.adTagUrl = directVast; console.log('Outstream DIRECT CAMPAING advert: ' + directVast); videoContent.muted = true; videoContent.volume = 0; outstreamDirectPlayed = true; } else { adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Outstream advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; } // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = outstreamWidth; // adsRequest.linearAdSlotHeight = outstreamHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function setupReplayScreen() { replayScreen.addEventListener('click', function () { iinfoOutstreamPosition.remove(); iinfoVastUrlIndex = 0; outstreamInit(); }); } /** * Sets the 'adContainer' div as the IMA ad display container. */ function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. outstreamDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( outstreamDocument.getElementById('adContainer'), videoContent); } function unmuteAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } } /** * Loads the video content and initializes IMA ad playback. */ function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(outstreamWidth, outstreamHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } /** * Handles the ad manager loading and sets ad event listeners. * @param { !google.ima.AdsManagerLoadedEvent } adsManagerLoadedEvent */ function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } /** * Handles actions taken in response to ad events. * @param { !google.ima.AdEvent } adEvent */ function onAdEvent(adEvent) { // Retrieve the ad from the event. Some events (for example, // ALL_ADS_COMPLETED) don't have ad object associated. const ad = adEvent.getAd(); console.log('Outstream event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: // This is the first event sent for an ad - it is possible to // determine whether the ad is a video ad or an overlay. if (!ad.isLinear()) { // Position AdDisplayContainer correctly for overlay. // Use ad.width and ad.height. videoContent.play(); } outstreamDocument.getElementById('adContainer').style.width = '100%'; outstreamDocument.getElementById('adContainer').style.maxWidth = '640px'; outstreamDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); // This event indicates the ad has started - the video player // can adjust the UI, for example display a pause button and // remaining time. if (ad.isLinear()) { // For a linear ad, a timer can be started to poll for // the remaining time. intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } outstreamDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (outstreamLastError === 303) { if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } } break; case google.ima.AdEvent.Type.COMPLETE: // This event indicates the ad has finished - the video player // can perform appropriate UI actions, such as removing the timer for // remaining time detection. if (ad.isLinear()) { clearInterval(intervalTimer); } if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } break; } } /** * Handles ad errors. * @param { !google.ima.AdErrorEvent } adErrorEvent */ function onAdError(adErrorEvent) { // Handle the error logging. console.log(adErrorEvent.getError()); outstreamLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { renderBanner(); } } function renderBanner() { if (isBanner) { console.log('Outstream: Render Banner'); iinfoOutstreamPosition.innerHTML = ""; iinfoOutstreamPosition.style.height = "330px"; iinfoOutstreamPosition.appendChild(bannerDiv); } else { console.log('Outstream: Banner is not set'); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoOutstreamPosition.remove(); outstreamInit(); } else { return false; } adVolume = 1; return true; } /** * Pauses video content and sets up ad UI. */ function onContentPauseRequested() { videoContent.pause(); // This function is where you should setup UI for showing ads (for example, // display ad timer countdown, disable seeking and more.) // setupUIForAds(); } /** * Resumes video content and removes ad UI. */ function onContentResumeRequested() { videoContent.play(); // This function is where you should ensure that your UI is ready // to play content. It is the responsibility of the Publisher to // implement this function when necessary. // setupUIForContent(); } function onActiveView() { if (outstreamContainer) { const containerOffset = outstreamContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (outstreamPaused) { adsManager.resume(); outstreamPaused = false; } return true; } else { if (!outstreamPaused) { adsManager.pause(); outstreamPaused = true; } } } return false; } let outstreamInitInterval; if (typeof cpexPackage !== "undefined") { outstreamInitInterval = setInterval(tryToInitializeOutstream, 100); } else { const wrapper = getWrapper(); if (wrapper) { let outstreamInitialized = false; window.addEventListener('scroll', () => { if (!outstreamInitialized) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { outstreamInit(); outstreamInitialized = true; } } }); } } function tryToInitializeOutstream() { const wrapper = getWrapper(); if (wrapper) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { if (cpexPackage.adserver.displayed) { clearInterval(outstreamInitInterval); outstreamInit(); } } } else { clearInterval(outstreamInitInterval); } } }
OSZAR »