Cara Mencegah SQL Injection Dengan PDO PHP

Dalam era digital yang semakin berkembang, aplikasi web menjadi bagian integral dari kehidupan sehari-hari. Namun, dengan kemudahan aksesibilitas ini juga datang tantangan baru dalam menjaga keamanan data sensitif. Salah satu serangan paling umum yang dihadapi aplikasi web adalah SQL Injection, di mana penyerang memanfaatkan celah keamanan dalam kode SQL untuk menyusupkan dan menjalankan perintah SQL yang tidak diinginkan.

Untuk mengatasi risiko SQL Injection, pengembang web perlu mengadopsi praktik terbaik dalam pengembangan perangkat lunak mereka. Salah satu cara yang efektif adalah dengan menggunakan PDO (PHP Data Objects), sebuah ekstensi PHP yang memberikan antarmuka untuk berinteraksi dengan database dengan aman.

PDO

PDO (PHP Data Objects) adalah sebuah ekstensi PHP yang menyediakan antarmuka untuk berinteraksi dengan berbagai jenis database relasional menggunakan kode yang seragam. PDO memungkinkan pengembang untuk bekerja dengan database tanpa terikat pada satu jenis database tertentu, sehingga memungkinkan portabilitas kode antar platform database yang berbeda.

Salah satu keunggulan utama PDO adalah kemampuannya untuk menyediakan lapisan abstraksi yang aman terhadap database, termasuk fitur-fitur yang berguna dalam mencegah serangan SQL Injection. PDO menggunakan parameterized queries, yang memungkinkan pengembang untuk menyediakan nilai-parameter terpisah dari perintah SQL, sehingga mengurangi risiko penyisipan kode SQL yang tidak sah.

Selain itu, PDO juga menyediakan berbagai fitur lain seperti transaksi, koneksi yang dapat dipooling, dan penanganan kesalahan yang lebih baik. Ini membuatnya menjadi pilihan yang populer untuk pengembangan aplikasi web PHP yang aman dan efisien dalam berinteraksi dengan database. Dengan menggunakan PDO, pengembang dapat membangun aplikasi web yang lebih aman dan lebih mudah dipelihara dengan mengurangi kerentanan terhadap serangan SQL Injection dan masalah keamanan database lainnya.

Cara Kerja SQL Injection

SQL Injection adalah serangan keamanan pada aplikasi web yang memanfaatkan kerentanan dalam pemrosesan input pengguna yang diabaikan atau tidak di validasi dengan benar. Serangan ini terjadi ketika penyerang menyisipkan kode SQL yang berbahaya atau tidak diinginkan ke dalam input yang diterima oleh aplikasi, sehingga memungkinkan mereka untuk menjalankan perintah SQL yang tidak sah atau merusak.

    Cara kerja SQL Injection umumnya melibatkan langkah-langkah berikut:
  1. Identifikasi Celah Keamanan: Penyerang mencari input pengguna dalam aplikasi web yang kemungkinan besar akan digunakan dalam perintah SQL. Ini bisa berupa formulir pencarian, formulir login, atau parameter URL.
  2. Sisipkan Kode SQL Berbahaya: Setelah menemukan input yang rentan, penyerang menyisipkan kode SQL yang berbahaya ke dalam input tersebut. Contoh kode berbahaya termasuk perintah SQL yang menambah, menghapus, atau mengambil data dari database, seperti "DROP TABLE", "SELECT *", atau "DELETE FROM".
  3. Eksekusi Kode SQL yang Disisipkan: Setelah menyisipkan kode SQL yang berbahaya, aplikasi web yang rentan kemudian menjalankan perintah SQL tersebut tanpa memvalidasi atau membersihkan input secara benar. Ini mengakibatkan eksekusi perintah SQL yang tidak diinginkan oleh penyerang.
  4. Hasil dan Dampak: Penyerang kemudian dapat memanfaatkan hasil dari serangan SQL Injection ini untuk mencuri, menghapus, atau memodifikasi data dalam database, merusak integritas data, atau bahkan mengambil alih kontrol atas aplikasi web secara keseluruhan.

