El Motivo
Bueno, el motivo de escribir esto es que estube leyendo un par de los threads de PHP de cami y vi que nadie se detubo a opinar sobre los agujeros de seguridad en el 99% (diria 100% pero no lei todo el codigo posteado) del codigo...
El Problema
Me estoy refiriendo en particular a los errores de injeccion, tanto SQL Injections como Cross-site scripting (XSS para los amigos).
Primero, una descripcion con ejemplos sacados de sus ejemplos:
SQL Injection
esta vulnerabilidad consiste en colocar valores levantados directamente de un formulario en queries SQL. De este modo, un usuario podria ingresar codigo SQL en el campo, que al insertarse directamente en el querry le permitiria ejecutar practicamente lo que quiera contra la DB.
Ejemplo de codigo vulnerable (http://www.egelforum.net/forum/showp...7&postcount=12, cortesia de Sam Fisher):
se ve claramente que el campo nombre del formulario esta insertado directamente en el querry string. bastaria que ingresara como nombre "'; DROP TABLE orcos; SELECT * FROM otra_tabla WHERE algun_campo = '" para que nos llevaramos una desagradable sorpresa al mirar el sitio nuevamente... (y si piensan "pero si no conoce otra_tabla y algun_campo no hace nada", estan muy equivocados. he visto flacos en seminarios de seguridad mandarse cada SQL Injection que sinceramente le hacen a uno no subestimar jamas este tipo de vulnerabilidades)
Cross-site Scripting
Esta vulnerabilidad consiste en escribir en la pagina web contenido levantado directamente de la DB, que a su vez tiene que poder ser dado de alta por los usuarios. De este modo, un usuario podria ingresar como valor de un campo "<script language=JavaScript>alert('Hola, tu pagina ha sido hackeada por TuViejaEnTanga TEAM!');</script>", que al momento de ser levantado de la DB y escrito en pantalla va a causar que el el codigo HTML ingresado se imprima como tal, ejecutandose en el navegador del cliente.
Ejemplo de codigo vulnerable (http://www.egelforum.net/forum/showp...5&postcount=13, cortesia de CcCamikaze):
como ven, los campos nombre, clan, lugar y edad son levantados directamente del recordset e impresos en pantalla, haciendo posible el ataque.
La Solucion
Espero que para este momento ya se hayan convencido de que esto ES un problema serio de seguridad y que si bien quiza en sus proyectos actuales no sea un tema de vida o muerte, es algo que en el mercado laboral puede terminar siendo bastante contraproducente...
Aparte, la solucion es lo suficientemente facil como para que valga la pena acostumbrarse a programar siempre asi y dormir tranquilo por las noches.
SQL Injection
Aqui tenemos dos tipos posibles de causas. La primera es la del ejemplo que cite: hay un '$cadena' en el querry SQL. Basta impedir que el usuario ingrese comillas en $cadena para asegurarnos de que no puedan usurpar el querry string. Pero hay un caso mas grave aun: cuando se inserta $cadena directamente en el querry string, por ejemplo "... WHERE orco.edad = $edad". Resolver esta vulnerabilidad requiere tres cuidados:
Primero, asegurarse de que todo lo que vayamos a poner en un querry string que sea una cadena de caracteres este entre comillas simples ("nombre = '$nombre'" y no "nombre = $nombre").
Segundo, asegurarse que los valores levantados de un form SON DEL TIPO QUE ESPERO QUE SEAN. aca no importa que el submit del form tenga una validacion x javascript que controle todo. al momento de protejerte contra fallas de seguridad, SIEMPRE ASUMI QUE LOS SUBMITS VIENEN DE PAGINAS QUE EL ATACANTE ARMO, COPIANDO EL FORM Y SU ACTION PERO QUITANDO TODA LA VALIDACION PREVIA QUE PUEDAS HABER PUESTO. esto es molesto pero puede formalizarse y llevarse a algo metodico y rapido/simple de implementar: por cada parametro $_POST o $_GET que vayas a insertar en un querry SQL, haces lo siguiente (este codigo es un ejemplo, siguiendo con el $edad de antes, donde el valor va a insertarse en un campo del tipo entero):
Opcion 1: el valor x defecto del campo es 0
El casteo de cadena a numeros en PHP empieza a leer caracter x caracter de la cadena hasta leer algo que ya no pueda ser interpretado como un numero, y toma este valor y lo pasa a lo que corresponda (int, float, etc.). si la cadena no empieza con algo numericamente valido, devuelve 0.
por ejemplo, si el valor de $_POST['edad'] era "12 monos", devuelve el entero 12. si era "monos", devuelve 0. y si el valor numerico resultara ser de punto flotante, se va a redondear hacia el 0 (osea, "1.2 monos" daria 1 y "-1.2 monos" daria -1. coloquialmente, se truncan los decimales).
Opcion 2: el valor x defecto del campo es otro, o tengo que parar y avisarle al usuario en caso de que haya ingresado un dato invalido.
Ahora ya sabemos que lo que no es cadena esta protejido y que las cadenas estan siempre entre comillas en el querry string, por lo que nos resta impedir que el usuario ingrese comillas al querry. La forma fea es validar que no hayan comillas en la cadena, pero puede resultar imposible (un campo de apellidos deberia permitirle al usuario irlandes ingresar su O'Connor sin problemas). Por suerte de la misma manera en que ciertos caracteres pueden escaparse en las cadenas de PHP (por ejemplo, "\"", que es una cadena conteniendo un unico caracter, el de las comillas), lo mismo ocurre con SQL. y tenemos una hermosa funcion que se encarga de hacer esto por nosotros. Simplemente adaptamos la rutina anterior para que en el caso de un campo string quede asi:
ahora $p_nombre es una cadena que al insertarla en un querry string (SIEMPRE Y CUANDO LA INSERTEMOS ENTRE COMILLAS) estamos seguros que no va a alterar el comportamiento del querry. ojo que dependiendo del tipo de validacion que requiramos puede ser necesario hacerla ANTES de ejecutar mysql_real_escape_string. por ejemplo, si estamos validando por longitud, ya que la cadena segura contiene una \ extra por cada caracter escapado. y al momento de comparar valores de una cadena ya escapada, hay que escapar tambien el valor de comparacion. es decir que si quiero testear que no se registre ningun O'Connor, deberia comparar $p_nombre con mysql_real_escape_string("O'Connor") (o, a mano, con "O\\'Connor").
Con estos tres recaudos en mente, evitamos por completo los ataques de este tipo.
Cross-site Scripting
Nuevamente voy a optar por una solucion que no limite lo que el usuario pueda escribir (ya que una alternativa seria rechazar cadenas del form que contengan > o <, y pedirle al user que ingrese otra cosa).
Aqui la solucion requiere conocimientos de HTML antes que de PHP. Como escribirian en una pagina web el caracter > o < sin que te lo tome como un tag mal abierto/cerrado? Usando entidades! en este caso en particular, > y <. Por suerte nuevamente PHP nos brinda una funcion para hacer esta tarea por nosotros:
Basta colocar esta funcion en todos los lugares donde estemos escribiendo en pantalla un valor que no podamos controlar (no tiene porque ser de una DB, puede ser un valor recivido de un formulario, o peor aun: uno que nosotros mismos nos mandamos hardcodeado!! por ejemplo: supongamos que para mostrar mensajes de error tenemos un error.php que recive por GET una cadena descriptiva para mostrar. asi mostrar errores es tan facil como hacer un redirect a, por ejemplo, "error.php?desc=Clave%20incorrecta". en este caso el valor de desc siempre lo hardcodeamos nosotros, pero alguien que se percate de que estamos escribiendolo directamente en pantalla podria aprovecharlo para realizar un ataque XSS) para evitar esta vulnerabilidad.
La conclusion
Al momento de validar los datos, hay que ser muy ciudadosos en evitar que valores diseñados malignamente puedan afectar el funcionamiento del sistema. Espero que las descripciones y ejemplos de estas dos vulnerabilidades, asi como las formas que sujeri para evitarlas, les sirvan de ayuda para ver en que mundo se estan metiendo, y para que tengan claro que hay que estar al dia con el enemigo, y conocer sus metodos para poder protejernos. Tambien espero que les haya dado un pantallazo de como piensan los "malos", y a no subestimar el potencial danino de un usuario. Asumir que los datos los valido otro modulo es un potencial error de seguridad, nunca hay que confiar en lo que quedo atras. Hay que suponer que todas las demas barreras fueron rotas y la que estamos programando va a ser la ultima de ellas, y que es nuestra responsabilidad el desarollar un sistema robusto y seguro.
A continuacion les dejo las paginas del help pertinentes a las funciones que estube usando. Leanlas asi saben bien QUE es lo que hacen y que parametros les pueden pasar:
is_*
mysql_real_escape_string
htmlspecialchars
htmlentities
Esta ultima no la use. Es una version mas fuerte de htmlspecialchars ya que pasa a entidades TODO lo que pueda ser pasado a entidades (por ejemplo, si el usuario ingreso "había", se va a escribir "había", permitiendole al navegador encargarse de los charsets correctamente)
K
Bueno, el motivo de escribir esto es que estube leyendo un par de los threads de PHP de cami y vi que nadie se detubo a opinar sobre los agujeros de seguridad en el 99% (diria 100% pero no lei todo el codigo posteado) del codigo...
El Problema
Me estoy refiriendo en particular a los errores de injeccion, tanto SQL Injections como Cross-site scripting (XSS para los amigos).
Primero, una descripcion con ejemplos sacados de sus ejemplos:
SQL Injection
esta vulnerabilidad consiste en colocar valores levantados directamente de un formulario en queries SQL. De este modo, un usuario podria ingresar codigo SQL en el campo, que al insertarse directamente en el querry le permitiria ejecutar practicamente lo que quiera contra la DB.
Ejemplo de codigo vulnerable (http://www.egelforum.net/forum/showp...7&postcount=12, cortesia de Sam Fisher):
Código PHP:
$query = sprintf("delete from orcos where nombre='%s'",$_POST["nombre"]);
mysql_query($query);
Cross-site Scripting
Esta vulnerabilidad consiste en escribir en la pagina web contenido levantado directamente de la DB, que a su vez tiene que poder ser dado de alta por los usuarios. De este modo, un usuario podria ingresar como valor de un campo "<script language=JavaScript>alert('Hola, tu pagina ha sido hackeada por TuViejaEnTanga TEAM!');</script>", que al momento de ser levantado de la DB y escrito en pantalla va a causar que el el codigo HTML ingresado se imprima como tal, ejecutandose en el navegador del cliente.
Ejemplo de codigo vulnerable (http://www.egelforum.net/forum/showp...5&postcount=13, cortesia de CcCamikaze):
Código PHP:
while ($fila = mysql_fetch_object($rs)){
echo <<< HTML
<tr bgcolor="#76AEC9">
<td height="8" align="center" valign="middle" bgcolor="#C2DBE7">
<font size="2" face="Arial, Helvetica, sans-serif">$fila->nombre</font></td>
<td height="8" align="center" valign="middle" bgcolor="#C2DBE7">
<font size="2" face="Arial, Helvetica, sans-serif">$fila->clan</font></td>
<td height="8" align="center" valign="middle" bgcolor="#C2DBE7">
<font size="2" face="Arial, Helvetica, sans-serif">$fila->lugar</font></td>
<td height="8" align="center" valign="middle" bgcolor="#C2DBE7">
<font size="2" face="Arial, Helvetica, sans-serif">$fila->edad</font></td>
</tr>
HTML;
}
La Solucion
Espero que para este momento ya se hayan convencido de que esto ES un problema serio de seguridad y que si bien quiza en sus proyectos actuales no sea un tema de vida o muerte, es algo que en el mercado laboral puede terminar siendo bastante contraproducente...
Aparte, la solucion es lo suficientemente facil como para que valga la pena acostumbrarse a programar siempre asi y dormir tranquilo por las noches.
SQL Injection
Aqui tenemos dos tipos posibles de causas. La primera es la del ejemplo que cite: hay un '$cadena' en el querry SQL. Basta impedir que el usuario ingrese comillas en $cadena para asegurarnos de que no puedan usurpar el querry string. Pero hay un caso mas grave aun: cuando se inserta $cadena directamente en el querry string, por ejemplo "... WHERE orco.edad = $edad". Resolver esta vulnerabilidad requiere tres cuidados:
Primero, asegurarse de que todo lo que vayamos a poner en un querry string que sea una cadena de caracteres este entre comillas simples ("nombre = '$nombre'" y no "nombre = $nombre").
Segundo, asegurarse que los valores levantados de un form SON DEL TIPO QUE ESPERO QUE SEAN. aca no importa que el submit del form tenga una validacion x javascript que controle todo. al momento de protejerte contra fallas de seguridad, SIEMPRE ASUMI QUE LOS SUBMITS VIENEN DE PAGINAS QUE EL ATACANTE ARMO, COPIANDO EL FORM Y SU ACTION PERO QUITANDO TODA LA VALIDACION PREVIA QUE PUEDAS HABER PUESTO. esto es molesto pero puede formalizarse y llevarse a algo metodico y rapido/simple de implementar: por cada parametro $_POST o $_GET que vayas a insertar en un querry SQL, haces lo siguiente (este codigo es un ejemplo, siguiendo con el $edad de antes, donde el valor va a insertarse en un campo del tipo entero):
Opcion 1: el valor x defecto del campo es 0
Código PHP:
$p_edad = (int) $_POST['edad'];
por ejemplo, si el valor de $_POST['edad'] era "12 monos", devuelve el entero 12. si era "monos", devuelve 0. y si el valor numerico resultara ser de punto flotante, se va a redondear hacia el 0 (osea, "1.2 monos" daria 1 y "-1.2 monos" daria -1. coloquialmente, se truncan los decimales).
Opcion 2: el valor x defecto del campo es otro, o tengo que parar y avisarle al usuario en caso de que haya ingresado un dato invalido.
Código PHP:
if (is_int($_POST['edad'])) {
$p_edad = $_POST['edad'];
if (!es_edad_valida($p_edad)) $p_edad = -1;
} else $p_edad = -1;
// ahora, si $p_edad = -1, o la edad ingresada no era un valor numerico, o no estaba dentro del rango aceptado por es_edad_valida, nuestra funcion de validacion interna, por lo que podemos decidir que accion tomar, sea asignandole un valor x defecto a $p_edad o mostrando un mensaje de error apropiado.
Código PHP:
$p_nombre = mysql_real_escape_string($_POST['nombre'], $mysql_connection);
// o simplemente $p_nombre = mysql_real_escape_string($_POST['nombre']);
Con estos tres recaudos en mente, evitamos por completo los ataques de este tipo.
Cross-site Scripting
Nuevamente voy a optar por una solucion que no limite lo que el usuario pueda escribir (ya que una alternativa seria rechazar cadenas del form que contengan > o <, y pedirle al user que ingrese otra cosa).
Aqui la solucion requiere conocimientos de HTML antes que de PHP. Como escribirian en una pagina web el caracter > o < sin que te lo tome como un tag mal abierto/cerrado? Usando entidades! en este caso en particular, > y <. Por suerte nuevamente PHP nos brinda una funcion para hacer esta tarea por nosotros:
Código PHP:
while ($fila = mysql_fetch_object($rs)){
$f_nombre = htmlspecialchars($fila->nombre);
echo <<< HTML
<tr bgcolor="#76AEC9">
<td height="8" align="center" valign="middle" bgcolor="#C2DBE7"><font size="2" face="Arial, Helvetica, sans-serif">$f_nombre</font></td>
</tr>
HTML;
}
La conclusion
Al momento de validar los datos, hay que ser muy ciudadosos en evitar que valores diseñados malignamente puedan afectar el funcionamiento del sistema. Espero que las descripciones y ejemplos de estas dos vulnerabilidades, asi como las formas que sujeri para evitarlas, les sirvan de ayuda para ver en que mundo se estan metiendo, y para que tengan claro que hay que estar al dia con el enemigo, y conocer sus metodos para poder protejernos. Tambien espero que les haya dado un pantallazo de como piensan los "malos", y a no subestimar el potencial danino de un usuario. Asumir que los datos los valido otro modulo es un potencial error de seguridad, nunca hay que confiar en lo que quedo atras. Hay que suponer que todas las demas barreras fueron rotas y la que estamos programando va a ser la ultima de ellas, y que es nuestra responsabilidad el desarollar un sistema robusto y seguro.
A continuacion les dejo las paginas del help pertinentes a las funciones que estube usando. Leanlas asi saben bien QUE es lo que hacen y que parametros les pueden pasar:
is_*
mysql_real_escape_string
htmlspecialchars
htmlentities
Esta ultima no la use. Es una version mas fuerte de htmlspecialchars ya que pasa a entidades TODO lo que pueda ser pasado a entidades (por ejemplo, si el usuario ingreso "había", se va a escribir "había", permitiendole al navegador encargarse de los charsets correctamente)
K




Comentario