Uygulama geliştirirken güvenlik açıklarından kaçınmak - Bölüm 6: CGI betikleri

ArticleCategory:

Software Development

AuthorImage:

[image of the authors]

TranslationInfo:

Original in fr Frédéric Raynal, Christophe Blaess, Christophe Grenier

fr to en Georges Tarbouriech

en to en Lorne Bailey

en to tr İnanç Özçubukçu

AboutTheAuthor:

Christophe Blaess bağımsız bir havacılık mühendisi. Bir Linux tutkunu ve işinin çoğunu bu sistem üzerinde gerçekleştiriyor. Linux Documentation Project tarafından yayınlanan yardım sayfalarının çevrimini koordine ediyor.

Christophe Grenier ESIEA'de 5. yıl öğrencisi. Orada sistem yöneticisi olarak ta çalışıyor. Bilgisayar güvenliği konusuna tutkusu var.

Frédéric Raynal kirletilmediği, hormonlanmadığı için uzunca yıllardır Linux kullanıyor, MSG yada köpek kemik yemeği... sadece tatlı ve hünerli.

Abstract

Kötü programlanmış bir Perl betiğinden dosya almak, program çalıştırmak... "Bunu Yapmanın Birden Çok Yolu Var!"

Serideki önceki yazılar :

ArticleIllustration:[illustration]

[article illustration]

ArticleBody:[The real article: put the text and html-codes here]

Web sunucusu, URI ve tanımlama sorunları

(Çok kısa) Web sunucusu nasıl çalışır ve bir URI nasıl kurulura giriş

İstemci bir HTML dosyası sorduğu zaman, sunucu istenen dosyayı (yada bir hata mesajı) gönderir. Tarayıcı, HTML kodunu işlemek ve ekranda göstermek için yorumlar. Örnek olarak, http://www.linuxdoc.org/HOWTO/ HOWTO-INDEX/howtos.html
yazarak URL (Uniform Request Locator), sunucu HTTP protokolünü kullanarak www.linuxdoc.org sunucusuna bağlanır ve /HOWTO/HOWTO-INDEX/howtos.html sayfasını sorar Eğer sayfa varsa, sunucu istenen sayfayı gönderir. Bu durağan modelle, eğer dosya sunucuda varsa, istemciye "olduğu gibi" gönderilir, öbür türlü bir hata mesajı gönderilir (iyi bilinen 404 - Not Found hatası).

Maalesef bu, e-iş, tatiller için e-rezervasyon yada mümkün olan her neyse kullanıcı ile interaktifliği sağlamıyor.

Çok şükür, değişken olarak HTML sayfaları yaratmanın yolları var. CGI (Common Gateway Interface) betikleri bunlardan bir tanesi. Bu örnekte, sayfalara erişmek için kullanılan URL biraz değişik bir yolla yaratılmış:

http://<server><pathToScript>[?[param_1=val_1][...] [&param_n=val_n]]
Veri listesi QUERY_STRING ortam değişkeni içerisinde veri listesi QUERY_STRING ortam değişkeni içerisinde bir çalıştırılabilir dosyadır. İçerisinden geçen verileri almak için stdin (standart girdi) yada bir QUERY_STRING ortam değişkeni kullanmaktadır. Kod çalıştırıldıktan sonra sonuç, stdout'de (standard çikti) gözükür ve ardından sanal yöre sunucusuna yönlenir. Çoğunlukla her programlama dili CGI betiği (compiled C program, Perl,shell-scripts...) yazmakta kullanılabilir.

Örnek olarak, www.linuxdoc.org'dan ssh ile örnek olarak, www.linuxdoc.org'dan ssh ile ilgili HOWTO arayalım:

http://www.linuxdoc.org/cgi-bin/ldpsrch.cgi? svr=http%3A%2F%2Fwww.linuxdoc.org&srch=ssh&db=1& scope=0&rpt=20
Aslında bu gözüktüğünden daha basit. Bu URL'yi inceleyelim:

Genelde veri isimleri ve değerleri anlamlarını anlamak için yeterlidir. Dahası, görüntülenen sayfanın içeriği oldukça manalıdır.

Şimdi CGI betiklerinin iyi tarafının kullanıcının verilere ulaşma imkanı olduğunu biliyorsunuz... Fakat karanlık yönü kötü yazılmış betiklerin güvenlik açığı yaratmasıdır.

