Mencegah Injeksi SQL di PHP

oleh Vincy. Terakhir diubah pada 24 Juni 2021.

Tahukah Anda bahwa 42% serangan yang dihadapi oleh sistem publik adalah serangan injeksi SQL? Cegah injeksi SQL untuk menghindari kerentanan keamanan yang paling sering terjadi dalam sistem yang dihadapi publik.

Injeksi SQL adalah ancaman paling tinggi yang dihadapi oleh sistem perangkat lunak apa pun. Yang Anda butuhkan hanyalah kueri berparameter untuk mencegah injeksi SQL dan membuat aplikasi Anda lebih aman.

Contoh cepat

Bagaimana mencegah injeksi SQL di PHP menggunakan MySQLi.


// use prepared statement to prevent SQL injection
$preparedStatement = $dbConnection->prepare('SELECT * FROM animals WHERE name = ?');
$preparedStatement->bind_param('s', $name); 
$preparedStatement->execute();
$result = $preparedStatement->get_result();
while ($row = $result->fetch_assoc()) {
// Process $row
}

Apa itu serangan injeksi SQL?

Injeksi SQL adalah kerentanan keamanan perangkat lunak yang memungkinkan pengguna untuk mencegat eksekusi kueri SQL dan mengeksekusi kueri pilihan mereka.

Pencegahan injeksi SQL sangat penting, karena kerentanan keamanan ini akan memungkinkan pengguna untuk melakukan CRUD yang tidak disengaja,

  • memodifikasi data
  • hapus data
  • melihat data yang tidak sah

Dalam skenario terburuk, pengguna yang menyerang dapat mengendalikan infrastruktur perangkat lunak lengkap menggunakan injeksi sql. Cegah injeksi SQL untuk menghindari kerugian bisnis yang serius.

Statistik Injeksi SQL

Laporan oleh Edgescan. Laporan Statistik Kerentanan 2020.

Ketika audit keamanan perangkat lunak dilakukan, mencegah injeksi SQL harus menjadi item pertama dan terpenting dalam daftar periksa. Pendekatan lesu dalam pencegahan injeksi SQL dapat menyebabkan pelanggaran data, kehilangan data sensitif seperti informasi bank dan kartu kredit, dll.

Injeksi SQL dapat menyebabkan akses tidak sah yang berkepanjangan ke sistem perangkat lunak. Ini akan menyebabkan kehilangan informasi keuangan dan pribadi yang serius.

Mencegah injeksi SQL di PHP bahkan sangat mudah. Kerangka kerja PHP yang kami gunakan untuk interaksi database seperti PDO, MySQLi menyediakan mekanisme yang canggih.

Cara mendeteksi kerentanan injeksi SQL

Injeksi SQL adalah ancaman keamanan serius pertama dan terpenting untuk perangkat lunak. Untuk mencegah injeksi SQL, kita tidak perlu menghabiskan banyak tenaga, karena dapat dengan mudah diidentifikasi dan diperbaiki.

Ada alat pemindaian keamanan yang tersedia yang dapat mendeteksi dan melaporkan kerentanan injeksi SQL. Injeksi SQL juga dapat dengan mudah dideteksi menggunakan tinjauan kode manual dan tes fungsional.

Berikut ini adalah beberapa contoh kasus uji. Masukkan di bawah ini dan periksa perilaku aplikasi. Jika mengembalikan kesalahan dan menghasilkan kondisi yang tidak terduga, maka Anda memiliki tugas untuk mencegah injeksi sql.

  • Masukkan karakter kutipan tunggal ‘ dan kirim.
  • Masukkan ekspresi boolean yang bernilai benar atau salah. Contohnya adalah 1=1 atau 1=2.

Dua di atas adalah kasus sederhana. Itu akan mendeteksi kerentanan injeksi SQL.

Bagaimana mencegah injeksi SQL

Untuk mencegah injeksi SQL, tidak memerlukan implementasi kerangka kerja yang besar atau proses yang mahal. Yang perlu kita lakukan adalah menghindari penggabungan input pengguna dalam kueri SQL.

Saat Anda mendapatkan input pengguna melalui formulir, sebagai tindakan pencegahan keamanan, selalu filter dan sanitasi input pengguna menggunakan fungsi PHP sebelum digunakan.

Untuk mencegah injeksi SQL, gunakan kueri berparameter, yang seharusnya cukup di sebagian besar kasus.

Contoh untuk injeksi SQL

Kueri SQL di bawah ini mengambil parameter. Masukan pengguna langsung ditambahkan dengan kueri.


// pseudo code describing SQL injection vulnerability
String sqlQuery = "SELECT * FROM animals WHERE type=""+ $userInput + """;
Statement sqlStatement = dbConnection.createStatement();
ResultSet result = sqlStatement.executeQuery(sqlQuery);


Dalam skenario di atas, ‘userInput’ langsung ditambahkan ke kueri SQL. Misalnya, pengguna dapat memberikan input yang dapat menghentikan kueri SQL dan melanjutkan sub-kuerinya sendiri.


