Pregunta ¿Cuál es el mejor método para desinfectar la entrada del usuario con PHP?


¿Hay alguna función de captura en alguna parte que funcione bien para desinfectar la entrada del usuario para la inyección de SQL y los ataques XSS, al tiempo que permite ciertos tipos de etiquetas html?


975
2017-09-24 20:20


origen


Respuestas:


Es un concepto erróneo común que la entrada del usuario se puede filtrar. PHP incluso tiene una "característica" (ahora en desuso), llamada citas mágicas, que se basa en esta idea. No tiene sentido. Olvídate de filtrar (o limpiar, o como lo llamen las personas).

Lo que debe hacer para evitar problemas es bastante simple: cada vez que inserta una cadena dentro de un código extraño, debe escapar de ella, de acuerdo con las reglas de ese idioma. Por ejemplo, si inserta una cadena en algún SQL dirigido a MySql, debe escapar de la cadena con la función de MySql para este propósito (mysqli_real_escape_string) (O, en el caso de las bases de datos, usar declaraciones preparadas es un mejor enfoque, cuando sea posible)

Otro ejemplo es HTML: si incrusta cadenas dentro del marcado HTML, debe escapar de él con htmlspecialchars. Esto significa que cada uno echo o print declaración debe usar htmlspecialchars.

Un tercer ejemplo podría ser comandos de shell: si va a incrustar cadenas (como argumentos) en comandos externos y llamarlos con exec, entonces debes usar escapeshellcmd y escapeshellarg.

Y así sucesivamente y así sucesivamente ...

los solamente En caso de que necesite filtrar activamente los datos, es si está aceptando una entrada preformateada. P.ej. si permite que sus usuarios publiquen marcado HTML, que planea mostrar en el sitio. Sin embargo, debe ser prudente evitar esto a toda costa, ya que no importa qué tan bien lo filtre, siempre será un agujero de seguridad potencial.


1078
2017-09-24 22:30



No intente evitar la inyección de SQL desinfectando los datos de entrada.

En lugar, no permita que se usen datos para crear su código SQL. Utilice declaraciones preparadas (es decir, utilizando parámetros en una consulta de plantilla) que utilice variables vinculadas. Es la única forma de garantizarse contra la inyección SQL.

Por favor mira mi sitio web http://bobby-tables.com/ para más información sobre la prevención de inyección SQL.


184
2017-10-09 06:28



No. No puede filtrar los datos de forma genérica sin un contexto de para qué sirve. A veces querrá tomar una consulta SQL como entrada y, a veces, querrá tomar HTML como entrada.

Debe filtrar la entrada en una lista blanca: asegúrese de que los datos coincidan con algunas especificaciones de lo que espera. Luego debe escapar antes de usarlo, según el contexto en el que lo esté utilizando.

El proceso de escanear datos para SQL, para evitar la inyección de SQL, es muy diferente del proceso de escaneo de datos para (X) HTML, para evitar XSS.


72
2017-09-24 20:24



PHP tiene ahora las nuevas y agradables funciones filter_input, que por ejemplo te liberan de encontrar 'la máxima expresión regular del correo electrónico' ahora que hay un tipo incorporado FILTER_VALIDATE_EMAIL

Mi propia clase de filtro (usa javascript para resaltar campos defectuosos) puede iniciarse mediante una solicitud de AJAX o una publicación de formulario normal. (ver el ejemplo a continuación)     

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



}

Por supuesto, tenga en cuenta que también necesita escaparse de su consulta sql, dependiendo del tipo de db que esté utilizando (mysql_real_escape_string () es inútil para un servidor sql, por ejemplo). Es probable que desee manejar esto automáticamente en la capa de aplicación adecuada, como un ORM. Además, como se mencionó anteriormente: para la salida a html use las otras funciones dedicadas de php como htmlspecialchars;)

Permitir realmente la entrada de HTML con clases y / o etiquetas similares depuradas depende de uno de los paquetes de validación de xss dedicados. ¡NO ESCRIBAS TUS PROPIOS REGEXES PARA PARSE HTML!


43
2017-09-24 23:12



No no hay.

En primer lugar, la inyección de SQL es un problema de filtrado de entrada, y XSS es un escape de salida, por lo que ni siquiera ejecutaría estas dos operaciones al mismo tiempo en el ciclo de vida del código.

Reglas básicas

  • Para la consulta SQL, enlace los parámetros (como con PDO) o use una función de escape nativa del controlador para variables de consulta (como mysql_real_escape_string())
  • Utilizar strip_tags() para filtrar el HTML no deseado
  • Escape de todos los demás resultados con htmlspecialchars() y tenga en cuenta los parámetros 2º y 3º aquí.

39
2017-09-24 20:30



Para abordar el problema de XSS, eche un vistazo a Purificador de HTML. Es bastante configurable y tiene una buena trayectoria.

En cuanto a los ataques de inyección SQL, asegúrese de verificar la entrada del usuario y luego ejecutarlo a través mysql_real_escape_string (). Sin embargo, la función no derrotará a todos los ataques de inyección, por lo que es importante que verifique los datos antes de volcarlos en su cadena de consulta.

Una mejor solución es usar declaraciones preparadas. los Biblioteca PDO y la extensión mysqli los admite.


20
2017-09-24 20:29



PHP 5.2 introdujo el filter_var función.

Admite una gran cantidad de filtros SANITIZE, VALIDATE.

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


18
2017-10-15 08:40



Un truco que puede ayudar en la circunstancia específica en la que tienes una página como /mypage?id=53 y usas el id. en una cláusula WHERE para asegurarte de que el ID sea definitivamente un entero, así:

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

Pero, por supuesto, eso solo corta un ataque específico, así que lee todas las otras respuestas. (Y sí, sé que el código anterior no es excelente, pero muestra la defensa específica).


15
2018-03-08 23:14



Lo que estás describiendo aquí es dos cuestiones separadas:

  1. Sanitización / filtrado de los datos de entrada del usuario.
  2. Salida de escape.

1) Siempre se debe suponer que la entrada del usuario es mala.

Usar sentencias preparadas, o / y filtrar con mysql_real_escape_string es definitivamente una necesidad. PHP también tiene filter_input integrado, que es un buen lugar para comenzar.

2) Este es un tema extenso y depende del contexto de los datos que se envían. Para HTML hay soluciones como htmlpurifier por ahí. como regla general, siempre escapa de todo lo que generes.

Ambas cuestiones son demasiado grandes para incluirlas en una sola publicación, pero hay muchas publicaciones que se detallan a continuación:

Métodos PHP de salida

Salida de PHP más segura


10
2017-07-16 10:44