Muhtemelen tercih ettiğiniz web tarayıcınızda ilginç karakterlere rastlamışsınızdır. Bu karakterler ISO 8859-1 karakter seti ile kodlanmışlardır (>man iso_8859_1'e bir göz atınız). table 1 bu kodların bazılarının anlamını vermektedir. Bazı IIS4.0 ve IIS5.0 sunucularının unicode bug olarak adlandırılan "/" and "\" uzatılmış unicode temsilcileri üzerine kurulu çok tehlikeli açıkları olduğunu belirtmeliyim.

"SSI Server Side Include" ile Apache Tanımlaması.

Server Side Include bir web sunucusu işlevinin bir parçasıdır. Web sayfalarına emir göndermeye, aynı zamanda bir dosyayı "olduğu gibi" içermeye yada bir komutu (kabuk yada CGI betiği) çalıştırmaya olanak sağlar.

Apache tanımlama dosyası httpd.conf içerisinde "AddHandler server-parsed .shtml" bu düzeneği harekete geçirir. Genelde, .html ve .shtml arasındaki farktan kurtulmak için, .html uzantısı ilave edilebilir. Elbette bu sunucuyu yavaşlatır... Bu, dizin seviyesinde şu yönerge ile kontrol edilebilir :



Ekteki guestbook.cgi betiğinde, kullanıcı tarafından sağlanan metin bir HTML dosyası içerisinde, '<' ve ' >' içerisinde &lt; ve &gt; karakter dönüştürmesi olmadan bulunur. Meraklı birisi şu talimatlardan birisini gönderebilir:

Birincisiyle,
guestbook.cgi?email=pappy&texte=%3c%21--%23printenv%20--%3e
sistem hakkında birkaç satır bilgi alırsınız :
DOCUMENT_ROOT=/home/web/sites/www8080
HTTP_ACCEPT=image/gif, image/jpeg, image/pjpeg, image/png, */*
HTTP_ACCEPT_CHARSET=iso-8859-1,*,utf-8
HTTP_ACCEPT_ENCODING=gzip
HTTP_ACCEPT_LANGUAGE=en, fr
HTTP_CONNECTION=Keep-Alive
HTTP_HOST=www.esiea.fr:8080
HTTP_PRAGMA=no-cache
HTTP_REFERER=http://www.esiea.fr:8080/~grenier/cgi/guestbook.cgi?
 email=&texte=%3C%21--%23include+file%3D%22guestbook.cgi%22--%3E
HTTP_USER_AGENT=Mozilla/4.76 [fr] (X11; U; Linux 2.2.16 i686)
PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin
REMOTE_ADDR=194.57.201.103
REMOTE_HOST=nef.esiea.fr
REMOTE_PORT=3672
SCRIPT_FILENAME=/mnt/c/nef/grenier/public_html/cgi/guestbook.html
SERVER_ADDR=194.57.201.103
SERVER_ADMIN=master8080@nef.esiea.fr
SERVER_NAME=www.esiea.fr
SERVER_PORT=8080
SERVER_SIGNATURE=<ADDRESS>Apache/1.3.14 Server www.esiea.fr Port 8080</ADDRESS>

SERVER_SOFTWARE=Apache/1.3.14 (Unix)  (Red-Hat/Linux) PHP/3.0.18
GATEWAY_INTERFACE=CGI/1.1
SERVER_PROTOCOL=HTTP/1.0
REQUEST_METHOD=GET
QUERY_STRING=
REQUEST_URI=/~grenier/cgi/guestbook.html
SCRIPT_NAME=/~grenier/cgi/guestbook.html
DATE_LOCAL=Tuesday, 27-Feb-2001 15:33:56 CET
DATE_GMT=Tuesday, 27-Feb-2001 14:33:56 GMT
LAST_MODIFIED=Tuesday, 27-Feb-2001 15:28:05 CET
DOCUMENT_URI=/~grenier/cgi/guestbook.shtml
DOCUMENT_PATH_INFO=
USER_NAME=grenier
DOCUMENT_NAME=guestbook.shtml



çalıştırma talimatı, genelde size bir kabuk eşdeğeri sağlar :



guestbook.cgi?email=ppy&texte=%3c%21--%23exec%20cmd="cat%20/etc/passwd"%20--%3e

Şunu denemeyin "<!--#include file="/etc/passwd"-->", HTML dosyasını bulabileceğiniz dizin yolu görelidir ve ".." içeremez. Apache error_log dosyası, yasaklanmış bir dosyaya erişim girişimi olduğu mesajını içerir. Kullanıcı HTML sayfasında [an error occurred while processing this directive] mesajını görebilir.

SSI her zaman gerekli değildir. Bu sebeple sunucu üzerinde kapatılabilir. Ancak sorunun sebebi kırık guestbook uygulaması ve SSI birleşimidir.

Perl betikleri

Bu bölümde, PERL ile yazılmış CGI betikleri ile ilgili güvenlik açıklarını tanıtacağız. Olayları açıklamak için örnek kodların tamamını değil, sadece sorunun sebebini anlamamıza yarayacak kısımlarını vereceğiz.

Betiklerimizin hepsi asağıdaki şablona uygun olarak yaratılmıştır :

#!/usr/bin/perl -wT
BEGIN { $ENV{PATH} = '/usr/bin:/bin' }
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};   # Make %ENV safer =:-)
print "Content-type: text/html\n\n";
print "<HTML>\n<HEAD>";
print "<TITLE>Remote Command</TITLE></HEAD>\n";
&ReadParse(\%input);
# now use $input e.g like this:
# print "<p>$input{filename}</p>\n";
# #################################### #
# Start of problem description         #
# #################################### #



# ################################## #
# End of problem description         #
# ################################## #

form:
print "<form action=\"$ENV{'SCRIPT_NAME'}\">\n";
print "<input type=texte name=filename>\n </form>\n";
print "</BODY>\n";
print "</HTML>\n";
exit(0);

# first arg must be a reference to a hash. 
# The hash will be filled with data.
sub ReadParse($) {
  my $in=shift;
  my ($i, $key, $val);
  my $in_first;
  my @in_second;

  # Read in text
  if ($ENV{'REQUEST_METHOD'} eq "GET") {
    $in_first = $ENV{'QUERY_STRING'};
  } elsif ($ENV{'REQUEST_METHOD'} eq "POST") {
    read(STDIN,$in_first,$ENV{'CONTENT_LENGTH'});
  }else{
    die "ERROR: unknown request method\n";
  }

  @in_second = split(/&/,$in_first);

  foreach $i (0 .. $#in_second) {
    # Convert plus's to spaces
    $in_second[$i] =~ s/\+/ /g;

    # Split into key and value.  
    ($key, $val) = split(/=/,$in_second[$i],2); 

    # Convert %XX from hex numbers to alphanumeric
    $key =~ s/%(..)/pack("c",hex($1))/ge;
    $val =~ s/%(..)/pack("c",hex($1))/ge;

    # Associate key and value
    # \0 is the multiple separator
    $$in{$key} .= "\0" if (defined($$in{$key})); 
    $$in{$key} .= $val;

  }
  return length($#in_second); 
}

Perl'e geçirilmiş tezler hakında daha fazlası (-wT) sonra. $ENV ve $PATH çevre değişkenlerini temizlemeye başlıyoruz ve HTML başlığını (bu bir çeşit tarayıcı ile sunucu arasındaki html protokolün bir parçası gibidir. Tarayıcıda görüntülenen web sayfası içerisinde göremezsiniz) gönderiyoruz. ReadParse()fonksiyonu betiğe geçmiş tezleri okur. Modüllerle daha kolay gerçekleştirilebilir fakat bu yolla tüm kodu görebilirsiniz. Ardından, örnekleri sunuyoruz. En son, HTML dosyası ile bitiriyoruz.

Boş byte

Perl her karakteri aynı yolla değerlendirir, C fonksiyonundan farklı olan nedir, örneğinde olduğu gibi. Perl için, bir dizi sonu boş karakteri diğerleri gibi bir karakterdir. Yani ?

showhtml.cgi betiğini yaratmak için şu kodu ilave edelim   :

  # showhtml.cgi
  my $filename= $input{filename}.".html";
  print "<BODY>File : $filename<BR>";
  if (-e $filename) {
      open(FILE,"$filename") || goto form;
      print <FILE>;
  }


ReadParse() fonksiyonu tek bir tez alır : görüntülenecek dosyanın ismi. HTML dosyasından fazlasını okuyarak olan bazı "kaba konukları" önlemek için, dosya sonuna ".html" uzantısını ekliyoruz. Fakat, unutmayın ki, boş byte diğerleri gibi bir karakterdir...

Böylece, eğer isteğimiz showhtml.cgi?filename=%2Fetc%2Fpasswd%00 ise, çağrılan dosya my $filename = "/etc/passwd\0.html" dir ve şaşkın gözlerimiz HTML olmayan bir şeye dikilmiştir.

Neler oluyor ? strace komutu Perl bir dosyayı nasıl açıp gösteriyor:

  /tmp >>cat >open.pl << EOF
  > #!/usr/bin/perl
  > open(FILE, "/etc/passwd\0.html");
  > EOF
  /tmp >>chmod 0700 open.pl 
  /tmp >>strace ./open.pl 2>&1 | grep open
  execve("./open.pl", ["./open.pl"], [/* 24 vars */]) = 0
  ...
  open("./open.pl", O_RDONLY)             = 3
  read(3, "#!/usr/bin/perl\n\nopen(FILE, \"/et"..., 4096) = 51
  open("/etc/passwd", O_RDONLY)           = 3


