Ver Mensaje Individual
  #3  
Antiguo 05-12-2006
Avatar de dec
dec dec is offline
Moderador
 
Registrado: dic 2004
Ubicación: Alcobendas, Madrid, España
Posts: 13.141
Reputación: 36
dec Tiene un aura espectaculardec Tiene un aura espectacular
Hola,

Bueno... pues ya estoy más tranquilo (acabo de suspirar y todo, no te digo yo que...). Menudo repaso le he pegado al asunto (léase Loturak - ¡ajá!). Lo bueno y lo malo de estas cosas es que no ves errores que los tienes ahí en tus narices... y que luego aparecen como por arte de magia. He solucionado no pocos errores o potenciales problemas (¿los llamaré "bugs"?) mientras andaba con el tema de los "escapes"...

Por otro lado creo que me ha quedado claro para siempre lo que he comentado más arriba: una cosa es el "escape" por el que tiene que pasar variable que luego tiene que pasar por una consulta SQL, y otra cosa es el "escape" que tiene que pasar una variable que luego imprimiremos en el código HTML de la página Web de turno. Si hubiera estado con más proyectos además de Loturak estoy seguro de que el problema se hubiera producido en todos ellos.


El "escape" de variables para consultas SQL

Supongamos algo como esto:

Código PHP:
 
 $titulo 
'';
 if(isset(
$_GET['titulo']))
   
$titulo $_GET['titulo'];
 
 
$consultaSql "SELECT * FROM enlaces WHERE titulo = '$titulo';"
Lo que entiendo ahora como correcto queda tal que así:

Código PHP:
 
 $titulo 
'';
 if(isset(
$_GET['titulo']))
   
$titulo $bdatos->Escapar($_GET['titulo']);
 
 
$consultaSql "SELECT * FROM enlaces WHERE titulo = '$titulo';"
Y el método "Escapar" utilizado es:

Código PHP:

