Pregunta

¿Existe alguna función general que funcione bien para desinfectar la entrada del usuario para inyección SQL y ataques XSS, y al mismo tiempo permita ciertos tipos de etiquetas HTML?

¿Fue útil?

Solución

Es un error común pensar que la entrada del usuario se puede filtrar. PHP incluso tiene una (ahora obsoleta) & Quot; característica & Quot ;, llamada magic-quotes, 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 incruste una cadena dentro de un código extranjero, debe escapar, de acuerdo con las reglas de ese idioma. Por ejemplo, si incrusta 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 inserta cadenas dentro del marcado HTML, debe escapar con <= > . Esto significa que cada declaración htmlspecialchars o echo debe usar print.

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

Y así sucesivamente ...

El caso solo donde necesita filtrar datos activamente, es si está aceptando entradas preformateadas. P.ej. si deja que sus usuarios publiquen marcado HTML, que planea mostrar en el sitio. Sin embargo, debe ser prudente para evitar esto a toda costa, ya que no importa qué tan bien lo filtre, siempre será un agujero de seguridad potencial.

Otros consejos

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

En cambio, no permite que se usen datos para crear su código SQL . Use declaraciones preparadas (es decir, usar parámetros en una consulta de plantilla) que usa variables enlazadas. Es la única forma de garantizar la inyección SQL.

Consulte mi sitio web http://bobby-tables.com/ para obtener más información sobre cómo prevenir la inyección de SQL.

No. No puede filtrar datos genéricamente sin ningún contexto de para qué sirve. Algunas veces querrás tomar una consulta SQL como entrada y otras veces querrás tomar HTML como entrada.

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

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

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

Mi propia clase de filtro (usa JavaScript para resaltar campos defectuosos) puede iniciarse mediante una solicitud 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 hacer su escape de consulta sql dependiendo del tipo de db que esté usando (mysql_real_escape_string () es inútil para un servidor sql, por ejemplo). Probablemente desee manejar esto automáticamente en su capa de aplicación apropiada como un ORM. Además, como se mencionó anteriormente: para enviar a html, use otras funciones dedicadas de php como htmlspecialchars;)

Para permitir realmente la entrada de HTML con clases y / o etiquetas despojadas similares, depende de uno de los paquetes de validación xss dedicados. ¡NO ESCRIBA SUS PROPIOS REGEXOS PARA ANALIZAR HTML!

No no hay.

En primer lugar, la inyección SQL es un problema de filtrado de entrada y XSS es un problema de 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 consultas SQL, vincule parámetros (como con PDO) o use una función de escape nativa del controlador para variables de consulta (como mysql_real_escape_string())
  • Usar strip_tags() para filtrar HTML no deseado
  • Escapar de todas las demás salidas con htmlspecialchars() y tenga en cuenta el segundo y tercer parámetro aquí.

Para abordar el problema de XSS, eche un vistazo a HTML Purifier . Es bastante configurable y tiene un historial decente.

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

Una mejor solución es usar declaraciones preparadas. La biblioteca PDO y la extensión mysqli son compatibles con estas.

PHP 5.2 introdujo la función filter_var .

Es compatible con una gran cantidad de filtros SANITIZE, VALIDATE.

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

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 asegurar que el id definitivamente sea un número 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 elimina un ataque específico, así que lea todas las demás respuestas.(Y sí, sé que el código anterior no es excelente, pero muestra la defensa específica).

  