Untuk mencegah SQL Injection, penting untuk melakukan validasi input secara ketat, menggunakan parameterized queries atau prepared statements untuk menghindari penyisipan kode SQL yang berbahaya, dan menerapkan prinsip-prinsip keamanan yang baik dalam pengembangan perangkat lunak.

Katakanlah penulis memiliki payload SQL injection di bawah ini :

'OR 1 = 1 -- 

Payload di atas ketika di eksekusi pada aplikasi yang rentan akan mengetahui semua kredensial akun pengguna pada sistem aplikasi target. Contoh nya saja, penulis memiliki DVWA untuk di contoh kan pada video di bawah ini :


Kode PDO PHP

Ok, penulis mulai. Pertama, penulis membuat koneksi PHP nya dengan nama db.php terlebih dahulu di bawah ini :

<?php
$servername = "localhost";
$username = "root";
$password = "";

$conn = new PDO("mysql:host=$servername;dbname=db_blogger", $username, $password);

Penjelasan kode :

  • $servername, $username, $password: Variabel ini digunakan untuk menyimpan informasi tentang server database MySQL yang akan dihubungi (seperti nama host, nama pengguna, dan kata sandi). Dalam contoh ini, server database MySQL berada di localhost, dan pengguna yang digunakan adalah "root" tanpa kata sandi.
  • $conn: Variabel ini digunakan untuk menyimpan objek koneksi ke database. Pada baris kode ini, sebuah objek PDO dibuat dengan menggunakan konstruktor PDO(). Konstruktor ini menerima tiga parameter:

    1. "mysql:host=$servername;dbname=db_blogger": Parameter pertama adalah string yang menentukan jenis database dan detail koneksi, di mana "mysql" adalah jenis database, "$servername" adalah nama host, dan "db_blogger" adalah nama database yang digunakan.

    2. $username: Parameter kedua adalah nama pengguna yang digunakan untuk mengautentikasi ke database.

    3. $password: Parameter ketiga adalah kata sandi yang digunakan untuk mengautentikasi ke database. Dengan menggunakan konstruktor PDO() seperti ini, sebuah objek koneksi PDO dibuat yang terhubung ke database MySQL yang ditentukan.

Setelah itu, untuk script login nya penulis membuat sebuah file dengan nama login.php :

<?php
session_start();

include "db.php";

if (isset($_SESSION['login'])) {
    header('Location: index.php');
    die();
}
?>

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login</title>
</head>

<body>
    <?php
    if (isset($_POST['login']) && $_SERVER["REQUEST_METHOD"] == "POST") {

        $username = $_POST['username'];
        $password = $_POST['password'];

        $stmt = $conn->prepare("SELECT * FROM tbl_auth WHERE username = :username");
        $stmt->bindParam(":username", $username);
        $stmt->execute();
        $fetchData = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($fetchData) {
            if (password_verify($password, $fetchData['password'])) {

                $_SESSION['login'] = true;
                header('Location: index.php');
            } else {
                echo "<h3>Gagal login!</h3>";
            }
        } else {
            echo "<h3>Gagal login!</h3>";
        }
    }
    ?>

    <h3>Login Admin!</h3>

    <form method="post">
        <ul>
            <li><label for="username">Username :</label>
                <input type="text" name="username" id="username" autocomplete="off" required>
            </li>

            <br />

            <li><label for="password">Password :</label>
                <input type="password" name="password" id="password" autocomplete="off" required>
            </li>

            <br />

            <li>
                <button type="submit" name="login">Login</button>
            </li>
        </ul>
    </form>
</body>

</html>

Baik, kita bedah satu satu arti kode di atas. Kita fokus pada kode PHP nya saja. Pertama pada awal baris ini :

<?php
session_start();

include "db.php";

if (isset($_SESSION['login'])) {
    header('Location: index.php');
    die();
}
?>
  1. session_start();: Pernyataan ini memulai atau melanjutkan sesi yang ada di PHP. Diperlukan untuk menggunakan variabel sesi di halaman PHP.
  2. include "db.php";: Pernyataan ini mengambil (atau meng-include) file db.php tadi ke dalam halaman saat ini. Ini memungkinkan penggunaan fungsi, variabel, atau kelas yang didefinisikan di dalam file db.php.
  3. if (isset($_SESSION['login'])) { ... }: Pernyataan ini memeriksa apakah variabel $_SESSION['login'] telah diset. Jika sudah di set, paksa ke halaman index agar tidak di alihkan kembali ke halaman login ketika pengguna mengunjungi file login.php beserta header('Location: index.php'); yang akan mengarahkan pengguna ke halaman index.php dan pernyataan die(); akan menghentikan eksekusi kode lebih lanjut di halaman saat ini.

