seguridad

Cadena aleatoria segura criptográficamente con Python

Generar contraseña o cadena segura en Python

Con el lenguaje de programación Python es muy fácil generar un token o cadena segura, hablando criptográficamente.

Recientemente se ha introducido el módulo secrets que nos permite generar una contraseña aleatoria y segura para usarla como token, clave de encriptación o cosas de esas.

Cadena aleatoria segura criptográficamente con Python

Cadena aleatoria segura criptográficamente con Python

Si no tienes Python 3, es hora de actualizar. Mira cómo instalarlo aquí.

Continue reading…

Cuando descubrí que las contraseñas de una web no estaban hasheadas

Introducción

Esto es otra historia personal, se trata sobre una plataforma web que está mal programada, de la cual descubrí que no hashea (o encripta como dicen por ahí, aunque no es la forma correcta) las contraseñas de los usuarios, además de que no valida nada.

Todo comenzó porque por gracioso cambié mi contraseña y puse una de 100 caracteres. La cambié, cerré sesión y magia, ya no podía entrar.

Continue reading…

Columnas con autoincremento en SQLite3

Introducción

El mismo sitio de SQLite3 dice que no recomienda el autoincremento o las columnas auto incrementables. Sin embargo, algunas veces es necesario hacer esto y según yo, no afecta tanto al rendimiento.

Veamos cómo crear una tabla con autoincremento y cómo es que SQLite3 maneja eso internamente. Por cierto, ya sabemos que este motor crea la columna rowid pero no podemos confiar en ella.

Continue reading…

Ejemplo y prevención de secuestro o robo de sesión en PHP

Introducción

En la mayoría de nuestras apps escritas en PHP utilizaremos sesiones. Las sesiones sirven para guardar datos que persisten aunque el usuario refresque la página. En este post veremos cómo robar una sesión y cómo prevenirlo.

Las sesiones generan una cookie y con dicha cookie identificamos al usuario. Por ejemplo, si yo inicio sesión se me da la cookie asd123 y si otro usuario inicia, se le da la cookie asd666 (son ejemplos).

Ahora supongamos que el usuario tiene permisos de administrador, y yo no. Pero si le robo su cookie y me la pongo a mí mismo, PHP pensará que soy el usuario administrador.

En otras palabras, inicialmente yo tenía la cookie asd123 y el usuario la asd666. Se la robo y ahora yo tengo la asd666.

Vamos a ver un ejemplo y prevención de secuestro o robo de sesión en PHP

Continue reading…

Qué es un ataque CSRF y cómo prevenirlo

Introducción

Hoy vamos a ver qué es un ataque CSRF. Es un ataque que ya es viejo pero sin dudas muchos de nosotros seguimos teniendo esa vulnerabilidad en nuestros sitios. No importa el lenguaje de programación que utilicemos, con el simple hecho de realizar operaciones en el servidor ya estamos expuestos.

Tampoco es para preocuparse tanto, pero bueno, vamos allá.

Continue reading…

Hasheando y comprobando contraseñas en Golang

Introducción

Seguimos con los tutoriales de Go. Ahora veremos cómo encriptar una contraseña y luego comprobar si esa contraseña coincide.

Sigue leyendo para que te des una mejor idea.

¿Cómo funciona?

En todos nuestros sistemas en donde implementemos contraseñas, debemos guardarlas de tal forma que sean, como yo lo llamo, “de un sólo camino”. Es decir, que una vez encriptadas ya nada las pueda desencriptar.

¿y cómo las compruebo después, si no las puedo desencriptar?

Muy fácil, supongamos que tenemos un algoritmo muy complejo que las encripta. Entonces, al registrar la contraseña, algo como “123” se convierte en “aaa”.

Dicho valor ya no podrá ser desencriptado, así que cuando el usuario quiera iniciar sesión, no podremos comprobar si su contraseña al desencriptar se convierte en “aaa”.

Pero hay algo que sí podemos hacer, encriptar la contraseña que nos está mandando, y comprobarla con la contraseña encriptada que ya tenemos guardada.

