domingo, 19 de septiembre de 2010

Charla UNNE- RFI LFI & Directory transversal en Descarga de archivo

El sabado 18/09, (ayer :)), los chicos de la Franja me invitaron a un evento que se realializo en la Unversidad de la UNNE, FaCENA, a las 9 hs, donde hubieron 2 charlas, una de Evaluación de Sistemas y otra mia que fue sobre Seguridad en PHP. Quiero agradecer a los chicos de la Franja (Chici, Carlitos, Aguntin ), por haberme dado la oportunidad de dictar una conferencia en la UNNE donde soy alumno, y donde si dios quiere termino este año Licenciatura.
Bien les paso algunas fotos y la presentación, donde no esta muchas cosas que mostre de codigo en php ya que no estraban en los slays, pero si algunas cosas importantes.





Fotos del Evento


con Bernardo, que hizo un video de LFI por metodo POST










Asado en lo de Padilla, y Yamila cocinando naaaa jajajaja




Saludos

lunes, 23 de agosto de 2010

Prepared Statements PHP y MySQL- Alta usuario

Anduve probando unas cosas en PHP, en estos dias que estaba mas libre con la facultad, y me decidi crear un alta de usuario, donde haya una seguridad relativamente alta.
Primero que nada use lo que se llama como Prepared Statements, que nos permite realizar una consulta al DBMS, pudiendo por asi decirlo, separar lo que ingresa el usuario del codigo mismo, es decir que el motor sabe que valor es ingresado por el usuario, con la ventaja pricipal en seguridad de que no pueden inyectar codigo arbitrario en la consulta conocido como SQL INJECTIO

Coloquemos un ejemplo de lo que seria una consulta con Prepared Statements, un consulta a un articulo de una noticia.

$mysqli = new mysqli('host', 'user', 'pass', 'db');
$stmt=$mysqli->prepare("SELECT * FROM NOTICIAS WHERE ID=?");
$stmt->bind_param('i', $_GET['id']);
$stmt->execute();

En la primer linea creamos el un objeto mysqli, en la segunda preparamos la consulta para enviar al motor, note que el valor que se espera se coloca con el caracter ?, tercera linea agrega la variable a la sentencia preparada, fijese que está el valor i, lo cual significa que lo que se espera será un entero. Tambien podemos usar s (string), d (double), b (binary data) dependiendo lo que se necesita.
En la cuarta linea ejecutamos el Statement Prepared. :)
En la sección de Prevencion de SQL INJECTION de la OWASP, lo tenemos como primer medida de seguridad. Lo bueno de esto es que el codigo no queda tan sucio, al filtrar todas las entradas del usuario con funciones como int(), settype(), is_numeric(), mysql_real_escape_string() etc.

El alta creado tiene las siguientes caracteristicas: La contraseña se encripta 3 veces en md5 (lo cual se puede cambiar desde el codigo), debe ser mayor a 7 caracteres, y debe contener al menos una mayuscula y un numero.
Este es el codigo con el formulario en HTML y el PHP.


<html>

Alta Usuario

<form action='' method='post'>
<br>
Nombre:
<br>
<input type='text' name='nombre'>
<br>
Email
<br>
<input type='text' name='email'>
<br>
Usuario:
<br>
<input type='text' name='user'>
<br>
Contraseña:
<br>
<input type='password' name='pass'>
<br>
<input type='submit' value='Enviar' name='enviar'>
</form>

