security contraseña - ¿Cuál es el mejor método para sanear la entrada del usuario con PHP?




autenticación control (16)

¿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?


Answers

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.


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.

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

En su lugar, no permita que se utilicen datos al crear su código SQL . Use declaraciones preparadas (es decir, que utiliza parámetros en una consulta de plantilla) que usa variables encuadernadas. Es la única forma de garantizar contra la inyección de SQL.

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


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 &
" (double quote) becomes "
' (single quote) becomes '
< (less than) becomes &lt;
> (greater than) becomes &gt;


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

El motor de plantillas como Twig o Latte tiene una salida que se escapa de forma predeterminada: no tiene que resolverlo manualmente si ha escapado correctamente de su salida, según el contexto (parte de la página web de HTML o Javascript).

Framework está saneando automáticamente la entrada y no debes 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 marco de software?


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.


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 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 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.


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.


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


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


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.


Un truco que puede ayudar en la circunstancia específica en la que tiene una página como /mypage?id=53 y usa el id en una cláusula WHERE es para asegurarse de que id 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 otras respuestas. (Y sí, sé que el código anterior no es excelente, pero muestra la defensa específica).


¿Cómo es esto?

foreach( $options as $option )
{
  foreach( $m as $m_key => $m_value )
  {
    if( $option == $m_key )
    {
      echo 'Name for ' . $options . ' is ' . $m_value;
      break;
    }
  }
}




php security xss sql-injection user-input