Postřehy z bezpečnosti: LDAPNightmare a útok na rozšíření v Chromu

6. 1. 2025
Doba čtení: 10 minut

Sdílet

Perex image
Autor: Depositphotos
Perex image
Podíváme se na kampaň čínského Flax Typhoon proti americkým cílům, exploit s označením LDAPNightmare, přechod Microsoftu ze starých .NET domén nebo kompromitace desítek rozšíření prohlížeče Chrome.

USA uvalily sankce na čínskou firmu

Úřad pro kontrolu zahraničních aktiv (OFAC) amerického ministerstva financí vydal minulý pátek sankce proti pekingské kyberbezpečnostní společnosti Integrity Technology Group, Incorporated za organizování několika kybernetických útoků proti americkým cílům.

Tyto útoky byly veřejně připsány čínskému státem sponzorovanému aktérovi sledovanému jako Flax Typhoon (také Ethereal Panda nebo RedJuliett), který byl loni odhalen jako provozovatel botnetu internetu věcí (IoT) s názvem Raptor Train.

Tato hackerská skupina je aktivní nejméně od poloviny roku 2021 a zaměřuje se na různé subjekty v Severní Americe, Evropě, Africe a napříč Asií. Útoky organizované Flax Typhoon obvykle využívaly známé zranitelnosti k získání počátečního přístupu k počítačům obětí a poté využívaly legitimní software pro vzdálený přístup k udržení trvalého přístupu.

Ministerstvo financí označilo čínské kybernetické aktéry za jednu z „nejaktivnějších a nejvytrvalejších hrozeb pro národní bezpečnost USA“, která se opakovaně zaměřuje na americké vládní systémy, včetně systémů spojených s federální agenturou. „Ministerstvo financí nebude váhat pohnat tyto kybernetické aktéry k odpovědnosti za jejich činy,“ uvedl úřadující náměstek ministra financí pro terorismus a finanční zpravodajství Bradley T. Smith. „Spojené státy využijí všechny dostupné nástroje k narušení těchto hrozeb a budou i nadále spolupracovat na posílení kybernetické obrany veřejného i soukromého sektoru.“

Společnost Integrity Group, známá také jako Yongxin Zhicheng, byla obviněna z poskytování infrastrukturní podpory kybernetickým kampaním skupiny Flax Typhoon v období od poloviny roku 2022 do konce roku 2023 a americké ministerstvo zahraničí ji klasifikovalo jako vládního dodavatele s vazbami na Ministerstvo státní bezpečnosti Čínské lidové republiky (ČLR). Byla založena v září 2010. „Poskytuje služby státním a městským úřadům státní a veřejné bezpečnosti, jakož i dalším vládním dodavatelům ČLR v oblasti kybernetické bezpečnosti,“ uvedlo ministerstvo zahraničí. „Flax Typhoon se úspěšně zaměřil na řadu amerických a zahraničních společností, univerzit, vládních agentur, poskytovatelů telekomunikačních služeb a mediálních organizací.“

Exploit LDAPNightmare PoC působí pád LSASS a restart řadičů domény Windows

V minulém týdnu byl vydán proof-of-concept (PoC) pro nyní opravenou bezpečnostní chybu ovlivňující protokol LDAP (Lightweight Directory Access Protocol) systému Windows, která může vyvolat stav odepření služby (DoS).

Zranitelnost typu out-of-bounds reads je sledována jako CVE-2024–49113 (skóre CVSS: 7,5). Společnost Microsoft ji řešila v rámci aktualizací Patch Tuesday v prosinci 2024 spolu s kritickou chybou integer overflow s označením CVE-2024–49112 (skóre CVSS: 9,8), která může vést ke vzdálenému spuštění kódu. Za objevení a nahlášení obou zranitelností je zodpovědný bezpečnostní výzkumník Yuki Chen ( @guhe120).

CVE-2024–49113 PoC od společnosti SafeBreach Labs s kódovým označením LDAPNightmare je navržen tak, aby způsobil pád jakéhokoliv neopraveného serveru Windows „bez jakýchkoliv předpokladů kromě toho, že DNS server napadeného DC má připojení k internetu“.