strace tarafından temsil edilen sonda ki open(), C ile yazılmış sistem çağrısıyla uyuşur. .html uzantısının kaybolduğunu görebiliriz ve bu, /etc/passwd dosyasını açmamızı sağlar.

Bu problem, tüm boş byte'ları kaldıran tek bir mutat ifade ile çözülmüştür:

s/\0//g;


Boruları kullanmak

İşte korumasız bir betik. /home/httpd/ dizin ağacından bir dosyayı göstermekte:

#pipe1.cgi

my $filename= "/home/httpd/".$input{filename};
print "<BODY>File : $filename<BR>";
open(FILE,"$filename") || goto form;
print <FILE>;


Gülmeyin bu örneğe ! Böyle çok betik gördüm.

İlk işletici (exploit) apaçık ortada:

pipe1.cgi?filename=..%2F..%2F..%2Fetc%2Fpasswd
Yapılması gereken tek şey dizinde yukarı gitmek ve dosyaya erişmek. Fakat daha ilginç başka bir olasılık daha var: tercih ettiğiniz komutu çalıştırmak. Perl'de, open(FILE, "/bin/ls") komutu "/bin/ls" ikili taban dosyasını açar... fakat open(FILE, "/bin/ls |") belirtilen komutu çalıştırır. Tek bir boru | eklemek open() 'nın davranışını değiştirir.

