¿Cómo puedo desinfectar la entrada del usuario con PHP?

1124

¿Existe una función de captura en algún lugar que funcione bien para desinfectar la entrada del usuario para la inyección SQL y los ataques XSS, al tiempo que permite ciertos tipos de etiquetas HTML?

Brent
fuente
42
Hoy en día, para evitar la inyección sql, use PDO o MySQLi.
Francisco Presencia
76
Usar PDO o MySQLi no es suficiente. Si construye sus declaraciones SQL con datos no confiables, como select * from users where name='$name', entonces no importa si usa PDO o MySQLi o MySQL. Sigues en peligro. Debe usar consultas parametrizadas o, si es necesario, usar mecanismos de escape en sus datos, pero eso es mucho menos preferible.
Andy Lester
26
@AndyLester ¿Estás insinuando que alguien usa PDO sin declaraciones preparadas? :)
64
Estoy diciendo que "Usar PDO o MySQLi" no es información suficiente para explicar a los principiantes cómo usarlos de manera segura. Usted y yo sabemos que las declaraciones preparadas son importantes, pero no asumo que todos los que lean esta pregunta lo sabrán. Por eso agregué las instrucciones explícitas.
Andy Lester
30
El comentario de Andy es completamente válido. Convertí mi sitio web mysql a PDO recientemente pensando que ahora estaba de alguna manera a salvo de ataques de inyección. Fue solo durante el proceso que me di cuenta de que algunas de mis declaraciones sql todavía se construyeron utilizando la entrada del usuario. Luego lo arreglé usando declaraciones preparadas. Para un novato completo, no está completamente claro que haya una distinción ya que muchos expertos arrojan el comentario sobre el uso de PDO pero no especifican la necesidad de declaraciones preparadas. La suposición es que esto es obvio. Pero no para un novato.
GhostRider

Respuestas:

1184

Es un error común pensar que la entrada del usuario se puede filtrar. PHP incluso tiene una "característica" (ahora obsoleta), 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 inserte una cadena dentro del código extranjero, debe escapar de ella, 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, el uso de declaraciones preparadas es un mejor enfoque, cuando sea posible).

Otro ejemplo es HTML: si incrusta cadenas dentro del marcado HTML, debe escapar con htmlspecialchars. Esto significa que cada uno echoo printdeclaración debe usar htmlspecialchars.

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

Y así sucesivamente y así sucesivamente ...

El único caso en el que necesita filtrar datos de forma activa es si acepta entradas con formato previo. Por ejemplo, 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.

troelskn
fuente
245
"Esto significa que cada declaración de eco o impresión debe usar htmlspecialchars", por supuesto, se refiere a "cada ... declaración que genera la entrada del usuario"; htmlspecialchars () - ifying "echo 'Hello, world!';" sería una locura;)
Bobby Jack
10
Hay un caso en el que creo que el filtrado es la solución correcta: UTF-8. No desea secuencias UTF-8 no válidas en toda su aplicación (es posible que obtenga una recuperación de error diferente según la ruta del código), y UTF-8 se puede filtrar (o rechazar) fácilmente.
Kornel
66
@jbyrd: no, LIKE utiliza un lenguaje de expresiones regulares especializado. Tendrá que escapar de su cadena de entrada dos veces, una para la expresión regular y otra para la codificación de cadena mysql. Es código dentro de código dentro de código.
troelskn
66
En este momento mysql_real_escape_stringestá en desuso. Se considera una buena práctica hoy en día usar declaraciones preparadas para evitar la inyección de SQL. Entonces cambie a MySQLi o PDO.
Marcel Korpel
44
Porque limitas la superficie de ataque. Si desinfecta temprano (cuando ingresa), debe asegurarse de que no haya otros agujeros en la aplicación por los que puedan ingresar datos incorrectos. Mientras que si lo hace tarde, su función de salida no tiene que "confiar" en que se le proporcionen datos seguros, simplemente supone que todo es inseguro.
troelskn
217

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

En cambio, no permita que se utilicen datos para crear su código SQL . Usar 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.

Andy Lester
fuente
18
O visite la documentación oficial y aprenda PDO y declaraciones preparadas. Pequeña curva de aprendizaje, pero si conoce SQL bastante bien, no tendrá problemas para adaptarse.
un codificador
2
Para el caso específico de inyección SQL, ¡ esta es la respuesta correcta!
Scott Arciszewski
44
Tenga en cuenta que las declaraciones preparadas no agregan ninguna seguridad, las consultas parametrizadas sí. Simplemente resultan ser muy fáciles de usar juntos en PHP.
Básico
No es la única forma garantizada. Hex la entrada y unhex en la consulta también evitarán. Además, los ataques hexagonales no son posibles si usas el hechizo derecho.
Ramon Bakker
¿Qué sucede si está ingresando algo especializado, como direcciones de correo electrónico o nombres de usuario?
Abraham Brookes
79

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, dependiendo del 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.

Daniel Papasian
fuente
52

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 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');
 *  $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;
    }       



}

Por supuesto, tenga en cuenta que también debe 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 las otras funciones dedicadas de php como htmlspecialchars;)

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