Konkrétně jde o odeslání požadavku DCE/RPC na server oběti, který nakonec způsobí pád služby LSASS (Local Security Authority Subsystem Service) a vynutí si restart, pokud je odeslán speciálně vytvořený paket s odpovědí na doporučení CLDAP s nenulovou hodnotou pro lm_referral. Kalifornská společnost zabývající se kybernetickou bezpečností navíc zjistila, že stejný řetězec zneužití lze využít také k dosažení vzdáleného spuštění kódu (CVE-2024–49112) úpravou paketu CLDAP.

„V kontextu zneužití řadiče domény pro server LDAP musí útočník úspěšně odeslat speciálně vytvořené volání RPC na cíl, aby se spustilo vyhledávání útočníkovy domény,“ uvedl Microsoft. „V souvislosti se zneužitím klientské aplikace LDAP musí útočník úspěšně přesvědčit nebo oklamat oběť, aby provedla vyhledávání řadiče domény pro doménu útočníka nebo aby se připojila ke škodlivému serveru LDAP. Neověřená volání RPC by však nebyla úspěšná.“ Kromě toho může útočník použít připojení RPC k řadiči domény ke spuštění operací vyhledávání řadiče domény proti doméně útočníka, uvedla společnost.

Pro zmírnění rizika, které tyto zranitelnosti představují, je nezbytné, aby organizace aplikovaly záplaty vydané společností Microsoft v prosinci 2024. V situacích, kdy okamžité záplatování není možné, se doporučuje „implementovat detekce sledující podezřelé odpovědi CLDAP referral (s konkrétní nastavenou škodlivou hodnotou), podezřelá volání DsrGetDcNameEx2 a podezřelé dotazy DNS SRV“.

Aktualizace starých domén .NET před 7. lednem 2025

Společnost Microsoft oznámila, že provádí „neočekávanou změnu“ ve způsobu distribuce instalačních souborů a archivů .NET, jež vyžaduje, aby vývojáři aktualizovali svou produkční a DevOps infrastrukturu.

„Očekáváme, že většina uživatelů nebude přímo ovlivněna, nicméně je velmi důležité, abyste si ověřili, zda se vás to týká, a sledovali, zda nedojde k výpadkům nebo jiným druhům poruch,“ uvedl minulý týden v prohlášení Richard Lander, programový manažer v týmu .NET.

Některé binární soubory a instalační programy .NET jsou hostovány na doménách sítě Azure Content Delivery Network (CDN), které končí na .azureedge.net  – dotnetcli.azureedge.net a dotnetbuilds.azureedge.net – které jsou hostovány na Edgio. Minulý měsíc získala společnost Akamai vybraná aktiva společnosti Edgio po jejím úpadku. V rámci tohoto přechodu je plánováno ukončení provozu platformy Edgio na 15. ledna 2025.

Vzhledem k tomu, že domény .azureedge.net by mohly být v budoucnu nedostupné, Microsoft přechází na Azure Front Door CDN. Výrobce systému Windows uvedl, že pokud nebudou podniknuty žádné kroky, dojde k automatické migraci u zákazníků do 7. ledna 2025.

Je však třeba poznamenat, že automatická migrace nebude možná pro koncové body s doménami *.vo.msecnd.net. Uživatelé, kteří plánují migraci k Akamai nebo jinému poskytovateli CDN, musí také před 7. lednem 2025 nastavit příznak funkce DoNotForceMigrateEdgioCDNProfiles, aby zabránili automatické migraci do Azure Front Door.

„Upozorňujeme, že na dokončení migrace na jinou CDN budete mít čas do 14. ledna 2025, ale společnost Microsoft opět nemůže zaručit, že Vaše služby budou na platformě Edgio dostupné před tímto datem,“ uvedla společnost Microsoft. „Upozorňujeme, že od 3. ledna 2025 budeme muset zastavit všechny změny konfigurace Azure CDN pomocí profilů Edgio. To znamená, že nebudete moci aktualizovat konfiguraci svých profilů CDN, ale Vaše služby na Azure CDN od Edgia budou stále fungovat, dokud nebudete migrováni nebo dokud nebude platforma Edgio 15. ledna 2025 vypnuta. Pokud použijete příznak funkce DoNotForceMigrateEdgioCDNProfiles před 3. lednem, Vaše konfigurace nebude zmrazena pro změny.“

