Pertanyaan Apa metode terbaik untuk membersihkan input pengguna dengan PHP?


Apakah ada fungsi catchall di suatu tempat yang berfungsi baik untuk membersihkan input pengguna untuk injeksi SQL dan serangan XSS, sementara masih mengizinkan jenis tag html tertentu?


975
2017-09-24 20:20


asal


Jawaban:


Merupakan kesalahpahaman umum bahwa masukan pengguna dapat difilter. PHP bahkan memiliki fitur "sekarang", yang disebut kutipan-ajaib, yang dibangun di atas ide ini. Itu tidak masuk akal. Lupakan tentang penyaringan (Atau membersihkan, atau apa pun yang orang menyebutnya).

Apa yang harus Anda lakukan, untuk menghindari masalah, cukup sederhana: setiap kali Anda menanamkan string dalam kode asing, Anda harus meloloskannya, sesuai dengan aturan bahasa itu. Misalnya, jika Anda menanamkan string dalam beberapa penargetan SQL MySql, Anda harus melepaskan string dengan fungsi MySql untuk tujuan ini (mysqli_real_escape_string). (Atau, dalam kasus database, menggunakan pernyataan siap adalah pendekatan yang lebih baik, bila memungkinkan)

Contoh lain adalah HTML: Jika Anda menanamkan string dalam markup HTML, Anda harus menghindarinya htmlspecialchars. Ini artinya setiap satu echo atau print pernyataan harus digunakan htmlspecialchars.

Contoh ketiga bisa berupa perintah shell: Jika Anda akan menanamkan string (Seperti argumen) ke perintah eksternal, dan memanggilnya dengan exec, maka Anda harus menggunakannya escapeshellcmd dan escapeshellarg.

Dan seterusnya dan seterusnya ...

Itu hanya kasus di mana Anda perlu secara aktif memfilter data, adalah jika Anda menerima input yang sudah diformat. Misalnya. jika Anda membiarkan pengguna Anda mengirim markup HTML, yang Anda rencanakan untuk ditampilkan di situs. Namun, Anda harus bijak untuk menghindari hal ini dengan cara apa pun, karena tidak peduli seberapa baik Anda memfilternya, itu akan selalu menjadi lubang keamanan potensial.


1078
2017-09-24 22:30



Jangan mencoba mencegah injeksi SQL dengan membersihkan data input.

Sebagai gantinya, jangan izinkan data untuk digunakan dalam pembuatan kode SQL Anda. Gunakan Pernyataan yang Telah Disiapkan (yaitu menggunakan parameter dalam permintaan template) yang menggunakan variabel terikat. Ini adalah satu-satunya cara untuk dijamin terhadap injeksi SQL.

Silakan lihat situs web saya http://bobby-tables.com/ untuk lebih lanjut tentang mencegah injeksi SQL.


184
2017-10-09 06:28



Tidak. Anda tidak dapat secara umum memfilter data tanpa konteks apa pun itu. Terkadang Anda ingin mengambil kueri SQL sebagai masukan dan terkadang Anda ingin memasukkan HTML sebagai masukan.

Anda perlu memfilter masukan pada daftar putih - pastikan bahwa data tersebut sesuai dengan spesifikasi apa yang Anda harapkan. Maka Anda harus melarikan diri sebelum menggunakannya, tergantung pada konteks di mana Anda menggunakannya.

Proses keluarnya data untuk SQL - untuk mencegah injeksi SQL - sangat berbeda dari proses keluar data untuk (X) HTML, untuk mencegah XSS.


72
2017-09-24 20:24



PHP memiliki fungsi filter_input baru yang bagus sekarang, yang misalnya membebaskan Anda dari menemukan 'regex email utama' sekarang karena ada jenis FILTER_VALIDATE_EMAIL bawaan

Kelas filter saya sendiri (menggunakan javascript untuk menyorot bidang yang salah) dapat dimulai oleh permintaan ajax atau posting formulir normal. (lihat contoh di bawah)     

/**
 *  Pork.FormValidator
 *  Validates arrays or properties by setting up simple arrays. 
 *  Note that some of the regexes are for dutch input!
 *  Example:
 * 
 *  $validations = array('name' => 'anything','email' => 'email','alias' => 'anything','pwd'=>'anything','gsm' => 'phone','birthdate' => 'date');
 *  $required = array('name', 'email', 'alias', 'pwd');
 *  $sanatize = array('alias');
 *
 *  $validator = new FormValidator($validations, $required, $sanatize);
 *                  
 *  if($validator->validate($_POST))
 *  {
 *      $_POST = $validator->sanatize($_POST);
 *      // now do your saving, $_POST has been sanatized.
 *      die($validator->getScript()."<script type='text/javascript'>alert('saved changes');</script>");
 *  }
 *  else
 *  {
 *      die($validator->getScript());
 *  }   
 *  
 * To validate just one element:
 * $validated = new FormValidator()->validate('blah@bla.', 'email');
 * 
 * To sanatize just one element:
 * $sanatized = new FormValidator()->sanatize('<b>blah</b>', 'string');
 * 
 * @package pork
 * @author SchizoDuckie
 * @copyright SchizoDuckie 2008
 * @version 1.0
 * @access public
 */
