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
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.
İ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][...] [¶m_n=val_n]]
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
www.linuxdoc.org
;/cgi-bin/ldpsrch.cgi
olarak adlandırılır. ;?
uzun veri listesinin başlangıcıdır.
:
srv=http%3A%2F%2Fwww.linuxdoc.org
isteğin geldiği sunucudur;srch=ssh
isteğin kendisini içerir;db=1
sadece HOWTO ları içerdiğini anlatır;scope=0
isteğin sadece başlığı değil içerikleri
kapsadığını anlatır;rpt=20
görüntülenen cevapları 20 ile sınırlar.
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 :
Options Includes
her SSI'yı aktifleştirir ;OptionsIncludesNoExec
, exec
cmd
ve exec cgi
'yı engeller.Ekteki guestbook.cgi
betiğinde, kullanıcı tarafından
sağlanan metin bir HTML dosyası içerisinde, '<' ve ' >' içerisinde < ve >
karakter dönüştürmesi olmadan bulunur. Meraklı birisi şu talimatlardan birisini gönderebilir:
<!--#printenv -->
(printenv
sonrası boşluğu unutma)<!--#exec cmd="cat /etc/passwd"-->
guestbook.cgi?email=pappy&texte=%3c%21--%23printenv%20--%3e
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.
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.
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;
İş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%2FpasswdYapı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|
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ç :
;
: ilk yönergeyi bitiriyor ve diğerine geçiyor;&&
: eğer ilk yönerge başarırsa
(örneğin; kabuğa 0 düşerse), sıradaki çalıştırılır;||
: eğer ilk yönerge başarısız olursa
(örneğin; kabukta boş bir değere düşmezse), bir sonraki çalıştırılır.Ö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/passwdAş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/passwdKabuk bunu iki farklı yönergeye ayırır :
/usr/bin/finger kmaster\
.. fakat umursamıyoruz ;-)cat /etc/passwd
file.\
' de kaçabilir. 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%22Kabuk 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 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 :
open(FILE,"<$filename") || ...
sadece okumak için;open(FILE,">$filename") || ...
sadece yazmak içinBir 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ı.
İ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 :
$safe
riskli olarak tanımlanmayanları içerir (burada, sadece rakamlar ve harfler);$danger
izin verilen ancak muhtemel tehlikeli olan kaçınılacak karakterleri içerir.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.
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.
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 | > |
man perlsec
: güvenlikle ilgili Perl kılavuz sayfası;#!/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); }