function pass_cript($pass){
for($i=0;$i <3;$i++){ pass="md5($pass);" stmt=" $mysqli">prepare("SELECT usuario FROM usuarios WHERE usuario=?");
$stmt->bind_param('s', $usuario);
$stmt->execute();
$stmt->bind_result($result);
#tercer if
if($stmt->fetch()== false)
{
verificar_pass();
}
else {
echo "El usuario ya existe";
}
}

function alta(){

global $mysqli;
global $usuario;
global $password;
global $email;
global $nombre;
$password=pass_cript($password);
$stmt=$mysqli->prepare("INSERT INTO usuarios (usuario, password, Email, Nombre) VALUES (?, ?, ?, ?)");
// Bind your variables to replace the ?s
$stmt->bind_param('ssss', $usuario, $password, $email, $nombre);
// Execute query
$stmt->execute();
printf("%d Fila Insertada.\n", $stmt->affected_rows);
// Close statement object
$stmt->close();
echo "El usuario ".$usuario." ha sido dado de alta!!";
}

function verificar_pass(){
global $password;

if(strlen($password) > 7 )
{
if (preg_match('/[A-Z]+[0-9]+/', $password) || preg_match('/[0-9]+[A-Z]+/', $password))
{
alta();
}
else
{
echo "El password debe estar constituido por al menos una mayuscula y al menos un digito";
exit;
}
}
else
{
echo "El password debe ser mayor a 7 caracteres";

}
}




#Prepared Statements
$mysqli = new mysqli('host', 'user', 'pass', 'db');
if (mysqli_connect_errno())
{
printf("Can't connect to MySQL Server. Errorcode: %s\n", mysqli_connect_error());
exit;
}
#Primer if
if(!empty($_POST['enviar']))
{
#segundo if
if (isset($_POST['nombre']) && !empty($_POST['nombre']) && isset($_POST['email']) && !empty($_POST['email']) && isset($_POST['user']) && !empty($_POST['user']) && isset($_POST['pass']) && !empty($_POST['pass']))
{

$nombre=$_POST['nombre'];
$usuario=$_POST['user'];
$password=$_POST['pass'];
$email=$_POST['email'];
verificar_usuario();

}#cierre segundo if
else
{
echo "Debe rellenar los camposs gracias";
}
}#cierro primer if

?>


Voy a explicar lo que hace el codigo, osea su secuencia para que se entienda y luego los que quieren usarlo lo usen para sus
altas XD o para probar.
En la linea 105 es donde verifico que todos los datos del formulario esten definidos y que no esten en blanco, en caso afirmativo
tomo esos valores y voy a la función verificar_usuario().
Esta función lo que hace es verificar si el usuario que se quiere dar de alta existe en la base de datos,
en caso de que llegara a existir imprimira una leyenda con "El usuario ya existe", y se terminaria el proceso. En caso negativo se
se dirige a la función verificar_pass().
Esta función realiza el testeo de seguridad del password donde debe cumplir que: debe ser mayor a 7 caracteres y
debe contener al menos una mayuscula y un numero en el.
Cumpliendo estas restricciones en el password se dirige a la función alta(), que en la linea 57,
el valor de $password es pasado a a la función pass_cript(), para encriptarlo en una cantidad de 3 veces en MD5, luego basicamente
se realiza el INSERT INTO correspondiente al alta :).

Aca les dejo algunos enlaces para que sigan leyendo sobre Prepared Statemen

http://mattbango.com/notebook/web-development/prepared-statements-in-php-and-mysqli/
http://www.ultramegatech.com/blog/2009/07/using-mysql-prepared-statements-in-php/
http://www.hiteshagrawal.com/mysql/mysql-prepared-statement-in-php



Saludos

miércoles, 18 de agosto de 2010

JOINEA 2010


Bueno despues de mas de un mes sin postear, por motivos de la facultad y otras cosas vuelvo.

Este fin de semana se viene la JOINEA - Jornade de Integración, Extensión y Actualización de Estudiantes de Sistemas - el cual se realiza en Apostoles Misiones organizado por los chicos de la UNaM.
Hubo un llamado de papers, envie uno y quedo seleccionado, sobre Segridad Web: SQL injection y Directory Transversal, pero por motivos de tiempo en la expocicion lo reduje solamente a SQL Injection ya que se haria interminable.
Hay buen nivel de las expociciones, asi que seguro voy a ir a aprender mucho y conocer gente nueva el cual son los objetivos principales. Aqui les dejo el cronograma del evento.
Mañana a las 12 salimos de la Costanera, ya que el centro de estudiantes de la FA.CE.NA organizo el viaje para cualquiera a un costo accesible que incluye viaje, hospedaje y comida, en donde se lleno un colectivo con capacidad para 45 personas.
Aqui les dejo mi presentacion,




Saludos

lunes, 21 de junio de 2010

Directory Transversal en descarga de Archivo

Me encontre con una web la cual nesesitaba descargar un fichero que me resultaba de interes, donde la direccion de descarga era similar a esto:
http://www.sitio.com/descarga/download.php?file=archivo.extension, por lo que me propuse verificar si dicho downloader tenia alguna validación, para provocar un Directory Transversal.
Lo que realize fue la peticion de este modo http://www.sitio.com/descarga/download.php?file=download.php, para descargarme el archivo download.php
y heureca :)



Al abrir el archivo download.php podemos empezar a verificar las fallas de la validaciones:


$filename = $_GET['file'];

// addition by Jorg Weske
$file_extension = strtolower(substr(strrchr($filename,"."),1));

if ( ! file_exists( $filename ) )
{
echo "not file";
exit;
};
switch( $file_extension )
{
case "pdf": $ctype="application/pdf"; break;
case "exe": $ctype="application/octet-stream"; break;
case "zip": $ctype="application/zip"; break;
case "doc": $ctype="application/msword"; break;
case "xls": $ctype="application/vnd.ms-excel"; break;
case "ppt": $ctype="application/vnd.ms-powerpoint"; break;
case "gif": $ctype="image/gif"; break;
case "png": $ctype="image/png"; break;
case "jpeg":
case "jpg": $ctype="image/jpg"; break;
default: $ctype="application/force-download";

header("Pragma: public"); // required
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false); // required for certain browsers
header("Content-Type: $ctype");
// change, added quotes to allow spaces in filenames, by Rajkumar Singh
header("Content-Disposition: attachment; filename=\"".basename($filename)."\";" );
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($filename));
readfile("$filename");


