Ocenite etot tekst:


This file: ftp://ftp.cert.org/pub/tech_tips/cgi_metacharacters

Last revised November 13, 1997
Version 1.3
---------------------------------------------------------------


We have noticed several reports to us and to public mailing lists about CGI
scripts that allow an attacker to execute arbitrary commands on a WWW
server under the effective user-id of the server process.

In many of these cases, the author of the script has not sufficiently
sanitized user-supplied input.




Consider an example where a CGI script accepts user-supplied data. In
practice, this data may come from any number of sources of user-supplied
data; but for this example, we will say that the data is taken from an
environment variable $QUERY_STRING. The manner in which data was inserted
into the variable is not important - the important point here is that the
programmer needs to gain control over the contents of the data in
$QUERY_STRING before further processing can occur. The act of gaining this
control is called "sanitizing" the data.




A script writer who is aware of the need to sanitize data may decide to
remove a number of well-known meta-characters from the script and replace
them with underscores. A common but inadvisable way to do this is by
removing particular characters.

For instance, in Perl:

	#!/usr/local/bin/perl
	$user_data = $ENV{'QUERY_STRING'};	# Get the data
	print "$user_data\n";
	$user_data =~ s/[\/ ;\[\]\≤\≥&\t]/_/g;	# Remove bad characters. WRONG!
	print "$user_data\n";
	exit(0);

In C:

	#include ≤stdio.h≥
	#include ≤string.h≥
	#include ≤stdlib.h≥

	int
	main(int argc, char *argv[], char **envp)
	{
	    static char bad_chars[] = "/ ;[]≤≥&\t";

	    char * user_data;	/* our pointer to the environment string */
	    char * cp;		/* cursor into example string */

	    /* Get the data */
	    user_data = getenv("QUERY_STRING");
	    printf("%s\n", user_data);

	    /* Remove bad characters. WRONG! */
	    for (cp = user_data; *(cp += strcspn(cp, bad_chars)); /* */)
	        *cp = '_';
	    printf("%s\n", user_data);
	    exit(0);
	}

In this method, the programmer determines which characters should NOT be
present in the user-supplied data and removes them. The problem with this
approach is that it requires the programmer to predict all possible inputs.
If the user uses input not predicted by the programmer, then there is the
possibility that the script may be used in a manner not intended by the
programmer.




