martes, 7 de febrero de 2012

Blog Mudado...

he creado mi nuevo blog con mi propio dominio:

maycolalvarez.com

sábado, 10 de abril de 2010

Seguridad en PHP: XSS o Scripting de Sitios Cruzados

De los ataques para sistemas web, los XSS (Cross-site Scripting) suelen ser letales, pero no para el sitio, sino para lo usuarios, los ataques XSS se aprovechan de las fallas en la validación de código HTML por parte del servidor para así extraer información del usuario.

¿pero si mi vista no me falla seria CSS y NO XSS?: estas en lo cierto, se les denominó así debido a que podría confundirse con el ya establecido lenguaje de definición de estilos en cascada o Cascading Style Sheets.

Imagina que estas en un sitio con un típico buscador que se basa en un recuadro y el clásico botón "buscar", es un simple formulario pero si desde el servidor no se valida debidamente la data enviada con el fin de evitar la introducción de código html o scripts, podrás colocar lo siguiente:


<script>alert("Hola no estoy protegido contra XSS XD");</script>

Cuando presiones en el botón buscar, es muy probable que el resultado que envié el servidor sea que no se ha encontrado dicho texto, y por gajes del oficio se muestre el mismo texto buscado, es decir un clásico echo $_GET['texto_buscado']; en php; y como lo ingresado es una declaración de script, el resultado en tu navegador seria el cuadro de alerta con el mensaje en cuestión

Te resultará gracioso y te preguntarás ¿¡pero esta cosa tan infantil puede hacerme tanto daño!? y soltarás alguna risa, ¿pero si resulta que te llega un correo o das click en un mensaje publicitario (que o muy bien pudo ser inyectado en el sitio, o lo abres desde otra página que tienes abierta) que contenga algún link+javascript, preparado para hacer uso de ese formulario no validado, que en lugar de ese inocente alert trate de robar tus datos de las cookies del sitio para ser almacenados en otro sitio atacante?: el resultado sera que el mismo podría enviar todos los datos de las cookies almacenados en el navegador a un servidor y con los mismos datos podrían hacerte mucho daño, ¡sobre todo si el formulario tuviese relación alguna con dinero!.

Ahora el tema se vuelve un poco más interesante, pero aclaremos una cosa, esto es sólo la punta del isberg, porque los ataques XSS se clasifican en dos tipos:

XSS indirecto o reflejado: tal como el ejemplo anterior, se basa en enviar código javascript que trate de robar las cookies de la sesión del usuario, de manera que el mismo sea ejecutado por el navegador debido a que o bien sea impreso en la salida del html (<script> embebido) o sea el destino (src="javascript:") de un iframe que sea llamado con la pagina HTML, permitiendo una exitosa ejecución de scripts

XSS Directo o persistente: a diferencia del anterior se basa en tratar de explotar la debilidad el filtro de HTML con etiquetas y métodos que permiten ejecutar código y que no son muy conocidos. dependen también del navegador, por ejemplo, en Wikipedia aparecen muy buenos ejemplos:


<BR SIZE = "&{alert('XSS')}" >
<FK STYLE = "behavior: url(http://yoursite/xss.htc);" >
<DIV STYLE = "background-image: url(javascript:alert('XSS'))">
<div fu = "alert('Hola mundo');" STYLE = "background-image: url(javascript:eval(this.fu))" >


¿¡SOLUCIONES!?: muchas, y dependen mucho de como tienes desarrollado tu site php, las más sofisticadas son las que proveen algunos frameworks php como symfony en el mecanismo que pasa variables a la vista desde el controlador, de no usar un framework un simple htmlentities o htmlespecialchars serian una solución rápida para tal lío, o si te comprometes a realizar un avanzado filtro para eliminar todo vestigio de html con script peligrosos para tu sistema.

Como dije este artículo solo es la punta del isberg, todavía se siguen descubriendo nuevas y novedosas formas de ataques XSS, pero siempre es bueno estar preparado para las batallas a venir, mucha suerte!

viernes, 26 de marzo de 2010

Seguridad en PHP: CSRF o Falsificación de petición en sitios cruzados

De entre todos los ataques que puede recibir tu sistema en PHP los CSRF o (Cross-site request forgery) son los más fáciles de implementar, ya que éstos se aprovechan de la "confianza" que le tiene el sistema al usuario.

¿pero como que mi sistema confía en el usuario?: pues es simple, si lo miras desde el punto de vista del servidor, cuando éste recibe un formulario, simplemente recibe los datos del formulario, porque se supone que el usuario ya está logueado (o ha iniciado sesión, cosa que procesas previamente obvio) y por ende él solo se encarga de una cosa: procesar la data y generar la salida.

Supongamos que nuestro querido usuario está ingresando registros nuevos en un CRUD de nuestro sistema, y por cosas del destino se dispone a chatear en messeger o hasta en el mismo facebook, como estos sitios te permiten compartir links, un atacante puede simplemente pasarle un link a nuestro usuario para que ingrese en determinada página, cuya página esta preparada para hacer un submit de un formulario oculto hacia el url de nuestro servidor, pudiendo enviar data hacia el archivo php que se encarga de "crear" ese nuevo registro, como el usuario está logueado y generalmente el navegador mantiene la cookie de la sesión (depende del navegador), el ataque maestro logra pasar el sistema de login y pudiendo afectar a nuestro sistema; en ningún caso el código de nuestro php espera una identificación del formulario, porque simplemente validamos las sesiones; otra modalidad consisten en colocar links de peticiones hacia el mismo domino en imágenes, por ejemplo (<img src="http://mydomain/archivo.php?dato=valor" alt="" />) en cuyo caso del lado del servidor (php por ejemplo) no verifique el tipo de petición Post o Get, ¿donde se evidencia?: en el mal uso de $_REQUEST en el caso de PHP.

¡¡¡OMG ¿y como lo resuelvo?!!!: pues por suerte la solución es tan simple que da risa, este pequeño (y menudo) inconveniente se resuelve con los llamados tokens; los tokens son simplemente un ID único que podremos generar al momento que nuestro usuario solicita el formulario, de esta forma al crear nuestro formulario añadimos un dato oculto adicional que nos permitirá verificar (cuando el formulario sea recibido) que dicha data es confiable, para ello ese mismo ID necesitamos registrarlo en el servidor y nada mejor que con una variable de sesión, y no sólo eso para darle más gusto a la sopa es preferible marcar el tiempo justo en que el form se creó, para así verificar cuanto tiempo se demoró (depende de la cantidad de data en el formulario) y hacerlo más seguro, es inverosímil que un formulario tan simple se demore tanto en ser enviado, así sancionamos además a los usuarios perezosos XD.

Pero, si tengo que crear un ID para cada formulario, ¿necesito abrir todos los php relacionados y añadírselos manualmente, ¡menuda tarea!?: sí, es lamentable, pero es la única forma, aunque gracias a la re-utilización de código la tarea es más fácil, simplemente creamos una clase que automatice esta tarea y así ahorrarnos muchas lineas de código en cada formulario que tengamos:


archivo tokenform.php: (PHP 5.0+)
<?php
/**
* administra un ID único (tokens) para un formulario previamente registrado
* GPL versión 1
* @author Maycol Alvarez
*/
class tokenForm {

/**
* Constante para prefijos de la las variables de sesion
*/
const _prefix = 'token_';

/**
* Nombre del formulario
* @var string
*/
private $_formname;
/**
* Tiempo en segundos para validar caducidad del token
* @var int
*/
private $_seconds=300;

/**
* Construye un manejador de tokens
* @param string $nameForm Nombre del formulario
* @param bool $preventSessionStart previene el inicio de sesion omitido
*/
public function __construct($nameForm,$preventSessionStart=true) {
if($preventSessionStart){
@session_start();
}
$this->_formname=self::_prefix.$nameForm;
//genera el token si no existe
if(!isset($_SESSION[$this->_formname])){
$this->create();
}
}

/**
* Crea los datos de sesion del token
*/
public function create(){
$token= md5(uniqid(rand(), true));
$timestamp = mktime();
$_SESSION[$this->_formname]['uid']=$token;
$_SESSION[$this->_formname]['mkt']=$timestamp;
}

/**
* Devuelve el UID del token
* @return string token
*/
public function getToken(){
return $_SESSION[$this->_formname]['uid'];
}

/**
* Devueve la fecha de creacion del token
* @return int mktime
*/
public function getTime(){
return $_SESSION[$this->_formname]['mkt'];;
}

/**
* Valida si el token del formulario enviado es confiable
* @param mixed $source permite cambiar el origen del array con la data del
* formulario, por defecto es null y se utiliza $_POST
* @return bool true en caso de exito
*/
public function validate($source=null) {
if($source==null){
$srctoken=$_POST[$this->_formname];
}else{
$srctoken=$source[$this->_formname];
}
//valida si los tokens coinciden:
if($this->getToken()==$srctoken){
//valida si se evalua caducidad:
if($this->_seconds!=0){
//ahora valida si estan el el rango de tiempo
if((mktime() - $this->getTime())<= $this->_seconds){
$returned= true;
}else{
$returned= false;
}
}else{
$returned= true;
}
}else{
$returned= false;
}
$this->clear();
return $returned;
}

/**
* Devuelve el input correspondiente al token para el form
* @return string Input HTML tag
*/
public function getInput(){
return sprintf(
'<input type="hidden" name="%s" value="%s" />',
$this->_formname,
$this->getToken()
);
}

/**
* Imprime el input correspondiente al token para el form
*/
public function dump() {
echo $this->getInput();
}

/**
* Borra el token
*/
public function clear(){
unset($_SESSION[$this->_formname]);
}

/**
* Establece el margen de segundos para considerar caduco un token
* @param int $seconds 0 para omitir esta validacion
*/
public function setTime($seconds){
$this->_seconds=$seconds;
}
}
?>