class FormValidator
{
    public static $regexes = Array(
            'date' => "^[0-9]{1,2}[-/][0-9]{1,2}[-/][0-9]{4}\$",
            'amount' => "^[-]?[0-9]+\$",
            'number' => "^[-]?[0-9,]+\$",
            'alfanum' => "^[0-9a-zA-Z ,.-_\\s\?\!]+\$",
            'not_empty' => "[a-z0-9A-Z]+",
            'words' => "^[A-Za-z]+[A-Za-z \\s]*\$",
            'phone' => "^[0-9]{10,11}\$",
            'zipcode' => "^[1-9][0-9]{3}[a-zA-Z]{2}\$",
            'plate' => "^([0-9a-zA-Z]{2}[-]){2}[0-9a-zA-Z]{2}\$",
            'price' => "^[0-9.,]*(([.,][-])|([.,][0-9]{2}))?\$",
            '2digitopt' => "^\d+(\,\d{2})?\$",
            '2digitforce' => "^\d+\,\d\d\$",
            'anything' => "^[\d\D]{1,}\$"
    );
    private $validations, $sanatations, $mandatories, $errors, $corrects, $fields;


    public function __construct($validations=array(), $mandatories = array(), $sanatations = array())
    {
        $this->validations = $validations;
        $this->sanatations = $sanatations;
        $this->mandatories = $mandatories;
        $this->errors = array();
        $this->corrects = array();
    }

    /**
     * Validates an array of items (if needed) and returns true or false
     *
     */
    public function validate($items)
    {
        $this->fields = $items;
        $havefailures = false;
        foreach($items as $key=>$val)
        {
            if((strlen($val) == 0 || array_search($key, $this->validations) === false) && array_search($key, $this->mandatories) === false) 
            {
                $this->corrects[] = $key;
                continue;
            }
            $result = self::validateItem($val, $this->validations[$key]);
            if($result === false) {
                $havefailures = true;
                $this->addError($key, $this->validations[$key]);
            }
            else
            {
                $this->corrects[] = $key;
            }
        }

        return(!$havefailures);
    }

    /**
     *
     *  Adds unvalidated class to thos elements that are not validated. Removes them from classes that are.
     */
    public function getScript() {
        if(!empty($this->errors))
        {
            $errors = array();
            foreach($this->errors as $key=>$val) { $errors[] = "'INPUT[name={$key}]'"; }

            $output = '$$('.implode(',', $errors).').addClass("unvalidated");'; 
            $output .= "new FormValidator().showMessage();";
        }
        if(!empty($this->corrects))
        {
            $corrects = array();
            foreach($this->corrects as $key) { $corrects[] = "'INPUT[name={$key}]'"; }
            $output .= '$$('.implode(',', $corrects).').removeClass("unvalidated");';   
        }
        $output = "<script type='text/javascript'>{$output} </script>";
        return($output);
    }


    /**
     *
     * Sanatizes an array of items according to the $this->sanatations
     * sanatations will be standard of type string, but can also be specified.
     * For ease of use, this syntax is accepted:
     * $sanatations = array('fieldname', 'otherfieldname'=>'float');
     */
    public function sanatize($items)
    {
        foreach($items as $key=>$val)
        {
            if(array_search($key, $this->sanatations) === false && !array_key_exists($key, $this->sanatations)) continue;
            $items[$key] = self::sanatizeItem($val, $this->validations[$key]);
        }
        return($items);
    }


    /**
     *
     * Adds an error to the errors array.
     */ 
    private function addError($field, $type='string')
    {
        $this->errors[$field] = $type;
    }

    /**
     *
     * Sanatize a single var according to $type.
     * Allows for static calling to allow simple sanatization
     */
    public static function sanatizeItem($var, $type)
    {
        $flags = NULL;
        switch($type)
        {
            case 'url':
                $filter = FILTER_SANITIZE_URL;
            break;
            case 'int':
                $filter = FILTER_SANITIZE_NUMBER_INT;
            break;
            case 'float':
                $filter = FILTER_SANITIZE_NUMBER_FLOAT;
                $flags = FILTER_FLAG_ALLOW_FRACTION | FILTER_FLAG_ALLOW_THOUSAND;
            break;
            case 'email':
                $var = substr($var, 0, 254);
                $filter = FILTER_SANITIZE_EMAIL;
            break;
            case 'string':
            default:
                $filter = FILTER_SANITIZE_STRING;
                $flags = FILTER_FLAG_NO_ENCODE_QUOTES;
            break;

        }
        $output = filter_var($var, $filter, $flags);        
        return($output);
    }