Así que si en nuestra base de datos tenemos “aaa” y el usuario quiere entrar con la contraseña “123” la encriptamos y al encriptarla se convertirá en “aaa” el cual es el mismo valor que tenemos.

Si en cambio el usuario intenta entrar con la contraseña “456” que al encriptarla se convierte en “bbb” entonces no coincidirá.

Obviamente esto fue un ejemplo, en la vida real las contraseñas se encriptan de diferente manera, pero la teoría es casi la misma.

Instalar paquete

No viene incluido por defecto, así que lo instalamos:

go get golang.org/x/crypto/bcrypt

Con esto ya podemos usar a bcrypt.

Hashear contraseña en Go

Es muy fácil generar un hash de contraseña segura. Para ello llamamos al método GenerateFromPassword de bcrypt.

Como primer argumento le pasamos un arreglo de bytes con la contraseña en texto plano, y como segundo le pasamos el costo.

Sobre el costo

El costo es muy importante a la hora de hashear nuestras contraseñas. Uno muy alto (el límite es 31) es casi imposible de romper, pero tomará mucho tiempo hashear una contraseña.

Por eso Go propone un costo mínimo, uno por defecto y uno máximo. El mínimo es 4, el que es por defecto es 10 y el máximo 31.

Recomiendo usar el 10, como se verá en el código. Pero si estamos dispuestos a que se consuman más recursos, podemos establecerlo más alto.

Cabe mencionar que también será pesado comprobar un hash cuando el usuario quiera loguearse, así que hay que establecer bien el costo.

Y finalmente, si pasas un costo menor que el mínimo (por ejemplo, 3 o 2) se pondrá el costo por defecto (10).

Código de ejemplo

El siguiente código hashea la contraseña “123” con un costo de 10.

package main

import (
	"fmt"

	"golang.org/x/crypto/bcrypt"
)

func main() {
	contraseñaPlana := "123"
	contraseñaPlanaComoByte := []byte(contraseñaPlana)
	hash, err := bcrypt.GenerateFromPassword(contraseñaPlanaComoByte, bcrypt.DefaultCost) //DefaultCost es 10
	if err != nil {
		fmt.Println(err)
	}
	hashComoCadena := string(hash)
	fmt.Printf("El hash generado a partir de %s es %s\n", contraseñaPlana, hashComoCadena)
}

Como se aprecia, debemos convertir la cadena a un arreglo de bytes. Y luego, como GenerateFromPassword regresa igualmente un arreglo de bytes lo convertimos a cadena.

Si lo ejecuto, esto pasa:

Hashear contraseña en Go

Hashear contraseña en Go

Si tú lo ejecutas te dará un resultado diferente, pero tranquilo que es normal y es una excelente práctica, ya que así se previenen los ataques de diccionario.

El hash como cadena ya podemos guardarlo en una base de datos o en donde queramos usarlo. Ahora veamos cómo comparar si la contraseña coincide.

Comprobar si hash y contraseña en texto plano coinciden

Ahora veamos cómo comprobar si una contraseña en texto plano y nuestro hash coinciden. Para ello pongamos el ejemplo de que nuestro hash es $2a$10$yXR.Jd79OMcHfNK8zZU5N.nQ7ZL5N1d65sGIbPr.vIf6Q3o7e540i (como en la imagen de arriba) y lo vamos a comprobar primero con “555” (contraseña incorrecta) y luego con “123” (correcta).

Go provee el método CompareHashAndPassword que recibe 2 argumentos de tipo arreglo de bytes: el hash y la contraseña en texto plano.

Importante: esta función no devuelve un booleano, sino que devuelve un error (o nil en caso de que no haya error). Si devuelve nil, entonces las contraseñas conciden. Si devuelve error, entonces las contraseñas no coinciden.

Código de ejemplo

Primero probemos con este, en donde la contraseña es incorrecta:

package main

import (
	"fmt"

	"golang.org/x/crypto/bcrypt"
)