SchizoDuckie
fuente
18
Parece que podría ser un script útil para validar entradas, pero es completamente irrelevante para la pregunta.
rjmunro
43

No no hay.

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

Reglas básicas de oro

  • 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() )
  • Utilizar strip_tags() para filtrar HTML no deseado
  • Escápese de todas las demás salidas htmlspecialchars()y tenga en cuenta los parámetros segundo y tercero aquí.
Peter Bailey
fuente
1
Entonces, solo usa strip_tags () o htmlspecialchars () cuando sabe que la entrada tiene HTML del que desea deshacerse o escapar, respectivamente, no lo está utilizando para ningún propósito de seguridad, ¿verdad? Además, cuando haces el enlace, ¿qué hace para cosas como Bobby Tables? "Robert '); TABLA DE DESCENSO Estudiantes; -" ¿Se escapa de las citas?
Robert Mark Bram
2
Si tiene datos de usuario que irán a una base de datos y luego se mostrarán en páginas web, ¿no suele leerse mucho más de lo que está escrito? Para mí, tiene más sentido filtrarlo una vez (como entrada) antes de almacenarlo, en lugar de tener que filtrarlo cada vez que lo visualiza. ¿Me estoy perdiendo algo o un montón de personas votaron por gastos innecesarios en este y la respuesta aceptada?
jbo5112
2
La mejor respuesta para mi. Es breve y responde bien a la pregunta si me preguntas. ¿Es posible atacar PHP de alguna manera a través de $ _POST o $ _GET con alguna inyección o es esto imposible?
Jo Smo
oh sí, las matrices $ post y $ get aceptan todos los caracteres, pero algunos de esos caracteres se pueden usar en su contra si se permite enumerar el carácter en la página php publicada. así que si no escapa de encapsular caracteres (como "," y `) podría abrir un vector de ataque. El carácter` a menudo se pierde y puede usarse para formar hacks de ejecución de línea de comando. pero no lo ayudará con los hacks de firewall de aplicaciones web
drtechno
22

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

jasonbar
fuente
no hay una "mejor manera" de hacer algo como desinfectar la entrada. Use alguna biblioteca, el purificador html es bueno. Estas bibliotecas han sido golpeadas muchas veces. Por lo tanto, es mucho más a prueba de balas que cualquier cosa que pueda surgir usted mismo
paan
Consulte también bioinformatics.org/phplabware/internal_utilities/htmLawed . Según tengo entendido, WordPress usa una versión anterior, core.trac.wordpress.org/browser/tags/2.9.2/wp-includes/kses.php
Steve Clay
El problema con WordPress es que no es necesariamente un ataque de inyección php-sql que causa violaciones de la base de datos. Miss complementos programados que almacenan datos que una consulta xml revela secretos es más problemático.
drtechno
17

Un truco que puede ayudar en las circunstancias específicas en las que tiene una página como /mypage?id=53y usa la identificación en una cláusula WHERE es asegurarse de que la identificación sea definitivamente 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).

Hamish Downer
fuente
11
Yo uso $ id = intval ($ id) en su lugar :)
Duc Tran
La conversión de números enteros es una buena manera de garantizar que solo se inserten datos numéricos.
prueba
1
$id = (int)$_GET['id']y también $que = sprintf('SELECT ... WHERE id="%d"', $id)es bueno
vladkras
16

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

  • Utilice versiones modernas de MySQL y PHP.

  • Establecer juego de caracteres explícitamente:

    • $ mysqli-> set_charset ("utf8");
      manual
    • $ pdo = nuevo PDO ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ usuario, $ contraseña);
      manual
    • $ pdo-> exec ("establecer nombres utf8");
      manual
    • $ pdo = nuevo PDO (
      "mysql: host = $ host; dbname = $ db", $ usuario, $ pass, 
      formación(
      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 comillas alrededor de la 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 = nuevo PDO ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ usuario, $ contraseña); conjunto explícito del conjunto de caracteres
      $ pdo-> setAttribute (PDO :: ATTR_EMULATE_PREPARES, falso); deshabilite las declaraciones preparadas de emulación para evitar el retroceso a las declaraciones de emulación que MySQL no puede preparar de forma nativa (para evitar la inyección)
      $ var = $ pdo-> quote ("'OR 1 = 1 / *"); no solo escapa al literal, sino que también lo cita (en comillas simples) $ stmt = $ pdo-> query ("SELECT * FROM test WHERE name = $ var LIMIT 1");

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

      $ pdo = nuevo PDO ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ usuario, $ contraseña); conjunto explícito del conjunto de caracteres
      $ pdo-> setAttribute (PDO :: ATTR_EMULATE_PREPARES, falso); deshabilite las declaraciones preparadas de emulación para evitar el retroceso a las declaraciones de emulación que MySQL no puede preparar de forma nativa (para evitar la inyección) $ 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 Escapan caracteres especiales en una cadena para usar en una instrucció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 - Comprueba los caracteres numéricos; 
      $ valor = (int) $ valor;
      $ valor = intval ($ valor);
      $ var = filter_var ('0755', FILTER_VALIDATE_INT, $ opciones);
    • Para uso de cadenas:
      is_string () - Encuentra si el tipo de una variable es string

      Usar función de filtro filter_var (): 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 () - Obtiene una variable externa específica por nombre y opcionalmente la filtra:
      $ search_html = filter_input (INPUT_GET, 'buscar', FILTER_SANITIZE_SPECIAL_CHARS);
    • preg_match () - Realiza una coincidencia de expresión regular;
    • Escribe tu propia función de validación.