Métodos para desinfectar la entrada del usuario con PHP:

  • Use versiones modernas de MySQL y PHP.

  • Establecer juego de caracteres explícitamente:

    • $mysqli->set_charset("utf8");
      manual
    • $pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF8', $user, $password);
      manual
    • $pdo->exec("set names utf8");
      manual
    • $pdo = new PDO(
      "mysql:host=$host;dbname=$db", $user, $pass, 
      array(
      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
      PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"
      )
      );
      manual
    • mysql_set_charset('utf8')
      [en desuso en PHP 5.5.0, eliminado en PHP 7.0.0].
  • Use charsets seguros:

    • Seleccione utf8, latin1, ascii .., no use charsets vulnerables big5, cp932, gb2312, gbk, sjis.
  • Usar función espacializada:

    • Declaraciones preparadas de MySQLi:
      $stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1'); 
      $param = "' OR 1=1 /*";
      $stmt->bind_param('s', $param);
      $stmt->execute();
    • PDO :: quote () - coloca citas alrededor del cadena de entrada (si es necesario) y escapa caracteres especiales dentro de la cadena de entrada, utilizando un estilo de comillas apropiado para el controlador subyacente:

      $pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF8', $user, $password);explicit set the character set
      $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);disable emulating prepared statements to prevent fallback to emulating statements that MySQL can't prepare natively (to prevent injection)
      $var = $pdo->quote("' OR 1=1 /*");not only escapes the literal, but also quotes it (in single-quote ' characters) $stmt = $pdo->query("SELECT * FROM test WHERE name = $var LIMIT 1");

    • Declaraciones preparadas de PDO : las declaraciones preparadas de MySQLi admiten más bases de datos controladores y parámetros con nombre:

      $pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF8', $user, $password);explicit set the character set
      $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);disable emulating prepared statements to prevent fallback to emulating statements that MySQL can't prepare natively (to prevent injection) $stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1'); $stmt->execute(["' OR 1=1 /*"]);

    • mysql_real_escape_string [en desuso en PHP 5.5.0, eliminado en PHP 7.0.0].
    • mysqli_real_escape_string escapa de caracteres especiales en una cadena para usar en una declaración SQL, teniendo en cuenta el conjunto de caracteres actual de la conexión. Pero se recomienda usar declaraciones preparadas porque no son simplemente cadenas escapadas, una declaración presenta un plan completo de ejecución de consultas, incluidas las tablas e índices que usaría, es una forma optimizada.
    • Use comillas simples ('') alrededor de sus variables dentro de su consulta.
  • Verifique que la variable contenga lo que espera:

    • Si espera un número entero, use:
      ctype_digit — Check for numeric character(s);
      $value = (int) $value;
      $value = intval($value);
      $var = filter_var('0755', FILTER_VALIDATE_INT, $options);
    • Para usar cadenas:
      is_string() — Find whether the type of a variable is string

      Use Función de filtro filter_var () & # 8212 ; filtra una variable con un filtro especificado:
      $email = filter_var($email, FILTER_SANITIZE_EMAIL);
      $newstr = filter_var($str, FILTER_SANITIZE_STRING);
      más filtros predefinidos
    • filter_input () & # 8212; Obtiene una variable externa específica por nombre y opcionalmente la filtra:
      $search_html = filter_input(INPUT_GET, 'search', FILTER_SANITIZE_SPECIAL_CHARS);
    • preg_match () & # 8212; Realizar una coincidencia de expresión regular;
    • Escriba su propia función de validación.

Lo que está describiendo aquí son dos problemas separados:

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

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

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

2) Este es un tema amplio 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 entrar en una sola publicación, pero hay muchas publicaciones que entran en más detalles:

Métodos de salida PHP

Salida PHP más segura

Si está utilizando PostgreSQL, la entrada de PHP se puede escapar con pg_escape_string ()

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