func main() {
	hash := "$2a$10$yXR.Jd79OMcHfNK8zZU5N.nQ7ZL5N1d65sGIbPr.vIf6Q3o7e540i"
	hashComoByte := []byte(hash)
	contraseña := "555"
	contraseñaComoByte := []byte(contraseña)
	error := bcrypt.CompareHashAndPassword(hashComoByte, contraseñaComoByte)
	if error == nil {
		fmt.Println("Las contraseñas coinciden :)")
	} else {
		fmt.Println("Las contraseñas no coinciden. El error es: ", error)
	}
}

Si nos fijamos, dijimos que la contraseña es 555 pero realmente es 123. Al ejecutar el programa:

Comprobar hash y contraseña

Comprobar hash y contraseña

Es importante conocer el error, para ello lo estamos imprimiendo. Y como podemos ver, dice que la contraseña hasheada no es el hash de la contraseña en texto plano que proporcionamos.

Ahora cambiaré la contraseña a 123:

package main

import (
	"fmt"

	"golang.org/x/crypto/bcrypt"
)

func main() {
	hash := "$2a$10$yXR.Jd79OMcHfNK8zZU5N.nQ7ZL5N1d65sGIbPr.vIf6Q3o7e540i"
	hashComoByte := []byte(hash)
	contraseña := "123"
	contraseñaComoByte := []byte(contraseña)
	error := bcrypt.CompareHashAndPassword(hashComoByte, contraseñaComoByte)
	if error == nil {
		fmt.Println("Las contraseñas coinciden :)")
	} else {
		fmt.Println("Las contraseñas no coinciden. El error es: ", error)
	}
}

Y si ejecuto el código:

Hash y contraseña coinciden

Hash y contraseña coinciden

Así es como finalizamos este post.

Conclusión

Vimos las formas básicas de hashear y comprobar contraseñas. Recuerda que esto es un ejemplo fácil para no confundir al lector, pero puedes tomar atajos o crear tus propias funciones dependiendo de tus necesidades.

Finalmente aquí dejo la documentación oficial: bcrypt – GoDoc

Cifrando y comprobando contraseñas en PHP

Introducción

Es una buena práctica (y algo que debemos hacer todos) cifrar las contraseñas si las vamos a guardar en una base de datos. También es importante hacerlo con un algoritmo de un solo camino; es decir, que se pueda cifrar pero que nunca se pueda hacer el proceso inverso.

¿Por qué cifrarlas, si mi base de datos está protegida?

Recordemos que la seguridad absoluta no existe y que siempre estamos propensos a un ataque de cualquier tipo, así que hay que proteger cada cosa individualmente.

Supongamos que nuestro servidor FTP sí está bien protegido, y que ningún usuario malicioso puede acceder a él. Ahora imaginemos que alguien logra hacer un ataque a la base de datos, y consigue todos los nombres de usuario y contraseñas. Lo que conseguirá serán contraseñas cifradas que no le servirán para nada. Pero si no las ciframos, podrán hacer lo que se les antoje.

Cifrando contraseñas

Debilidad de SHA1 y MD5

PHP ya viene con una opción para cifrar las contraseñas. No debemos usar nunca SHA1 ni MD5, porque aunque no son reversibles sí son predecibles con ataques de diccionario.

Aquí pongo un ejemplo:

Si yo uso SHA1 para cifrar “123” obtendré “40bd001563085fc35165329ea1ff5c5ecbdbbeef“. Esto nunca cambiará; siempre que yo cifre “123” obtendré el mismo resultado. Si alguien obtiene la contraseña cifrada que será “40bd001563085fc35165329ea1ff5c5ecbdbbeef”, no sabrá cuál contraseña plana es.

Pero comenzará a probar diferentes contraseñas y las irá cifrando, una por una. Puede comenzar con las más comunes, como 12345, 0000, etcétera. Por lo que conseguirá los siguientes resultados:

  • 12345 se convierte en 8cb2237d0679ca88db6464eac60da96345513964. No es esta contraseña porque no coincide con 40bd001563085fc35165329ea1ff5c5ecbdbbeef
  • 0000 se convierte en 39dfa55283318d31afe5a3ff4a0e3253e2045e43. Tampoco es esta porque no coincide con 40bd001563085fc35165329ea1ff5c5ecbdbbeef