Bu gerçekten yola çıkarak gelen başka bir sorunda dosyanın varlığının test edilmemiş olmasıdır. Her komutu çalıştırmamızı sağlayan ama özellikle her tezi veren : pipe1.cgi?filename=..%2F..%2F..%2Fbin%2Fcat%20%2fetc%2fpasswd%20| şifre dosyasının içerisini gösterir.

Dosyanın varlığını test etmek daha az özgürlük tanıyor :

#pipe2.cgi

my $filename= "/home/httpd/".$input{filename};
print "<BODY>File : $filename<BR>";
if (-e $filename) {
  open(FILE,"$filename") || goto form;
  print <FILE>
} else {
  print "-e failed: no file\n";
}
Önceki örnek hiç işlemiyor. "-e" testi "../../../bin/cat /etc/passwd |" dosyasını bulamadığından ötürü başaramıyor.

Şimdi /bin/ls komutunu deneyelim. Sonuç öncekinin aynısı olacak. İşte, örnek olarak /etc dizininin içeriğini görüntülemek istersek, "-e", "../../../bin/ls /etc | dosyasının varlığını sorgular, fakat her ikisi de var değil. "hayalet" dosyasının ismini en kısa zamanda sağlamadıkça, herhangi ilginç birşey elde etmeyeceğiz :(

Yine de hala bir "çıkış yolu" var, sonuç iyi olmasa bile. /bin/ls dosyası var (sistemlerin çoğunda), fakat eğer open() bu dosya ismi tarafından çağırılırsa, dosya çalışmayacak ancak ikili taban görüntülenecek. Bunun ardından ismin sonuna "-e" tarafından yapılan incelemede kullanılmadan bir boru '|' koymanın yolunu bulmalıyız. Çözümü zaten biliyoruz: boş byte. Eğer "../../../bin/ls\0|" isim olarak gönderirsek, mevcudiyet incelemesi sadece "../../../bin/ls" dizinini göz önüne aldığında başarılı olur, fakat open() boruyu görebilir ve komutu çalıştırır. Böylece, dizinin içeriğini veren URL :