De la documentación ( http://php.net/manual /es/function.pg-escape-string.php ):

  

pg_escape_string () escapa a una cadena para consultar la base de datos. Devuelve una cadena escapada en el formato PostgreSQL sin comillas.

La forma más fácil de evitar errores al desinfectar la entrada y el escape de datos es usar el marco PHP como Symfony , Nette etc. o parte de ese marco (motor de plantillas, capa de base de datos, ORM).

Templating engine como Twig o Latte tiene salida escapando de forma predeterminada: no tiene que resolver manualmente si ha escapado correctamente su salida dependiendo del contexto (parte HTML o Javascript de la página web).

Framework está desinfectando automáticamente la entrada y no debe usar las variables $ _POST, $ _GET o $ _SESSION directamente, sino a través de mecanismos como enrutamiento, manejo de sesiones, etc.

Y para la capa de base de datos (modelo) hay marcos ORM como Doctrine o envoltorios alrededor de PDO como Nette Database.

Puede leer más sobre esto aquí - ¿Qué es un framework de software?

No hay una función de catchall, porque hay varias inquietudes que deben abordarse.

  1. Inyección SQL : en la actualidad, en general, todos los proyectos PHP deben usar declaraciones preparadas a través de PHP Data Objects (PDO) como práctica recomendada, evita un error de una cita extraviada, así como una solución completa contra la inyección . También es el más flexible y amp; Manera segura de acceder a su base de datos.

    Consulte (el único tutorial de PDO adecuado) para casi todo lo que necesita Necesito saber sobre la DOP. (Sincero gracias al principal colaborador de SO, @YourCommonSense, por este gran recurso sobre el tema).

  2. XSS: desinfectar los datos en el camino hacia ...

    • Purificador de HTML ha existido desde hace mucho tiempo y todavía se actualiza activamente. Puede usarlo para sanear la entrada maliciosa, mientras que todavía permite un generoso & amp; Lista blanca configurable de etiquetas. Funciona muy bien con muchos editores WYSIWYG, pero puede ser pesado para algunos casos de uso.

    • En otros casos, donde no queremos aceptar HTML / Javascript, esta función simple me parece útil (y ha pasado varias auditorías contra XSS):

      / * Prevent XSS input * / función 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: desinfecte los datos al salir ... a menos que garantice que se desinfectaron correctamente antes de agregarlos a su base de datos, deberá desinfectarlos antes de mostrarlos. Para su usuario, podemos aprovechar estas funciones útiles de PHP:

    • Cuando llame a echo o print para mostrar los valores proporcionados por el usuario, use htmlspecialchars a menos que los datos se hayan desinfectado de forma segura y se permita mostrar HTML.
    • json_encode es una caja fuerte forma de proporcionar valores proporcionados por el usuario de PHP a Javascript
  4. ¿Llama a comandos de shell externos utilizando exec () o system () funciones, o en backtick ¿operador? Si es así, además de SQL Injection & amp; XSS es posible que tenga una inquietud adicional que abordar, usuarios que ejecutan comandos maliciosos en su servidor . Debe utilizar escapeshellcmd si desea desea escapar de todo el comando O escapeshellarg para escapar de los argumentos individuales.

Nunca confíes en los datos del usuario.

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

La función trim () elimina los espacios en blanco y otros caracteres predefinidos de ambos lados de una cadena.

La función stripslashes () elimina las barras invertidas

La función htmlspecialchars () convierte algunos caracteres predefinidos a entidades HTML.

Los caracteres predefinidos son:

& (ampersand) becomes &amp;
" (double quote) becomes &quot;
' (single quote) becomes &#039;
< (less than) becomes &lt;
> (greater than) becomes &gt;

Solo quería agregar eso al tema de la salida de salida, si utiliza phD DOMDocument para hacer su salida html, se escapará automáticamente en el contexto correcto. Un atributo (valor = " ") y el texto interno de un < span > no son iguales Para estar seguro contra XSS lea esto: Hoja de trucos de prevención OWASP XSS

Nunca desinfectas la entrada.

Siempre desinfectas la salida.

Las transformaciones que aplica a los datos para que sea segura para su inclusión en una declaración SQL son completamente diferentes de las que solicita para su inclusión en HTML son completamente diferentes de aquellas que solicita para su inclusión en Javascript son completamente diferentes de aquellas que solicita para su inclusión en LDIF son completamente diferentes de aquellos que aplica para la inclusión en CSS son completamente diferentes de aquellos que aplica para inclusión en un correo electrónico ....

Por todos los medios validar entrada - decida si debe aceptarlo para su posterior procesamiento o comunique al usuario que es inaceptable. Pero no aplique ningún cambio a la representación de los datos hasta que esté a punto de abandonar PHP land.

Hace mucho tiempo, alguien intentó inventar un mecanismo de talla única para escapar de datos y terminamos con " magic_quotes " que no escapó correctamente a los datos de todos los destinos de salida y dio lugar a una instalación diferente que requirió un código diferente para funcionar.

Existe la extensión del filtro ( howto-link , manual ), que funciona bastante bien con todas las variables GPC. Sin embargo, no es una cosa mágica, aún tendrás que usarla.

Puedo ver que el filtro de php desinfecta los caracteres especiales especiales son útiles.

como:

    $a=fliter_var(

Puedo ver que el filtro de php desinfecta los caracteres especiales especiales son útiles.

como:

    void php_filter_special_chars(PHP_INPUT_FILTER_PARAM_DECL)
{
unsigned char enc[256] = {0};

php_filter_strip(value, flags);

/* encodes ' " < > & \0 to numerical entities */
enc['\''] = enc['"'] = enc['<'] = enc['>'] = enc['&'] = enc[0] = 1;

/* if strip low is not set, then we encode them as &#xx; */
memset(enc, 1, 32);

if (flags & FILTER_FLAG_ENCODE_HIGH) {
    memset(enc + 127, 1, sizeof(enc) - 127);
}

php_filter_encode_html(value, enc);
}

Sin embargo, por stock, creo que podría ser mejor, porque al mirar el código c, solo filtra la " '\ < > &erio; y \ 0, así que puedo ver que esta es una buena manera de desinfectar. Sin embargo, cambiar el código fuente para incluir estos otros caracteres como / {} []. ; `fortalecería esta función en la línea de codificación (enc ['']):

<*>POST['a'],FILTER_SANITIZE_SPECIAL_CHARS);

Sin embargo, por stock, creo que podría ser mejor, porque al mirar el código c, solo filtra la " '\ < > &erio; y \ 0, así que puedo ver que esta es una buena manera de desinfectar. Sin embargo, cambiar el código fuente para incluir estos otros caracteres como / {} []. ; `fortalecería esta función en la línea de codificación (enc ['']):

<*>

El mejor método BÁSICO para sanear la entrada del usuario con PHP:


    function sanitizeString($var)
    {
        $var = stripslashes($var);
        $var = strip_tags($var);
        $var = htmlentities($var);
        return $var;
    }

    function sanitizeMySQL($connection, $var)
    {
        $var = $connection->real_escape_string($var);
        $var = sanitizeString($var);
        return $var;
    }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top