¿Cómo puedo desinfectar la entrada del usuario con PHP?
¿Existe alguna función general que funcione bien para desinfectar la entrada del usuario para inyección SQL y ataques XSS, y al mismo tiempo permita ciertos tipos de etiquetas HTML?
Es un error común pensar que la entrada del usuario se puede filtrar. PHP incluso tenía una "función" (ahora desaparecida), llamada comillas mágicas , que se basa en esta idea. No tiene sentido. Olvídate de filtrar (o limpiar, o como la gente lo llame).
Lo que debes hacer, para evitar problemas, es bastante simple: cada vez que incrustas un dato dentro de un código externo, debes formatearlo de acuerdo con las reglas de ese código. Pero debes comprender que dichas reglas pueden ser demasiado complicadas para intentar seguirlas todas manualmente. Por ejemplo, en SQL, las reglas para cadenas, números e identificadores son todas diferentes. Para su comodidad, en la mayoría de los casos existe una herramienta dedicada a dicha integración. Por ejemplo, cuando se deben usar algunos datos en la consulta SQL, en lugar de agregar una variable directamente a la cadena SQL, se debe hacer a través de un parámetro en la consulta, utilizando una declaración preparada. Y se encargará de todo el formato adecuado.
Otro ejemplo es HTML: si incrusta cadenas dentro del marcado HTML, debe utilizar como escape htmlspecialchars
. Esto significa que cada declaración echo
o print
debe usar htmlspecialchars
.
Un tercer ejemplo podrían 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
.
Además, un ejemplo muy convincente es JSON. Las reglas son tan numerosas y complicadas que nunca podrás seguirlas todas manualmente. Es por eso que nunca debes crear una cadena JSON manualmente, sino usar siempre una función dedicada json_encode()
que formatee correctamente cada bit de datos.
Y así sucesivamente y así sucesivamente ...
El único caso en el que necesita filtrar datos activamente es si acepta entradas preformateadas. Por ejemplo, si permite que sus usuarios publiquen etiquetas HTML que planea mostrar en el sitio. Sin embargo, debes evitar esto a toda costa, ya que no importa qué tan bien lo filtres, siempre será un potencial agujero de seguridad.
No intente evitar la inyección de SQL desinfectando los datos de entrada.
En su lugar, no permita que se utilicen datos para crear su código SQL . Utilice declaraciones preparadas (es decir, utilice parámetros en una consulta de plantilla) que utilice variables vinculadas. Es la única forma de estar garantizado contra 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 se pueden filtrar datos de forma genérica sin ningún contexto de para qué sirven. A 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 debes escapar de él antes de usarlo, dependiendo del contexto en el que lo estés 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 nuevas funciones agradables que, por ejemplo, le liberan de tener que buscar "la expresión regular de correo electrónico definitiva" ahora que hay un tipo filter_input
integradoFILTER_VALIDATE_EMAIL
Mi propia clase de filtro (usa JavaScript para resaltar campos defectuosos) se puede iniciar mediante una solicitud ajax o una publicación de formulario normal. (ver el ejemplo a continuación) <? /** * Validador de forma de cerdo. valida campos mediante expresiones regulares y puede desinfectarlos. Utiliza funciones integradas PHP filter_var y expresiones regulares adicionales * @package pork */
/**
* 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 que su consulta SQL escape dependiendo del tipo de base de datos que esté utilizando (mysql_real_escape_string() es inútil para un servidor SQL, por ejemplo). Probablemente desee manejar esto automáticamente en la capa de aplicación adecuada como un ORM. Además, como se mencionó anteriormente: para enviar a html use otras funciones dedicadas de php como htmlspecialchars;)
Para permitir realmente la entrada HTML con clases y/o etiquetas eliminadas similares, depende de uno de los paquetes de validación xss dedicados. ¡NO ESCRIBA SUS PROPIAS REGEXACIONES PARA ANALIZAR HTML!