pipe2.cgi?filename=../../../bin/ls%00|

Satır besleme

finger.cgi betiği, makinede ki finger yönergesini çalıştırır :

#finger.cgi

print "<BODY>";
$login = $input{'login'};
$login =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"])/\\$1/g;
print "Login $login<BR>\n";
print "Finger<BR>\n";
$CMD= "/usr/bin/finger $login|";
open(FILE,"$CMD") || goto form;
print <FILE>


Bu betik, (en azından) kullanışlı bir tedbir alır : bazı ilginç karakterlerin, başa '\' kabuğu yerleştirerek önüne geçilmesini engellemek için tedbirini alır. Böylece, noktalı virgül düzenli anlatımla "\;" olarak değişir. Ancak liste her önemli karakteri içermez. Diğerlerinin arasında, besleme satırı '\n' kayıptır.

Tercihiniz olan kabuk komut satırında, \n' karakteri gönderen RETURN yada ENTER tuşlarına basarak bit yönergeyi geçerli kılarsınız. Perl'de, aynısını yapabilirsiniz. Zaten open() yönergesinin '|' borusuyla biten bir satırın komut çalıştırmamıza izin verdiğini görmüştük.

Bu davranışı canlandırmak için finger komutuna gönderilen login ardına bir satırbaşı ve bir yönerge ilave ettik :

finger.cgi?login=kmaster%0Acat%20/etc/passwd


Satırda çeşitli yönergeler çalıştırmak için diğer karakterler biraz daha ilginç :



Mutat ifadeyle korunduklarından itibaren çalışmazlar. Ancak, gelin bunu atlatmanın bir yolunu bulalım.

Ters kesme ve noktalı virgül

Önceki finger.cgi betiği bazı ilginç karakterlerle sorunlardan kaçıyor. Böylece, <finger.cgi?login=kmaster;cat%20/etc/passwd adresi, noktalı virgül kaçtığı zaman çalışmıyor. Yine de bir karakter korunmuyor : ters kesme '\'.

Örnek olarak bizi ".." dan mutat s/\.\.//g ifadesi kurtaran dizin ağacından yukarı aşağı gitmemizi engelleyen bir betik alalım. Fark etmez! Kabuklar çok sayıda '/''yi tek seferde yönetebilirler (ikna olmak için cat ///etc//////passwd 'yi deneyin).

Örnek olarak, yukarıda ki pipe2.cgi betiğinde, $filename değişkeni "/home/httpd/" örneğinden başlar. Önceki yönergeyi kullanarak dizinler arasında yukarı aşağı gidilmesini engellemede etkili olduğu görülebilir. Elbette, bu ifade ".." dan itibaren korur, ancak '.' karakterini korursak ne olacak ? Eğer dosya ismi .\./.\./etc/passwd ise mutat ifade uyuşmaz. Belirtmeliyiz ki, system() (yada ` ... `) ile çok iyi çalışmakta, fakat open() yada "-e" başarısız olur.

finger.cgi betiğine geri dönelim. Noktalı virgül kullanarak finger.cgi?login=kmaster;cat%20/etc/passwd URI si noktalı virgül mutat ifade tarafından korunduğundan beri umulan sonucu vermez. Kabuk şu yönermeyi verir :

/usr/bin/finger kmaster\;cat /etc/passwd
Aşağıdaki hatalar web sunucu kayıtlarından bulundu :
finger: kmaster;cat: no such user.
finger: /etc/passwd: no such user.
Bu mesajlar bir kabukta yazarak elde edebileceğiniz sonuçlarla eştir. Sorun olgudan gelmektedir. Korunmalı ';' , "kmaster;cat" dizisini yararlı olacak şekilde dikkate alır.

Tüm yönergeleri ayırmak istiyoruz, birini betikten ve diğerini kullanmak istediğimizden. Böylece korumamız gereken ';' : <A HREF="finger.cgi?login=kmaster\;cat%20/etc/passwd"> finger.cgi?login=kmaster\;cat%20/etc/passwd</A>. "\; dizisi, betik tarafından "\\;"'ya çevrilir ve sonra kabuğa gönder. Sonuncunun okuduğu:

/usr/bin/finger kmaster\\;cat /etc/passwd
Kabuk bunu iki farklı yönergeye ayırır :
  1. Muhtemelen başarısız olacak olan /usr/bin/finger kmaster\ .. fakat umursamıyoruz ;-)
  2. Şifreyi görüntüleyen cat /etc/passwd file.