Selanjutnya bagian ini :

 <?php
if (isset($_POST['login']) && $_SERVER["REQUEST_METHOD"] == "POST") {

$username = $_POST['username'];
$password = $_POST['password'];
  1. if (isset($_POST['login']) && $_SERVER["REQUEST_METHOD"] == "POST") {: Pernyataan ini memeriksa apakah form login telah disubmit dan permintaan yang diterima adalah POST. Hal ini dilakukan dengan memeriksa apakah variabel $_POST['login'] telah diatur dan metode permintaan ($_SERVER["REQUEST_METHOD"]) adalah POST. Jika kedua kondisi ini terpenuhi, maka kode di dalam blok if akan dieksekusi.
  2. $username = $_POST['username'];: Pernyataan ini mengambil nilai yang dikirimkan melalui input dengan atrribute name username dan menyimpannya ke dalam variabel $username.
  3. $password = $_POST['password'];: Pernyataan ini mengambil nilai yang dikirimkan melalui input dengan atrribute name password dan menyimpannya ke dalam variabel $password.

Lanjut lagi, pada bagian ini :

$stmt = $conn->prepare("SELECT * FROM tbl_auth WHERE username = :username");
$stmt->bindParam(":username", $username);
$stmt->execute();
$fetchData = $stmt->fetch(PDO::FETCH_ASSOC);
  1. $stmt = $conn->prepare("SELECT * FROM tbl_auth WHERE username = :username");: Pernyataan ini menyiapkan sebuah pernyataan SQL untuk dieksekusi oleh server database. Perintah SQL yang disiapkan adalah SELECT * FROM tbl_auth WHERE username = :username, di mana :username adalah parameter yang akan diikat (bind) nilainya kemudian dieksekusi. Dalam hal ini, kita mencari baris di tabel tbl_auth di mana kolom username sama dengan nilai yang diberikan.
  2. $stmt->bindParam(":username", $username);: Pernyataan ini mengikat nilai yang disimpan dalam variabel $username ke parameter :username dalam pernyataan yang telah disiapkan sebelumnya. Dengan cara ini, kita memberikan nilai yang dikirimkan oleh pengguna melalui form login ke dalam pernyataan SQL yang telah disiapkan.
  3. $stmt->execute();: Pernyataan ini mengeksekusi pernyataan SQL yang telah disiapkan dan diikat nilainya. Ini akan mengirimkan perintah ke server database untuk mengeksekusi kueri yang telah disiapkan.
  4. $fetchData = $stmt->fetch(PDO::FETCH_ASSOC);: Pernyataan ini mengambil hasil dari eksekusi pernyataan SQL sebelumnya dan menyimpannya ke dalam variabel $fetchData. Fungsi fetch(PDO::FETCH_ASSOC) digunakan untuk mengambil baris pertama hasil kueri sebagai array asosiatif yang menghubungkan nama kolom dengan nilai-nilai yang sesuai. Jika tidak ada baris yang cocok, maka $fetchData akan bernilai false.

Terakhir, pada kode ini :

  if ($fetchData) {
            if (password_verify($password, $fetchData['password'])) {

                $_SESSION['login'] = true;
                header('Location: index.php');
            } else {
                echo "<h3>Gagal login!</h3>";
            }
        } else {
            echo "<h3>Gagal login!</h3>";
        }
  1. if ($fetchData) {: Pernyataan ini memeriksa apakah hasil kueri yang diperoleh ($fetchData) tidak kosong. Artinya, jika ada baris yang cocok dengan kriteria pencarian (nama pengguna yang dimasukkan), maka kondisi ini akan bernilai true.
  2. if (password_verify($password, $fetchData['password'])) {: Di dalam blok if yang pertama, pernyataan ini memeriksa apakah password yang dimasukkan oleh pengguna sesuai dengan password yang disimpan di database setelah di-hash. Ini dilakukan dengan menggunakan fungsi password_verify(). Parameter pertama adalah password yang dimasukkan oleh pengguna ($password), dan parameter kedua adalah password yang di-hash yang diperoleh dari hasil kueri database ($fetchData['password']).
  3. $_SESSION['login'] = true;: Jika kedua kondisi di atas terpenuhi, artinya pengguna telah berhasil login. Dalam hal ini, variabel sesi login diatur menjadi true, menandakan bahwa pengguna telah login.
  4. header('Location: index.php');: Setelah pengguna berhasil login, akan diarahkan ke halaman index.php menggunakan fungsi header().
  5. echo "Gagal login!";: Jika salah satu dari kondisi di atas tidak terpenuhi, yaitu jika tidak ada baris yang cocok dengan kriteria pencarian di database atau jika password yang dimasukkan tidak cocok, maka pesan "Gagal login!" akan ditampilkan kepada pengguna.

Dengan adanya PDO PHP dan penggunaan fungsi password_verify(), kata sandi akan di-hash menggunakan metode one-way hashing. Ini berarti bahwa setelah kata sandi di-hash, tidak mungkin untuk mengembalikan kata sandi tersebut ke teks biasa (plain text). Hal ini memiliki keuntungan keamanan yang signifikan karena bahkan jika data di database diakses oleh pihak yang tidak berwenang, kata sandi pengguna tetap terlindungi.

Penggunaan password_verify() memungkinkan kita untuk memeriksa apakah kata sandi yang dimasukkan oleh pengguna cocok dengan kata sandi yang di-hash yang disimpan di database. Proses ini membandingkan kata sandi yang dimasukkan oleh pengguna dengan nilai hash yang tersimpan, dan jika cocok, pengguna diizinkan masuk.

Dengan demikian, penggunaan metode one-way hashing dan password_verify() membantu meningkatkan keamanan aplikasi web dengan melindungi kata sandi pengguna dari serangan dan eksploitasi yang berpotensi. Bonus, kamu bisa copy dan paste saja file register.php nya untuk INSERT data password menggunakan metode one way hashing yang nanti akan di cocokkan dengan password_verify() nya :

<?php
include "db.php";
?>

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Registrasi</title>
</head>

<body>
    <?php
    if (isset($_POST['register']) && $_SERVER["REQUEST_METHOD"] == "POST") {

        $username = $_POST["username"];
        $password = $_POST["password"];

        $hashPassword = password_hash($password, PASSWORD_DEFAULT);

        $stmt = $conn->prepare("INSERT INTO tbl_auth (username, password) VALUES (:username, :password)");

        $stmt->bindParam(':username', $username);
        $stmt->bindParam(':password', $hashPassword);

        $stmt->execute();

        echo "Registrasi berhasil! Username: $username, Password: $hashPassword";
    }
    ?>

    <form method="post">
        <ul>
            <li>
                <label for="username">Username :</label>
                <input type="text" name="username" id="username" autocomplete="off" required>
            </li>
            <br />
            <li>
                <label for="password">Password :</label>
                <input type="password" name="password" id="password" autocomplete="off" required>
            </li>
            <br />
            <li>
                <button type="submit" name="register">Register</button>
            </li>
        </ul>
    </form>
</body>

</html>

Script di atas penjelasan nya sama saja, perbedaan signifikan nya adalah pada ini :

$hashPassword = password_hash($password, PASSWORD_DEFAULT);

Password yang di ketikkan oleh pengguna pada bidang inputan password akan di simpan pada variabel $hashPassword yang nanti hasil masukkan password tadi akan di ubah ke hash menggunakan metode one way hashing. Dan terakhir, perbedaan query SQL nya saja yaitu INSERT :

$stmt = $conn->prepare("INSERT INTO tbl_auth (username, password) VALUES (:username, :password)");

Penutup

Sebagai penutup nya, fokus utama pada artikel ini adalah pencegahan SQL injection terutama pada fitur autentikasi web yaitu login nya. Bagaimana dengan file index.php dan logout.php nya? Kamu bisa kunjungi repo nya SQL injection PDO PHP. Semoga bermanfaat!