E irá probando hasta que llegue a 123. Se verá así:

  • 123 se convierte en 40bd001563085fc35165329ea1ff5c5ecbdbbeef. Sí es, porque coincide con 40bd001563085fc35165329ea1ff5c5ecbdbbeef.

Lo peor de esto es que ya hay diccionarios online que tienen la mayoría de contraseñas más comunes y no tan comunes. Por eso nunca se debe usar esta función.

Con MD5 es lo mismo, así que no lo explicaré

Usando sal

Cuando usamos la función que PHP recomienda, se le añade sal a la contraseña cifrada. Es una cadena aleatoria que siempre producirá resultados diferentes.

La primera vez que cifre 123 puede que el resultado sea $2y$10$I4tJZFA.mxRSaxiy/uLUWOAB8CCoBI5UJUJZ/pkJ278oYJm6nstJ. La segunda $2y$10$uxHsXhUq90CzCbPNeoCL/enuYtJVmChl2Bc9biUJnq/LYtDDusk5K, la tercera $2y$10$8lVMtOjKf6nsorwQp4cWW.hzfuVqk.MyuKzesEPZu2r7fKt0X2V4K y así sucesivamente. Por lo que siempre dará un resultado distinto, y nunca se le podrá hacer un ataque como el que menciono arriba.

Algoritmos

Así que para guardar estas contraseñas el algoritmo es:

  1. Tener la contraseña plana del usuario
  2. Cifrarla con password_hash()
  3. Guardar el resultado del paso anterior (la contraseña ya cifrada) en la base de datos

Y para comprobar si esta contraseña es correcta cuando el usuario intente acceder es:

  1. Obtener contraseña cifrada de la base de datos
  2. Utilizar password_verify()
  3. Si el resultado del paso anterior es true, entonces la contraseña es correcta. De otro modo, no.

Ejemplo

Estructura de la tabla

El manual oficial recomienda que si vamos a usar password_hash declaremos nuestro campo en la base de datos con una longitud de 255.

Registrar usuario

Voy a abstraer y omitir  la conexión a la base de datos porque explicarlo no es el propósito de este post. Así que supondré que el usuario sabe manejar bases de datos y sus conexiones.

if(isset($_POST["nombre_usuario"]) && isset($_POST["password"])){
  $usuario = $_POST["nombre_usuario"];
  $pass = $_POST["password"];
  $passCifrada = password_hash($pass, PASSWORD_DEFAULT);
  $baseDeDatos->prepare("INSERT INTO usuarios (nombre, pass) VALUES (?,?);");
  $baseDeDatos->execute([$usuario, $passCifrada]);
}

En el código de arriba tomamos el usuario y la contraseña del usuario, que pudieron ser enviadas por un formulario. Preparamos la consulta SQL para evitar inyecciones y guardamos.

Es importante notar que usamos password_hash() y le pasamos como primer argumento la contraseña plana, y como segundo una constante que es el algoritmo a ser utilizado. Recomiendo usar siempre esta constante.

Ese fue el proceso para guardar el usuario y la contraseña.

Comprobando credenciales

 

Ahora imaginemos que el usuario intenta acceder, y que igualmente manda el nombre de usuario y la contraseña por un formulario. El código quedaría así:

if(isset($_POST["nombre_usuario"]) && isset($_POST["password"])){
  $nombreUsuario = $_POST["nombre_usuario"];
  $pass = $_POST["password"];
  $baseDeDatos->prepare("SELECT pass FROM usuarios WHERE nombre = ? LIMIT 1;");
  $baseDeDatos->execute([$nombreUsuario]);
  $passObtenida = $baseDeDatos->fetch()->pass;
  //Aquí comprobamos:
  if(password_verify($pass, $passObtenida) === TRUE){
    //Datos correctos
  }else{
    //Datos incorrectos
  }
}

