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

1124

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

казарка
источник
42
В настоящее время, чтобы избежать внедрения SQL, используйте PDO или MySQLi.
Франциско Presencia
76
Использование PDO или MySQLi недостаточно. Если вы строите свои операторы SQL с недоверенными данными, например select * from users where name='$name', тогда не имеет значения, используете ли вы PDO, MySQLi или MySQL. Вы все еще в опасности. Вы должны использовать параметризованные запросы или, если необходимо, использовать механизмы экранирования ваших данных, но это гораздо менее предпочтительно.
Энди Лестер
26
@AndyLester Вы подразумеваете, что кто-то использует PDO без подготовленных заявлений? :)
64
Я говорю, что «Использовать PDO или MySQLi» недостаточно для того, чтобы объяснить новичкам, как их безопасно использовать. Мы с вами знаем, что подготовленные заявления имеют значение, но я не предполагаю, что каждый, кто читает этот вопрос, будет знать это. Вот почему я добавил явные инструкции.
Энди Лестер
30
Комментарий Энди полностью действителен. Я недавно преобразовал свой веб-сайт mysql в PDO, думая, что теперь я каким-то образом защищен от инъекционных атак. Только во время процесса я понял, что некоторые из моих SQL-операторов все еще строились с использованием пользовательского ввода. Затем я исправил это, используя подготовленные заявления. Для новичка не совсем ясно, что есть различие, так как многие эксперты отказываются от комментариев по поводу использования PDO, но не указывают на необходимость подготовленных заявлений. Предполагается, что это очевидно. Но не новичку.
GhostRider

Ответы:

1184

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

То, что вы должны сделать, чтобы избежать проблем, довольно просто: всякий раз, когда вы встраиваете строку в чужой код, вы должны избегать ее в соответствии с правилами этого языка. Например, если вы встраиваете строку в некоторый SQL, ориентированный на MySQL, вы должны экранировать строку с помощью функции MySQL для этой цели ( mysqli_real_escape_string). (Или, в случае баз данных, использование подготовленных заявлений является лучшим подходом, когда это возможно.)

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

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

И так далее ...

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

troelskn
источник
245
«Это означает, что каждый оператор echo или print должен использовать htmlspecialchars» - конечно, вы имеете в виду «каждый ... оператор, выводящий пользовательский ввод»; htmlspecialchars () - ifying "echo 'Hello, world!';" был бы сумасшедшим;)
Бобби Джек
10
В одном случае я думаю, что фильтрация - правильное решение: UTF-8. Вы не хотите, чтобы в вашем приложении были недопустимые последовательности UTF-8 (вы можете получить различное исправление ошибок в зависимости от пути кода), и UTF-8 можно легко фильтровать (или отклонять).
Корнель
6
@jbyrd - нет, LIKE использует специализированный язык регулярных выражений. Вам нужно будет дважды экранировать входную строку - один раз для регулярного выражения и один раз для кодировки строки mysql. Это код внутри кода внутри кода.
troelskn
6
В этот момент mysql_real_escape_stringне рекомендуется. В настоящее время считается хорошей практикой использовать подготовленные операторы для предотвращения внедрения SQL. Так что переключайтесь на MySQLi или PDO.
Марсель Корпель
4
Потому что вы ограничиваете поверхность атаки. Если вы выполняете санитарную обработку на раннем этапе (при вводе), вы должны быть уверены, что в приложении нет других дыр, через которые могут проникнуть плохие данные. Принимая во внимание, что если вы сделаете это поздно, ваша выходная функция не должна «доверять» полученным безопасным данным - она ​​просто предполагает, что все небезопасно.
troelskn
217

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

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

Пожалуйста, посетите мой веб-сайт http://bobby-tables.com/ для получения дополнительной информации о предотвращении внедрения SQL.

Энди Лестер
источник
18
Или посетите официальную документацию и изучите PDO и подготовленные заявления. Крошечная кривая обучения, но если вы хорошо знаете SQL, у вас не возникнет проблем с адаптацией.
кодер
2
Для конкретного случая SQL-инъекции это правильный ответ!
Скотт Аркишевский
4
Обратите внимание, что подготовленные операторы не добавляют никакой безопасности, параметризованные запросы делают. Их просто очень легко использовать вместе в PHP.
Основное
Это не единственный гарантированный способ. Hex предотвратит также ввод и unhex в запросе. Также шестнадцатеричные атаки невозможны, если вы используете правильное гексагональное решение
Рамон Баккер
Что делать, если вы вводите что-то специализированное, например, адреса электронной почты или имена пользователей?
Авраам Брукс
79

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

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

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

