php

Errores comunes al crear sitios web seguros en PHP

errores-php

¿Recuerdas el último sitio web que fue hackeado por unos niños? ¿Qué fue lo primero que pensaste, quizás «Uf, eso no es mío»? Un sitio web hackeado es terrible y la limpieza es mucho trabajo para el propietario del sitio. Como propietario de un sitio web, tienes que asegurarte de que tu sitio es siempre seguro. Es importante para tu negocio y, por supuesto, para que tu sitio aparezca en Google. Recuerde estos consejos cuando cree sitios web PHP seguros:

Consultas MySQL y ataques de inyección SQL

Si su página web acepta la entrada del usuario, una inyección SQL podría ocurrir si los datos no son validados o desinfectados antes de la consulta a la base de datos. El siguiente ejemplo es un error común, cometido por el programador o webmaster principiante. Comprueba este código, ¿has escrito alguna vez una consulta MySQL como esta?

$result = mysql_query("SELECT * FROM users WHERE username = '". $_POST["username"] ."' && password = '". $_POST["password"] ."');

Esta fila de código está escrita para aceptar dos valores de un formulario de acceso: un nombre de usuario y una contraseña. Pero qué pasa si alguien envía un nombre de usuario vacío y para el valor de la contraseña la siguiente cadena: ‘ O username = ‘admin la consulta se vería así:

$result = mysql_query("SELECT * FROM users WHERE username = '' && password = '' OR username = 'admin'");

Si esta consulta se utilizara para un script de inicio de sesión, el hacker obtendría acceso a la cuenta del usuario con el nombre «admin». Imagine lo que un hacker podría hacer en su aplicación web si tiene derechos de administrador. No te preocupes, hay varias formas de proteger tus consultas contra las inyecciones SQL.

En primer lugar es mejor utilizar la extensión mejorada de MySQL (MySQLi). Las antiguas funciones de PHP para MySQL todavía están disponibles, pero la extensión MySQLi ofrece características más seguras. Si utiliza las funciones de MySQLi puede elegir entre el estilo procedimental y el estilo orientado a objetos. Yo uso para mis ejemplos el estilo orientado a objetos.

Antes de escapar los valores para nuestras consultas usamos la función filter_var() para sanear los valores de entrada.

$username = filter_var($_POST["username"], FILTER_SANITIZE_STRING);
$password = filter_var($_POST["password"], FILTER_SANITIZE_STRING);

Mi ejemplo utiliza el valor por defecto para el tipo de filtro FILTER_SANITIZE_STRING. A continuación utilizo la variante MySQLi de mysql_real_escape_string() para preparar las cadenas para la consulta a la base de datos. Antes de continuar con la validación de la entrada, necesito crear un objeto de base de datos primero. ¡No almacene las contraseñas como texto plano! Para hacer coincidir el valor en su base de datos necesitamos usar el mismo md5 / salt (cadena secreta) para crear la cadena de comparación dentro de la consulta.

$db = new mysqli("localhost", "db_user", "db_password", "db_name");

if (mysqli_connect_errno()) { /* check connection */
    die("Connect failed: ".mysqli_connect_error());
}
$username = $db->mysqli_real_escape_string($username);
$password = $db->mysqli_real_escape_string(md5('YOUR_SECRET_STRING', $password));

Ahora es seguro pasar estos valores a nuestra consulta de la base de datos:

$result = $db->query(sprintf("SELECT FROM users WHERE username = '%s' && password = '%s'", $username, $password));
$db->close();

En mi ejemplo utilicé la función sprintf() para añadir los valores a la consulta y utilizar $db->close() para destruir el objeto de la base de datos. Otra gran y segura característica de MySQLi es la función prepared statements.

Ataques de falsificación de sitio cruzado (CSRF)

El principio básico detrás de un ataque CSRF no es obtener acceso a un sitio, sino forzar a un usuario o administrador a una acción no deseada. Por ejemplo, tenemos una página de administración con una estructura HTML como esta.

<ul>
    <li><a href="delete.php?page_id=10">delete home page</a></li>
    <li><a href="delete.php?page_id=20">delete news page</a></li>
</ul>

Hemos protegido la página llamada delete.php con algún script de login, para que un usuario sin permisos no pueda acceder a la página o al script.

if( logged_in() == false ) {
    // User not logged in
    die();
} else { 
    // User logged in
    $db->query(sprintf("DELETE FROM pages WHERE page_id = %d", $_GET['page_id']));
}

El script muere si el usuario no está conectado y, en caso contrario, la página con el ID de la página de la variable GET será eliminada. Usando la función sprintf() soy capaz de formatear el valor a un valor entero usando el especificador de tipo %d. ¿Parece seguro o no?

Digamos que el autorizado (conectado) visita una página donde se ha publicado un comentario que incluye una imagen. Si un hacker ha publicado una imagen con la URL como la de abajo no se daría cuenta, porque la imagen no aparece porque la URL no existe.

<img src="http://www.yoururl.com/delete_page.php?page_id=20" />

Seguro que este es un ejemplo muy estúpido y el hackeo sólo es posible si el hacker conoce la seguridad de tu sitio web en PHP. Una solución mucho mejor sería utilizar un token único para cada acción importante. La mejor manera es crear un token único para el usuario administrador durante el inicio de sesión. Digamos que el código de abajo es una parte de su script de inicio de sesión. Dentro de la función login_user() creo una variable de sesión que contiene una cadena cifrada md5().

session_start();

function logged_in() { 
    // your code to check a valid login 
}
function login_user() {
    // your authentication process
    // comes
    $id = md5(uniqid(mt_rand(), true));
    $_SESSION['token'] = $id;
}
function get_token() {
    return (!empty($_SESSION['token'])) ? $_SESSION['token'] : 0;
}

Dentro de la página con la estructura HTML necesito añadir las variables token después de cada enlace.

$token = get_token();
echo '
<ul>
    <li><a href="delete.php?page_id=10&token='.$token.'">delete home page</a></li>
    <li><a href="delete.php?page_id=20&token='.$token.'">delete news page</a></li>
</ul>';

Con esta variable token adicional dentro del script delete.php puedo validar la entrada de datos.

if( logged_in() == false ) {
    // User not logged in
    die();
} else {
    // User logged in
    if (empty($_GET['token']) || $_GET['token'] != $_SESSION['token']) {
        die();
    } else {
        $db->query(sprintf("DELETE FROM pages WHERE page_id = %d", $_GET['page_id']));
    }
}

Esta simple validación detendrá el script si falta el token o no es igual a la variable de sesión del token.

Ataques de secuencias de comandos en sitios cruzados (XSS)

La idea básica del ataque XSS es que un hacker ha incrustado algún código del lado del cliente en su sitio web que es ejecutado o descargado por un visitante. Esto ocurre de diferentes maneras. Por ejemplo, utilizando un enlace a su sitio web en el que se añade algún JavaScript malicioso o el hacker ha publicado algún código JavaScript en su sitio web. Esto último ocurre sobre todo al utilizar formularios de comentarios inseguros en los que el contenido encuentra un lugar en su sitio web.

En cualquier situación es importante que su aplicación web sanee la entrada del usuario antes de que los datos sean almacenados o analizados en su página web. Utilice diferentes funciones de validación como preg_match(), filter_var() o mejor htmlspecialchars() para filtrar o convertir posibles ataques de hackers. La función htmlspecialchars() convertirá las etiquetas HTML en entidades.

Limitar la funcionalidad de los scripts

Si su sitio web tiene una función de inicio de sesión, es posible que un hacker utilice un script que intente adivinar un nombre de usuario y una contraseña. Al utilizar miles de combinaciones es posible que el hacker tenga éxito. Reduzca el acceso a su página de inicio de sesión si un visitante ha realizado más de X envíos y utilice siempre contraseñas difíciles de adivinar. Contraseñas comunes como «welcome» o «default» son la mayoría de las veces la causa de ser hackeado.

Ten cuidado con la función de recuperación de la contraseña. Nunca envíes las instrucciones de recuperación a una nueva dirección de correo electrónico y deja que el propietario de la dirección de correo electrónico registrada ejecute la acción de recuperación.

Las imágenes Captcha son una forma estupenda y sencilla de impedir que los bots accedan a tus formularios web. Utilízalas cuando un envío remoto pueda dañar tu aplicación web.

Desactivar los informes de error de PHP

Hay muchas razones por las que puede ocurrir algún error de PHP en un sitio web. Muchos de ellos no tienen ninguna influencia en la funcionalidad del sitio web. Para un hacker un aviso o mensaje de error es una fuente para obtener información sobre su sitio web y/o la configuración del servidor. Pruebe siempre su sitio web en busca de posibles errores, porque también son malos para su sitio web y/o negocio. ¿Confiarías en un servicio que está roto?
¡También hay una función que desactiva la salida de mensajes de error, añada ini_set(‘display_errors’, 0); a su script y muestre los errores SOLO en su sitio de prueba!

Deja una respuesta