A better approach is to define a list of acceptable characters and replace any
character that is NOT acceptable with an underscore. The list of valid input
values is typically a predictable, well-defined set of manageable size. For
example, consider the tcp_wrappers package written by Wietse Venema. In the
percent_x.c module, Wietse has defined the following:

        char   *percent_x(...)
        {
                {...}
            static char ok_chars[] = "1234567890!@%-_=+:,./\
        abcdefghijklmnopqrstuvwxyz\
        ABCDEFGHIJKLMNOPQRSTUVWXYZ";

                {...}

        for (cp = expansion; *(cp += strspn(cp, ok_chars)); /* */ )
                *cp = '_';

                {...}


The benefit of this approach is that the programmer is certain that
whatever string is returned, it contains only characters now under his or her
control.

This approach contrasts with the approach we discussed earlier. In the earlier
approach, which we do not recommend, the programmer must ensure that he or she
traps all characters that are unacceptable, leaving no margin for error. In
the recommended approach, the programmer errs on the side of caution and only
needs to ensure that acceptable characters are identified; thus the programmer
can be less concerned about what characters an attacker may try in an attempt
to bypass security checks.

Building on this philosophy, the Perl program we presented above could be
thus sanitized to contain ONLY those characters allowed. For example:

	#!/usr/local/bin/perl
	$_ = $user_data = $ENV{'QUERY_STRING'};	# Get the data
	print "$user_data\n";
	$OK_CHARS='-a-zA-Z0-9_.@';	# A restrictive list, which
					# should be modified to match
					# an appropriate RFC, for example.
	s/[^$OK_CHARS]/_/go;
	$user_data = $_;
	print "$user_data\n";
	exit(0);

Likewise, the same updated example in C:

	#include ≤stdio.h≥
	#include ≤string.h≥
	#include ≤stdlib.h≥

	int
	main(int argc, char *argv[], char **envp)
	{
	    static char ok_chars[] = "abcdefghijklmnopqrstuvwxyz\
	ABCDEFGHIJKLMNOPQRSTUVWXYZ\
	1234567890_-.@";

	    char * user_data;	/* our pointer to the environment string */
	    char * cp;		/* cursor into example string */

	    user_data = getenv("QUERY_STRING");
	    printf("%s\n", user_data);
	    for (cp = user_data; *(cp += strspn(cp, ok_chars)); /* */)
	        *cp = '_';
	    printf("%s\n", user_data);
	    exit(0);
	}




We strongly encourage you to review all CGI scripts available via your web
server to ensure that any user-supplied data is sanitized using the approach
described in Section 4, adapting the example to meet whatever specification
you are using (such as the appropriate RFC).




The following comments appeared in CERT Advisory CA-97.12 "Vulnerability in
webdist.cgi" and AUSCERT Advisory AA-97.14, "SGI IRIX webdist.cgi
Vulnerability."

    We strongly encourage all sites should consider taking this opportunity
    to examine their entire httpd configuration. In particular, all CGI
    programs that are not required should be removed, and all those
    remaining should be examined for possible security vulnerabilities.

    It is also important to ensure that all child processes of httpd are
    running as a non-privileged user. This is often a configurable option.
    See the documentation for your httpd distribution for more details.

    Numerous resources relating to WWW security are available. The
    following pages may provide a useful starting point. They include
    links describing general WWW security, secure httpd setup, and secure
    CGI programming.

        The World Wide Web Security FAQ:

            http://www-genome.wi.mit.edu/WWW/faqs/www-security-faq.html

    The following book contains useful information including sections on
    secure programming techniques.

        _Practical Unix & Internet Security_, Simson Garfinkel and
        Gene Spafford, 2nd edition, O'Reilly and Associates, 1996.

    Please note that the CERT/CC and AUSCERT do not endorse the URL that
    appears above. If you have any problem with the sites, please contact
    the site administrator.

Another resource that sites can consider is the CGI.pm module. Details
about this module are available from:

    http://www.genome.wi.mit.edu/ftp/pub/software/WWW/cgi_docs.html

This module provides mechanisms for creating forms and other web-based
applications. Be aware, however, that it does not absolve the programmer
from the safe-coding responsibilities discussed above.



Copyright 1997 Carnegie Mellon University. Conditions for use, disclaimers,
and sponsorship information can be found in
http://www.cert.org/legal_stuff.html and ftp://info.cert.org/pub/legal_stuff .
If you do not have FTP or web access, send mail to cert@cert.org with
"copyright" in the subject line.

CERT is registered in the U.S. Patent and Trademark Office.






---------------------------------------------------------------
 STATXYA - 11 / 11 / 99 * 
 Author: Rain Forest Puppy, http://www.wiretrip.net
 Original stat'i lezhit na sajte Team Void.Ru
 © Copyright Rain Forest Puppy
 © Copyright Team Void, perevod
---------------------------------------------------------------
 
Dannyj tekst - stat'ya iz zhurnala Phrack #55. Avtor stat'i - .Rain.forest.puppy., s ch'ego lyubeznogo razresheniya Team Void i predstavlyaet Vam etot material. Sil'no rekomenduetsya oznakomit'sya s dannym materialom vsem pisatelyam na perle - ved' iz-za melkoj oshibki v skripte, kotoryj vy pishete po zakazu klienta, on mozhet stat' zhertvoj zlyh hakerov i pred®yavit' Vam isk ;)
YA schitayu, chto neploho bylo by napisat' nekoe podobie vstupleniya. Itak, po bol'shej chasti ya zanimalsya napisaniem i analizom razlichnyyj skriptov, i pytalsya vyyasnit' - kakim obrazom mozhno ispol'zovat' nebol'shie oshibki v sovih celyah ? Mm.. eta stat'ya kak raz rasskazyvaet ob etom.
Kogda stroka "root" ne ravna "root", no, odnovremenno, ravna ? (vy smushcheny ? ya imeyu v vidu raznye yazyki programmirovaniya). Odnazhdy ya reshil vyyasnit', chto zhe na samom dele pozvolyaet perl - i mogu li ya ispol'zovat' v svoih celyah oshibki kompilyatora ? Itak, ya nachal peredavat' sovershenno strannye dannye razlichnym sistemnym vyzovam. Nichego osbennogo ne proizoshlo, za isklyucheniem odnoj veshchi...
Kak vy vidite nizhe, trebovaos' otkryt' ukazannyj fajl, rfp.db. YA ispol'zoval http-zapros, chtoby peredat' skriptu znachenie rfp, prishil k nemu rasshirenie i zatem otkryl fajl. Na Perle rabochij skript vyglyadit primerno tak:
# parse $user_input