function Escapar($cadena) {
  if(
get_magic_quotes_gpc()) $cadena stripslashes($cadena);        
    return 
mysql_real_escape_string($cadena$this->enlaceConexionBd);

Con eso, según el manual de PHP estamos evitando un posible "ataque SQL Injection" de esos... que haberlos haylos, que me lo han dicho a mí.

No sigo el manual al dedillo en mi caso, puesto que según se dice en el mismo "la mejor forma de hacer esto es":

Código PHP:
function quote_smart($value)
{
    
// Stripslashes
    
if (get_magic_quotes_gpc()) {
        
$value stripslashes($value);
    }
    
// Quote if not a number or a numeric string
    
if (!is_numeric($value)) {
        
$value "'" mysql_real_escape_string($value) . "'";
    }
    return 
$value;

Es muy probable que acabe utilizando algo así. Si no lo hago ahora es porque las consultas SQL acostumbro a realizarlas incluyendo por mi cuenta las comillas "simples" para valores de tipo "cadena" y no incluirlas para valores de tipo numérico: por lo demás, ambos métodos de escape son iguales. De hecho yo me copié del manual.

Nótese, sin embargo, que el método de escape no valida la entrada del usuario, es decir, se limita a escapar determinados caracteres para impedir el SQL Injection. Para validar la entrada del usuario debemos contar con las correspondientes reglas, que tendremos que aplicar por nuestra cuenta. Es decir, el código anterior quedaría algo así:


Código PHP:
  
  $titulo 
'';
  if(isset(
$_GET['titulo']))
    
$titulo $bdatos->Escapar($_GET['titulo']);
  
if(!
ValidarTitulo($titulo))
{
  
MostrarError();  
  die;
}
else
{
  
$consultaSql "SELECT * FROM enlaces WHERE titulo = '$titulo';";
  
EjecutarConsulta($consultaSql);
  } 
Pero creo que puede verse más claro de este modo:

Código PHP:
   
   $titulo 
'';
   if(isset(
$_GET['titulo']))
     
$titulo $bdatos->Escapar($_GET['titulo']);
   
 if(!
ValidarTitulo($titulo))
 {
   
MostrarError();  
   die;
 }
 else
 {
   
$consultaSql "INSERT INTO enlaces (titulo) VALUES('$titulo');";
   
EjecutarConsulta($consultaSql);
   } 
Es decir, no aceptaremos en la base de datos enlaces que no cumplan determinadas reglas, sea que miremos por la longitud (en caracteres) del título, sea que reviremos que no contenga determinados caracteres, etc.

Así que como puede verse una cosa no excluye a la otra. Se escapan las variables que van a entrar a formar parte de una consulta SQL. Se valida la entrada del usuario siguiendo las reglas que consideremos oportunas. No son cosas iguales... aunque a mí me lo pareciera hasta anteayer.


El "escape" de variables que van a imprimirse en el HTML

Este tema es curioso. O a mí me lo parece, vamos. Ya visteis cómo la segunda URL que puso el compañero que informó del problema en Loturak conseguía introducir un "evento JavaScript" en un determinado elemento HTML.

Lo que más me llamó la atención (aunque no intuía aún la posible solución) es que el "problema" no se daba sólo con la URL que el compañero propuso, sino que ocurría también en todas las casillas de todos los formularios de la aplicación...

Más tarde descubrí que ni siquiera eso... el problema se daba en más sitios que en las casillas de los formularios, pero, en ambos casos estábamos hablando de lo mismo.

Cuando nos llegan variables vía GET, por ejemplo, y estas van a imprimirse en el código HTML de la página Web también hay que "escaparlas", pues, aunque ya sabemos que no estamos hablando del mismo tipo de "escape" que arriba comentamos para las consultas SQL.

Código PHP:

function Escapar($entrada
{
  return 
htmlentities(strip_tags
   
(stripslashes($entrada)), 
    
ENT_QUOTES'utf-8');    

El "atacante" puede hacer virgerías... así que aplicamos determinados filtros a la entrada (del usuario) para tratar de "paliar" las consecuencias o eliminarlas del todo... si se puede.

Con las funciones de que se compone nuestra función "Escapar" tratamos de convertir algunos caracteres especiales de HTML a su entidad correspondiente. Por ejemplo, sustituimos los "&" por "&". A continuación eliminamos de la variable "entrada" las etiquetas HTML y PHP que pudiera contener. Y para terminar eliminamos los "\" de la entrada.

Todo esto para evitar, por ejemplo, que recibamos en una variable de entrada algo como esto:

Código:
demo" onmouseover=alert(123) "
Si no escapáramos esa entrada y la misma terminara imprimiéndose tal cual en el código HTML de la página Web tendríamos algo como esto:

Código:
<input name="b" value="demo\" onmouseover=alert(123) \"" type="text" id="casillabusqueda" class="inputtext" />
Es decir, que cuando el usuario pasara el ratón por la casilla de búsqueda, en este caso, iba a recibir un pequeño susto....

¿Qué ocurre cuando se escapa la cadena de entrada con el método anteriormente visto? Algo bastante distinto, gracias, en este caso más que nada, a la función "htmlentities":

Código:
<input name="b" value="demo&quot; onmouseover=alert(123) "" id="casillabusqueda" class="inputtext" type="text">
De ese modo le mostramos al usuario lo que realmente ha buscado... pero, principalmente impedimos que el evento que trataba de introducirse surta efecto alguno.

Ahora bien. Lo curioso del asunto es que este tipo de escape no se ciñe sólo variables que nos llegan vía HTTP GET, por ejemplo. Supongamos una aplicación que permita a sus usuarios guardar datos de sus enlaces favoritos (¡ajá!).

Supongamos que alguien introduce como título de un enlace la cadena anterior. Puede que sea válida, es decir, puede que la aplicación considere que esa cadena resulta válida como título de un enlace: cumple con las reglas.

Pero, en cuanto esa cadena se introduzca como título de un enlace tendremos el problema multiplicado por la aplicación... en todo lugar en que se muestre el enlace del usuario puede terminar ocurriendo que "el evento JavScript" funcione... no sólo en las casillas de los formularios, sino en cualquier otra parte.

Precisamente, en cualquier otra parte en que se muestre el título del enlace... porque lo mismo da esto:

Código:
<input name="b" value="demo\" onmouseover=alert(123) \"" type="text" id="casillabusqueda" class="inputtext" />
Que esto otro:

Código:
<li name="b" title="demo\" onmouseover=alert(123) \"" />
El evento se añadirá esta vez en la lista en la que mostramos una serie de enlaces, por ejemplo. Como cada elemento de la lista muestra el título del enlace y el título del enlace contiene el "código malicioso"... pues eso.

Es decir, ya no recibimos la cadena "peligrosa" y a escapar desde la entrada del usuario, bueno, sí, pero, esta puede estar ya en nuestra base de datos... porque, como he dicho, la cadena anterior puede ser válida como título de un enlace y será guardada en la base de datos religiosamente... y cada vez que la precisemos mostrar en el código HTML de la aplicación tendremos problemas...

Mejor dicho tendríamos problemas. Y es que si procuramos escapar este tipo de variables con la función de más arriba, aparentemente, al menos, se acabaron las disputas en ese sentido al menos.

No es que haya que escapar todos los datos de un enlace (siguiendo con el ejemplo, para hacerme entender). Por ejemplo, si en nuestra tabla de enlaces guardamos el ID, el título y la URL de un enlace,... lo que puede resultar "peligroso" es el título y la URL, precisamente, porque es el usuario quien determina el título y la URL de los enlaces. Como el ID es un dato en el que no interviene el usuario no es necesario que lo escapemos. Y quien dice el ID dice la fecha del enlace, el número de visitas, etc., cualquier dato que el usuario no pueda editar.


Conclusiones

Dicho todo esto, se entenderá porqué tenía los problemas que tenía en la Web que me traigo entre manos: estaba utilizando el mismo método "Escapar" para todo... entradas de usuario, datos de la base de datos, variables para consultas SQL... para todo usaba el mismo método, además no del todo en condiciones... ¿cómo iba a ir la cosa? Pues mal.

Así que agradezco un montón a quien se molestó en informarme de este problema. Creo que me ha quedado lo suficientemente claro el asunto como para andarme con ojo en la aplicación de aquí en adelante y saber por dónde ando en este sentido. Muchas gracias también a todos cuantos colaborásteis de algún modo.

Y ya está. Eso es todo. Disculpad el rollo, y, si tenéis cualquier cosa que decir respecto a lo dicho aquí no dejéis de hacerlo, por favor. Cualquier comentario, sugerencia, crítica, correción, todo será bienvenido.
__________________
David Esperalta
www.decsoftutils.com
Responder Con Cita