"'dummy'; DELETE FROM animals"


Jika pengguna memasukkan hal di atas. Penggabungan string dalam kueri SQL menjadikan ini peluang untuk injeksi SQL.

Bagian pertama dari kueri tiba-tiba selesai hanya dengan mencari nilai ‘dummy’. Kemudian kueri SQL kedua dieksekusi yang menghapus semua catatan dari tabel database ‘hewan’.

Kueri SQL kedua ini melakukan aktivitas berbahaya melalui injeksi SQL. Jadi aturan emasnya adalah, untuk mencegah injeksi SQL, gunakan Pernyataan yang Disiapkan.

Pernyataan yang disiapkan untuk mencegah injeksi SQL

Pernyataan yang disiapkan atau pernyataan berparameter memungkinkan untuk mendeklarasikan kueri SQL dalam formulir templat. Kemudian ganti parameter menggunakan nilai dan jalankan kueri.

Selain mencegah injeksi SQL, pernyataan yang disiapkan juga memberikan kinerja yang lebih baik. Substitusi dan eksekusi dapat diulang beberapa kali yang meningkatkan efisiensi eksekusi query SQL. Ini akan menghasilkan peningkatan kecepatan di mana kueri SQL yang sama dieksekusi beberapa kali.


// prevent SQL injection using prepared statement
PreparedStatement preparedStatement = dbConnection.prepareStatement("SELECT * FROM animals WHERE type = ?");
preparedStatement.setString(1, $userInput);
ResultSet sqlResultSet = preparedStatement.executeQuery();


Kode semu di atas menjelaskan bagaimana pernyataan yang disiapkan dapat digunakan untuk mencegah injeksi sql. MySQLi dan PDO PHP mendukung Pernyataan yang Disiapkan dan mudah digunakan.

Jadi, selalu praktikkan untuk tidak menambahkan nilai dinamis ke kueri, hanya melalui penggabungan string. Untuk mencegah injeksi SQL, setiap bagian dari kueri SQL tidak boleh dibuat menggunakan rangkaian string dinamis.

Cegah injeksi SQL di PHP menggunakan PDO

PDO adalah ‘objek data PHP’, ekstensi untuk PHP untuk mengakses database. PDO tersedia dengan PHP secara default. Rujuk CRUD menggunakan PHP PDO untuk contoh PHP berbasis PDO yang lengkap.


// prevent SQL injection in PHP using PDO prepared statement
$sqlStatement = $pdoConnection->prepare('SELECT * FROM animals WHERE type = :type');
$sqlStatement->execute([ 'type' => $type ]);
foreach ($sqlStatement as $resultRow) {
// process $resultRow
}


Saat Anda membuat koneksi database PHP PDO, ingatlah untuk menyetel atribut ATTR_EMULATE_PREPARES ke nilai false. Karena, secara default sudah disetel ke true.

Ini menginstruksikan PHP PDO untuk menonaktifkan pernyataan siap yang ditiru. Ini untuk memastikan bahwa nilainya tidak diurai sebelum dikirim ke server database MySQL.


$pdoConnection = new PDO('mysql:dbname=animaldb;host=127.0.0.1;charset=utf8', 'dbusername', 'dbpassword');
$pdoConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);


Pada kode di atas “:type” adalah placeholder dalam template SQL. Ini adalah kunci untuk mencegah injeksi SQL.

Dengan menggunakan metode substitusi template ini, kita tidak mem-parsing nilai di sisi PHP. Kami hanya meneruskan nilai “$type” sebagai nilai substitusi ke server database.

Itu digantikan oleh mesin database dan kueri dievaluasi dan dieksekusi. Dengan cara ini kami mencegah injeksi SQL.

Contoh penyisipan PDO PHP

Cuplikan kode berikut menjelaskan cara mencegah injeksi SQL menggunakan PHP PDO selama operasi penyisipan basis data.


// example to prevent SQL injection using PHP PDO insert
$preparedStatement = $pdoConnection->prepare('INSERT INTO table (column) VALUES (:columnname)');
$preparedStatement->execute([ 'columnname' => $userInput ]);


Cegah injeksi SQL di PHP menggunakan MySQLi

MySQLi adalah ekstensi ‘MySQL Improved’ untuk mengakses database MySQL di PHP. Ini adalah versi perbaikan dari ekstensi MySQL yang sekarang sudah usang. Rujuk CRUD dengan MySQLi menggunakan pernyataan yang disiapkan untuk contoh lengkap.


// prevent SQL injection in PHP using MySQLi prepared statement
$preparedStatement = $dbConnection->prepare('SELECT * FROM animals WHERE type = ?');
// 's' specifies the variable data type as a 'string'
$preparedStatement->bind_param('s', $type); 
$preparedStatement->execute();
$result = $preparedStatement->get_result();
while ($row = $result->fetch_assoc()) {
// Process $row
}