$database="$user_input.db";

open(FILE "<$database");

Otlichno. YA peredal user_input=rfp, skript pytaetsya otkryt' fajl rfp.db. Dovol'no prosto (pro puti tipa /../../../../ my pogovorim pozzhe).
Zatem ya popytalsya peredat' skriptu znachenie user_input=rfp%00. Perl sozdal $database="rfp\0.db i popytalsya eto otkryt'. V rezul'tate byl otkryt' fajl rfp (ili byl by otkryt, esli by on sushchestvoval). Kuda zhe delos' rasshirenie ? |to interesnaya chast'.
Kak vy vidite, perl dopuskaet nulevye bajty v strokovyh peremennyh kak dannye. V Si zhe nulevoj bajt - priznak konca stroki. Itak, "root" != "root\0". No vse sistemnye vyzovy programmirovany v Si, gde nol' est' priznak konca stroki. A rezul'tat - perl pereda£t sistemnym vyzovam parametr rfp\0.db, no vse vyzyvaemye biblioteki i funkcii perestayut obrabatyvat' dannye, kak tol'ko dohodyat do nulya.
CHto zhe my poluchim, esli popytaemsya napisat' skript, kotoryj pozvolyaet administratoru menyat' paroli u razlichnyh pol'zovatelej KROME ruta ? Kod budet primerno takov:
$user=$ARGV[1] #

