Předávání parametrů pomocí rozhraní CGI

Jiří Kosek ml.

CGI-skripty, které jsme si ukázali v posledním díle seriálu, byly velmi jednoduché. Zejména proto, že ke své činnosti nepotřebovaly žádné informace od uživatele. To však není příliš typický příklad. Obvykle slouží CGI-skripty jako rozhraní pro práci s různými databázemi. Uživatel může například zadat klíčová slova, která ho zajímají. Klíčová slova se předají CGI-skriptu a ten jako svůj výsledek může vygenerovat seznam všech článků z databáze, které obsahují zadaná klíčová slova.

Z minula již víme, že parametry nejprve předá prohlížeč serveru a ten je pak pomocí rozhraní CGI předá skriptu. Nejprve se tedy podíváme na to, jak může nějaká data poslat prohlížeč serveru.

Existují dvě metody, jak přenos dat uskutečnit. První se jmenuje GET a slouží pro přenos kratších informací. Pro přenos většího množství dat pak slouží metoda POST.

Při použití metody GET se všechny předávané informace připojí jako dotaz za otazník na konec URL, které ukazuje na CGI-skript. V dotazu je potřeba provést drobné úpravy: všechny mezery se nahradí znakem '+' a znaky se speciálním významem se nahradí sekvencí znaků '%xx', kde xx je hexadecimální kód znaku. Například lomítko se převede na sekvenci '%2F'.

Pokud tedy chceme skriptu pokus.cgi jako parametr předat jméno 'Jan Novak', můžeme použít jedno z následujících URL:

http://server/cgi-bin/pokus.cgi?Jan+Novak
http://server/cgi-bin/pokus.cgi?Jan%20Novak
V obou dvou případech jsme museli překódovat mezeru tak, aby URL tvořilo jeden dlouhý nepřerušený řetězec.

Takovéto zadávání parametrů však není pro uživatele zrovna pohodlné. Naštěstí existuje několik uživatelsky příjemnějších způsobů, jak přimět prohlížeč k vygenerování potřebného URL s parametry:

Pokud chceme pro předání dat použít metodu POST, musíme na stránce opět použít formulář, ale jako metodu uvést POST. Data se kódují stejným způsobem jako v metodě GET, ale pro jejich odeslání se vytvoří zvláštní datové spojení mezi prohlížečem a serverem. To umožňuje přenášet větší objemy dat než přidání parametrů na konec URL.

Způsob zakódování údajů z formuláře si předvedeme na malé ukázce. Předpokládejme náš známý formulář z předchozích dílů:

<FORM ACTION="http://server/cgi-bin/obsluha.cgi" METHOD=GET>
Jméno: <INPUT TYPE=TEXT NAME=jmeno><BR>
Věk: <INPUT TYPE=TEXT NAME=vek><BR>
<INPUT TYPE=SUBMIT VALUE="Odeslání formuláře">
</FORM>
Pokud uživatel vyplní jako jméno 'Pavel Severa' a jako věk '47', prohlížeč po stisknutí tlačítka Odeslání formuláře vygeneruje následující požadavek:
http://server/cgi-bin/obsluha.cgi?jmeno=Pavel+Severa&vek=47
Vidíme, že obsah jednotlivých polí je identifikován svým jménem a pole jsou oddělena znakem '&'.

Už víme, jak může prohlížeč poslat data na server. Vraťme se tedy k otázce, jak server předá data našemu skriptu. Rozhraní CGI nám nabízí tři možnosti:

  1. Data jsou skriptu předána jako parametry na příkazové řádce. To přichází v úvahu při předávání opravdu krátké informace pomocí metody GET (klíčová slova zadaná pomocí <ISINDEX> nebo souřadnice na klikací mapě).
  2. Data jsou předána v proměnné prostředí QUERY_STRING. Tento způsob je typický pro předávání dat z formuláře odeslaného metodou GET.
  3. Data jsou předána na standardní vstup skriptu -- způsob typický pro předání dat z formuláře odeslaného metodou POST.
Kromě dat od uživatele, která předá server skriptu, máme k dispozici další užitečné informace uložené v proměnných prostředí (viz tab. 1).

Tab. 1: Proměnné prostředí předávané skriptu rozhraním CGI
ProměnnáObsah
REQUEST_METHOD určuje způsob předávání informací -- GET nebo POST
QUERY_STRING obsahuje data přenášená metodou GET
PATH_INFO cesta, která má být zpracována skriptem; nejčastěji jde o část cesty v URL za jménem skriptu
PATH_TRANSLATED cesta ke stejnému souboru jako PATH_INFO; v tomto případě však byla cesta přemapována podle konfigurace serveru
CONTENT_TYPE MIME typ dat zasílaných metodou POST
CONTENT_LENGTH délka dat zasílaných metodou POST
SCRIPT_NAME URL právě prováděného skriptu
SERVER_NAME jméno serveru
SERVER_PORT číslo portu
SERVER_SOFTWARE jméno a verze programu pracujícího jako WWW-server
SERVER_PROTOCOL jméno a verze protokolu, kterým přišel požadavek (typicky HTTP/1.0 nebo HTTP/1.1)
GATEWAY_INTERFACE označení a verze použitého rozhraní ke spuštění skriptu (typicky CGI/1.1)
REMOTE_HOST doménová adresa počítače, z nějž přišel požadavek
REMOTE_ADDR IP-adresa počítače, z nějž přišel požadavek
AUTH_TYPE způsob použité autorizace uživatele
REMOTE_USER v případě, že byl uživatel autorizován, obsahuje tato proměnná jeho jméno
REMOTE_IDENT informace o identitě získaná zpětným dotazem u klienta; tuto vlastnost příliš mnoho serverů nevyužívá

