Sie sind auf Seite 1von 20

Cómo crear un script de inicio de sesión

segura en php y MySQL


Creado por Oscaravila2, Oscar Avila, DXTER

Descargado de: http://es.wikihow.com/crear-un-script-de-inicio-de-sesi%C3%B3n-segura-en-php-y-MySQL

8 partes:

1. Configurar tu servidor

2. Configurar la base de datos MySQL

3. Crear la página de conexión para la base de datos

4. Crear funciones PHP

5. Crear páginas de procesamiento

6. Crear archivos de Javascript

7. Crear páginas HTML

8. Proteger páginas

Con más y más historias de la piratería en las noticias, los desarrolladores buscan mejores maneras de asegurar su
sitio. Si tu sitio tiene un sistema de miembros, podría estar en riesgo de ser crackeado y los datos de los usuarios
podrían verse comprometidos. La presente guía te mostrará un intento de crear un inicio de sesión segura con PHP.
Hemos puesto nuestro mejor esfuerzo en programar el código, pero la seguridad y sobre todo la encriptación son
temas complejos que cambian todo el tiempo y no podemos decir que dominamos todo ese campo. Por lo tanto,
podríamos haber obviado unos cuantos trucos en nuestra programación. De ser así, háznoslos saber y trataremos de
incorporar toda mejora a lo que tenemos. Seguir la presente guía te ayudará a cuidarte de muchos tipos de ataques
que podrían emplear los crackers para apoderarse del control de las cuentas de otros usuarios, eliminar cuentas y/o
cambiar datos. A continuación te presentaremos un lista de posibles ataques de los cuales la presente guía procura
defenderse:

 SQL Injections
 Session Hijacking
 Network Eavesdropping
 Cross Site Scripting
 Brute Force Attacks

El enfoque consiste en emplear una combinación de filtros de datos, encriptación y otros métodos para hacerles la
vida un poco más difícil a quienes piensen atacarte.

Tratamos de mejorar continuamente el presente script. La versión más reciente del código está disponible en
github. Podría haber algunas diferencias entre el código que descargues en esa página y el código citado en el
presente artículo. Deberás tener presente que nuestro objetivo no ha sido que la presentación de las páginas HTML
hecha por la aplicación se vea bonita en lo absoluto.

También deberás tener presente que no cerramos las etiquetas PHP en los archivos que contienen solamente código
PHP. Esto está en línea con la mayoría de recomendaciones de formato de código.

Por último, es necesario que sepas que te pedimos crear todos los archivos no-HTML de la aplicación en diversos
directorios dentro del directorio raíz de la aplicación. La manera más fácil de crear la estructura del directorio
correcto consiste en descargar el código más reciente haciendo clic en uno de los enlaces antes mencionados.

Por favor, siéntete con la libertad de usar la presente aplicación como base para tu propia implementación, pero no
la uses a modo de ejemplo de una buena práctica de programación.
Cosas que necesitarás
Debido a que estaremos utilizando mysqli_* que es un conjunto de clases PHP para acceder a nuestra base de datos
mySQL, necesitarás las siguientes versiones de PHP y MySQL.

 PHP versión 5.3 o posterior


 MySQL versión 4.1.3 o posterior

Evidentemente, también necesitarás un servidor web configurado para usar PHP para alojar tus páginas. Este será
muy probablemente el servidor web de alojamiento de tu página, a menos que estés alojando tú mismo el sitio.

Para revisar la versión de PHP y MySQL en tu servidor, emplea la función phpinfo();.

Parte 1 de 8: Configurar tu servidor

1. 1

Instala un servidor web, PHP y MySQL en tu servidor.

La mayoría de los servicios de alojamiento tiene PHP y mySQL ya instalados, pero tendrás que revisar que
tengan las versiones más recientes de PHP y mySQL para que la presente guía pueda serte de ayuda. Si no
tienen al menos PHP5.3 y MySQL5, podrías poner en tela de juicio su compromiso con la seguridad.
Mantener tu software actualizado es parte del proceso de seguridad.

Si tienes tu propio servidor o computadora, deberás instalar el software requerido normalmente según tu
sistema. En general, si no vas a usar la configuración por motivos de producción y vas a desarrollar en
Windows o OS X, instalar un paquete de aplicaciones (stack) XAMPP será lo más recomendable. Consigue
la versión apropiada para tu sistema operativo en:

http://www.apachefriends.org/en/xampp.html

Sin embargo, ten presente que bajo ninguna circunstancia deberás utilizar XAMPP para crearte un
ambiente de servidor de producción.

En Linux, usa el gestor de paquetes para descargar e instalar los paquetes necesarios. Algunas
distribuciones, como Ubuntu, contienen todas las aplicaciones necesarias en un solo paquete. Tan solo
tendrás que hacer lo siguiente en una ventana de terminal de Ubuntu:

sudo apt-get install lamp-server^ phpmyadmin

Pero aunque instales los elementos necesarios, asegúrate de configurar MySQL con una contraseña de raíz
segura.

Parte 2 de 8: Configurar la base de datos MySQL


1. 1

Crea una base de datos MySQL.

Inicia la sesión en tu base de datos como usuario administrador (normalmente “raíz”).

En la presente guía crearemos una base de datos llamada “inicio_seguro”.

Ve cómo crear una base de datos en phpMyAdmin.

Podrás usar el código a continuación o hacer lo mismo en phpMyAdmin o en tu cliente GUI MySQL
favorito, si lo deseas:

CREATE DATABASE `secure_login`;