if ($user ne "root"){

# osushchestvit' dal'nejshie operacii }

Itak, esli teper' nekto, pol'zuyushchijsya etim skriptom skazhet - pomenyat' papol' u pol'zovatelya root - to nichego ne proizojd£t. Esli zhe on pomenyaetsya pomenyat' parol' u pol'zovatelya root\0 = to test budet projden, no vse vyzovy, kotorye delaet chast' skripta, menyayushchaya parol', budut schitat' chto operaciya vypolnyaetsya nad pol'zovatelem root.
No v obshchem - eto ne est' ochen' opasnaya uyazvimost' - no ne stoit zabyvat' ob etom. K primeru, sushchestvuet kucha skriptov, kotorye dobavlyayut rasshirenie .html k poluchennym dannym. K primeru, prostejshij katalogizator mozhet dobavlyat' .html k nomeru, kotoryj vvodit pol'zovatel', t.e. page.cgi?page=1 pokazhet mne 1.html. Otchasti bezopasno, t.k. lyuboj okryvaemyj fajl dolzhen imet' rasshirenie .html. No - esli my poshl£m skriptu parametr page.cgi?page=page.cgi%00 (%00 == '\0' escaped), to skript poprostu pokazhet svo£ soderzhimoe. A takoj eksploit

$file="/etc/passwd\0.txt.whatever.we.want";

die("hahaha! Caught you!) if($file eq "/etc/passwd");

if (-e $file){

open (FILE, ">$file");}

otkroet fajl /etc/passwd dlya chteniya. Reshenie problemy - ochen' porstoe - prosto otfil'trujte vse nulevye simvoly : $insecure_data=~s/\0//g;
Ne zabud'te tak zhe otfil'trovat' ostal'nye simvol, perenapravlyayushchie potoki dannyh. Spisok ih (kak skazano na oficial'nom sajte W3C.ORG) -
&;`'\"|*?~<>^()[]{}$\n\r

Tak zhe ne stoit zabyvat' pro bekslesh (\). Predstav'te sebe, esli vdrug pol'zovatel' peredast vashemu skriptu dannye user data `rm -rf /`. Posle togo, kak oni budut vychishcheny proceduroj ochistki ot nezhelatel'nyh simvolov, eto budet chto-to vrode user data \\`rm -rf / \\`. Itak, chto chto nahoditsya v kavychkah - ostanetsya nevychishchennym i zapustit `rm -rf / \`. Itak - sleduet tak zhe vychishchat' i dvojnye beksleshi (eto tak zhe prived£t k nevozmozhnosti osushchestvit' prosmotr direktorii po puti vida /../../../. (s/\.\.//g;).
Itak, /usr/tmp/../../etc/passwd prevratitsya v /usr/tmp///etc/passwd, chto ne srabotaet. No - teper' zajm£msya beksleshem. Stroka /usr/tmp/.\./.\./etc/passwd ne budet raspoznana fil'trom, kak ne sootvetsvuyushchaya pravilam - i budet peredana skriptu dalee. Teper', chtoby ispol'zovat' eto imya fajla v perle, trebuetsya chto-to vrode

$file="/usr/tmp/.\\./.\\./etc/passwd";

$file=s/\.\.//g;

system("ls -l $file");

No vs£ vysheopisannoe rabotaet lish' na sistemnyh vyzovah i vyzovah, zaklyuchennyh v kavychki (''). Opciya -e ne pozvolit otkrytym (non-piped) funkciyam rabotat'.

$file="/usr/tmp/.\\./.\\./etc/passwd";

open(FILE, "<$file") or die("No such file");

zavershit rabotu s soobshcheniem NO SUCH FILE.
Reshenie takih problem prostoe - izbavlyajtes' ot beksleshej.
V perle dobavlenie pajpa k imeni otkryvaemogo fajla zapustit ego, a ne otkroet. Itak, open(FILE, "/bin/ls") dast vam kuchu ispolnyaemogo koda, togda kak open(FILE, "/bin/ls|") ispolnit ls i dast vam zhelaemyj rezul'tat. fil'tr s/(\|)/\\$1/g predotvratit eto (perl budet zavershat' rabotu s soobshcheniem - unexpacted end of file) - tak kak sh ozhidaet, chto sleduyushchaya stroka budet nachinat'sya s \.
Itak, teper' mozhno popytat'sya ispol'zovat' eto v komplekse s tem, o ch£m my govorili ranee. Predpolozhim, u nas v skripte est' stroka open(FILE, "$FORM"). Ustanoviv $FORM v ls| - my poluchim listing direktorii. Teper' podumajte, chto my imeem konstrukciyu vrode

$filename="/safe/dir/to/read/$FORM"

open(FILE, $filename)

My dolzhny tochno ukazat' direktoriyu, gde nahoditsya ls - posemu my ustanavlivaem $FROM v "../../../../bin/ls|", chto da£t nam listing direktorii.
Itak - teper' u nas situaciya, nemnogo bolee slozhnaya

$filename="/safe/dir/to/read/$FORM"

if(!(-e $filename)) die("I don't think so!")

open(FILE, $filename)

Trebuetsya odurachit' opciyu -e. Problema v tom, chto "-e" prekratit rabotu fonkcii, kak tol'ko udostoveritsya, chto fajl, kotoryj nado otkryt', zakanchivaetsya pajpom (|). Itak - nam nado sdelat' pajp nevidimym dlya -e, no dobit'sya togo, chtoby perl po prezhnemu znal o ego sushchustvovanii. Dlya etogo my budem ispol'zovat' tot samyj nulevoj bajt. Itak - my otkryvaem chto-to vrode ls%00| , -e okanchivaet obrabotku dannyj na nulevom bajte, i komanda ls ispolnyaetsya!
Itak, kod

$filename="/bin/ls /etc\0|"

if(!(-e $filename)) exit;

open(FILE, $filename)

ne vydast nam soderzhimoe papki /etc. |to proishodit potomu, chto -e vidit, chto /bin/ls /etc ne sushchestvuet. Pravil'nyj variant budet vyglyadet' tak:

$filename="/bin/ls\0 /etc|"

if(!(-e $filename)) exit;

open(FILE, $filename)
|to srabotaet, no my poluchim listing tekushchej papki - (prostoe ls) - /etc ne vosprinimaetsya kak argument.
YA tak zhe hotel sdelat' zamechanie vsem programmistam - esli vy lenivye programmisty na perle - dvazhdy pereprover'te vashi programmy na podobnye veshchi. Tak zhe pomnite ob upravlenii potokami danyh. Est' simvoly < i > - kotorye ne pozvolyayut, k priemru, perenapravit' annye v rabotayushchij ls, no vpolne normal'no osushchestvlyayut zapis' v otkrytyj fajl. Vkratce -

$bug="ls|"

open(FILE, $bug)

open(FILE, "$bug")

srabotaet. No open(FILE, "<$bug") open(FILE, ">$bug") open(FILE, ">>$bug") ne srabotaet i vs£ budet v poryadke. Esli vam nado tol'ko chitat' chtolibo iz fajla, prosto otkryvajte <$file, a ne $file.
Izvestnyj rassadnik dyryavyh skriptov www.freecode.com izobiluet primerami nebezopasnyh skriptov. Ne berite ottuda nichego. Rassmotrim neskol'ko primerov:
Skript-menedzher reklamnyh ob®yavlenij
|to pervyj skript s www.freecode.com.

# First version 1.1

# Dan Bloomquist dan@lakeweb.net

Avtor pereda£t vse parametry, s kotorymi byl vyzvan skript v %DATA. On ne ochishchaet '..', ne ochishaet i nulevye bajty. Itak, poglyadim kod...

#This sets the real paths to the html and lock files.

#It is done here, after the POST data is read.

#of the classified page.

$pageurl= $realpath . $DATA{ 'adPath' } . ".html";

$lockfile= $realpath . $DATA{ 'adPath' } . ".lock";

Ispol'zuya 'adPath=/../../../../../etc/passwd%00' - my mozhem ukazat' $pageurl na fajl s parolyami. Posmotrim na $lockfile. My ne mozhem ispol'zovat' pajp dlya nashih nuzhd - tak kak rasshirenie (.html) dobavlyaetsya v poslednyuyu ochered'.

#Read in the classified page

open( FILE,"$pageurl" ) || die "can't open to read

$pageurl: $!\n";

@lines= ;

close( FILE );

Tut v $pageurl zanositsya imya fajla, kooryj zatem otkryvaetsya. K schast'yu dlya avtora, on nemedlenno okryvaet $pageurl na zapis'. Itak, my dolzhny imet' prava na zapis' v to, chto, voobshche-to pytaemsya otkryt' na chtenie. |to ogranichivaet nashi vozmozhnosti ispol'zovaniya dannoj uyazvimosti, no eto est' zhivoj primer togo, kak sam togo ne znaya, avtor sil'no svyazal nam ruki v raskalyvanii serverov cherez ego skript.
Ves'ma interesno otkryvaetsya i mejler.

#Send your mail out.

#

open( MAIL, "|$mailprog $DATA{ 'adEmail' }" )

|| die "can't open sendmail: $adEmail: $!\n";

Oh.. eto my videli uzhe sto raz. Ne proveryayutsya ni simvoly perenapravleniya potoka dannyh, ni pajpy.. my mozhem vypolnit' lyubuyu komandu cherez eto.
Tak zhe ya nash£l prosten'kij logger dannyh, kotorye pol'zovatel' vvodit v veb-formu.

# flexform.cgi

# Written by Leif M. Wright

# leif@conservatives.net

Itak, vhodnye dannye peredayutsya v %CONTENTS, i opyat' nikakoj poverki na podozritel'nye simvoly. Zatem

$output = $basedir . $contents{'file'};

open(RESULTS, ">>$output");

Ispol'zuya standartnyj vyhod za predely papki, kuda nas posadili (/../../) , nam dazhe ne prid£tsya ispol'zovat' nefil'traciyu 0x00. No chto by my ne otkryli - fajl otkryvaetsya dlya dopisyvaniya, i poetomu my dolzhny imet' prava na zapis' v tot fajl, kotoryj pytaemsya otkryt'. Po etoj zhe prichine ne srabotaet i pipe (|) bug.
Itak, na segodnya obzor nedokumentirovannyh vozmozhnostej razlichnyh skriptov, i rasskazy o tom, kak ih sdelat' bolee bezopasnymi, okochneny. Spasibo vsem za vnimanie!
.rain.worest.puppy. [ADM/Wiretrip] rfp@wiretrip.net

Last-modified: Thu, 27 Jan 2000 16:40:27 GMT
Ocenite etot tekst: