Errores comunes al crear sitios web seguros en PHP

¿Recuerdas tu último sitio web que fue hackeado por ciertos piratas turcos? Seguro que tu primero pensamiento fue: “Que no sea culpa mía, que no sea culpa mía...”. Un sitio web hackeado es lo peor y limpiarlo se traduce en un montón de trabajo para el propietario del sitio. Como programador y propietario de un sitio web, debes asegurarte de que tu web esté siempre segura. Es importante para tu negocio y, por supuesto, para que tus relaciones con Google sean las más adecuadas. Recuerda estos consejos al crear sitios web seguros en PHP:

Inyecciones SQL

Si tu página web acepta que el usuario introduzca valores, como por ejemplo en un formulario, pueden producirse inyecciones SQL si los datos no se validan y formatean antes de introducirlos en la base de datos. El siguiente ejemplo es un error muy común realizado habitualmente por un programador novato o un webmaster. Echa un vistazo a este código, ¿has realizado alguna vez una query SQL similar?

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

Esta línea de código acepta dos valores de un formulario de login: un username (nombre de usuario) y un password (contraseña). Pero qué pasa si alguien envía el usuario vacío y en el campo de password introduce este valor: 'OR username = 'admin. La query quedaría tal que así:

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

Si esta query da acceso a la parte pivada de la web, el hacker podría campar a sus anchas por el sitio como si fuese un administrador. Imagina lo perjudicial que puede ser eso para un sitio web. Pero no te preocupes, hay muchas maneras de proteger tu queries contra inyecciones SQL.

Lo primero de todo es utilizar la extensión mejorada de MySQL, MySQLi. Las antiguas funciones de MySQL todavía están disponibles, pero la extensión MySQLi ofrece características más seguras. Si utilizas las funciones de MySQLi puedes escoger entre estilo procedural y estilo orientado a objetos. Yo, personalmente, utilizo el orientado a objetos.

Previamente, formateo los valores de las queries con la función filter_var() en los valores que introduce el usuario.

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

En el ejemplo puedes ver como utilizo el tipo de filtro FILTER_SANITIZE_STRING. Después uso la variante de MySQLi mysql_real_escape_string() para preparar las cadenas que van a ser introducidas en la base de datos. Sobre todo, no albergues las contraseñas como texto plano en la base de datos. Para hacer coincidir el valor introducido por el usuario con el que hay almacenado en la BD utiliza encriptaciones como MD5/salt. Es decir, comprueba siempre que el valor del usuario en MD5 (u otra encriptación) sea el mismo que el valor en MD5 que existe en la BD.

$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 los valores del usuario a la query SQL.

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

Ataques Cross Site Request Forgery (CSRF)

El principal problema detrás de un ataque CSRF no es que acceden a tu sitio web, sino que fuerzan a un usuario o un administrador a realizar una acción no deseada. Por ejemplo, imaginemos que tenemos una página de administración con una estructura HTML similar a 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 un script de login. Es decir, un usuario sin permisos no podrá acceder a la página o 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 no funciona si el usuario no está logueado, y si por el contrario lo está, eliminará la página con ID que traiga en la variable dento del GET. Mediante el uso de la función sprintf() formateo el valor a un entero utilizando el tipo %d. Parece seguro, ¿verdad?

Supongamos que un usuario logueado visita un página donde se ha publicado un comentario que incluye una imagen. Si un hacker ha publicado una imagen con una URL similar a la de abajo no lo notaras, porque la imagen no se mostrará al no estar la URL disponible.

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

Ya se que este es un ejemplo muy estúpido y que solo vulnerará la seguridad de tu sitio web si el hacker conoce como está montado tu sitio web, pero puedes hacerte una idea de por donde van los tiros. Lo más seguro para evitar esto es crear un token único para cada acción importante, y crearlo para el usuario administrador cuando inicie sesión. Es decir, dentro de la función login_user() creas una variable de sesión que contenga una cadena cifrada en 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 del HTML incluimos la variable token en cada uno de los links.

$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 dentro del script delete.php puedo realizar las acciones adecuadas al script.

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']));
    }
}

Ataques Cross Site Scripting (XSS)

La idea básica de un ataque XSS es que el hacker embebe cierto código de parte del cliente en tu web que se ejecuta o se descarga por un visitante. Esto ocurre de diversas formas. Por ejemplo, utilizando un link a tu sitio web donde se ha añadido algo de Javascript malicioso o el hacker ha publicado algo de codigo Javascript en tu sitio web. La última forma, y la más popular, es utilizando el formulario de comentarios donde el contenido se adhiere en tu sitio web.

En cualquier situación es importante que tu app web formatee los inputs del usuario antes que los datos se almacenen o parseen en tu página web. Utiliza diferentes funciones de validación como preg_match(), filter_var() o mejor htmlspecialchars() para filtrar o converir posibles ataques de los hackers.

Limita la funcionalidad de tu script

Si tu sitio web cuenta con una función de login, es posible que un hacker utilice un script que intente adivinar un nombre de usuario y una contraseña. Al utilizar un script de fuerza bruta, puede ser que al final tenga éxito. Es por eso que lo mejor es bloquear el script de login a los X intentos, y también utilizar contraseñas 100% seguras. Contraseñas más que obvias como “1234” o “admin” la mayoría de las veces son la principal causa de que un sitio web haya sido hackeado.

Ten mucho cuidado también con la función de recuperar contraseñas. Nunca envíes las instrucciones de recuperación a una nueva dirección de correo electrónico y permite que el propietario del emial registrado ejecute la acción de recuperación.

Los captcha son también un método ideal y muy sencillo de parar los pies a los bots que acceden a tus formularios web. Úsalos en aquellos formularios que pueden dañar tu aplicación web.

Deshabilita los errores de PHP

Existen muchas razones por las cuales pueda mostrarse un error de PHP en tu sitio web. Muchos de ellos no tienen ninguna influencia en la funcionalidad del sitio web. Para un hacker, un warning o error es una fuente para obtener información acerca de tu sitio web y/o de la configuración de tu servidor. Testea siempre tu sitio web ante posibles errores por estos motivos y porque, ¿confiarías en un servicio que está roto?

También existen funciones que inhabilitan la salida de mensajes de error. Agrega ini_set( 'display_errors', 0); en tu script y muestra solo los errores en desarrollo y no en producción.

Fuente: finalwebsites.com

COMPARTE ESTE ARTÍCULO

ENVIAR A UN AMIGO
COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN GOOGLE +
¡SÉ EL PRIMERO EN COMENTAR!
Conéctate o Regístrate para dejar tu comentario.