Даниэль Папасян
источник
52

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

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

/**
 *  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');
 *  $sanitize = array('alias');
 *
 *  $validator = new FormValidator($validations, $required, $sanitize);
 *                  
 *  if($validator->validate($_POST))
 *  {
 *      $_POST = $validator->sanitize($_POST);
 *      // now do your saving, $_POST has been sanitized.
 *      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 sanitize just one element:
 * $sanitized = new FormValidator()->sanitize('<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->sanitations = $sanitations;
        $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);
    }


    /**
     *
     * Sanitizes an array of items according to the $this->sanitations
     * sanitations will be standard of type string, but can also be specified.
     * For ease of use, this syntax is accepted:
     * $sanitations = array('fieldname', 'otherfieldname'=>'float');
     */
    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);
    }


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

    /**
     *
     * Sanitize a single var according to $type.
     * Allows for static calling to allow simple sanitization
     */
    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);
    }

    /** 
     *
     * 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;
    }       



}

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

Для того, чтобы действительно разрешить ввод HTML с разделенными классами и / или тегами, используйте один из выделенных пакетов проверки xss. НЕ ПИШИТЕ СВОИ СОБСТВЕННЫЕ РЕКЕКСЫ ДЛЯ РАЗДЕЛЕНИЯ HTML

SchizoDuckie
источник
18
Похоже, что это может быть удобный скрипт для проверки входных данных, но он совершенно не имеет отношения к вопросу.
rjmunro
43

Нет, нет.

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

Основные правила большого пальца

  • Для SQL-запроса связывайте параметры (как в случае с PDO) или используйте встроенную функцию драйвера для переменных запроса (например, mysql_real_escape_string())
  • Используйте strip_tags()для фильтрации нежелательного HTML
  • Избегайте всех других выходных данных htmlspecialchars()и помните о 2-м и 3-м параметрах здесь.
Питер Бейли
источник
1
Таким образом, вы используете strip_tags () или htmlspecialchars () только тогда, когда знаете, что вход содержит HTML-код, от которого вы хотите избавиться или, соответственно, избегаете его - вы не используете его для каких-либо целей безопасности, верно? Кроме того, когда вы делаете привязку, что это делает для таких вещей, как Bobby Tables? "Robert '); DROP TABLE студентов; -" Это просто избежать кавычек?
Роберт Марк Брэм
2
Если у вас есть пользовательские данные, которые войдут в базу данных, а затем будут отображаться на веб-страницах, разве они обычно не читаются намного больше, чем записаны? Для меня имеет смысл фильтровать его один раз (как входные данные) перед его сохранением, вместо того, чтобы фильтровать его каждый раз, когда вы отображаете его. Я что-то упустил или группа людей проголосовала за ненужные потери производительности в этом и принятом ответе?
jbo5112
2
Лучший ответ для меня. Это коротко и хорошо отвечает на вопрос, если вы спросите меня. Можно ли как-то атаковать PHP через $ _POST или $ _GET с какой-то инъекцией или это невозможно?
Джо Смо
о да, массивы $ post и $ get принимают все символы, но некоторые из этих символов могут быть использованы против вас, если этот символ можно перечислить на опубликованной странице php. поэтому, если вы не избежите инкапсуляции символов (таких как "," и `), это может открыть вектор атаки. Символ` часто пропускается и может использоваться для формирования хаков выполнения командной строки. Санитария предотвратит взлом пользовательского ввода, но не поможет вам с
взломами
22

Чтобы решить проблему XSS, взгляните на HTML Purifier . Это довольно настраиваемый и имеет достойный послужной список.

Что касается атак с использованием SQL-инъекций, убедитесь, что вы проверили пользовательский ввод, а затем запустили его через mysql_real_escape_string (). Однако эта функция не победит все атаки с использованием инъекций, поэтому важно проверить данные перед тем, как вывести их в строку запроса.

Лучшее решение - использовать подготовленные заявления. Библиотека PDO и расширение mysqli поддерживают их.

jasonbar
источник
не существует «лучшего способа» сделать что-то вроде очистки входных данных. Используйте некоторую библиотеку, html очиститель хорош. Эти библиотеки были загнаны много раз. Так что это намного более пуленепробиваемо, чем что-либо, что вы можете придумать сами
paan
Смотрите также bioinformatics.org/phplabware/internal_utilities/htmLawed . Насколько
Стив Клей
Проблема с WordPress заключается в том, что это не обязательно атака php-sql, которая приводит к нарушениям базы данных. Мисс запрограммированных плагинов, которые хранят данные, которые XML-запрос раскрывает секреты, является более проблематичным.
drtechno
17

Одна хитрость, которая может помочь в определенных обстоятельствах, когда у вас есть страница, подобная которой, /mypage?id=53и вы используете идентификатор в предложении WHERE, это убедиться, что идентификатор определенно является целым числом, например так:

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

Но, конечно, это исключает только одну конкретную атаку, поэтому прочитайте все остальные ответы. (И да, я знаю, что приведенный выше код не очень хорош, но он показывает конкретную защиту.)

Хэмиш Даунер
источник
11
Вместо этого я использую $ id = intval ($ id) :)
Дык Тран
Приведение целого числа - хороший способ убедиться, что вставлены только числовые данные.
тест
1
$id = (int)$_GET['id']и $que = sprintf('SELECT ... WHERE id="%d"', $id)тоже хорошо
владкрас
16

Методы для очистки пользовательского ввода с помощью PHP:

  • Используйте современные версии MySQL и PHP.

  • Установите кодировку явно:

    • $ Mysqli-> set_charset ( "utf8");
      руководство
    • $ pdo = новый PDO ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ user, $ password);
      руководство
    • $ pdo-> exec ("set names utf8");
      руководство
    • $ pdo = новый PDO (
      "mysql: host = $ host; dbname = $ db", $ user, $ pass, 
      массив (
      PDO :: ATTR_ERRMODE => PDO :: ERRMODE_EXCEPTION,
      PDO :: MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"
      )
      );
      руководство
    • mysql_set_charset ( 'utf8')
      [устарело в PHP 5.5.0, удалено в PHP 7.0.0].
  • Используйте безопасные кодировки:

    • Выберите utf8, latin1, ascii .., не используйте уязвимые наборы символов big5, cp932, gb2312, gbk, sjis.
  • Используйте пространственную функцию:

    • MySQLi подготовил заявления:
      $ stmt = $ mysqli-> prepare ('SELECT * FROM test WHERE name =? LIMIT 1'); 
      $ param = "'OR 1 = 1 / *";
      $ stmt-> bind_param ('s', $ param);
      $ stmt-> Execute ();
    • PDO :: quote () - помещает кавычки вокруг входной строки (если требуется) и экранирует специальные символы во входной строке, используя стиль цитирования, соответствующий базовому драйверу:

      $ pdo = новый PDO ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ user, $ password); явный набор символов
      $ pdo-> setAttribute (PDO :: ATTR_EMULATE_PREPARES, false); отключить эмуляцию подготовленных операторов, чтобы предотвратить переход к эмуляции операторов, которые MySQL не может подготовить изначально (для предотвращения внедрения)
      $ var = $ pdo-> quote ("'OR 1 = 1 / *"); не только экранирует литерал, но и заключает его в кавычки (в одинарных кавычках) $ stmt = $ pdo-> query ("SELECT * FROM test WHERE name = $ var LIMIT 1");

    • Подготовленные операторы PDO : против подготовленных MySQLi операторов поддерживается больше драйверов базы данных и именованных параметров:

      $ pdo = новый PDO ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ user, $ password); явный набор символов
      $ pdo-> setAttribute (PDO :: ATTR_EMULATE_PREPARES, false); отключите эмуляцию подготовленных операторов, чтобы предотвратить переход к эмуляции операторов, которые MySQL не может подготовить изначально (для предотвращения внедрения) $ stmt = $ pdo-> prepare ('SELECT * FROM test WHERE name =? LIMIT 1'); $ stmt-> execute (["'OR 1 = 1 / *"]);

    • mysql_real_escape_string [устарело в PHP 5.5.0, удалено в PHP 7.0.0].
    • mysqli_real_escape_string Экранирует специальные символы в строке для использования в операторе SQL с учетом текущего набора символов соединения. Но рекомендуется использовать подготовленные операторы, поскольку они не являются просто экранированными строками, оператор предлагает полный план выполнения запроса, включая оптимизированные таблицы и индексы, которые он будет использовать.
    • Используйте одинарные кавычки ('') вокруг переменных внутри вашего запроса.
  • Проверьте, что переменная содержит то, что вы ожидаете:

    • Если вы ожидаете целое число, используйте:
      ctype_digit - проверять числовые символы; 
      $ value = (int) $ value;
      $ value = intval ($ value);
      $ var = filter_var ('0755', FILTER_VALIDATE_INT, $ options);
    • Для строк используйте:
      is_string () - определяет, является ли тип переменной строковым

      Использовать функцию фильтра filter_var () - фильтрует переменную с указанным фильтром:
      $ email = filter_var ($ email, FILTER_SANITIZE_EMAIL); 
      $ newstr = filter_var ($ str, FILTER_SANITIZE_STRING);
      более предопределенные фильтры
    • filter_input () - получает определенную внешнюю переменную по имени и дополнительно фильтрует ее:
      $ search_html = filter_input (INPUT_GET, 'search', FILTER_SANITIZE_SPECIAL_CHARS);
    • preg_match () - выполняет сопоставление с регулярным выражением;
    • Напишите свою собственную функцию проверки.
Марк Мартин
источник
11

То, что вы описываете здесь, это две отдельные проблемы:

  1. Санитарная обработка / фильтрация пользовательских данных.
  2. Выходящий выход.

1) Пользовательский ввод всегда следует считать плохим.

Использование подготовленных операторов или / и фильтрация с помощью mysql_real_escape_string, безусловно, необходимо. PHP также имеет встроенный фильтр filter_input, с которого можно начать.

2) Это большая тема, и она зависит от контекста выводимых данных. Для HTML существуют такие решения, как htmlpurifier. как правило, всегда избегайте всего, что вы выводите.

Обе проблемы слишком велики, чтобы их можно было рассмотреть в одном посте, но есть много постов, которые рассматриваются более подробно:

Методы вывода PHP

Более безопасный вывод PHP

Эндрю
источник
9

Если вы используете PostgreSQL, ввод из PHP может быть экранирован с помощью pg_escape_string ()

 $username = pg_escape_string($_POST['username']);

Из документации ( http://php.net/manual/es/function.pg-escape-string.php ):

pg_escape_string () экранирует строку для запроса к базе данных. Возвращает экранированную строку в формате PostgreSQL без кавычек.

Алехандро Силва
источник
1
pg_escape_literal () - рекомендуемая функция для использования с PostgreSQL.
загадочно ツ
8

Там нет функции catchall, потому что есть несколько проблем, которые необходимо решить.

  1. SQL-инъекция. Сегодня, как правило, каждый PHP-проект должен использовать подготовленные операторы через PHP Data Objects (PDO) в качестве лучшей практики, предотвращающей ошибку из-за случайных цитат, а также полнофункциональное решение для внедрения . Это также самый гибкий и безопасный способ доступа к вашей базе данных.

    Прочтите (Единственное правильное) руководство по PDO, чтобы узнать почти все, что вам нужно знать о PDO. (Искренне благодарим ведущего SO SO, @YourCommonSense, за этот замечательный ресурс по этой теме.)

  2. XSS - очистить данные на пути в ...

    • HTML Purifier существует уже давно и до сих пор активно обновляется. Вы можете использовать его для дезинфекции вредоносного ввода, в то же время позволяя использовать щедрый и настраиваемый белый список тегов. Прекрасно работает со многими редакторами WYSIWYG, но может быть тяжелым для некоторых случаев использования.

    • В других случаях, когда мы вообще не хотим принимать HTML / Javascript, я обнаружил, что эта простая функция полезна (и прошла несколько проверок XSS):

      /* Prevent XSS input */ function sanitizeXSS () { $_GET = filter_input_array(INPUT_GET, FILTER_SANITIZE_STRING); $_POST = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING); $_REQUEST = (array)$_POST + (array)$_GET + (array)$_REQUEST; }

  3. XSS - Санитарная обработка данных при выходе ... если вы не гарантируете, что данные были должным образом очищены, прежде чем добавлять их в базу данных, вам нужно будет очистить их перед отображением для вашего пользователя, мы можем использовать эти полезные функции PHP:

    • При вызове echoили printдля отображения предоставленных пользователем значений используйте, htmlspecialcharsесли данные не были должным образом очищены безопасным и не разрешено отображать HTML.
    • json_encode это безопасный способ предоставления пользовательских значений из PHP в Javascript
  4. Вы вызываете команды внешней оболочки, используя exec()или system()функции, или backtickоператору? Если это так, в дополнение к SQL-инъекции и XSS у вас может возникнуть дополнительная проблема, пользователей, запускающих вредоносные команды на вашем сервере . Вам нужно использовать, escapeshellcmdесли вы хотите экранировать всю команду ИЛИ, escapeshellargчтобы избежать отдельных аргументов.