Çözüm basit : ters kesme '\' de kaçabilir.

Korumasız bir " kullanmak

Bazen parametreler kotalar kullanılarak korunur. Önceki finger.cgi betiğini, korunaklı $login betiğine değiştirdik.

Yine de, eğer bu kotalar saklı değilse, kullanışsızdır. Hatta talebinize ilave edilen bir tanesi başarısız olacaktır. Bu oluyor çünkü ilk gönderilen kota betikten açık olan olan birisini kapıyor. Ardından komutu yazıyorsun, ve ikinci bir kota en son (kapanan) kotayı betikten açıyor.

finger2.cgi betiği şunu gerçekleştiriyor :

#finger2.cgi

print "<BODY>";
$login = $input{'login'};
$login =~ s/\0//g;
$login =~ s/([<>\*\|`&\$!#\(\)\[\]\{\}:'\n])/\\$1/g;
print "Login $login<BR>\n";
print "Finger<BR>\n";
#New (in)efficient super protection :
$CMD= "/usr/bin/finger \"$login\"|";  
open(FILE,"$CMD") || goto form;
while(<FILE>) {
  print;
}


Komut çalıştıma URI'si şu şekli alıyor :

finger2.cgi?login=kmaster%22%3Bcat%20%2Fetc%2Fpasswd%3B%22
Kabuk komutu alıyor /usr/bin/finger "$login";cat /etc/passwd"" ve kotalar artık sorun olmuyor.

Bu, eğer ki parametreleri kotalarla korumak, önceden bahsedildiği gibi ters kesme ve noktalı virgülden kaçırmak istiyorsanız o derece önemlidir.

Perl'de yazmak

Uyarı ve kusur tercihleri

Perl'de programlama yaparken, w yada "use warnings;" (Perl 5.6.0 and later) seçeneklerini kullanın. Bu sizi incelenmemiş değişkenlere ve eski (modası geçmiş) terimlere/fonksiyonlara karşı uyarır.

T seçeneği (kusur modu) yüksek güvenlik sağlar. Bu mod çeşitli testler gerçekleştirir. En önemlisi, değişkenlerin muhtemel kusurlar'ı ile ilgilenir. Değişkenler temiz yada kusurludur. Dışarıdan gelen veri temizlenmediği sürece program tarafından kusurlu kabul edilir. Kusurlu bir değişken böylece program dışı kullanılan değerleri atayamaz (diğer kabuk komutlarını çağırma).

Kusur modunda, komut satırı tezleri, çevre değişkenleri, bazı sistem çağrı sonuçları (readdir(), readlink(), readdir(), ...) ve dosyalardan gelen veriler, şüpheli ve kusurlu olarak değerlendirilir.

Değişkenleri temizlemek için, mutat terimlerin filtresinden geçirmek gerekir. Belli ki .* kullanmak yersizdir. Amaç sağlanan tezlerden sizi endişe duymaya zorlamaktır. Her zaman mümkün olduğunda özel bir mutat terim kullanmaya çalışın.

Yine de bu mod herşeyden korumaz : bir liste değişkeni olarak system() yada exec() 'den geçen tez kusurları incelenmez. Eğer betiklerinizden biri bu fonksiyonlardan birini kullanıyorsa çok temkinli olmalısınız. $arg'ın kusurlu olup olmadığı bilinmeden, exec "sh", '-c', $arg; talimatı güvenli kabul edilir :(

Aynı zamanda programınızın başına "use strict;" ilave etmeniz tavsiye olnunur. Bu sizi değişkenleri ilan etmenize zorlayacak; bazı insanlar bunu can sıkıcı bulacaktır fakat mod-perl kullanırsanız bu mecburidir..

Böylece, Perl CGI betikleriniz aşağıdaki gibi başlamalıdır :

#!/usr/bin/perl -wT
use strict;
use CGI;
Yada Perl 5.6.0 ile:
#!/usr/bin/perl -T
use warnings;
use strict;
use CGI;


open() çağırmak

Çoğu yazılımcı basitçe open(FILE,"$filename") || ... kullanarak dosya açar. Bu tür kodların risklerini önceden görmüştük. Riski azaltmak için, açma modunu özelleştirelim :

Özelleştirilmemiş bir yolla dosyalarınızı açmayın.

Bir dosyaya erişmeden evvel, dosyanın varlığının kontrol edilmesi tavsiye edilir. Önceki başlıkta anşatılan bazı sorun tipleri önüne geçilmesinde yarış havası yaratmaz fakat tazli komutlar gibi bazı tuzaklardan kaçılmasına yardımcı olur.

if ( -e $filename ) { ... }

Perl 5.6'de başlarken, open() için yeni bir yazım var: open(FILEHANDLE,MODE,LIST). '<' modu ile, dosya okumaya hazırdır; '>' modu ile, dosya kısaltılmış yada gerekliyse yaratılmış ve yazma için açılmıştır. Diğer işlemlerle konuşan modlar için ilginçtir. Eğer mod '|-' or '-|' ise, LIST tezi bir komut olarak yorumlanır ve sıraya göre borudan önce yada sonra bulunur.

Perl 5.6'dan ve 3 tezliopen()'dan önce, bazı insanlar sysopen() komutunu kullanırlardı.

Kaçma ve filtreleme girdisi

İki yöntem var : yasaklanmış karakterleri belirlersin yada mutat terimleri kullanan izin verilen karakterleri açıkça belirlersin. Örnek programlar sizi muhtemel tehlikeli karakterleri unutmanın oldukça kolay olduğuna inandırmalı. Bu yüzden ikinci yöntem tavsiye edilmektedir.

Hemen hemen yapılması gereken şöyle bir şeydir : ilk olarak, izin verilen karakterlere sahip istekler kontrol edilir. Ardından, izin verilenler arasında tehlikeli karakter olarak tanımlananlar atlanır.

#!/usr/bin/perl -wT

# filtre.pl

#  $safe ve $danger değişkenleri sırasıyla riskli olanları 
#  ve olmayanları birbirinden ayırır. 
#  Filtreyi değiştirmek için ekleme yada çıkarma.
#  Sadece tanımlardaki karakterleri içeren $input geçerlidir.


use strict;

my $input = shift;

my $safe = '\w\d';
my $danger = '&`\'\\|"*?~<>^(){}\$\n\r\[\]';
#Note:
#  '/', boşluk ve atlama, amaçtaki tanımların parçası değil


if ($input =~ m/^[$safe$danger]+$/g) {
    $input =~ s/([$danger]+)/\\$1/g;
} else {
    die "Bad input chars in $input\n";
}
print "input = [$input]\n";


Bu betik iki karakter seti tanımlıyor :

İki setten birinde bulunmayan bir karakter içeren her talep anında geri çevrilmelidir.

PHP betikleri

Tartışmaya yol açmak istemiyorum, ancak PERL yerine betikleri PHP 'de yazmak daha iyi. Sistem yönetici olarak, kullanıcılarımın betiklerini PERL'den çok PHP de yazmalarını tercih ediyorum. PHP'de güvensiz programlama yapan birisi Perl de olduğu gibi tehlikeli olacaktır. Öyleyse neden PHP'yi tercih ediyoruz? Eğer PHP ile bazı programlama sorunlarınız varsa güvenli modu aktifleştirebilir (safe_mode=on) yada fonksiyonları pasifleştirebilirsiniz disable_functions=...). Bu mod kullanıcıya ait olmayan dosyalara erişimi, açıkça izin verilemediği taktirde çevre değişkenlerini değiştirmeyi, komutları çalıştırmayı, vs. engeller.

Ön kabul olarak, Apache bayrağı bizi PHP kullanılacağı yönünde bilgilendirir.

$ telnet localhost 80
Trying 127.0.0.1...
Connected to localhost.localdomain.
Escape character is '^]'.
HEAD / HTTP/1.0
 
HTTP/1.1 200 OK
Date: Tue, 03 Apr 2001 11:22:41 GMT
Server: Apache/1.3.14 (Unix)  (Red-Hat/Linux) mod_ssl/2.7.1
        OpenSSL/0.9.5a PHP/4.0.4pl1 mod_perl/1.24
Connection: close
Content-Type: text/html

Connection closed by foreign host.
Aşağıdaki bilgiyi gizlemek için /etc/php.ini içerisine expose_PHP = Off yazın:
Server: Apache/1.3.14 (Unix)  (Red-Hat/Linux) mod_ssl/2.7.1 
OpenSSL/0.9.5a mod_perl/1.24 


/etc/php.ini dosyası (PHP4) ve /etc/httpd/php3.ini sistemi katılaştırmak için birçok seçeneğe sahiptir. Örnek olarak, "magic_quotes_gpc" seçeneği GET, POST metodları ve kurabiyeler ile ile alınan tezlere kotalar ilave eder; bu Perl örneklerimizde bulunan bir çok sorunun aşılmasına yardımcı olur.

Sonuç

Muhtemelen bu yazı serinin içerisinde en anlaşılır olanıydı. Hergün sanal yörede ortaya çıkan zayıflıkları gösteriyor. Sıkça kötü programlamaya bağlı olarak çokça başkaları da var (örnek olarak, From: kısmını bir tez olarak alarak mesaj gönderen bir betik, saldırı için güzel bir site teşkil eder). Örnekler çokça. Betik web sitesi üzerinde oldukça, en azından bir kişinin kötü amaçla kullanmaya çalışacağına bahse girebilirsiniz.

Bu yazı güvenlik yazılımları serisini bitiriyor. Umarım bir çok uygulamada bulunan bir çok temel güvenlik açığını keşfetmenizde yardımcı olabildik ve "güvenlik" seçeneklerini uygulamalarınızı tasarlarken ve programlarken dikkate alacaksınız. Genelde sınırlı uygulama imkanları sebebi ile (iç kullanım, kişisel bilgisayar ağı kullanımı, geçici modeller, vb.) güvenlik sorunları gözardı edilmektedir. Yine de, çok özel bir kullanım için tasarlanmış bir modül ileride daha büyük bir uygulamanın temelini oluşturabilir ve sonradan yapılanacak değişiklikler çok daha pahalıya çıkabilir.


Bazı URI kodlu karakterleri

URI Encoding (ISO 8859-1) Character
%00 \0 (end of string)
%0a \n (carriage return)
%20 space
%21 !
%22 "
%23 #
%26 & (ampersand)
%2f /
%3b ;
%3c <
%3e >
Tab 1 : ISO 8859-1 ve karakter karşılıkları

Linkler


guestbook.cgi programı

#!/usr/bin/perl -w

# guestbook.cgi

BEGIN { $ENV{PATH} = '/usr/bin:/bin' }
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};   # Make %ENV safer =:-)
print "Content-type: text/html\n\n";
print "<HTML>\n<HEAD><TITLE>Buggy Guestbook</TITLE></HEAD>\n";
&ReadParse(\%input);
my $email= $input{email};
my $texte= $input{texte};
$texte =~ s/\n/<BR>/g;
 