Con esta simple clase resolvemos muchas de las funciones que debe cumplir un token, para ejemplo veamos su funcionamiento en el archivo que crea el formulario, y el que lo procesa:


archivo formulario.php:
<?php
require_once 'tokenform.php';
$formulario = new tokenForm('form1');
?>
<form action="proceso.php" method="POST">
<input type="text" name="dato1" />
<input type="text" name="dato_n" />
<?php $formulario->dump(); ?>
<input type="submit" />
</form>


archivo proceso.php:
<?php
//procesando el token
require_once 'tokenform.php';
$formulario = new tokenForm('form1');
if(!$formulario->validate()){
echo 'Formulario no valido';
}
?>


Y con tan pocas líneas por archivo ya resolvemos semejante problema y hacemos a nuestro sistema más seguro XD.

jueves, 18 de diciembre de 2008

Compilando en PHP

Aunque esto no sea novedad para algunos, en PHP también podemos "compilar" el código, gracias a una extensión de PEAR(PHP Extensión and Application Repository) llamada bcompiler; ¿el porqué coloco compilar entre comillas?: porque en realidad bcompiler no compila, sino que genera los "bytecodes" que php normalmente genera para ejecutar los scripts en memoria.

Para los que conocen JAVA sabemos que los bytecodes son un semi-lenguaje de máquina optimizado para una rápida lectura y ejecución por medio de las denominadas "Máquinas Virtuales" como lo es JAVA, y últimamente .NET Framework, entre otras.

En el manual sobre bcompiler (en inglés) se trata de la compresion del bytecode, y para configurarlo se requiere de muchas cosas que decidí omitir; lo que pasa es que el bytecode resultante del script puede ser incluso más del doble del tamaño (en bytes) que el archivo fuente original, ¿por qué?: simplemente porque en el bytecode se especifican muchos más datos, además del algoritmo en sí, de modo que la máquina virtual pueda traducirlo al lenguaje de máquina de la máquina (valga la redundancia) en donde se ésta ejecutando, como la mundialmente conocida arquitectura x86.

Instalación

Windows: En paquetes como WAMP y XAMMP (windows), ésta librería ya viene incluida, sólo basta instalar/configurar primero PEAR y luego incluir en el archivo de configuración php.ini la línea: extensión=php_bcompiler.dll en "dynamic extensions", PEAR no es realmente necesario, pero puede ser que necesiten de algunas funciones de PEAR.

GNU/Linux: aquí es otra Historia, pueden probar si el paquete XAMMP para linux trae la librería, pero no se los aseguro, además con PEAR lo puende instalar, de todos modos intente añadir al archivo de configuración php.ini: extensión=bcompiler.so y luego pruebe con phpinfo(); si la extensión bcompiler aparece (debe reiniciar apache).

Ubuntu 8 LTS: con el APACHE/PHP instalados por synaptic, instale los siguiente paquetes por
synaptic:

  • pear
  • php5-dev
  • bzip2
  • libbz2-1.0
  • libbz2-dev
  • libzip1
  • libzip1-dev
  • pbzip2
Una vez instalados abra el terminal (consola) y ejecute (le solicitará contraseña del administrador):

sudo pecl install bcompiler-0.8.tgz

Añada al archivo de configuración php.ini: extensión=bcompiler.so
Reinicie apache y compruebe que aparece la extensión bcompiler por phpinfo();

¡Manos a la Obra!

Para precompilar nuestro primer script necesitamos 3 archivos (origen.php, destino.php, compila.php); en origen.php estará el código fuente, a destino.php debe otorgarle PERMISOS DE ESCRITURA (GNU/Linux), y en compila.php coloque lo siguiente:

<?php
$fh = fopen("destino.php", "w");
bcompiler_write_header($fh);
bcompiler_write_file($fh, "origen.php");
bcompiler_write_footer($fh);
fclose($fh);
?>

Sólo basta ejecutar desde el explorador http://localhost/compila.php y si no aparecen errores está listo!: ejecute http://localhost/destino.php y aparecerá lo mismo que en origen.php, pero si revisa el código de destino.php notará que ya no es el mismo código, sino un conjunto de datos incomprensibles.

Puede incluso compilar todo tu sistema en php y puede utilizar ambos archivos: los bytecodes o los normales, bcompiler se encarga de todo, puede incluso hacer include y require a un archivo bytecode php de bcompiler, ambos pueden tener la extensión ".php".

Eso sí: se puede duplicar el tamaño en bytes del sistema, pero el rendimiento es el mismo: los no-compilados deben de interpretarse y luego compilarse en memoria antes de ser ejecutados; los precompilados "pesados" pasan a memoria inmediatamente, y si utiliza bytecodes comprimidos tienen que "descomprimirse" y ejecutarse. así que básicamente el rendimiento de su sistema no variará mucho.

El único detalle sería el comportamiento de __LINE__ y de los reportes de errores, como es una extensión experimiental puede ser que el valor retornado por éstas funciones no sea el mismo que cuando se ejecuta un script normal (no precompilado); por eso le recomiendo evaluar bien todo su código antes de precompilarlo.

lunes, 6 de octubre de 2008

Instalando... Postgres en Ubuntu GNU/Linux

Instalar Postgres en GNU/Linux no es tan sencillo como en windows, en este post sólo trataremos de instalarlo en Ubuntu 8.04 GNU/Linux que también aplica para el Debian 4 de donde deriva éste.

1) En aptitude o synaptic instala los siguientes paquetes:
  • postgresql (la última versión disponible 8.03)
  • postgresql-client (generalmente es requerido por el anterior y se instala al instalarse el postgres)
  • pgAdmin3 (última versión)
  • php5-pgsql (para conectarse desde PHP)
Te recomiendo synaptic, es más cómodo; luego de instalar los paquetes para utilizar postgres hay que cambiar la contraseña del usuario administrador en postgres y la del usuario postgres en ubuntu, es decir, son 2 usuarios: uno de la base de datos y uno del sistema operativo; en principio trata de elegir una contraseña simple si lo que quieres es desarrollar, cuando instales tu aplicación y la base de datos en el equipo donde se ejecutará elije una contraseña compleja, no lo olvides.

2) Cambiando la contraseña del usuario administrador de postgres (postgres): abrimos el terminal y tecleamos:

sudo su postgres -c psql template1 (te pedirá la contraseña del administrador del Sistema root)

