php - usuarios - validar usuario y contraseña en html




¿Cuál es el mejor método para sanear la entrada del usuario con PHP? (12)

¿Existe alguna función general que funcione bien para sanear las entradas de los usuarios para la inyección SQL y los ataques XSS, a la vez que permite ciertos tipos de etiquetas html?


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

  • Usa versiones modernas de MySQL y PHP.

  • Establecer conjunto 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')
      [obsoleto en PHP 5.5.0, eliminado en PHP 7.0.0].
  • Use juegos de caracteres seguros:

    • Seleccione utf8, latin1, ascii .., no use conjuntos de caracteres vulnerables big5, cp932, gb2312, gbk, sjis.
  • Utilice la función espacializada:

    • MySQLi preparó declaraciones:
      $stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1'); 
      $param = "' OR 1=1 /*";
      $stmt->bind_param('s', $param);
      $stmt->execute();
    • PDO::quote() : coloca comillas alrededor de la cadena de entrada (si es necesario) y escapa de los caracteres especiales dentro de la cadena de entrada, utilizando un estilo de cita 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 DOP : vs Las declaraciones preparadas de MySQLi admiten más controladores de base de datos 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 [obsoleto en PHP 5.5.0, eliminado en PHP 7.0.0].
    • mysqli_real_escape_string Escapa 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 de escape, una declaración presenta un plan de ejecución de consulta completo, que incluye las tablas e índices que usaría, es una forma optimizada.
    • Use comillas simples ('') alrededor de sus variables dentro de su consulta.
  • Compruebe que la variable contiene lo que espera:

    • Si está esperando 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 cuerdas usar:
      is_string() — Find whether the type of a variable is string

      Usar función de filtro filter_var (): filtra una variable con un filtro específico:
      $email = filter_var($email, FILTER_SANITIZE_EMAIL);
      $newstr = filter_var($str, FILTER_SANITIZE_STRING);
      filtros más predefinidos
    • filter_input() : 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() - Realiza una coincidencia de expresión regular;
    • Escribe tu propia función de validación.

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 las que solicita para su inclusión en LDIF. completamente diferente de los que aplica a la inclusión en CSS es completamente diferente de aquellos que aplica a la inclusión en un correo electrónico ...

De todos modos, valide la entrada : decida si debe aceptarla para su posterior procesamiento o para decirle 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 todos los datos de escape y terminamos con " magic_quotes " que no escapaban correctamente de los datos de todos los destinos de salida y que resultaba en una instalación diferente que requería un código diferente para funcionar.


Es un error común que la entrada del usuario se puede filtrar. PHP incluso tiene una "característica" (ahora en desuso), llamada comillas 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 incrusta una cadena dentro de un código extranjero, debe evitarlo, de acuerdo con las reglas de ese idioma. Por ejemplo, si incrusta una cadena en algún SQL que apunta a MySql, debe escapar de la cadena con la función de mysqli_real_escape_string para este propósito ( mysqli_real_escape_string ). (O, en el caso de las bases de datos, el uso de declaraciones preparadas es un mejor enfoque, cuando sea posible)

Otro ejemplo es HTML: si incrusta cadenas en el marcado HTML, debe escapar con htmlspecialchars . Esto significa que cada declaración de echo o print debe usar htmlspecialchars .

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

Y así sucesivamente y así sucesivamente ...

El único caso en el que necesita filtrar datos activamente es si está aceptando una entrada con formato previo. P.ej. si permite que sus usuarios publiquen un 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 posible agujero de seguridad.


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


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

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

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

El uso de declaraciones preparadas, y / y el filtrado con mysql_real_escape_string es definitivamente una necesidad. PHP también tiene filter_input incorporado, lo que es un buen lugar para comenzar.

2) Este es un tema amplio y depende del contexto de los datos que se generan. Para HTML hay soluciones como htmlpurifier por ahí. Como regla general, siempre escape de todo lo que produzca.

Ambos temas son demasiado grandes para incluirlos en una sola publicación, pero hay muchas publicaciones que se detallan más:

Métodos de salida PHP

Salida PHP más segura


No hay una función general, porque hay múltiples preocupaciones que deben abordarse.

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

    Consulte el tutorial de PDO (el único apropiado) para casi todo lo que necesita saber sobre PDO. (Un sincero agradecimiento al principal colaborador de SO, @YourCommonSense, por este gran recurso sobre el tema).

  2. XSS - Desinfectar datos en el camino en ...

    • El Purificador de HTML ha existido por mucho tiempo y todavía se actualiza activamente. Puede usarlo para desinfectar las entradas maliciosas, al tiempo que permite una lista blanca generosa y 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 en absoluto, he encontrado útil esta simple función (y ha pasado varias auditorías contra 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: desinfecte los datos a su salida ... a menos que garantice que los datos se hayan desinfectado correctamente antes de agregarlos a su base de datos, deberá desinfectarlos antes de mostrarlos a 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 menos que los datos estén correctamente desinfectados de forma segura y se le permita mostrar HTML.
    • json_encode es una forma segura de proporcionar valores proporcionados por el usuario desde PHP a Javascript
  4. ¿Llama a comandos de shell externo utilizando las funciones exec() o system() , o al operador de backtick ? Si es así, además de SQL Injection y XSS, es posible que tenga una inquietud adicional que atender, los usuarios que ejecutan comandos maliciosos en su servidor . escapeshellcmd usar escapeshellcmd si desea escapar de todo el comando O escapeshellarg para escapar de argumentos individuales.


No no hay.

En primer lugar, la inyección de SQL es un problema de filtrado de entrada, y XSS es una salida que se escapa, 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() )
  • Utilice strip_tags() para filtrar HTML no deseado
  • Escape todos los demás resultados con htmlspecialchars() y tenga en cuenta los parámetros 2 y 3 aquí.

No. No puede filtrar datos de forma genérica sin ningún contexto para lo que es. A veces, querría tomar una consulta SQL como entrada y otras veces querría 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 de él antes de usarlo, dependiendo del contexto en el que lo esté utilizando.

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 y agradables funciones filter_input, que, por ejemplo, lo liberan de encontrar "la última expresión regular de correo electrónico" ahora que hay un tipo FILTER_VALIDATE_EMAIL integrado

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('[email protected]', '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 debe hacer que su consulta de SQL se escape dependiendo del tipo de db que esté utilizando (mysql_real_escape_string () no sirve para un servidor de SQL, por ejemplo). Probablemente quiera manejar esto automáticamente en la capa de aplicación apropiada como un ORM. Además, como se mencionó anteriormente: para enviar a html use las otras funciones dedicadas de php como htmlspecialchars;)

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


Si está utilizando PostgreSQL, la entrada de PHP puede escaparse 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 de una cadena para consultar la base de datos. Devuelve una cadena de escape en el formato PostgreSQL sin comillas.


Solo quería agregar eso al tema de la salida de salida, si usa php 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 referencia de prevención de XSS de OWASP





user-input