print "<BODY><A HREF=\"guestbook.html\">
       GuestBook </A><BR><form action=\"$ENV{'SCRIPT_NAME'}\">\n
      Email: <input type=texte name=email><BR>\n
      Texte:<BR>\n<textarea name=\"texte\" rows=15 cols=70>
      </textarea><BR><input type=submit value=\"Go!\">
      </form>\n";
print "</BODY>\n";
print "</HTML>";
open (FILE,">>guestbook.html") || die ("Cannot write\n");
print FILE "Email: $email<BR>\n";
print FILE "Texte: $texte<BR>\n";
print FILE "<HR>\n";
close(FILE);
exit(0);
 
sub ReadParse {
  my $in =shift;
  my ($i, $key, $val);
  my $in_first;
  my @in_second;
 
  # Read in text
  if ($ENV{'REQUEST_METHOD'} eq "GET") {
    $in_first = $ENV{'QUERY_STRING'};
  } elsif ($ENV{'REQUEST_METHOD'} eq "POST") {
    read(STDIN,$in_first,$ENV{'CONTENT_LENGTH'});
  }else{
    die "ERROR: unknown request method\n";
  }
 
  @in_second = split(/&/,$in_first);
 
  foreach $i (0 .. $#in_second) {
    # Convert plus's to spaces
    $in_second[$i] =~ s/\+/ /g;
 
    # Split into key and value.
    ($key, $val) = split(/=/,$in_second[$i],2); 
 
    # Convert %XX from hex numbers to alphanumeric
    $key =~ s/%(..)/pack("c",hex($1))/ge;
    $val =~ s/%(..)/pack("c",hex($1))/ge;
 
    # Associate key and value
    $$in{$key} .= "\0" if (defined($$in{$key})); 
    $$in{$key} .= $val;
 
  }
 
  return length($#in_second);
}