Mark Martin
fuente
11

Lo que está describiendo aquí son dos cuestiones separadas:

  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, y / o 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 emiten. 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

Andrés
fuente
9

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.

Alejandro Silva
fuente
1
pg_escape_literal () es la función recomendada para usar para PostgreSQL.
críptico ツ
8

No hay una función catchall, porque hay múltiples preocupaciones que abordar.

  1. Inyección SQL : en la actualidad, en general, todos los proyectos PHP deberían utilizar declaraciones preparadas a través de PHP Data Objects (PDO) como una mejor práctica, evitando un error de una cita perdida, 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 PDO (El único correcto) para obtener 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 - Desinfecte los datos en el camino en ...

    • HTML Purifier ha existido durante mucho tiempo y todavía se actualiza activamente. Puede usarlo para desinfectar 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 función simple (y he pasado múltiples 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 al salir ... a menos que garantice que los datos se desinfectaron correctamente antes de agregarlos a su base de datos, deberá desinfectarlos antes de mostrarlos a su usuario, podemos aprovechar estas útiles funciones PHP:

    • Cuando llame echoo printmuestre los valores proporcionados por el usuario, úselos a htmlspecialcharsmenos que los datos se hayan desinfectado de forma segura y se 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 externos usando exec()o system()funciones, o al backtickoperador? Si es así, además de SQL Injection y XSS, es posible que tenga una preocupación adicional para abordar, los usuarios que ejecutan comandos maliciosos en su servidor . Debe usarlo escapeshellcmdsi desea escapar del comando completo O escapeshellargpara escapar de argumentos individuales.

webaholik
fuente
¿se podría usar mb_encode_numericentity en su lugar? ¿Ya que lo codifica todo?
drtechno
@drtechno - mb_encode_numericentityse discute en el htmlspecialcharsenlace en # 3 XSS
webaholik
5

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 motor como Twig o Latte tiene salida escaneada de forma predeterminada: no tiene que resolver manualmente si ha escapado adecuadamente 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 marco de software?

Ondřej Šotek
fuente
3

Solo quería agregar eso sobre el tema del escape de salida, si usa php DOMDocument para hacer que su salida html se escape 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: OWASP XSS Prevention Cheat Sheet

usuario138720
fuente
2

Nunca desinfectas la entrada.

Siempre desinfectas la producción.

Las transformaciones que aplica a los datos para que sea seguro 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 las que solicita para su inclusión en Javascript son completamente diferentes de las que solicita para su inclusión en LDIF. completamente diferentes de los que aplica para la inclusión en CSS son completamente diferentes de los que aplica para la inclusión en un correo electrónico ...

De todos modos valide la entrada : decida si debe aceptarla para su posterior procesamiento o 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.

Hace mucho tiempo, alguien trató de inventar un mecanismo único para todos los escapes de datos y terminamos con " magic_quotes ", que no escapó correctamente los datos para todos los objetivos de salida y resultó en una instalación diferente que requiere un código diferente para funcionar.

symcbean
fuente
Un problema con eso es que no siempre es un ataque a la base de datos, y todas las entradas del usuario deben estar protegidas del sistema. no solo un tipo de idioma. Entonces, en sus sitios, cuando enumera sus datos $ _POST, incluso con el uso de enlaces, podría escapar lo suficiente como para ejecutar shell o incluso otro código php.
drtechno 01 de
"no siempre es un ataque a la base de datos": "Las transformaciones que usted aplica a los datos para que sean seguras para su inclusión en una declaración SQL son completamente diferentes de las que ..."
symcbean
"todas las entradas del usuario deben estar protegidas del sistema": no, el sistema debe estar protegido de las entradas del usuario.
symcbean
bueno, se me acabaron las palabras, pero sí, es necesario evitar que la entrada afecte la operación del sistema. para aclarar esto ...
drtechno
Tanto la entrada como la salida deben desinfectarse.
Tajni
1

Nunca confíes en los datos del usuario.

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

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

La stripslashes()función elimina las barras invertidas

La htmlspecialchars()función convierte algunos caracteres predefinidos en 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;
Erik Thiart
fuente
1
¿De qué protegería esto? ¿Es esto para XSS? ¿Por qué se llama clean_inputentonces? ¿Por qué querrías pelar barras?
Dharman
55
ADVERTENCIA: Esto no hace mágicamente segura la información del usuario. Esta función dañará innecesariamente sus datos sin protegerlos de nada. ¡NO LO USES!
Dharman el
Tu declaración es falsa.
Erik Thiart el
0

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á que usarlo.

Hasta
fuente