Úvodem bych rád poznamenal, že článek popisuje řešení pro jeden konkrétní reklamní systém, ale s menšími úpravami by postup měl jít použít i pro jiné cizí objekty zobrazované na zabezpečených stránkách (kromě bannerů např. také počítadlo přístupů).
Když v reklamním systému BillBoard požádáte o kód banneru pro vložení na stránky, obdržíte zhruba toto:
<a href='http://ad2.billboard.cz/please/redirect/15102/1/1/1/'>
<img src ='http://ad2.billboard.cz/please/showit/15102/1/1/1/?typkodu=img' width='468' height='60'>
</a>
Přesněji řečeno, toto je pouze kód z části <noscript>. O tom, proč je lepší se bez JavaScript kódu obejít, se zmíním na závěr.
Když tento kód vložíte na zabezpečenou stránku, setkáte se ve správně nakonfigurovaném prohlížeči s varováním, že stránka obsahuje i nezabezpečené položky, což je potenciální bezpečnostní riziko.
Než přistoupíme k řešení tohoto problému, chtěl bych zmínit o tom, že kdyby BillBoard poskytoval i verzi banneru přístupnou přes HTTPS (což ale stejně neposkytuje), k vyřešení problému by to příliš nepomohlo, protože by byl certifikát jeho serveru nejspíš vydán jinou certifikační autoritou a uživateli by se kvůli tomuto banneru opět mohlo zobrazit nějaké varování, čemuž se právě chceme vyhnout.
Zobrazení banneru
Naše řešení spočívá v tom, že na serveru vytvoříme skript, který stáhne obrázek banneru pomocí HTTP a pošle ho dál. Značku pro zobrazení banneru potom nahradíme za:
<img src='banner.php' width='468' height='60'>
Kvůli tomu, že kód pro zobrazení banneru na serveru BillBoard nám nepošle rovnou obrázek, ale pouze přesměrování na něj, a také kvůli tomu, že se neobejde bez hlavičky Referer, nemůže být skript až tak jednoduchý jako např.:
<?php
readfile("http://ad2.billboard.cz/please/showit/15102/1/1/1/?typkodu=img");
?>
Skript tedy může vypadat nějak takhle:
<?php
$billboard = "ad2.billboard.cz";
$banner = "15102/1/1/1/";
$fp = fsockopen($billboard, 80);
fwrite($fp, "GET /please/showit/$banner?typkodu=img HTTP/1.1\r\n");
fwrite($fp, "Host: $billboard\r\n");
fwrite($fp, "Referer: $_SERVER[HTTP_REFERER]\r\n");
fwrite($fp, "\r\n");
function find_image($fp, $line) {
static $content_image = "";
if (eregi("^Content-Type: image/", $line))
$content_image = $line;
elseif ($line === "") {
if (!$content_image)
return false;
header($content_image);
fpassthru($fp);
fclose($fp);
exit();
}
return true;
}
while (!feof($fp)) {
$line = rtrim(fgets($fp, 1024), "\r\n");
if (eregi("^Location: http://$billboard(.*)", $line, $regs)) {
fclose($fp);
$fp = fsockopen($billboard, 80);
fwrite($fp, "GET $regs[1] HTTP/1.1\r\n");
fwrite($fp, "Host: $billboard\r\n");
fwrite($fp, "\r\n");
while (!feof($fp)) {
$line = rtrim(fgets($fp, 1024), "\r\n");
if (!find_image($fp, $line))
break; // nenarazili jsme na hlavičku Content-Type
}
break; // z nějakého důvodu se nám nepodařilo poslat obrázek
}
elseif (!find_image($fp, $line))
break; // nenarazili jsme na hlavičku Content-Type
}
fclose($fp);
// skript nenašel reklamní obrázek
header("Content-Type: image/gif");
readfile("img/nic.gif");
?>
Skript otevře připojení k serveru na port 80 (HTTP) a požádá ho o dokument s obrázkem. V hlavičce Referer mu předá URL dokumentu, na kterém se banner zobrazuje. Pak prochází hlavičky vráceného dokumentu, dokud nenarazí na přesměrování (hlavička Location). Jakmile na něj narazí, zavře původní připojení, otevře nové a vyžádá si dokument, na který byl přesměrován – to už by měl být obrázek s bannerem. Funkce find_image potom zjistí mime-typ obrázku, a jakmile skončí hlavičky, vypíše zbývající data. Skript bude pracovat i v případě, že na původní stránce nebude přesměrování, ale rovnou požadovaný obrázek. V případě, že se něco nepovede, skript místo požadovaného banneru zobrazí obrázek img/nic.gif.
Dlužno podotknout, že pro zajištění větší univerzálnosti by skript měl kromě hlavičky Content-Type přeposílat například také hlavičku Transfer-Encoding, ale pro zajištění lepší čitelnosti se na to ve skriptu nehledí. Možná vás napadlo, že po získání finální adresy banneru bychom mohli provést obyčejné readfile. To bohužel není možné proto, že z hlaviček potřebujeme získat Content-Type obrázku.
Tím je kód pro zobrazení banneru hotov, zbývá vyřešit obsluhu kliknutí na banner.
Kliknutí na banner
V okamžiku, kdy na takto zobrazený banner uživatel klikne, bude přesměrován na domácí stránku serveru BillBoard. Jak k tomu dojde? Banner se totiž samozřejmě zobrazuje pokaždé jiný a adresa http://ad2.billboard.cz/please/redirect/15102/1/1/1/ tedy není přesměrována na žádnou pevnou adresu. To, kam se tato adresa přesměruje, nastaví obrázek banneru v cookie. Pokud tedy chceme zajistit, aby fungovalo i kliknutí na banner, budeme muset tuto cookie od kódu pro zobrazení banneru získat a při kliknutí ji serveru opět poslat.
Změna v kódu pro zobrazení banneru nebude příliš složitá. Do funkce find_image přidáme tuto větev:
elseif (eregi("^Set-Cookie: bbcz=(.*); path=/please/redirect/$banner; domain=ad2.billboard.cz", $line, $regs))
setcookie("bbcz", $regs[1]);
Když skript narazí na cookie platnou pro adresu zajišťující obsluhu kliknutí, nastaví tuto cookie klientovi. Server BillBoard nastavuje více cookies, pro zajištění funkčnosti nám však stačí pouze tato.
Vzhledem k tomu, že tato cookie je platná pouze v rámci našeho serveru, budeme pro obsluhu kliknutí muset vytvořit skript, který ji serveru BillBoard přepošle. Mohli bychom to samozřejmě vyřešit i tak, že bychom při posílání cookie rovnou řekli klientovi, že je platná pro server BillBoard. Riskovali bychom však, že ji prohlížeč jakožto cookie třetí strany odmítne.
Kód pro zobrazení banneru včetně odkazu bude tedy nakonec vypadat nějak takhle:
<a href='banner_c.php'>
<img src='banner.php' width='468' height='60'>
</a>
Vytvoření skriptu banner_c.php nebude s předchozími zkušenostmi nikterak složité:
<?php
$billboard = "ad2.billboard.cz";
$banner = "15102/1/1/1/";
$fp = fsockopen($billboard, 80);
fwrite($fp, "GET /please/redirect/$banner HTTP/1.1\r\n");
fwrite($fp, "Host: $billboard\r\n");
fwrite($fp, "Referer: $_SERVER[HTTP_REFERER]\r\n");
fwrite($fp, "Cookie: bbcz=". stripslashes($_COOKIE["bbcz"]) ."\r\n");
fwrite($fp, "\r\n");
while (!feof($fp)) {
$line = rtrim(fgets($fp, 1024), "\r\n");
if (eregi("^Location: (.*)", $line, $regs)) {
header($line);
break;
}
elseif ($line === "") {
fpassthru($fp);
break;
}
}
fclose($fp);
?>
Skript se připojí k serveru BillBoard, pošle mu hlavičku Referer a cookie získanou od klienta a v hlavičkách začne hledat přesměrování. Když ho najde, přepošle ho klientovi, když ne, vrátí klientovi tělo dokumentu.
Zamyšlení na závěr
Uvedený postup umožňuje na zabezpečeném serveru zobrazit banner třetí strany. Tím, že banner klientovi posíláme přímo z našeho serveru, deklarujeme, že svým certifikátem ručíme za jeho obsah. Popsaný postup bychom tedy měli používat velmi obezřetně. Článek popisuje, jak v prostoru vymezeném pro banner zobrazíme cizí obrázek. Ať už bude obrázek obsahovat cokoliv, nemůže dojít ke zmatení uživatele, protože se tento obrázek zobrazí v prostoru vymezeném pro reklamu. Ale například pouhé opomenutí rozměrů ve značce <img> by mohlo znamenat katastrofu, protože by nám záškodník mohl podstrčit obrázek přes celou obrazovku, který by mohl uživatele dokonale zmátnout – např. by ho mohl přimět k tomu, aby peníze za objednané zboží poslal na účet záškodníka. Z tohoto důvodu také článek popisuje pouze řešení bez využití JavaScriptu, protože při jeho využití by záškodník uživatele mohl zmást dočista.
Na samý závěr bych rád připomenul, že podle pravidel BillBoardu „je jakýkoli zásah do HTML kódu bez povolení provozovatele sítě nepřípustný“. Před aplikací uvedeného postupu je tedy vhodné si toto povolení vyžádat. Podobné pravidlo pravděpodobně budou mít i ostatní systémy.