Utilitas basis data PHP

Berikut ini adalah utilitas database yang dapat digunakan sebagai kelas generik untuk mengakses database. Ini ditulis untuk ekstensi MySQLi dan dapat dengan mudah diadaptasi untuk PHP PDO. Cara perancangannya menggunakan Pernyataan yang Disiapkan dan secara implisit digunakan untuk mencegah injeksi SQL di PHP.

Dalam artikel skrip login PHP dengan sesi, saya telah menunjukkan cara menggunakan kelas PHP DataSource ini. Login adalah komponen penting di situs web mana pun. Jadi saat membangun situs web, Anda dapat menggunakan kelas utilitas basis data PHP ini dan mencegah injeksi SQL.

<?php
namespace Phppot;

/**
 * Generic datasource class for handling DB operations.
 * Uses MySqli and PreparedStatements.
 *
 * @version 2.3
 */
class DataSource
{

    // PHP 7.1.0 visibility modifiers are allowed for class constants.
    // when using above 7.1.0, declare the below constants as private
    const HOST = 'localhost';

    const USERNAME = 'root';

    const PASSWORD = '';

    const DATABASENAME = 'phpsamples';

    private $conn;

    /**
     * PHP implicitly takes care of cleanup for default connection types.
     * So no need to worry about closing the connection.
     *
     * Singletons not required in PHP as there is no
     * concept of shared memory.
     * Every object lives only for a request.
     *
     * Keeping things simple and that works!
     */
    function __construct()
    {
        $this->conn = $this->getConnection();
    }

    /**
     * If connection object is needed use this method and get access to it.
     * Otherwise, use the below methods for insert / update / etc.
     *
     * @return mysqli
     */
    public function getConnection()
    {
        $conn = new mysqli(self::HOST, self::USERNAME, self::PASSWORD, self::DATABASENAME);
        
        if (mysqli_connect_errno()) {
            trigger_error("Problem with connecting to database.");
        }
        
        $conn->set_charset("utf8");
        return $conn;
    }

    /**
     * To get database results
     * @param string $query
     * @param string $paramType
     * @param array $paramArray
     * @return array
     */
    public function select($query, $paramType="", $paramArray=array())
    {
        $stmt = $this->conn->prepare($query);
        
        if(!empty($paramType) && !empty($paramArray)) {
            $this->bindQueryParams($stmt, $paramType, $paramArray);
        }
        
        $stmt->execute();
        $result = $stmt->get_result();
        
        if ($result->num_rows > 0) {
            while ($row = $result->fetch_assoc()) {
                $resultset[] = $row;
            }
        }
        
        if (! empty($resultset)) {
            return $resultset;
        }
    }
    
    /**
     * To insert
     * @param string $query
     * @param string $paramType
     * @param array $paramArray
     * @return int
     */
    public function insert($query, $paramType, $paramArray)
    {
        print $query;
        $stmt = $this->conn->prepare($query);
        $this->bindQueryParams($stmt, $paramType, $paramArray);
        $stmt->execute();
        $insertId = $stmt->insert_id;
        return $insertId;
    }
    
    /**
     * To execute query
     * @param string $query
     * @param string $paramType
     * @param array $paramArray
     */
    public function execute($query, $paramType="", $paramArray=array())
    {
        $stmt = $this->conn->prepare($query);
        
        if(!empty($paramType) && !empty($paramArray)) {
            $this->bindQueryParams($stmt, $paramType="", $paramArray=array());
        }
        $stmt->execute();
    }
    
    /**
     * 1. Prepares parameter binding
     * 2. Bind prameters to the sql statement
     * @param string $stmt
     * @param string $paramType
     * @param array $paramArray
     */
    public function bindQueryParams($stmt, $paramType, $paramArray=array())
    {
        $paramValueReference[] = & $paramType;
        for ($i = 0; $i < count($paramArray); $i ++) {
            $paramValueReference[] = & $paramArray[$i];
        }
        call_user_func_array(array(
            $stmt,
            'bind_param'
        ), $paramValueReference);
    }
    
    /**
     * To get database results
     * @param string $query
     * @param string $paramType
     * @param array $paramArray
     * @return array
     */
    public function numRows($query, $paramType="", $paramArray=array())
    {
        $stmt = $this->conn->prepare($query);
        
        if(!empty($paramType) && !empty($paramArray)) {
            $this->bindQueryParams($stmt, $paramType, $paramArray);
        }
        
        $stmt->execute();
        $stmt->store_result();
        $recordCount = $stmt->num_rows;
        return $recordCount;
    }
}

Serangan terkait dengan injeksi SQL

  • Injeksi SQL Melewati WAF
  • Injeksi SQL Buta
  • Injeksi Kode
  • Pengkodean Ganda
  • Injeksi ORM

Kita akan melihat tentang masing-masing di artikel mendatang satu per satu.

Bacaan lebih lanjut

Kembali ke Atas


Source link