Zatímco spoléhání se na *.azureedge.net a *.azurefd.net se nedoporučuje kvůli rizikům dostupnosti, uživatelé mají dočasnou možnost migrace na Azure Front Door při zachování domén. „Abyste si zajistili větší flexibilitu a vyhnuli se jedinému bodu selhání (single point of failure), je vhodné přijmout vlastní doménu co nejdříve,“ varuje společnost Microsoft.

Microsoft převzal nad azureedge.net kontrolu, aby se předešlo bezpečnostním obavám z toho, že doménu získá špatný subjekt za účelem šíření malwaru nebo narušení dodavatelského řetězce softwaru. Pokud však jde o to, proč nebylo možné použít stará doménová jména k překladu na nové servery, bylo řečeno, že „tato možnost nebyla k dispozici“.

Uživatelům se doporučuje, aby zkontrolovali své kódové báze a našli v nich odkazy na azureedge.net a aktualizovali je na následující:

  • Aktualizujte adresu dotnetcli.azureedge.net na builds.dotnet.microsoft.com.
  • Aktualizujte dotnetcli.blob.core.windows.net na builds.dotnet.microsoft.com.

Kompromitace desítek rozšíření prohlížeče Chrome

Nová útočná kampaň se zaměřila na známá rozšíření prohlížeče Chrome, což vedlo ke kompromitaci nejméně 35 rozšíření a vystavení více než 2,6 milionu uživatelů riziku zneužití dat a krádeže jejich přihlašovacích údajů.

Útok se zaměřil na vydavatele rozšíření prohlížeče v internetovém obchodě Chrome prostřednictvím phishingové kampaně a využil jejich přístupová oprávnění k vložení škodlivého kódu do legitimních rozšíření s cílem ukrást soubory cookie a přístupové údaje uživatelů. První společností, která kampaň osvětlila, byla kyberbezpečnostní firma Cyberhaven, jejíž jeden ze zaměstnanců se 24. prosince stal terčem phishingového útoku.

Dne 27. prosince společnost Cyberhaven zveřejnila, že škodlivý aktér kompromitoval její rozšíření prohlížeče a injektoval do něj škodlivý kód, který komunikoval s externím příkazovým a řídicím (C&C) serverem umístěným na doméně cyberhavenext.pro, stahoval další konfigurační soubory a exfiltroval uživatelská data.

Cílem podvodného e-mailu, který údajně pocházel z podpory pro vývojáře webového obchodu Google Chrome, bylo vyvolat falešný pocit naléhavosti tvrzením, že jejich rozšíření bezprostředně hrozí odstranění z obchodu s rozšířeními s odkazem na porušení zásad programu pro vývojáře.

Příjemce byl také vyzván, aby kliknutím na odkaz přijal zásady, načež byl přesměrován na stránku pro udělení oprávnění škodlivé aplikaci OAuth s názvem Privacy Policy Extension. „Útočník získal potřebná oprávnění prostřednictvím škodlivé aplikace (Privacy Policy Extension) a nahrál škodlivé rozšíření Chrome do webového obchodu Chrome,“ uvedl Cyberhaven v samostatném technickém reportu. „Po obvyklém procesu kontroly zabezpečení webového obchodu Chrome bylo škodlivé rozšíření schváleno ke zveřejnění.“

„Rozšíření prohlížeče jsou měkkým podbřiškem zabezpečení webu,“ říká Or Eshed, generální ředitel společnosti LayerX Security, která se specializuje na zabezpečení rozšíření prohlížeče. „Ačkoliv máme tendenci považovat rozšíření prohlížeče za neškodná, v praxi jim jsou často udělována rozsáhlá oprávnění k citlivým informacím o uživateli, jako jsou soubory cookie, přístupové tokeny, informace o identitě a další. Mnoho organizací ani neví, jaká rozšíření mají na svých koncových bodech nainstalována, a neuvědomují si rozsah svého vystavení nebezpečí.“