webaholik
источник
вместо этого можно использовать mb_encode_numericentity? Так как это все кодирует?
drtechno
@drtechno - mb_encode_numericentityобсуждается в htmlspecialcharsссылке на # 3 XSS
webaholik
5

Самый простой способ избежать ошибок при очистке входных данных и экранировании данных - использовать PHP-фреймворк, такой как Symfony , Nette и т. Д., Или его часть (шаблонизатор, слой базы данных, ORM).

Шаблонный двигатель, как Twig или Latte, по умолчанию экранирование выходных данных не требуется - вам не нужно решать вручную, правильно ли вы экранировали выходные данные в зависимости от контекста (HTML или Javascript часть веб-страницы).

Framework автоматически очищает входные данные, и вы не должны использовать переменные $ _POST, $ _GET или $ _SESSION напрямую, а через такой механизм, как маршрутизация, обработка сеанса и т. Д.

А для слоя базы данных (модели) существуют платформы ORM, такие как Doctrine, или оболочки для PDO, такие как Nette Database.

Вы можете прочитать больше об этом здесь - Что такое программный каркас?

Ондржей Шотек
источник
3

Просто хочу добавить, что по теме экранирования вывода, если вы используете php DOMDocument для вывода html-кода, он автоматически выйдет в нужном контексте. Атрибут (value = "") и внутренний текст <span> не равны. Чтобы быть в безопасности от XSS, прочитайте это: Шпаргалка по профилактике OWASP XSS

