Web

Как я могу обезопасить вводимые пользователем данные с помощью PHP?

Есть ли какая-нибудь универсальная функция, которая хорошо работает для санации пользовательского ввода от SQL-инъекций и XSS-атак, но при этом позволяет использовать определенные типы HTML-тегов?

 

Ответ 1

Распространенное заблуждение, что вводимые пользователем данные можно фильтровать. В PHP даже есть (теперь устаревшая) «функция», называемая magic-quotes, которая основывается на этой идее. Забудьте о фильтрации (или очистке, или как еще это называют).

Что вам следует делать, чтобы избежать проблем. Решение довольно простое: всякий раз, когда вы встраиваете часть данных во внешний код, вы должны обрабатывать его в соответствии с правилами форматирования этого кода. Но вы должны понимать, что такие правила могут быть слишком сложными, чтобы пытаться соблюдать их все вручную. Например, в SQL правила для строк, чисел и идентификаторов разные. Для вашего удобства в большинстве случаев существует специальный инструмент для такого встраивания. Например, когда вам нужно использовать переменную PHP в запросе SQL, вы должны использовать подготовленный оператор, который позаботится о правильном форматировании/обработке.

Другой пример - HTML: если вы встраиваете строки в разметку HTML, вы должны экранировать их с помощью htmlspecialchars. Это означает, что каждый оператор echo или print должен использовать htmlspecialchars.

Третьим примером могут быть команды оболочки: если вы собираетесь вставлять строки (например, аргументы) во внешние команды и вызывать их с помощью exec, вы должны использовать escapeshellcmd и escapeshellarg.

Также очень показательный пример - JSON. Правила настолько многочисленны и сложны, что вы никогда не сможете выполнить их все вручную. Вот почему никогда не следует создавать строку JSON вручную, а всегда использовать специальную функцию, json_encode(), которая будет правильно форматировать каждый тип данных.

Есть только один случай, когда вам нужно активно фильтровать данные, если вы будете принимать преформатированный ввод. Например, если вы разрешите своим пользователям публиковать разметку HTML, которую вы планируете отображать на сайте. Однако вам следует проявить осторожность, чтобы избежать этого любой ценой, поскольку, независимо от того, насколько хорошо вы его фильтруете, это всегда будет потенциальной угрозой безопасности.

 

Ответ 2

Не пытайтесь предотвратить внедрение SQL-кода путем санации входных данных.

Вместо этого не позволяйте использовать данные при создании кода SQL. Используйте подготовленные операторы (т. е. используя параметры в шаблоне запроса), в котором используются связанные переменные. Это единственный способ гарантировать защиту от SQL-инъекций.

 

Ответ 3

Вы не можете фильтровать данные в целом без какого-либо контекста, для чего они нужны. Иногда вы можете использовать SQL-запрос в качестве входных данных, а иногда как HTML-код в таком же случае.

Вам необходимо отфильтровать входные данные в «белом» списке - убедитесь, что данные соответствуют некоторой спецификации того, что вы ожидаете. Затем вам нужно проверить его, прежде чем использовать, в зависимости от контекста, в котором он используется.

Процесс экранирования данных для SQL - для предотвращения внедрения SQL - сильно отличается от процесса экранирования данных для (X)HTML, чтобы предотвратить XSS.

 

Ответ 4

PHP теперь имеет новые функции filter_input, которые, например, освобождают вас от поиска регулярного выражения для валидации электронной почты, потому что появился встроенный тип FILTER_VALIDATE_EMAIL.

Мой собственный класс фильтра (использует JavaScript для выделения ошибочных полей) может быть инициирован либо запросом ajax, либо публикацией обычной формы. Валидатор проверяет поля с помощью регулярных выражений и может их обезопасить. Он использует встроенные функции PHP filter_var и дополнительные регулярные выражения:

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->sanitations = $sanitations;

        $this->mandatories = $mandatories;

        $this->errors      = array();

        $this->corrects    = array();

    }

    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);

    }

 

    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);

    }

    public function sanitize($items) {

        foreach($items as $key=>$val) {

            if(array_search($key, $this->sanitations) === false && !array_key_exists($key, $this->sanitations)) continue;

            $items[$key] = self::sanitizeItem($val, $this->validations[$key]);

        }

        return($items);

    }

   private function addError($field, $type='string') {

        $this->errors[$field] = $type;

    }

    public static function sanitizeItem($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);

    }

    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;

    }       

}

 

Конечно, имейте в виду, что вам нужно также выполнить экранирование вашего sql-запроса, в зависимости от того, какой тип базы данных вы используете (например, mysql_real_escape_string() бесполезен для sql-сервера). Вероятно, вы захотите обработать это автоматически на соответствующем уровне приложения, например, в ORM. Кроме того, как упоминалось выше: для вывода в html используйте другие специальные функции php, такие как htmlspecialchars.

 

Ответ 5

Прежде всего, SQL-инъекция - это проблема фильтрации ввода, а XSS - это вывод. Поэтому вы даже не сможете выполнять эти две операции одновременно в жизненном цикле кода.

Основные практические правила:

  • Для SQL запроса привяжите параметры (как в случае с PDO) или используйте встроенную в драйвер функцию экранирования для переменных запроса (например, mysql_real_escape_string());

  • Используйте strip_tags() для фильтрации нежелательного HTML;

  • Отключите все остальные выходные данные, используя htmlspecialchars(), и помните о 2-м и 3-м параметрах.

Схожие статьи

Оптимизация HTML, CSS и JavaScript: как настроить и ускорить
Web

Оптимизация HTML, CSS и JavaScript: как настроить и ускорить

Web

Могу ли я расширить некоторый класс, используя более одного базового класса в PHP

Web

Как извлечь данные из файла csv в PHP

Web

Как я могу хранить и извлекать изображения из базы данных MySQL с помощью PHP

×