?>


en la primera linea, podemos ver que toma el nombre del archivo por la variable $_GET['file'] y lo guarda en $filename, luego en la linea 4 lo que hace esta funcion es tomar todo el nombre del archivo y solo obtener su extension por ejemplo, si el archivo a descargar es archivo.pdf la variable $file_extension va a contener pdf, aca fue lo que me parecio algo muy bueno cuando estaba leyendo el codigo, pero solo lo usa para colocar el tipo de MIME(linea 12), lo cual esta muy bien pero tambien ubiera filtrado con ellos una black-list para que no se pueda descargar entensiones peligrosas.
En la linea 31 mostramos el nombre del archivo que le aparecera al usuario cuadno descarga y en la linea 33 el Content-Lenght es el tamaño del archivo, ah y la linea 29 es el MIME del que hablamos anteriormente. :)
Como la descarga se realizo con un php podemos llegar a pensar que podemos descargar cualquier php de la web, entonces buscaremos descargar el index que es donde puede haber datos interesantes de conexion de base de datos :)

http://www.sitio.com/descarga/download.php?file=../index.php



Al abrir el archivo index.php vemos esta informacion un poco mas interesante que la anterior



Nos descargamos el archivo class_DB.php, y dentro de ese archivo vemos una linea mucho mas interesante


include("conectar.php");


Y como piensan que sigue esto? ........... Si correcto, nos descargamos el archivo conectar.php




Bien ahora queda buscar el phpmyadmin, colocando www.sitio.com/phpmyadmin de la forma facil, y si no encontramos ahi podemos largar algun bruteador. O tambien que es la que mas me gusta por el cliente de mysql, primero comprobamos si el puerto esta abierto hacia el publico con Nmap :)



y ahora nos conectamos




Creo que queda entendida la peligrosidad de lo que se expone a no validar estas entradas asi como todas las entradas que se le da al usuario, siempre hay que verificarlas. Desde una simple bajada de archivo pudimos ingresar a sus datos, romper la confidencialidad de ellos, podriamos romper su integridad y hasta la disponibilidad de su sitio Web.

Para completar, al mismo script, le coloque seguridad, para que no se pueda descargar alguna extension peligrosa como php, asp, inc, etc. y para que no se pueda escalar directorios.



$index="http://".$_SERVER['SERVER_NAME']."/index.php";
$filename = basename($_GET['file']);
$ruta="descargas/".$filename;
// addition by Jorg Weske
$file_extension = strtolower(substr(strrchr($filename,"."),1));
#------------Bloque Seguridad ------------------
if(preg_match('/(php|asp|ini|inc|bak)/',$file_extension)){
header("Location:".$index);
}

elseif( $filename == "" )
{
header("Location:".$index);
}
elseif( ! file_exists( $ruta ) )
{
header("Location:".$index);
};

#-----------Fin Bloque Seguridad--------------
switch($file_extension )
{
case "pdf": $ctype="application/pdf"; break;
case "exe": $ctype="application/octet-stream"; break;
case "zip": $ctype="application/zip"; break;
case "doc": $ctype="application/msword"; break;
case "xls": $ctype="application/vnd.ms-excel"; break;
case "ppt": $ctype="application/vnd.ms-powerpoint"; break;
case "gif": $ctype="image/gif"; break;
case "png": $ctype="image/png"; break;
case "jpeg":
case "jpg": $ctype="image/jpg"; break;
default: $ctype="application/force-download";
}

header("Pragma: public"); // required
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false); // required for certain browsers
header("Content-Type: $ctype");
header("Content-Disposition: attachment; filename=\"".$filename."\";" );#nombre del archivo en la descarga
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($ruta));
readfile("$ruta");



?>


La segunda linea usamos la funciona basename, que logra que si el usuario coloca por ejemplo /carpeta/carpeta2/archivo.pdf, nos devilvera archivo.pdf, eliminado toda la ruta anterior y con la ayuda de la linea 3 ponemos una carpeta llamada descargas, la cual en ella contendra todos los archivos para descargas (valga la redundancia), y donde el usuario nunca podra escaparse de ella.
Asi por ejemplo si el usuario intenta descargar de esta forma
www.sitio.com.ar/download.php?file=/etc/passwd la variable $filename quedara con el valor passwd, y concatenando con la carpeta descargas la variable $ruta contendra descargas/passwd
En el bloque de seguridad, la primer linea verifica con una expresion regular que si existe esas extensiones, te direcciones al index (se puede colocar tantas como guste en esa lista).


Eso es todo

Saludos