usuarios - ¿Cuál es el mejor método para sanear la entrada del usuario con PHP?
sistema login php mysql seguro (18)
¿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.
- MySQLi preparó declaraciones:
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.
- Si está esperando un número entero, use:
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.
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;
}
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.
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?
Lo que estás describiendo aquí es dos cuestiones separadas:
- Desinfección / filtrado de los datos de entrada del usuario.
- 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:
No hay una función general, porque hay múltiples preocupaciones que deben abordarse.
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).
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; }
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
oprint
para mostrar los valores proporcionados por el usuario, usehtmlspecialchars
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
- Cuando llame a
¿Llama a comandos de shell externo utilizando las funciones
exec()
osystem()
, o al operador debacktick
? 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
usarescapeshellcmd
si desea escapar de todo el comando Oescapeshellarg
para escapar de argumentos individuales.
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.
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.
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 <
> (greater than) becomes >
PHP 5.2 introdujo la función filter_var .
Es compatible con una gran cantidad de filtros de SANITIZE, VALIDATE.
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(''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 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!
Para abordar el problema de XSS, eche un vistazo al purificador de HTML . 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 ejecútela 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 volcarlos en la cadena de consulta.
Una mejor solución es utilizar declaraciones preparadas. La biblioteca PDO y la extensión mysqli son compatibles con estos.
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
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).