Jamie Blasco, technický ředitel společnosti Nudge Security, identifikoval další domény, které se překládají na stejnou IP adresu serveru C&C použitého při narušení bezpečnosti společnosti Cyberhaven. Platformy pro zabezpečení rozšíření prohlížeče Secure Annex a Extension total odhalily další rozšíření, u nichž existuje podezření, že byly napadeny:

  • AI Assistant – ChatGPT and Gemini for Chrome
  • Bard AI Chat Extension
  • GPT 4 Summary with OpenAI
  • Search Copilot AI Assistant for Chrome
  • TinaMInd AI Assistant
  • Wayin AI
  • VPNCity
  • Internxt VPN
  • Vidnoz Flex Video Recorder
  • VidHelper Video Downloader
  • Bookmark Favicon Changer
  • Castorus
  • Uvoice
  • Reader Mode
  • Parrot Talks
  • Primus
  • Tackker – online keylogger tool
  • AI Shop Buddy
  • Sort by Oldest
  • Rewards Search Automator
  • ChatGPT Assistant – Smart Search
  • Keyboard History Recorder
  • Email Hunter
  • Visual Effects for Google Meet
  • Earny – Up to 20% Cash Back
  • Where is Cookie?
  • Web Mirror
  • ChatGPT App
  • Hi AI
  • Web3Password Manager
  • YesCaptcha assistant
  • Bookmark Favicon Changer
  • Proxy SwitchyOmega (V3)
  • GraphQL Network Inspector
  • ChatGPT for Google Meet
  • GPT 4 Summary with OpenAI

Tato další napadená rozšíření naznačují, že Cyberhaven nebyl jednorázovým cílem, ale součástí rozsáhlé útočné kampaně zaměřené na legitimní rozšíření prohlížeče.

Zakladatel společnosti Secure Annex John Tuckner řekl serveru The Hacker News, že existuje možnost, že kampaň probíhá již od 5. dubna 2023 a pravděpodobně ještě déle, a to na základě dat registrace domény. „Průzkum na této doméně mě přivedl k sedmi novým rozšířením. Jedno z těchto souvisejících rozšíření s názvem „Rewards Search Automator“ mělo (Code2), které se maskovalo jako funkce „bezpečného prohlížení“, ale exfiltrovalo data. ‚Rewards Search Automator‘ obsahoval také maskovanou funkci ‚ecommerce‘ (Code3) s novou doménou tnagofsg.com, která je funkčně neuvěřitelně podobná ‚safe-browsing‘. Při dalším hledání na této doméně jsem našel ‚Earny – Up to 20% Cash Back‘, která stále obsahuje kód ‚ecommerce‘ (Code3) a byla naposledy aktualizována 5. dubna 2023.“

Pokud jde o napadený doplněk Cyberhaven, analýza naznačuje, že škodlivý kód se zaměřoval na identifikační údaje a přístupové tokeny účtů na Facebooku, především se záměrem vyčlenit uživatele služby Facebook Ads. Obsahoval také kód, který naslouchal událostem kliknutí myší na webové stránce Facebook.com a při každém kliknutí uživatele na stránku kontroloval obrázky obsahující v atributu src podřetězec qr/show/code, a pokud je našel, odesílal je na server C&C. Existuje podezření, že záměrem bylo vyhledávání QR kódů za účelem obejití bezpečnostních kontrol, jako jsou požadavky na dvoufaktorové ověřování (2FA).

Cyberhaven uvádí, že škodlivá verze rozšíření prohlížeče byla odstraněna asi 24 hodin po jejím uvedení do provozu. Některá další odhalená rozšíření již byla také aktualizována nebo odstraněna z webového obchodu Chrome.

Skutečnost, že rozšíření bylo z obchodu Chrome odstraněno, však neznamená, že odhalení je u konce, říká Or Eshed. „Dokud je ohrožená verze rozšíření stále živá na koncovém zařízení, hackeři k ní mohou stále přistupovat a exfiltrovat data,“ říká.

bitcoin_smenarna

Mezitím se také ukázalo, že přítomnost kódu pro shromažďování dat v některých rozšířeních nebyla výsledkem kompromitace, ale pravděpodobně ji zahrnuli sami vývojáři jako součást vývojové sady pro monetizaci softwaru (SDK), která také nenápadně exfiltrovala podrobné údaje o prohlížení. „Než vývojář Visual Effects for Google Meet prodal své rozšíření společnosti Karma, pokusil se ho zpeněžit pomocí této ‚knihovny pro blokování reklam‘,“ uvedl bezpečnostní výzkumník Wladimir Palant. „V prodejní nabídce není uvedeno, kdo knihovnu vyvíjí, ale vše ukazuje na Urban VPN.“

V tuto chvíli není jasné, kdo za kampaní stojí a zda spolu tyto kompromitace souvisejí.

Ve zkratce

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 »