Conclusión

Así es como, a grosso modo, podemos comprobar si las credenciales de determinado usuario son válidas, al mismo tiempo que protegemos su contraseña.

Es muy importante mencionar que no debemos intentar crear nuestros propios algoritmos, por más que pensemos que somos listos nunca debemos de intentar hacerlo por nuestra cuenta.

Proteger, controlar y restringir acceso a imágenes usando PHP y Apache

Introducción

En este artículo mostraré cómo podemos proteger nuestras imágenes o fotos usando PHP para que sólo en casos específicos se puedan ver. Usaremos la configuración de Apache para restringir el acceso (para que no puedan acceder a ejemplo.com/imagenes/imagen_secreta.png). PHP será utilizado para leer ese archivo y mostrarlo sólo si se debería.

Preparando archivos para trabajar

En nuestro entorno de trabajo de PHP (en htdocs si estás usando xampp) crearemos una carpeta llamada “probar_fotos“. Dentro de ella crearemos un archivo index.php y otra carpeta llamada img.  Dentro de esta última carpeta pondremos algunas imágenes de nuestra elección (no importa el formato ni calidad) y un archivo llamado .htaccess.

En mi caso, el directorio se ve así:

Configurando htaccess

En primera instancia vamos a denegar cualquier acceso a la carpeta en donde tenemos nuestras imágenes. Para ello nos servirá el archivo que pusimos ahí dentro. Simplemente le diremos al servidor que deniegue cualquier acceso.

Antes de modificar el archivo, si navegamos a localhost/probar_fotos/img podemos ver todas las imágenes:

De esta manera todos podrían verlas. Y eso no es lo que queremos. Por ello, en el archivo .htaccess escribiremos:

Deny from all

Guardamos cambios y volvemos a la ruta. Ahora nos encontraremos con esto:

Listo, ya tenemos nuestra carpeta protegida. Nota: incluso si el usuario supiera el nombre de la imagen y quisiera acceder a ella a través de localhost/probar_fotos/img/st.jpg no podría; saldría esto:

Con esto hemos terminado de configurar a Apache. Ahora es el turno de PHP.

Leyendo archivos con PHP

Como PHP leerá los archivos sin pasar a través del servidor, no importa que éstos estén protegidos por Apache.

El algoritmo es simple: debemos saber cómo se llama la imagen que queremos leer, comprobar si existe, mandar los encabezados (para que el navegador interprete los datos como imagen) y finalmente mandar la imagen. Por ejemplo, yo tengo una imagen llamada st.jpg, y para mostrarla hago lo siguiente en index.php:

<?php
$rutaImagen = __DIR__ . "/img/st.jpg";
$informacionImagen = getimagesize($rutaImagen);
header("Content-type: {$informacionImagen['mime']}");
readfile($rutaImagen);
?>

En la primera línea declaro la ruta de la imagen. Luego, obtengo su información (para saber si es png, jpg, etcétera) y mando los encabezados. Finalmente hago uso de readfile para leer la imagen y mandarla a través del búfer de salida. Podríamos usar file_get_contents pero ésta función también carga la imagen a memoria, cosa que no queremos, ya que sólo queremos mandarla directamente, sin hacerle modificación alguna.

Si ahora voy a localhost/probar_fotos/index.php veré lo siguiente:

Podemos ver que la imagen ha sido mostrada.

Leyendo archivos sólo si se tiene permiso

Arriba mostramos la imagen a cualquier usuario, cosa que es insegura como si no protegiéramos nada. Pero para restringir el acceso a determinados usuarios dependerá de cómo sea nuestra aplicación. Por ejemplo, si sólo los usuarios logueados pueden verlas, sería más o menos así:

<?php
session_start();
if($_SESSION["logueado"] === true){

  $rutaImagen = __DIR__ . "/img/st.jpg";
  $informacionImagen = getimagesize($rutaImagen);
  header("Content-type: {$informacionImagen['mime']}");
  readfile($rutaImagen);
}
?>