Nota: algunos servicios de alojamiento no te permitirán crear una base de datos con phpMyAdmin, por eso
aprende hacerlo en cPanel.

2. 2

Crea un usuario solo con los privilegios SELECCIONAR, ACTUALIZAR e INSERTAR.

Crear un usuario con privilegios restringidos significa que en el caso de que alguna vez se viole la
seguridad en tu secuencia de comandos, el hacker no podrá borrar ni dejar nada desde nuestra base de
datos. Al utilizar este usuario podrás hacer casi lo que quieras con tu aplicación. Si eres realmente
paranoico, crea un usuario diferente para cada función.

Claro que necesitarás haber iniciado sesión en MySQL como un usuario con los privilegios suficientes para
poder crear otro usuario. Por lo general, este usuario será raíz.

Los siguientes detalles son del usuario que hemos creado:

 Usuario: "sec_user"
 Contraseña: "eKcGZr59zAa2BEWU"

Nota: te recomendamos cambiar la contraseña del que mencionamos anteriormente cuando vayas a
ejecutarlo en tu propio servidor. Si lo haces, asegúrate también de cambiar el código a continuación y el
código de conexión de la base de datos PHP en la aplicación que crearemos.

Recuerda que no tendrá que ser una contraseña que puedas recordar, así que hazla lo más complicada
posible. Por ejemplo, este es un generador de contraseñas aleatorias.

A continuación también estará el código SQL para crear el usuario de base de datos y otorgarle los
permisos necesarios. Si lo prefieres, también podrías hacerlo en un cliente de base de datos GUI como
phpmyadmin:

CREATE USER 'sec_user'@'localhost' IDENTIFIED BY 'eKcGZr59zAa2BEWU';


GRANT SELECT, INSERT, UPDATE ON `secure_login`.* TO 'sec_user'@'localhost';
Si ves que vas a eliminar registros de cualquiera de las tablas del presente módulo, deberás agregar
ELIMINAR a la lista de privilegios o sino podrías crear un usuario diferente que tenga solamente el
privilegio ELIMINAR y solo en la tabla en la que quieras borrar registros, si no quieres hacerlo en ambas.
No es necesario que otorgues el privilegio ELIMINAR en lo absoluto para el presente ejemplo de script.

3. 3

Crea una tabla MySQL que lleve por título "miembros".

El código a continuación creará una tabla con cinco campos (identificación, nombre de usuario, correo
electrónico, contraseña, sal). Utilizaremos el tipo de datos CHAR para los campos cuya extensión
conozcamos, ya que los campos “contraseña” y “sal” siempre tendrán 128 caracteres de largo. Utilizar
CHAR en esos casos ahorrará energía de procesamiento:

CREATE TABLE `secure_login`.`members` (


`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`username` VARCHAR(30) NOT NULL,
`email` VARCHAR(50) NOT NULL,
`password` CHAR(128) NOT NULL,
`salt` CHAR(128) NOT NULL
) ENGINE = InnoDB;

Como lo hemos mencionado anteriormente, podrás hacerlo en cualquier tipo de cliente que prefieras.

4. 4

Crea una tabla para almacenar intentos de inicio de sesión.

Utilizaremos esta tabla para almacenar los intentos de inicio de sesión de un usuario. Esta es una manera
con la que dificultaremos los ataques de fuerza bruta:

CREATE TABLE `secure_login`.`login_attempts` (


`user_id` INT(11) NOT NULL,
`time` VARCHAR(30) NOT NULL
) ENGINE=InnoDB

5. 5
Crea una fila de prueba en la tabla “miembros”.

Será importante poder ser capaz de probar el script de inicio de sesión, así que a continuación te
presentaremos el script para crear un usuario con detalles conocidos:

 Username: test_user
 Email: test@example.com
 Password: 6ZaxN2Vzm9NUJT2y

El código que necesitarás para poder iniciar sesión como este usuario es:

INSERT INTO `secure_login`.`members` VALUES(1, 'test_user', 'test@example.com',


'00807432eae173f652f2064bdca1b61b290b52d40e429a7d295d76a71084aa96c0233b82f1feac45529e07
26559645acaed6f3ae58a286b9f075916ebf66cacc',
'f9aab579fc1b41ed0c44fe4ecdbfcdb4cb99b9023abb241a6db833288f4eea3c02f76e0d35204a8695077d
cf81932aa59006423976224be0390395bae152d4ef');

Parte 3 de 8: Crear la página de conexión para la base de datos

1. 1

Crea una página de configuraciones globales.

Crea una carpeta llamada “includes” en el directorio raíz de la aplicación y luego crea un archivo PHP
nuevo en ese directorio. Ponle el nombre “psl-config.php”. En un ambiente de producción, deberás ubicar
ese archivo y todos los demás archivos “include” fuera de la raíz de documentos del servidor web. Si lo
haces, cosa que te recomendamos, tendrás que alterar el “include” o las declaraciones requeridas tanto
como sea necesario para que la aplicación pueda encontrar los archivos “include”.

Si ubicas estos archivos fuera de la raíz de documentos del servidor web, no se podrá encontrar tu archivo
con un URL. Entonces, en el caso de que alguien haya dejado la extensión PHP por error o haya echado a
perder los permisos de archivo, el archivo no podrá mostrarse en texto en una ventana del buscador.

El archivo tendrá variables de configuración global. Aspectos como si alguien puede registrarse, si es una
conexión (HTTPS) segura, entre otros, además de los detalles de la base de datos podrían ir a ese lugar.

<?php
/**
* Estos son los detalles de inicio de sesión de la base de datos:
*/
define("HOST", "localhost"); // El alojamiento al que deseas conectarte
define("USER", "sec_user"); // El nombre de usuario de la base de datos
define("PASSWORD", "4Fa98xkHVd2XmnfK"); // La contraseña de la base de datos
define("DATABASE", "secure_login"); // El nombre de la base de datos

define("CAN_REGISTER", "any");
define("DEFAULT_ROLE", "member");

define("SECURE", FALSE); // ¡¡¡SOLO PARA DESARROLLAR!!!!


2. 2

Crea la página de conexión para la base de datos.

Este será el código PHP que tendrás que utilizar para conectarte a la base de datos mySQL. Crea un nuevo
archivo PHP llamado “db_connect.php” en el directorio de “includes” de la aplicación y agrega el código a
continuación. Luego podrás incluir el archivo en cualquier página en la que desees conectarte con la base
de datos.

<?php
include_once 'psl-config.php'; // Ya que functions.php no está incluido.
$mysqli = new mysqli(HOST, USER, PASSWORD, DATABASE);

Parte 4 de 8: Crear funciones PHP


Estas funciones harán todo el procesamiento del script de conexión. Agrega todas las funciones a la página llamada
“functions.php” en el directorio “includes” de la aplicación.

1. 1

Inicia de manera segura la sesión PHP.


Las sesiones PHP tienen fama de no ser muy seguras, por eso será importante no solo poner
“session_start();” en la parte superior de cada página que quieras usar para las sesiones PHP. Crearemos
una función llamada “sec_session_start()”, ella iniciará una sesión PHP de manera segura. Deberás llamar
esta función en la parte superior de toda página en la que quieras tener acceso a una variable de sesión
PHP. Si estás realmente preocupado por la seguridad y la privacidad de las cookies, échale un vistazo al
siguiente artículo: Cómo crear un sistema de administración de sesión segura en PHP y MySQL.

Esta función hará que tu script de inicio de sesión sea mucho más seguro. Hará que los crackers dejen de
acceder al cookie de identificación de la sesión con JavaScript (por ejemplo en un ataque XSS). A su vez,
la función “session_regenerate_id()”, la cual regenera la identificación de la sesión en cada carga de la
página, ayudará a prevenir un robo de sesión. Nota: si vas a usar HTTPS en tu aplicación de inicio de
sesión, configura la variable “$secure” a “verdadero”. En un ambiente de producción, será esencial que
emplees HTTPS.

Crea un nuevo archivo llamado “functions.php” en el directorio “includes” de tu aplicación y agrégale el


código a continuación.
<?php
include_once 'psl-config.php';

function sec_session_start() {
$session_name = 'sec_session_id'; // Configura un nombre de sesión personalizado.
$secure = SECURE;
// Esto detiene que JavaScript sea capaz de acceder a la identificación de la
sesión.
$httponly = true;
// Obliga a las sesiones a solo utilizar cookies.
if (ini_set('session.use_only_cookies', 1) === FALSE) {
header("Location: ../error.php?err=Could not initiate a safe session
(ini_set)");
exit();
}
// Obtiene los params de los cookies actuales.
$cookieParams = session_get_cookie_params();
session_set_cookie_params($cookieParams["lifetime"],
$cookieParams["path"],
$cookieParams["domain"],
$secure,
$httponly);
// Configura el nombre de sesión al configurado arriba.
session_name($session_name);
session_start(); // Inicia la sesión PHP.
session_regenerate_id(); // Regenera la sesión, borra la previa.
}

2. 2

Crea la función de inicio de sesión.


Esta función comparará el correo electrónico y la contraseña con la base de datos y, si hay una
coincidencia, aparecerá como verdadero (true). Agrega esta función a tu archivo “functions.php”:
function login($email, $password, $mysqli) {
// Usar declaraciones preparadas significa que la inyección de SQL no será posible.
if ($stmt = $mysqli->prepare("SELECT id, username, password, salt
FROM members
WHERE email = ?
LIMIT 1")) {
$stmt->bind_param('s', $email); // Une “$email” al parámetro.
$stmt->execute(); // Ejecuta la consulta preparada.
$stmt->store_result();

// Obtiene las variables del resultado.


$stmt->bind_result($user_id, $username, $db_password, $salt);
$stmt->fetch();

// Hace el hash de la contraseña con una sal única.


$password = hash('sha512', $password . $salt);
if ($stmt->num_rows == 1) {
// Si el usuario existe, revisa si la cuenta está bloqueada
// por muchos intentos de conexión.

if (checkbrute($user_id, $mysqli) == true) {


// La cuenta está bloqueada.
// Envía un correo electrónico al usuario que le informa que su cuenta
está bloqueada.
return false;
} else {
// Revisa que la contraseña en la base de datos coincida
// con la contraseña que el usuario envió.
if ($db_password == $password) {
// ¡La contraseña es correcta!
// Obtén el agente de usuario del usuario.
$user_browser = $_SERVER['HTTP_USER_AGENT'];
// Protección XSS ya que podríamos imprimir este valor.
$user_id = preg_replace("/[^0-9]+/", "", $user_id);
$_SESSION['user_id'] = $user_id;
// Protección XSS ya que podríamos imprimir este valor.
$username = preg_replace("/[^a-zA-Z0-9_\-]+/",
"",
$username);
$_SESSION['username'] = $username;
$_SESSION['login_string'] = hash('sha512',
$password . $user_browser);
// Inicio de sesión exitoso
return true;
} else {
// La contraseña no es correcta.
// Se graba este intento en la base de datos.
$now = time();
$mysqli->query("INSERT INTO login_attempts(user_id, time)
VALUES ('$user_id', '$now')");
return false;
}
}
} else {
// El usuario no existe.
return false;
}
}
}

3. 3

La función de fuerza bruta.


Los ataques de fuerza bruta se dan cuando un hacker intenta acceder a una cuenta con 1000 contraseñas
diferentes, ya sean generadas al azar o de un diccionario. En nuestra secuencia de comandos, si la cuenta de
un usuario no inicia la sesión después de más de 5 intentos, su cuenta se bloqueará.

Los ataques de fuerza bruta son difíciles de prevenir, para hacerlo podrías utilizar pruebas de CAPTCHA,
bloquear las cuentas de usuario y agregar un retraso en los inicios de sesión fallidos, así el usuario no podrá
acceder por otros 30 segundos.

Recomendamos enfáticamente usar un CAPTCHA. Como no hemos implementado esta funcionalidad en


nuestro código ejemplo, esperamos hacerlo próximamente con SecureImage, ya que no requiere
inscripción. Podrías preferir algo más conocido como reCAPTCHA de Google.

Sea cual sea el sistema que decidas usar, te sugerimos mostrar la imagen de CAPTCHA después de dos
inicios de sesión fallidos para evitar incomodar al usuario innecesariamente.

Cuando nos enfrentamos a este problema, la mayoría de los desarrolladores simplemente bloquea la
dirección IP después de cierta cantidad de inicios de sesión fallidos. Sin embargo, existen muchas
herramientas para automatizar el proceso. Estas pueden pasar por una serie de proxies e incluso cambiar la
IP en cada solicitud. Bloquear todas estas direcciones IP podría bloquear las cuentas de tus usuarios
legítimos también. En nuestro código registraremos los intentos fallidos y bloquearemos la cuenta del
usuario después de cinco intentos sin éxito. Esto hará que se envíe un correo electrónico al usuario con un
enlace para resetearlo, pero no hemos implementado este punto en nuestro código. A continuación te
presentaremos la función “checkbrute()” hasta la fecha. Agrégaselo a tu código “functions.php”:

function checkbrute($user_id, $mysqli) {


// Obtiene el timestamp del tiempo actual.
$now = time();

// Todos los intentos de inicio de sesión se cuentan desde las 2 horas anteriores.
$valid_attempts = $now - (2 * 60 * 60);

if ($stmt = $mysqli->prepare("SELECT time


FROM login_attempts
WHERE user_id = ?
AND time > '$valid_attempts'")) {
$stmt->bind_param('i', $user_id);

// Ejecuta la consulta preparada.


$stmt->execute();
$stmt->store_result();
// Si ha habido más de 5 intentos de inicio de sesión fallidos.
if ($stmt->num_rows > 5) {
return true;
} else {
return false;
}
}
}

4. 4

Revisa el estado de la sesión iniciada.


Lo haremos mediante la comprobación de “user_id” y las variables de sesión “login_string”. La variable
SESSION “login_string” tiene la información del navegador de los usuarios junto con la contraseña unida
mediante una función hash. Utilizamos la información del navegador, ya que es muy poco probable que el
usuario lo vaya a cambiar a la mitad de la sesión. Hacerlo ayudará a prevenir un robo de sesión. Agrega
esta función a tu archivo “functions.php” en la carpeta “includes de tu aplicación:

function login_check($mysqli) {
// Revisa si todas las variables de sesión están configuradas.
if (isset($_SESSION['user_id'],
$_SESSION['username'],
$_SESSION['login_string'])) {

$user_id = $_SESSION['user_id'];
$login_string = $_SESSION['login_string'];
$username = $_SESSION['username'];

// Obtiene la cadena de agente de usuario del usuario.


$user_browser = $_SERVER['HTTP_USER_AGENT'];

if ($stmt = $mysqli->prepare("SELECT password


FROM members
WHERE id = ? LIMIT 1")) {
// Une “$user_id” al parámetro.
$stmt->bind_param('i', $user_id);
$stmt->execute(); // Ejecuta la consulta preparada.
$stmt->store_result();

if ($stmt->num_rows == 1) {
// Si el usuario existe, obtiene las variables del resultado.
$stmt->bind_result($password);
$stmt->fetch();
$login_check = hash('sha512', $password . $user_browser);

if ($login_check == $login_string) {
// ¡¡Conectado!!
return true;
} else {
// No conectado.
return false;
}
} else {
// No conectado.
return false;
}
} else {
// No conectado.
return false;
}
} else {
// No conectado.
return false;
}
}

5. 5

Sanea la URL de PHP_SELF


Esta función sanea la salida de la variable del servidor PHP_SELF. Es una modificación de una
función del mismo nombre usada por el sistema de gestión de contenido WordPress:

function esc_url($url) {

if ('' == $url) {
return $url;
}

$url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\\x80-\\xff]|i', '', $url);

$strip = array('%0d', '%0a', '%0D', '%0A');


$url = (string) $url;

$count = 1;
while ($count) {
$url = str_replace($strip, '', $url, $count);
}

$url = str_replace(';//', '://', $url);

$url = htmlentities($url);

$url = str_replace('&amp;', '&#038;', $url);


$url = str_replace("'", '&#039;', $url);

if ($url[0] !== '/') {


// Solo nos interesan los enlaces relativos de $_SERVER['PHP_SELF']
return '';
} else {
return $url;
}
}

El problema de usar una variable de servidor no filtrada es que podría usarse en un ataque de secuencias de
comandos en sitios cruzados. Según la mayoría de referencias, solo tendrás que filtrarla con
“htmlentities()”, sin embargo, sigue siendo insuficiente, por eso existen excesivas medidas de seguridad
para esta función.

Otros sugieren dejar en blanco el atributo de acción del formulario o configurarlo a una cadena vacía. Pero
hacerlo así podría dar lugar a un ataque de secuestro de clic iframe.

Parte 5 de 8: Crear páginas de procesamiento

1. 1
Crea la página de procesamiento de inicio de sesión (process_login.php)

Crea un archivo para procesar los inicios de sesión, con el nombre “process_login.php”, en el
directorio “includes” de la aplicación. Tendrá que ir a este directorio porque no tiene ningún formato
HTML.

Usaremos la serie de funciones PHP mysqli_*, puesto que esta es una de las extensiones mySQL más
actualizadas.

<?php
include_once 'db_connect.php';
include_once 'functions.php';

sec_session_start(); // Nuestra manera personalizada segura de iniciar sesión PHP.

if (isset($_POST['email'], $_POST['p'])) {
$email = $_POST['email'];
$password = $_POST['p']; // La contraseña con hash

if (login($email, $password, $mysqli) == true) {


// Inicio de sesión exitosa
header('Location: ../protected_page.php');
} else {
// Inicio de sesión exitosa
header('Location: ../index.php?error=1');
}
} else {
// Las variables POST correctas no se enviaron a esta página.
echo 'Solicitud no válida';
}

2. 2

Crea una secuencia de comandos para cerrar sesión.

El script para el cierre de sesión deberá iniciar sesión, destruirla y luego redireccionarla a otro lugar. Nota:
te recomendamos añadir una protección CSRF aquí en el caso de que alguien logre enviar un enlace oculto
a esta página. Para mayor información sobre CSRF, visita Coding Horror.

El código actual para desconectar al usuario, el cual deberás agregar al archivo titulado “logout.php” en el
directorio “includes” de la aplicación, es el siguiente:

<?php
include_once 'includes/functions.php';
sec_session_start();

// Desconfigura todos los valores de sesión.


$_SESSION = array();

// Obtiene los parámetros de sesión.


$params = session_get_cookie_params();

// Borra el cookie actual.


setcookie(session_name(),
'', time() - 42000,
$params["path"],
$params["domain"],
$params["secure"],
$params["httponly"]);

// Destruye sesión.
session_destroy();
header('Location: ../ index.php');

3. 3

Página de registro.

Deberás incluir el código de registro en dos archivos nuevos, llamados “register.php” en el directorio raíz
de la aplicación y “register.inc.php” en el directorio “includes”, lo cual hará lo siguiente:

 Obtiene y valida el nombre de usuario que el usuario desea adoptar.


 Obtiene y valida la dirección de correo electrónico del usuario.
 Obtiene y valida la contraseña que el usuario desea usar.
 Pone la contraseña con hash y la devuelve a la página “register.php” (o sea, la envía a sí misma).

La mayoría de la validación se hace en JavaScript, del lado del cliente. Esto se debe a que el usuario no
tiene la motivación para burlar estas verificaciones. ¿Por qué querría el usuario crearse una cuenta que no
sería tan segura? Hablaremos de JavaScript en la siguiente sección.

Por ahora, solo tendrás que crear el archivo “register.php” e incluye el código a continuación:

<?php
include_once 'includes/register.inc.php';
include_once 'includes/functions.php';
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Secure Login: Formulario de registro</title>
<script type="text/JavaScript" src="js/sha512.js"></script>
<script type="text/JavaScript" src="js/forms.js"></script>
<link rel="stylesheet" href="styles/main.css" />
</head>
<body>
<!-- Formulario de registro que se emitirá si las variables POST no se
establecen o si la secuencia de comandos de registro ha provocado un error. -
->
<h1>Regístrate con nosotros</h1>
<?php
if (!empty($error_msg)) {
echo $error_msg;
}
?>
<ul>
<li> Los nombres de usuario podrían contener solo dígitos, letras
mayúsculas, minúsculas y guiones bajos.</li>
<li> Los correos electrónicos deberán tener un formato válido. </li>
<li> Las contraseñas deberán tener al menos 6 caracteres.</li>
<li>Las contraseñas deberán estar compuestas por:
<ul>
<li> Por lo menos una letra mayúscula (A-Z)</li>
<li> Por lo menos una letra minúscula (a-z)</li>
<li> Por lo menos un número (0-9)</li>
</ul>
</li>
<li> La contraseña y la confirmación deberán coincidir exactamente.</li>
</ul>
<form action="<?php echo esc_url($_SERVER['PHP_SELF']); ?>"
method="post"
name="registration_form">
Nombre de usuario: <input type='text'
name='username'
id='username' /><br>
Correo electrónico: <input type="text" name="email" id="email" /><br>
Contraseña: <input type="password"
name="password"
id="password"/><br>
Confirmar contraseña: <input type="password"
name="confirmpwd"
id="confirmpwd" /><br>
<input type="button"
value="Register"
onclick="return regformhash(this.form,
this.form.username,
this.form.email,
this.form.password,
this.form.confirmpwd);" />
</form>
<p>Return to the <a href="index.php">login page</a>.</p>
</body>
</html>

El archivo “register.inc.php” en el directorio “includes” deberá tener el código a continuación:

<?php
include_once 'db_connect.php';
include_once 'psl-config.php';

$error_msg = "";

if (isset($_POST['username'], $_POST['email'], $_POST['p'])) {


// Sanear y validar los datos provistos.
$username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_STRING);
$email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
$email = filter_var($email, FILTER_VALIDATE_EMAIL);
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
// No es un correo electrónico válido.
$error_msg .= '<p class="error">The email address you entered is not
valid</p>';
}

$password = filter_input(INPUT_POST, 'p', FILTER_SANITIZE_STRING);


if (strlen($password) != 128) {
// La contraseña con hash deberá ser de 128 caracteres.
// De lo contrario, algo muy raro habrá sucedido.
$error_msg .= '<p class="error">Invalid password configuration.</p>';
}

// La validez del nombre de usuario y de la contraseña ha sido verificada en el


cliente.
// Esto será suficiente, ya que nadie se beneficiará de
// violar estas reglas.
//

$prep_stmt = "SELECT id FROM members WHERE email = ? LIMIT 1";


$stmt = $mysqli->prepare($prep_stmt);

// Verifica el correo electrónico existente.


if ($stmt) {
$stmt->bind_param('s', $email);
$stmt->execute();
$stmt->store_result();

if ($stmt->num_rows == 1) {
// Ya existe otro usuario con este correo electrónico.
$error_msg .= '<p class="error">A user with this email address already
exists.</p>';
$stmt->close();
}
$stmt->close();
} else {
$error_msg .= '<p class="error">Database error Line 39</p>';
$stmt->close();
}

// Verifica el nombre de usuario existente.


$prep_stmt = "SELECT id FROM members WHERE username = ? LIMIT 1";
$stmt = $mysqli->prepare($prep_stmt);

if ($stmt) {
$stmt->bind_param('s', $username);
$stmt->execute();
$stmt->store_result();

if ($stmt->num_rows == 1) {
// Ya existe otro usuario con este nombre de usuario.
$error_msg .= '<p class="error">A user with this username
already exists</p>';
$stmt->close();
}
$stmt->close();
} else {
$error_msg .= '<p class="error">Database error line 55</p>';
$stmt->close();
}

// Pendiente:
// También habrá que tener en cuenta la situación en la que el usuario no tenga
// derechos para registrarse, al verificar qué tipo de usuario intenta
// realizar la operación.

if (empty($error_msg)) {
// Crear una sal aleatoria.
//$random_salt = hash('sha512', uniqid(openssl_random_pseudo_bytes(16), TRUE));
// Did not work
$random_salt = hash('sha512', uniqid(mt_rand(1, mt_getrandmax()), true));

// Crea una contraseña con sal.


$password = hash('sha512', $password . $random_salt);

// Inserta el nuevo usuario a la base de datos.


if ($insert_stmt = $mysqli->prepare("INSERT INTO members (username, email,
password, salt) VALUES (?, ?, ?, ?)")) {
$insert_stmt->bind_param('ssss', $username, $email, $password,
$random_salt);
// Ejecuta la consulta preparada.
if (! $insert_stmt->execute()) {
header('Location: ../error.php?err=Registration failure: INSERT');
}
}
header('Location: ./register_success.php');
}
}

Si no hay ningún dato POST provisto al formulario, se mostrará el formulario de registro. El botón de
envío del formulario llama a la función de JavaScript “regformhash()”. Esta función realizará las
verificaciones de validación necesarias y enviará el formulario cuando todo sea correcto. Hablaremos de
las funciones de JavaScript en la siguiente sección.

Si existen datos POST, se realizarán algunas verificaciones del servidor para sanearlos y validarlos. TEN
PRESENTE que estas verificaciones no están culminadas hasta la fecha. Algunos de los problemas se
mencionan en los comentarios en el archivo. A la fecha, solo verificamos que la dirección de correo
electrónico tenga el formato correcto, que la contraseña con hash tenga la extensión correcta y que el
usuario no trate de registrar un correo electrónico ya registrado.

Si todo es correcto, se registrará al nuevo usuario y se escribirá un registro nuevo en la tabla de miembros.

Parte 6 de 8: Crear archivos de Javascript

1. 1

Crea el archivo “sha512.js”.

Este archivo es una implementación en JavaScript del algoritmo hash sha512. Haremos uso de la función
hash para que las contraseñas no se envíen en texto simple.

Podrás descargar el archivo de pajhome.org.uk

(también se guardará en el repositorio de github).

Guarda tu copia de este archivo en un directorio titulado “js” en el directorio de raíz de la aplicación.

2. 2

Crea el archivo “forms.js”.


Este archivo, el cual deberás crear en el directorio “js”de la aplicación, se encargará del hash de las
contraseñas para los formularios de inicio de sesión (formhash()) y de registro (regformhash()):

function formhash(form, password) {


// Crea una entrada de elemento nuevo, esta será nuestro campo de contraseña con
hash.
var p = document.createElement("input");

// Agrega el elemento nuevo a nuestro formulario.


form.appendChild(p);
p.name = "p";
p.type = "hidden";
p.value = hex_sha512(password.value);

// Asegúrate de que la contraseña en texto simple no se envíe.


password.value = "";

// Finalmente envía el formulario.


form.submit();
}

function regformhash(form, uid, email, password, conf) {


// Verifica que cada campo tenga un valor
if (uid.value == '' ||
email.value == '' ||
password.value == '' ||
conf.value == '') {

alert('Deberá brindar toda la información solicitada. Por favor, intente de


nuevo');
return false;
}

// Verifica el nombre de usuario

re = /^\w+$/;
if(!re.test(form.username.value)) {
alert("El nombre de usuario deberá contener solo letras, números y guiones
bajos. Por favor, inténtelo de nuevo");
form.username.focus();
return false;
}

// Verifica que la contraseña tenga la extensión correcta (mín. 6 caracteres)


// La verificación se duplica a continuación, pero se incluye para que el
// usuario tenga una guía más específica.
if (password.value.length < 6) {
alert('La contraseña deberá tener al menos 6 caracteres. Por favor, inténtelo
de nuevo');
form.password.focus();
return false;
}

// Por lo menos un número, una letra minúscula y una mayúscula


// Al menos 6 caracteres

var re = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,}/;
if (!re.test(password.value)) {
alert('Las contraseñas deberán contener al menos un número, una letra minúscula
y una mayúscula. Por favor, inténtelo de nuevo');
return false;
}

// Verifica que la contraseña y la confirmación sean iguales


if (password.value != conf.value) {
alert('La contraseña y la confirmación no coinciden. Por favor, inténtelo de
nuevo');
form.password.focus();
return false;
}

// Crea una entrada de elemento nuevo, esta será nuestro campo de contraseña con
hash.
var p = document.createElement("input");

// Agrega el elemento nuevo a nuestro formulario.


form.appendChild(p);
p.name = "p";
p.type = "hidden";
p.value = hex_sha512(password.value);

// Asegúrate de que la contraseña en texto simple no se envíe.


password.value = "";
conf.value = "";

// Finalmente envía el formulario.


form.submit();
return true;
}

En ambos casos, JavaScript le pone un hash a la contraseña y la transfiere a los datos POST al crear y
rellenar un campo escondido.

Parte 7 de 8: Crear páginas HTML


1. 1

Crea el formulario de inicio de sesión (login.php).

Esta es una forma de HTML con dos campos de texto, titulados “correo electrónico” y “contraseña”. El
botón de envío del formulario llamará a la función de JavaScript “formhash()”, la cual generará una
contraseña con hash y enviará el contenido de “correo electrónico” y “p” (contraseña con hash) al servidor.
Deberás crear este archivo en el directorio de raíz de la aplicación.

Al iniciar la sesión, lo más recomendable será utilizar algo que no sea público. En la presente guía
usaremos el correo electrónico como ID de inicio de sesión, pero el nombre de usuario podrá utilizarse
después para identificar al usuario. Si el correo electrónico se oculta en alguna página dentro de la
aplicación más amplia, se añadirá otra variable desconocida para crackear las cuentas.

Nota: pese a que hemos encriptado la contraseña de modo que no se envíe en texto simple, será vital que
uses el protocolo HTTPS (TLS/SSL) a la hora de enviar las contraseñas a un sistema de producción. No
está por demás insistir en que simplemente poner un hash a la contraseña será insuficiente. Podrías sufrir
un ataque “man-in-the-middle” que podría leer el hash enviado y usarlo para iniciar sesión.

<?php
include_once 'includes/db_connect.php';
include_once 'includes/functions.php';

sec_session_start();

if (login_check($mysqli) == true) {
$logged = 'in';
} else {
$logged = 'out';
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Secure Login: Log In</title>
<link rel="stylesheet" href="styles/main.css" />
<script type="text/JavaScript" src="js/sha512.js"></script>
<script type="text/JavaScript" src="js/forms.js"></script>
</head>
<body>
<?php
if (isset($_GET['error'])) {
echo '<p class="error">Error Logging In!</p>';
}
?>
<form action="includes/process_login.php" method="post" name="login_form">
Correo electrónico: <input type="text" name="email" />
Contraseña: <input type="password"
name="password"
id="password"/>
<input type="button"
value="Login"
onclick="formhash(this.form, this.form.password);" />
</form>
<p> Si no tiene una cuenta, por favor<a href="register.php">regístrese.</a></p>
<p> Si ha terminado, por favor<a href="includes/logout.php">cierre la
sesión.</a></p>
<p>Está conectado.<?php echo $logged ?>.</p>
</body>
</html>
2. 2

Crea la página “register_success.php.

Crea una página web PHP nueva que lleve por nombre “register_success.php” en el directorio raíz de la
aplicación. Esta es la página a donde se le redireccionará al usuario después de haberse registrado con
éxito. Claro que podrás hacer esta página como quieras o podrás redireccionarlo a otra página (o no).
Dependerá de ti. Deberás ubicar la página en el directorio raíz de la aplicación. La página actual
“register_success.php” que hemos escrito se ve así:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Inicio de sesión segura: Registro exitoso</title>
<link rel="stylesheet" href="styles/main.css" />
</head>
<body>
<h1>¡Registro exitoso!</h1>
<p>Ahora podrás regresar a<a href="index.php">la página de inicio de sesión
</a> e iniciar la sesión.</p>
</body>
</html>

3. 3

Crea la página de error.

Crea una página HTML nueva en el directorio raíz de la aplicación y ponle por nombre “error.php”. Esta es
la página a donde se le redireccionará al usuario en el caso de que ocurra algún error durante el proceso de
inicio de la sesión, de registro o cuando se trate de establecer una sesión segura. El código a continuación
simplemente mostrará una página de error general. Tal vez necesites algo un poco más sofisticado. No
obstante, ten presente que todo lo que se agregue a la página se deberá filtrar adecuadamente para
protegerse contra posibles ataques XSS. El código de ejemplo de la página es el siguiente:

<?php
$error = filter_input(INPUT_GET, 'err', $filter = FILTER_SANITIZE_STRING);

if (! $error) {
$error = “Ocurrió un error desconocido”;
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Secure Login: Error</title>
<link rel="stylesheet" href="styles/main.css" />
</head>
<body>
<h1>Hubo un problema.</h1>
<p class="error"><?php echo $error; ?></p>
</body>
</html>
Parte 8 de 8: Proteger páginas

1. 1

Secuencia de comandos para la protección de páginas.

Uno de los problemas más comunes con los sistemas de autenticación es que el desarrollador olvida
verificar si el usuario está conectado. Será de suma importancia que emplees el código a continuación en
cada página protegida para verificar que el usuario esté conectado. Asegúrate de emplear esta función.

// Agrega la conexión y las funciones de la base de datos aquí. Ver 3.1.


sec_session_start();
if(login_check($mysqli) == true) {
// ¡Agrega el contenido de tu página protegida aquí!
} else {
echo “No está autorizado para acceder a esta página. Por favor, inicie su
sesión”.;
}

Como ejemplo de lo que deberás hacer, hemos incluido una página protegida de muestra. Crea un archivo
con nombre “protected_page.php” en el directorio raíz de la aplicación. El archivo deberá ser parecido a lo
que mostraremos a continuación:

<?php
include_once 'includes/db_connect.php';
include_once 'includes/functions.php';

sec_session_start();
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Inicio de sesión segura: Página protegida</title>
<link rel="stylesheet" href="styles/main.css" />
</head>
<body>
<?php if (login_check($mysqli) == true) : ?>
<p>¡Bienvenido, <?php echo htmlentities($_SESSION['username']); ?>!</p>
<p>
Este es un ejemplo de página protegida. Para acceder a esta página,
los usuarios
deberán iniciar su sesión. En algún momento, también verificaremos el
rol
del usuario para que las páginas puedan determinar el tipo de usuario
autorizado para acceder a la página.
</p>
<p>Regresar a la<a href="index.php">página de inicio de sesión.</a></p>
<?php else : ?>
<p>
<span class="error">No está autorizado para acceder a esta
página.</span> Please <a href="index.php">login</a>.
</p>
<?php endif; ?>
</body>
</html>
Nuestra aplicación redireccionará al usuario a esta página tras haberse registrado con éxito. Evidentemente,
tu implementación no tendrá que ser igual.

Consejos
 Aléjate de la función md5 en los scripts de inicio de sesión, el algoritmo hash md5 se considera inseguro.
 Con muy pocos cambios en estos scripts de ejemplos podrás trabajar con otros sistemas SQL, tales como
SQLite o PostgreSQL.
 Usa HTML y CSS para dar formato al formulario de acceso y de salida de las páginas de tu agrado.
 Si quieres usar un algoritmo hash diferente en lugar de sha512, prueba Whirlpool. Evita usar Gost, sha1 (a
menos que esté bien salado y tenga varias iteraciones) y, como ya lo hemos mencionado, md5. Incentiva a
tus usuarios a crear contraseñas únicas, seguras, con letras mayúsculas, minúsculas, números y símbolos.
Considera la posibilidad de pedirles a tus usuarios crear un nombre de inicio de sesión aparte de su nombre
de usuario para que sea más seguro.

Advertencias
 La página de inicio y de registro deberán emplear HTTPS. Los scripts del presente artículo no te obligan a
hacerlo y, en realidad, sería más fácil no hacerlo durante el desarrollo, pero no deberás usar estos scripts en
un ambiente de producción a menos que utilices HTTPS.
 Asegúrate de que el usuario no pueda ver tus scripts PHP, lo cual podría ocurrir debido a una configuración
incorrecta del servidor. Existe la posibilidad de que los usuarios recojan información acerca de tu base de
datos como los nombres y contraseñas si tu código PHP es visible. Lo ideal sería que todas las secuencias
de comandos incluidas en otras secuencias o páginas estén ubicadas en un directorio fuera del sistema de
archivos del servidor y que se haga referencia a ellas con un camino relativo, por ejemplo, agrega:
“../../includes/myscript.inc.php”.
 Nada es 100% seguro. Recuerda mantenerte al tanto de las últimas noticias de seguridad para seguir
mejorando la seguridad de tus scripts.
 Esta secuencia de comandos anti fuerza bruta que bloquea la cuenta de un usuario podría emplearse de
manera errónea muy fácilmente. Te recomendamos firmemente usar una técnica anti fuerza bruta como
CAPTCHA.
 Te recomendamos usar un CAPTCHA en la página de inicio de sesión para dificultar los ataques de fuerza
bruta y DoS. El CAPTCHA deberá aparecer en el formulario después de dos intentos de inicio de sesión
fallidos, aunque todavía no está implementado en el código de ejemplo.
 Podrías conseguir una mejor solución con un marco como Zend 2, Symfony o CakePHP. Todos estos
marcos tienen arreglos para las sesiones seguras y módulos de seguridad para ayudar con el proceso de
inicio de sesión. También, si utilizas un marco, probablemente veas que escribes mejores aplicaciones.

Referencias
 http://crackstation.net/hashing-security.htm Hash de contraseña
 https://www.owasp.org/index.php/SQL_Injection Información sobre inyección SQL

Das könnte Ihnen auch gefallen