¿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?
fuente
¿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?
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 echo
o print
declaració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 escapeshellcmd
y 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.
mysql_real_escape_string
está 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.
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.
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.
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!
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
mysql_real_escape_string()
)strip_tags()
para filtrar HTML no deseadohtmlspecialchars()
y tenga en cuenta los parámetros segundo y tercero 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 estos.
PHP 5.2 introdujo la función filter_var .
Es compatible con una gran cantidad de filtros SANITIZE, VALIDATE.
Un truco que puede ayudar en las circunstancias específicas en las que tiene una página como /mypage?id=53
y 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).
$id = (int)$_GET['id']
y también $que = sprintf('SELECT ... WHERE id="%d"', $id)
es bueno
Métodos para desinfectar la entrada del usuario con PHP:
$ 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
[en desuso en PHP 5.5.0, eliminado en PHP 7.0.0].mysql_set_charset ('utf8')
$ 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 / *"]);
ctype_digit - Comprueba los caracteres numéricos;
$ valor = (int) $ valor;
$ valor = intval ($ valor);
$ var = filter_var ('0755', FILTER_VALIDATE_INT, $ opciones);
is_string () - Encuentra si el tipo de una variable es string
$ email = filter_var ($ email, FILTER_SANITIZE_EMAIL);más filtros predefinidos
$ newstr = filter_var ($ str, FILTER_SANITIZE_STRING);
$ search_html = filter_input (INPUT_GET, 'buscar', FILTER_SANITIZE_SPECIAL_CHARS);
Lo que está describiendo aquí son dos cuestiones separadas:
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:
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.
No hay una función catchall, porque hay múltiples preocupaciones que abordar.
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).
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;
}
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:
echo
o print
muestre los valores proporcionados por el usuario, úselos a htmlspecialchars
menos 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¿Llama a comandos de shell externos usando exec()
o system()
funciones, o al backtick
operador? 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 escapeshellcmd
si desea escapar del comando completo O escapeshellarg
para escapar de argumentos individuales.
mb_encode_numericentity
se discute en el htmlspecialchars
enlace en # 3 XSS
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?
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
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.
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 &
" (double quote) becomes "
' (single quote) becomes '
< (less than) becomes <
> (greater than) becomes >
clean_input
entonces? ¿Por qué querrías pelar barras?
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.
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.