Si en cambio dependiera de un nivel de acceso almacenado en sesión, sería algo así:

<?php
session_start();
if($_SESSION["permiso_usuario"] >= 5){ #Suponiendo que el nivel de permisos para ver imágenes es 5

  $rutaImagen = __DIR__ . "/img/st.jpg";
  $informacionImagen = getimagesize($rutaImagen);
  header("Content-type: {$informacionImagen['mime']}");
  readfile($rutaImagen);
}
?>

Todo esto cambia dependiendo de cómo sea nuestro software. Más abajo omitiré esta comprobación del usuario, y me centraré en cómo mostrar archivos de diversas maneras. Queda en manos del desarrollador proteger el acceso a determinados clientes.

Mostrando imágenes según nombre

Ahora vamos a hacer que se muestre cualquier imagen si es que sabemos su nombre, pero sólo si existe y si su extensión es .jpg. Para ello leeremos el nombre almacenado en la variable $_GET. Modificaré el código así:

<?php
if(isset($_GET["nombre"])){ #Comprobar si está definida la variable
	$nombreImagen = $_GET["nombre"];
	$rutaImagen = __DIR__ . "/img/$nombreImagen.jpg"; #Concatenar nombre con .jpg
	if(file_exists($rutaImagen)){ #Comprobar si el archivo existe
		$informacionImagen = getimagesize($rutaImagen);
		header("Content-type: {$informacionImagen['mime']}");
		readfile($rutaImagen);
	}
}
?>

Ahora iré a localhost/probar_fotos/index.php?nombre=the-good-dinosaur y veré lo siguiente:

Y si accedo a localhost/probar_fotos/index.php?nombre=st veré lo siguiente:

De esta manera podríamos leer cualquier imagen, aunque nunca debemos de hacerlo de esta manera, porque cualquier usuario malicioso podría ir probando nombres de imágenes y viendo si existen o no. Además, podría navegar a otros directorios.

Esto nos lleva a otro método que detallo más abajo.

Mostrando imágenes por nombre y expresión regular

Este método se parece mucho al de arriba, sólo que ahora comprobaremos el nombre con una expresión regular. Básicamente sólo permitiremos imágenes cuyo nombre esté compuesto por letras y números o guiones, por un punto y por una extensión que puede ser png, jpg o jpeg. Si no se da ningún nombre, la expresión no coincide o no existe el archivo, simplemente indicamos que no se encontró la imagen.

<?php
if(isset($_GET["nombre"])){ #Comprobar si está definida la variable
	$nombreImagen = $_GET["nombre"];
	$expresionRegular = '/^[a-z0-9A-Z-]*\.(?:png|jpg|jpeg)$/';
	if(preg_match($expresionRegular, $nombreImagen) === 1){
		$rutaImagen = __DIR__ . "/img/$nombreImagen"; #Concatenar nombre con __DIR__
		if(file_exists($rutaImagen)){ #Comprobar si el archivo existe
			$informacionImagen = getimagesize($rutaImagen);
			header("Content-type: {$informacionImagen['mime']}");
			readfile($rutaImagen);
		}else exit("Imagen no encontrada");
	}else exit("Imagen no encontrada");
}else exit("Imagen no encontrada");
?>

Ahora puedo navegar a localhost/probar_fotos/index.php?nombre=the-good-dinosaur.jpg y ver la imagen. Si pongo un nombre raro como ../../ veré el error:

Por otro lado, si el nombre de la imagen coincide con la expresión regular pero no existe, veremos lo mismo:

Conclusión

Puede que parezca que en realidad no estamos haciendo nada, pero sí que lo estamos haciendo. De esta manera podemos proteger el acceso a imágenes para algunos usuarios; podemos hacer todo tipo de cosas, todo depende de los requerimientos de nuestra aplicación, pero la base es la misma.

Referencias

Stack Overflow – Show image using file_get_contents

Stack Overflow – Deny direct access to all .php files except index.php