Nyní už toho víme dost na to, abychom si ukázali CGI-skript, který bude zpracovávat parametry zadané uživatelem. Použijeme náš osvědčený příklad, který hodnotí uživatele podle jeho věku. Zadání údajů umožníme uživateli výše uvedeným formulářem. Skript, který vyhodnotí údaje na formuláři, pojmenujeme obsluha.cgi.

#!/bin/sh
echo 'Content-type: text/html'
echo
echo '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">'
echo '<HTML>'
echo '<HEAD>'
echo '<TITLE>Obsluha formuláře</TITLE>'
echo '</HEAD>'
echo '<BODY>'
echo '<H1>Výsledek obsluhy formuláře</H1>'
eval `echo $QUERY_STRING | awk 'BEGIN{RS="&"} {printf "WWW_%s\n",$1}' `
WWW_jmeno=`echo $WWW_jmeno | tr "+" "\040"`
echo "$WWW_jmeno je"
if [ $WWW_vek -lt 10 ]; then
   echo 'pěknej mlíčnák'
elif [ $WWW_vek -lt 20 ]; then
   echo 'teenager'
elif [ $WWW_vek -lt 60 ]; then
   echo 'v nejlepších letech'
elif [ $WWW_vek -lt 100 ]; then
   echo 'je pravděpodobně prarodič'
else
   echo 'je někde mezi stovkou a smrtí'
fi
echo '</BODY>'
echo '</HTML>'
CGI-skript jsme opět zapsali v příkazovém interpretu sh. Předem upozorňuji, že po vyzkoušení skriptu, je nejlepší jej ihned smazat. Náš skript totiž není zdaleka bezpečný, jak si za chvíli ukážeme.

Podívejme se nyní na skript trošku podrobněji. První řádek obsahuje určení interpretu, který se na skript použije. Dále pak generujeme HTTP hlavičku a kostru HTML stránky.

Dva řádky na začátku těla stránky jsou opravdu magické. První z nich pro každé pole formuláře, jehož hodnota je předána v proměnné QUERY_STRING, vytvoří proměnnou WWW_jméno-pole s obsahem příslušného pole. To nám usnadní další práci s předanými parametry. Druhý řádek v proměnné WWW_jmeno (ta nese obsah vstupního pole jmeno) nahradí všechny výskyty znaku '+' mezerou (ASCII kód 40 v osmičkové soustavě).

Podmínka [ $WWW_vek -lt n ] je splněna pokud je proměnná $WWW_vek menší než n. Několik vnořených podmínek zajistí vytištění příslušného komentáře podle věku uživatele.

Vidíme, že tvorba skriptů cestou příkazového interpretu je určena spíše pro Unixového guru, nežli pro "normálního člověka".

Bezpečnost a CGI-skripty

Pro psaní CGI-skriptů se nejčastěji používají interpretované jazyky jako Bourne shell (sh) nebo Perl. To však přináší velká bezpečnostní rizika. Ukážeme si zde bezpečnostní díru v našem skriptu.

Pokud někdo bude chtít náš skript zneužít pro zjištění důležitých informací o systému, může využít některé méně známé vlastnosti interpretu sh. Mezi ně patří i to, že na jedné řádce lze příkazy oddělovat pomocí středníku. Pokud tedy nějaký "chytrák" zavolá náš skript pomocí následujícího URL:

http://server/cgi-bin/obsluha.cgi?;who
Dojde v CGI-skriptu k expanzi příkazu echo $QUERY_STRING na echo ;who. Výsledkem je, že na výstup skriptu se zapíše prázdná řádka (příkaz echo) a výsledek programu who. Zcela kdokoliv tak může zjistit, kdo v danou chvíli pracuje na serveru. Místo příkazu who můžeme použít libovolný jiný příkaz. Pokud použijeme např. URL:
http://server/cgi-bin/obsluha.cgi?;cat</etc/passwd
Vrátí se nám stránka, která na svém začátku bude obsahovat výpis informací o všech uživatelích systému. Z tohoto výpisu lze zjistit uživatele, kteří heslo nemají nastaveno, a u těch ostatních lze poměrně snadno jejich heslo rozšifrovat. Kdokoliv se pak pomocí telnetu může připojit na server a provádět tam téměř cokoliv.

Pokud tedy chceme vytvářet bezpečné skripty v interpretovaných jazycích, musíme vždy příchozí data zkontrolovat a před jejich zpracováním z nich eliminovat nebezpečné znaky jako je středník.

Ošetřit všechny nebezpečné kombinace vstupních parametrů není však vůbec jednoduché a je celkem pravděpodobné, že na nějakou možnost zapomeneme. Je proto lepší CGI-skripty psát v jazycích, ve kterých je nutno program před spuštěním přeložit do spustitelné formy. V těchto jazycích (C, C++, Java apod.) je pak výše zmíněné nebezpečí obelstění skriptu do značné míry eliminováno.


Viděli jsme, že CGI-skripty jsou jako oheň -- dobrý sluha, ale špatný pán. V seriálu se budeme pro příště zabývat především skriptovacími jazyky, které se vkládají přímo do HTML stránky, jako je PHP a ASP. Práce s nimi je jednodušší a většinou i efektivnější. Pro speciální aplikace však může být výhodné použít CGI-skripty -- nyní již znáte princip jejich práce a způsob předávání parametrů.

V příštích pokračováních seriálu se podíváme na různé druhy vstupních polí, které lze používat ve webovských formulářích.

© Jiří Kosek 1999