user138720
источник
2

Вы никогда не дезинфицируете ввод.

Вы всегда дезинфицируете продукцию.

Преобразования, которые вы применяете к данным для обеспечения их безопасности для включения в оператор SQL, полностью отличаются от тех, которые вы применяете для включения в HTML, полностью отличаются от тех, которые вы применяете для включения в Javascript, и полностью отличаются от тех, которые вы применяете для включения в LDIF. полностью отличается от тех, которые вы применяете для включения в CSS, полностью отличается от тех, которые вы применяете для включения в электронную почту ....

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

Давным-давно кто-то пытался изобрести универсальный механизм для экранирования данных, и мы в итоге получили « magic_quotes », который неправильно экранировал данные для всех выходных целей и привел к разной установке, требующей разного кода для работы.

symcbean
источник
Одна из проблем заключается в том, что это не всегда атака на базу данных, и весь ввод пользователя должен быть защищен от системы. не только один тип языка. Так что на ваших сайтах, когда вы перечисляете свои данные $ _POST, даже с использованием привязки, они могут выйти из-под контроля, чтобы выполнить shell или даже другой php-код.
Drtechno
«это не всегда атака базы данных»: «Преобразования, которые вы применяете к данным, чтобы сделать их безопасными для включения в оператор SQL, полностью отличаются от этих ....»
symcbean
«весь ввод пользователя должен быть защищен от системы»: система не должна быть защищена от ввода пользователя.
Symcbean
ну, у меня закончились слова, но да, входные данные не должны влиять на работу системы. чтобы уточнить это ...
drtechno
И вход, и выход должны быть санированы.
Тайни
1

Никогда не доверяйте пользовательским данным.

function clean_input($data) {
  $data = trim($data);
  $data = stripslashes($data);
  $data = htmlspecialchars($data);
  return $data;
}

В trim()функции удаляет пробелы и другие предопределенные символы с обеих сторон строки.

stripslashes()Функция удаляет обратные слэши

htmlspecialchars()Функция преобразует некоторые предопределенные символы в HTML сущности.

Предопределенные символы:

& (ampersand) becomes &amp;
" (double quote) becomes &quot;
' (single quote) becomes &#039;
< (less than) becomes &lt;
> (greater than) becomes &gt;
Эрик Тиарт
источник
1
От чего это защитит? Это для XSS? Почему это называется clean_inputтогда? Почему вы хотите снять косые черты?
Дхарман
5
ВНИМАНИЕ: Это не делает магически безопасными данные пользователя. Эта функция излишне повредит ваши данные, не защищая от чего-либо. НЕ ИСПОЛЬЗУЙТЕ ЕГО!
Дхарман
Ваше утверждение неверно.
Эрик Тиарт
0

Существует расширение фильтра ( howto-link , manual ), которое работает очень хорошо со всеми переменными GPC. Это не волшебство делай все, хотя, тебе все равно придется его использовать.

Пока
источник