    /** 
     *
     * Validates a single var according to $type.
     * Allows for static calling to allow simple validation.
     *
     */
    public static function validateItem($var, $type)
    {
        if(array_key_exists($type, self::$regexes))
        {
            $returnval =  filter_var($var, FILTER_VALIDATE_REGEXP, array("options"=> array("regexp"=>'!'.self::$regexes[$type].'!i'))) !== false;
            return($returnval);
        }
        $filter = false;
        switch($type)
        {
            case 'email':
                $var = substr($var, 0, 254);
                $filter = FILTER_VALIDATE_EMAIL;    
            break;
            case 'int':
                $filter = FILTER_VALIDATE_INT;
            break;
            case 'boolean':
                $filter = FILTER_VALIDATE_BOOLEAN;
            break;
            case 'ip':
                $filter = FILTER_VALIDATE_IP;
            break;
            case 'url':
                $filter = FILTER_VALIDATE_URL;
            break;
        }
        return ($filter === false) ? false : filter_var($var, $filter) !== false ? true : false;
    }       



}

Tentu saja, perlu diingat bahwa Anda perlu melakukan query sql Anda melarikan diri juga tergantung pada jenis db apa yang Anda gunakan (mysql_real_escape_string () tidak berguna untuk server sql misalnya). Anda mungkin ingin menangani ini secara otomatis di lapisan aplikasi yang sesuai seperti ORM. Juga, seperti yang disebutkan di atas: untuk keluaran ke html gunakan fungsi khusus php lainnya seperti htmlspecialchars;)

Untuk benar-benar memungkinkan input HTML dengan kelas-kelas dan / atau tag yang dilucuti bergantung pada salah satu paket validasi xss khusus. JANGAN MENULIS REGIAL ANDA SENDIRI UNTUK PARSE HTML!


43
2017-09-24 23:12



Tidak, tidak ada.

Pertama-tama, injeksi SQL adalah masalah penyaringan input, dan XSS adalah keluaran yang keluar - jadi Anda bahkan tidak akan mengeksekusi dua operasi ini pada waktu yang sama dalam siklus kode.

Aturan dasar praktis

  • Untuk kueri SQL, parameter bind (seperti dengan PDO) atau gunakan fungsi melarikan diri driver asli untuk variabel kueri (seperti mysql_real_escape_string())
  • Menggunakan strip_tags() untuk menyaring HTML yang tidak diinginkan
  • Lepaskan semua output lainnya dengan htmlspecialchars() dan berhati-hati terhadap parameter ke-2 dan ke-3 di sini.

39
2017-09-24 20:30



Untuk mengatasi masalah XSS, lihatlah Pembersih HTML. Ini cukup dapat dikonfigurasi dan memiliki rekam jejak yang layak.

Adapun serangan injeksi SQL, pastikan Anda memeriksa input pengguna, dan kemudian jalankan meskipun mysql_real_escape_string (). Fungsi ini tidak akan mengalahkan semua serangan injeksi, jadi penting bagi Anda untuk memeriksa data sebelum membuangnya ke string kueri Anda.

Solusi yang lebih baik adalah menggunakan pernyataan siap. Itu Perpustakaan PDO dan ekstensi mysqli mendukung ini.


20
2017-09-24 20:29



PHP 5.2 memperkenalkan filter_var fungsi.

Ini mendukung banyak filter SANITIZE, VALIDATE.

http://php.net/manual/en/function.filter-var.php


18
2017-10-15 08:40



Salah satu trik yang dapat membantu dalam keadaan tertentu di mana Anda memiliki halaman seperti /mypage?id=53 dan Anda menggunakan id dalam klausa WHERE adalah untuk memastikan bahwa id pasti adalah bilangan bulat, seperti:

if (isset($_GET['id'])) {
  $id = $_GET['id'];
  settype($id, 'integer');
  $result = mysql_query("SELECT * FROM mytable WHERE id = '$id'");
  # now use the result
}

Tapi tentu saja itu hanya memotong satu serangan khusus, jadi baca semua jawaban yang lain. (Dan ya saya tahu bahwa kode di atas tidak bagus, tetapi ini menunjukkan pertahanan spesifik.)


15
2018-03-08 23:14



Apa yang Anda uraikan di sini adalah dua masalah terpisah:

  1. Sanitasi / pemfilteran data masukan pengguna.
  2. Keluar dari output.

1) Masukan pengguna harus selalu dianggap buruk.

Menggunakan pernyataan siap, atau / dan menyaring dengan mysql_real_escape_string jelas merupakan suatu keharusan. PHP juga memiliki filter_input built in yang merupakan tempat yang baik untuk memulai.

2) Ini adalah topik yang besar, dan itu tergantung pada konteks dari data yang dihasilkan. Untuk HTML ada solusi seperti htmlpurifier di luar sana. sebagai aturan praktis, selalu menghindari apa pun yang Anda hasilkan.

Kedua masalah terlalu besar untuk masuk ke dalam satu posting, tetapi ada banyak posting yang masuk ke lebih detail:

Metode keluaran PHP

Output PHP lebih aman


10
2017-07-16 10:44