La inyección SQL es una de las vulnerabilidades más peligrosas que quita el sueño a los desarrolladores. Aprende a combatirla para proteger tu sitio web.
Lanzas un sitio, lo llenas de contenido, inicias una campaña publicitaria: el tráfico crece de manera constante, los usuarios comentan y comparten artículos activamente. Todo va bien hasta que un día, sin previo aviso, descubres que no queda ni un solo artículo en el sitio. Revisas los registros de consultas y ves que alguien ejecutó un comando DROP…
¿Qué es una Inyección SQL?
La inyección SQL es un intento de modificar una consulta a la base de datos. Se puede introducir a través de un formulario o un enlace que transmite parámetros mediante el método GET. Imagina que en tu sitio hay un formulario de registro donde el usuario ingresa un nombre de usuario y una contraseña.
<form action='reg.php' method='post'>
<input type='text' name='login' placeholder='Nombre de usuario' required><br>
<input type='password' name='password' placeholder='Contraseña' required><br>
<input type='submit' value='Registrarse'>
</form>
Estos datos se envían al servidor y se insertan en una consulta como esta:
$query = "INSERT INTO userlist (" . $keys . ") VALUES (" . $values . ")";
En la variable $keys
se almacenan las claves del superglobal $_POST
, y en $values
los valores:
$keys = "";
$values = "";
$first = 1;
foreach ($_POST as $key => $value) {
if ($first == 0) { // Añade una coma antes de cada elemento, excepto el primero
$keys .= ",";
$values .= ",";
}
$keys .= $key;
$values .= "'" . $value . "'";
$first = 0;
}
Esto es conveniente si el formulario tiene muchos campos, pero introduce una vulnerabilidad: si un usuario desea ejecutar una inyección SQL, puede simplemente crear un nuevo campo en el formulario con el nombre que desee e ingresar cualquier valor. Por ejemplo, puede crear un campo admin
y asignarle el valor 1
.
<br><input type='number' name='admin' value='1' required><b>
Si el sitio no está bien protegido, el hacker puede realizar muchos intentos, pero eventualmente podría obtener acceso con privilegios de administrador.
Cómo Proteger tu Sitio de la Inyección SQL
Primero, recuerda que no existen sistemas completamente seguros, por lo que debes buscar constantemente vulnerabilidades que puedan afectar tu sitio.
1. Utiliza listas blancas
En el ejemplo anterior, nos habría salvado usar una lista blanca, especificando las claves y valores permitidos.
$keys = "";
$values = "";
$first = 1;
$allowed = array("login", "password", "email", "nickname");
foreach ($_POST as $key => $value) {
if (in_array($key, $allowed)) {
if ($first == 0) {
$keys .= ",";
$values .= ",";
}
$keys .= $key;
$values .= "'" . $value . "'";
$first = 0;
}
}
Ahora, solo las claves y valores que están en la lista blanca $allowed
se incluirán en las variables $keys
y $values
.
2. No utilices el método GET en formularios
Transmitir variables de esta manera es muy peligroso, ya que quedan visibles para los usuarios. Si la información es importante, es mejor usar POST. De un enlace, un atacante puede no solo conocer los nombres de las variables, sino también los valores que deberían tener, permitiéndole elegir la variable óptima para la inyección.
No recomendamos usar variables directamente del superglobal; es mejor asignarlas a otra variable después de validarlas.
3. Procesa las variables
Procura escapar comillas, reemplazar caracteres especiales, eliminar espacios innecesarios, etc. Veamos qué pasaría si no lo haces:
Usa funciones como trim
(elimina espacios innecesarios), htmlspecialchars
(reemplaza corchetes angulares y otros caracteres especiales), addslashes
(escapa comillas y caracteres especiales), entre otras.
En el formulario de registro, un atacante podría usar comillas y comas para agregar una nueva clave y valor. Por ejemplo, al ingresar algo como pass', 'algo
en el campo, añadiría información adicional que no pasa la lista blanca.
También puedes verificar los tipos de variables: número, cadena, archivo, etc.
4. Verifica de dónde vienen los datos
No basta con procesar los datos, es necesario saber de dónde provienen. Puedes rastrear el origen de varias maneras:
- Verificarlo en
$_SERVER['HTTP_REFERER']
. - Crear un campo oculto en el formulario.
- Especificar el nombre del formulario.
- Especificar el nombre del botón de envío, etc.
Claro, esto solo es un pequeño extra en la protección, pero ayuda a descartar a atacantes inexpertos que abandonarán después de algunos intentos.
5. Usa PDO
Con PDO (PHP Data Objects) y placeholders, puedes reducir significativamente el riesgo de inyección, ya que los datos y la consulta se envían por separado. Primero se conecta a la base de datos, luego se prepara la consulta, se especifican las variables por separado y, finalmente, se ejecuta la consulta. Así se ve:
$db = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
$stmt = $db->prepare("SELECT * FROM articles WHERE id=:id");
$stmt->bindParam(':id', $id);
$stmt->execute();
Los datos se envían como variables. Si alguien coloca una comilla adicional en la variable, el servidor no la considerará parte de la consulta. Por lo tanto, cualquier intento de alterar la consulta mediante la variable no funcionará.
Este método es probablemente el más efectivo, así que cambia a PDO lo antes posible. Sin embargo, su uso no significa que las otras medidas de seguridad no sean necesarias. Cuantos más mecanismos de protección implementes, más segura estará la información.
Frecuentemente, los autodidactas que estudian de manera superficial omiten estas verificaciones. Pueden resolver problemas, pero rara vez piensan en eliminar vulnerabilidades.
Protección Adicional del Sitio Contra Ataques
Además de proteger las consultas y variables, es crucial eliminar todas las posibles brechas que puedan llevar a una filtración de información o acceso no autorizado a los derechos de administrador.
1. Prohibir el acceso directo a archivos de servicio
Todos los archivos en un sitio web pueden abrirse o descargarse si se conoce su dirección. Por ejemplo, se puede intentar descargar el archivo header.php
para buscar vulnerabilidades.
Es mejor mover todos los archivos incluidos en una carpeta separada y prohibir el acceso directo a ella. Puedes crear una carpeta llamada includes
y colocar allí:
- Bloques separados del sitio;
- Bibliotecas de funciones personalizadas;
- Archivo de conexión a la base de datos;
- Controladores de formularios, etc.
Para esto, crea un archivo .htaccess
en esa carpeta y añade la siguiente línea:
deny from all
El acceso directo será bloqueado, pero aún se podrán incluir estos archivos mediante PHP.
2. No publiques tu código en foros
A veces necesitamos ayuda de colegas, por lo que publicamos fragmentos de nuestro código en foros o Stackoverflow. Hazlo solo en casos extremos y asegúrate de que nadie pueda identificar tu sitio web.
Nada debe indicar la temática del sitio, su dirección, el hosting en el que se encuentra, etc. Cuanta más información divulgas voluntariamente, mayor es el riesgo de ser hackeado.
3. Verifica el código copiado
Siempre revisa el código que copias, ya que podría contener un exploit que perjudique tu sitio o permita al autor del código acceder a él. Trata de leer lo que estás pegando. Incluso una sola línea puede ser suficiente para crear una brecha en tu seguridad.
Idealmente, reescribe el código manualmente; así notarás cualquier comando sospechoso.
4. Desactiva la visualización de errores
Mostrar errores es muy útil durante el desarrollo, pero si el sitio ya está en línea, es mejor desactivar las notificaciones de errores. Un atacante podría ver los problemas del sitio e intentar explotar esas vulnerabilidades.
Puedes desactivar la visualización de errores en el archivo .htaccess
añadiendo las siguientes líneas:
php_flag display_errors off
php_value error_reporting 0
Además, elimina cualquier salida de errores que hayas incluido en tu código.
5. Restringe los permisos del usuario de la base de datos
Normalmente, para conectar a la base de datos se crea una cuenta con todos los permisos para aprovechar todas las capacidades de SQL. Sin embargo, es mejor restringirlos. Entra a phpmyadmin
, añade un nuevo usuario y dale solo algunos permisos.
Marca solo el permiso “Datos”. Esto permitirá operar con datos existentes, pero no crear nuevas tablas ni eliminar las antiguas. Así te protegerás de ataques con comandos como DROP
, que podrían eliminar todas las entradas del sitio.
Si el administrador de tu sitio necesita privilegios para modificar la estructura regularmente, crea un usuario separado para usar en la administración. Sin embargo, existe el riesgo de que alguien obtenga acceso a la administración. Limita el número máximo de conexiones y solicitudes.
También es mejor poner una contraseña a root
para evitar que un atacante se conecte con este usuario.
6. Instala la última versión del lenguaje
Las versiones antiguas de cualquier lenguaje suelen ser menos funcionales y tienen muchas vulnerabilidades. Las brechas críticas se conocen desde hace años, por lo que los hackers las usan para atacar sitios.
Sí, las actualizaciones también pueden tener errores, pero no tan peligrosos. Además, nadie los conocerá si ha pasado poco tiempo desde su lanzamiento.
7. Usa una contraseña segura
Es básico, pero una contraseña simple puede ser descifrada en segundos, especialmente si contiene datos personales:
- Fecha de nacimiento;
- Nombre de una mascota;
- Aniversario de boda;
- Apellido de soltera de la madre, etc.
Es común que esta información se publique libremente.
Conclusión
No existen sistemas completamente seguros; solo puedes reducir la probabilidad de un ataque. Para ello, debes ser un poco hacker:
- Introducir inyecciones;
- Cambiar tipos de datos;
- Añadir comillas y caracteres de escape en los campos;
- Intentar subir un archivo
.php
con código malicioso, etc.
Intenta hackear tu propio sitio para descubrir sus vulnerabilidades. Hazlo antes de que alguien más lo haga.
Pingback: Cómo los Sitios Web son Hackeados y Cómo Protegerlos