ALTER USER postgres WITH PASSWORD `password´; (colocamos la contraseña)

\q

3) Ahora cambiamos la contraseña del usuario postgres del sistema operativo en la misma consola, puede que nos vuelva a solicitar la contraseña del root:

sudo passwd -d postgres

sudo su postgres -c password (omite este paso si el anterior te muestra: password changed o contraseña cambiada)

¡Y listo!, con esto podremos utilizar postgres y abrir pgadmin, puede que tengas que reiniciar el servicio del postgres desde la consola:
sudo /etc/init.d/postgresql-8.3 restart.

Ésta es la conflagración básica del postgres que SÓLO ADMITE CONEXIONES LOCALHOST lo que quiere decir que el servicio postgres ésta cerrado a conexiones remotas, lo que es muy útil cuando se utiliza con PHP ya que éste se conecta de modo local y evitamos que se conecten a nuestra base de datos de afuera, si quieres que el servicio postgres admita conexiones externas (remotas) puedes seguir los pasos de la siguiente guía omitiendo los anteriores que yo expuse aquí: Linuxsan- Instalando Postgres en Ubuntu

Y hasta aquí esta pequeña pero útil guía para comenzar con postgres.

domingo, 5 de octubre de 2008

Scareware, Una FALSA amenaza

ESTO ES FALSO, NO SE DEJE ENGAÑAR¡ATENCIÓN!, las nuevas formas de ataques informáticos cada día son de lo más impresionantes, una de éstas es la denominada "scareware" o "software de susto", que consiste en un falso mensaje de un sitio web en que se le notifica al usuario que su computador ésta infectado, y en el cual le recomienda instalar un programa para supuestamente desinfectarlo como en la imagen de éste post que parece convincente, tal cosa no existe, lo único que se le asemeja es el servicio en línea de panda labs para escanear el equipo, pero éste servicio no notifica hasta instalarse la presencia de algún virus, por lo que usted debe ignorar cualquier mensaje o popup que le muestre que su equipo está infectado.

Hablando en términos de programador Javascript no tiene el acceso suficiente al sistema de archivos para "realmente" verificar si el equipo ésta infectado (a menos que el Script Host esté activado y el script con VBScript, pero eso ya es historia), además ¿cuanto tiempo tarda su antivirus en analizar su disco o la memoria completa? mínimo minutos, no segundos e imagine cuanto tardará el supuesto script en descargar la lista de firmas de virus si mínimo la del panda que tengo hasta hoy ¡pesa 8 MB!, es decir, úse la lógica, ni firefox ni Microsoft IE ni algún otro navegador podrán avisarle que su equipo está infectado.

Así que pendiente, ignore todo aquello que supuestamente le recomiende descargar "falsas soluciones", los antivirus nunca le preguntarán por la descarga de un programa, ellos sólo actualizan su firma y motor de búsqueda, y en el caso del servicio de panda labs éste debe instalarse primero, NUNCA le avisará antes.

sábado, 4 de octubre de 2008

Seguridad en PHP: Inyecciones SQL

Como lo prometido es deuda, voy a segmentar el tema Seguridad PHP en varias partes, debido a que es un tema delicado y en que debo enfatizar en el porqué de cada medida de seguridad.

Entrega 1: Inyecciones SQL

¿que qué es una SQL Inyection?: es un método para alterar la cadena SQL y que el interprete de la base de datos ejecute otras acciones perjudiciales para el sistema; la más simple que voy a explicar aquí trata de vulnerar a la mayoría de los sistemas que INICIAN SESIÓN desde una tabla en su base de datos; debo aclarar que ésta técnica NO ES CORRECTA para empezar, pero como la mayoría lo ha echo así, no me queda más remedio que indicarles las providencias necesarias para resolver éste problema.

Supongamos que su sentencia para iniciar sesión sea esta:
$usuario=$_POST['usuario'];
$contra=$_POST['contra'];
$sql = "SELECT * FROM usuarios WHERE user='".$usuario."' AND password='".$contra."';";
if (mysql_fetch_array(mysql_query($sql,$coneccion))) {
//acceso al sistema
}

Como podemos ver ésta sentencia devuelve TRUE en el caso de que se encuentre el usuario y la contraseña correctos dándole acceso al sistema, pero en el caso de que sea un cracker el que envía un formulario falso en donde coloque en la variable del POST $usuario el siguiente valor: ' OR 1=1 #
Pues sencillamente el carácter de apóstrofo (') cerraría la cadena en SQL y con el OR 1=1 quedaría que user='' OR 1=1 que devuelve TRUE y para rematar el carácter numeral (#) que en la mayoría de las bases de datos es el de comenzar comentarios, lo que significaría que se ignoraría el resto de la consulta quedando así para el interprete SQL: SELECT * FROM usuarios WHERE user='' OR 1=1 y por ende el resultado sería TRUE, otorgándole acceso al sistema.

¿Cómo puede ser?, ¿hay manera de evitarlo?, Las Magic Quotes activadas en PHP en la MAYORÍA DE LOS CASOS lo resuelven, pero ésta no es la única forma de SQL Inyection que existe y las Magic Quotes no lo resuelven todo, consulten el manual de PHP (Tópico: Seguridad en bases de datos/Inyección SQL) y verán lo que es bueno...; en fin, les daré los consejos fundamentales para evitar un SQL Inyection:
  • Revisa que no existan cadenas ni caracteres como éstos: (OR, AND, ', -- y #) en las variables del POST.
  • Escapa las cadenas con mysql_real_escape_string o addslashes ¡es mejor que las magic quotes!
  • Si la variable es numérica, verifica que lo sea con is_numeric().
  • NUNCA utilices al usuario administrador de la base de datos, crea uno con el acceso y privilegios necesarios.
  • HASHEA las contraseñas ¡por favor!, si no sabes de que te hablo busca en google Hash o MD5 y con eso te digo todo.
  • No vuelvas a iniciar sesión así en tus futuros sistemas, de ser posible utiliza el sistema de privilegios de usuario y crearle un usuario de base de datos a cada usuario; ésta es la forma correcta de hacer las cosas, tarda mucho lo sé pero te da un sinfín de ventajas.
Y con esto doy por terminada la primera entrega de Seguridad en PHP ¡No apto para novatos!, espero que tu cerebro quede lo suficientemente aturdido como para seguir mis consejos, ¡hasta la proxima!.