Inyección SQL (SQLi)
¿Qué es la Inyección SQL?
Inyección SQL o SQLi (SQL Inyection), es un tipo de ataque en el que un atacante inserta código SQL malicioso en campos de entrada, con el objetivo de manipular las consultas SQL que la aplicación Web realiza a la base de datos.
Impacto de la Inyección SQL
Los ataques de inyección SQL pueden permitir al atacante:
- Leer datos confidenciales, como: contraseñas, números de tarjetas de crédito, información personal, etc.
- Modificar o eliminar datos, como: alterar el contenido de la base de datos o borrar información crítica.
- Ejecutar comandos del sistema, como: tomar el control del servidor de la base de datos o del sistema operativo subyacente.
¿Cómo ocurre un ataque Inyección SQL?
La inyección SQL en Laravel es una vulnerabilidad de seguridad que ocurre cuando un atacante logra insertar código malicioso en las consultas que la aplicación web envía a la base de datos. Esto puede suceder si no se valida y sanitiza adecuadamente las entradas del usuario antes de incluirlas en las consultas o utilizar consultas sin parametrizar.
1. Acceso a datos sin credenciales
Para ilustrar un ataque de
Acceso a datos sin credencialesvamos a tomar como ejemplo un
Formulario de Inicio de Sesión, donde se solicitan las credenciales de acceso mediante
Usuarioy
Contraseña
1.1. Entrada Maliciosa
Un atacante que no ha inicio sesión, introduce datos maliciosos en un campo de entrada de una aplicación web, como: formularios, URLs, etc.
a. En el primer campo Usuario se escribe cualquier nombre de usuario, como: usuario-ficticio
. Seguido el atacante ingresa una sentencia SQL maliciosa:
-- sentencia SQL maliciosa' OR 1=1 --
b. En el segundo campo Contraseña se escribe cualquier cadena de caracteres, como: 123456
.
1.2. Consulta Vulnerable
Si la aplicación no valida ni sanitiza correctamente la entrada del usuario, esta sentencia SQL maliciosa se incorpora directamente en la consulta SQL que se envía a la base de datos.
Para seguir con la ilustración una consulta SQL básica que busca un usuario de acuerdo al nombre sería:
-- consulta SQL vulnerableSELECT name, passwordFROM usersWHERE name = 'usuario-ficticio'
c. Ccn la inyección de la sentencia SQL maliciosa, el atacante modifica la consulta SQL original de la siguiente manera:
-- inyección de la sentencia SQL maliciosa-- en la consulta SQL vulnerableSELECT name, passwordFROM usersWHERE name = 'usuario-ficticio' OR 1=1 --`
1.3. Ejecución del Código Malicioso
La base de datos interpreta el código SQL modificado. En este caso, en la primara parte la consulta para buscar usuarios mediante la condición de la coincidencia del nombre de usuario, si el usuario ingresado usuario-ficticio
no existe en la base de datos la condición no se cumpliría y la consulta devolvería un resultado vacío, pero la condición OR 1=1
inyectada siempre es verdadero, lo que significa que la consulta devolvería todos los registros de los usuarios. Además, el comentario --
anula el resto de la consulta original, evitando errores de sintaxis.
1.4. Consecuencias
El atacante podría obtener acceso no autorizado a datos sensibles, modificar información en la base de datos o incluso ejecutar comandos en el sistema operativo si el usuario asignado en la aplicación web para el manejo de la base de datos tiene permisos suficientes.
2. Acceso a datos con credenciales
Para ilustrar un ataque de
Acceso a datos con credencialesvamos a tomar como ejemplo un
Buscador de Productosque mediante el criterio de busqueda por
código del productose muestran los productos encontrados
2.1. Entrada Maliciosa
Un atacante que ha iniciado sesión e introduce datos maliciosos en un campo de entrada de una aplicación web, como: formularios, URL, etc.
a. En el campo Buscar se escribe cualquier código de producto, como: ABC123
. Seguido el atacante ingresa una sentencia SQL maliciosa:
-- sentencia SQL maliciosa' UNION SELECT name, password FROM users --
2.2. Consulta Vulnerable
Si la aplicación no valida ni sanitiza correctamente la entrada del usuario, esta sentencia SQL maliciosa se incorpora directamente en la consulta SQL que se envía a la base de datos.
Para seguir con la ilustración una consulta SQL básica que busca productos de acuerdo al código sería:
-- consulta SQL vulnerableSELECT code, productFROM productsWHERE code = 'ABC123'
b. Ccn la inyección de la sentencia SQL maliciosa, el atacante modifica la consulta SQL original de la siguiente manera:
-- inyección de la sentencia SQL maliciosa-- en la consulta SQL vulnerableSELECT code, productFROM productsWHERE code = 'ABC123' UNION SELECT name, password FROM users --'
2.3. Ejecución del Código Malicioso
La base de datos ejecuta la consulta SQL modificada. La primera parte de la consulta SELECT code, product FROM products WHERE code = 'ABC123'
se ejecuta normalmente, pero debido al operador UNION
, se combinan los resultados con los de la segunda consulta SELECT name, password FROM users
. Además, el comentario --
anula el resto de la consulta original, evitando errores de sintaxis.
2.4. Consecuencias
La aplicación devuelve los resultados de ambas consultas combinados. Esto significa que, además de los detalles del producto (si existe un producto con uno código ABC123
), la aplicación también mostrará los usuarios y contraseñas almacenados en la tabla usuarios.
3. Eliminación de datos maliciosamente
Para ilustrar un ataque de
Eliminación de datos maliciosamentevamos a tomar como ejemplo una
Suscripción de noticiasque mediante el ingreso de un
correo electrónicose registra al boletín de noticias
3.1. Entrada Maliciosa
Un atacante haya iniciado sesión o no, introduce datos maliciosos en un campo de entrada de una aplicación web, como: formularios, URL, etc.
a. En el campo Correo electrónico se escribe cualquier correo electrónico, como: [email protected]
. Seguido el atacante ingresa una sentencia SQL maliciosa:
-- sentencia SQL maliciosa'); DROP TABLE users --
3.2. Consulta Vulnerable
Si la aplicación no valida ni sanitiza correctamente la entrada del usuario, esta sentencia SQL maliciosa se incorpora directamente en la consulta SQL que se envía a la base de datos.
Para seguir con la ilustración una consulta SQL básica que registra un suscripción de acuerdo al correo electrónico sería:
-- consulta SQL vulnerableINSERT INTO subscribers (email)
b. Ccn la inyección de la sentencia SQL maliciosa, el atacante modifica la consulta SQL original de la siguiente manera:
-- inyección de la sentencia SQL maliciosa-- en la consulta SQL vulnerableINSERT INTO subscribers (email)
3.3. Ejecución del Código Malicioso
La base de datos ejecuta la consulta SQL modificada. La primera parte de la consulta INSERT INTO subscribers (email) VALUES ('[email protected]')
se ejecuta normalmente, pero en este caso también se ejecuta DROP TABLE users
, lo que significa la tabla users se elimina de la base de datos. Además, el comentario --
anula el resto de la consulta original, evitando errores de sintaxis.
3.4. Consecuencias
El atacante podría eliminar todos los registros de la tabla users de la base de datos o incluso eliminar la base de datos si el usuario asignado en la aplicación web para el manejo de la base de datos tiene permisos suficientes.
Mitigación de la Inyección SQL (SQLi)
Laravel proporciona herramientas para proteger las aplicaciones web contra inyecciones SQL, pero sin embargo puede ser vulnerable.
namespace App\Http\Controllers; use Illuminate\Http\Request;use Illuminate\Support\Facades\DB; class AuthController extends Controller{ public function login(Request $request) { $name = $request->input('name'); $password = $request->input('password'); /* Consulta SQL vulnerable */ $user = DB::select( "SELECT name, password ". "FROM users ". "WHERE name = '$name'" ); }}
La mitigación de la Inyección SQL en Laravel se la realiza mediante:
1. Validación de Entradas
Validar rigurosamente todas las entradas del usuario para asegurar que cumplan con los formatos y tipos de datos esperados. Laravel ofrece una variedad de reglas de validación como la validación en el controlador. Sin embargo, ASAWL recomienda siempre validar las entradas mediante Form Request. Referencia: Laravel Validation - Form Request (ver la documentación oficial en inglés).
Mediante el siguiente comando se creará un Form Request
para validar entradas llamado LoginRequest
:
php artisan make:request LoginRequest
Mediante las reglas de validación establecidas en LoginRequest
, se puede establecer el formato y el tipo de dato en la función rules()
para las entradas de correo electrónico y contraseña:
namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class LoginRequest extends FormRequest{ /* ... código oculto ... */ /** * Validación de entradas */ public function rules(): array { return [ 'name' => ['required', 'string'], 'password' => ['required', 'string'], ]; } /* ... código oculto ... */}
Laravel dispone de diferentes reglas de validación de entrada de acuerdo al tipo de datos. Referencia: Laravel Validation - Available Validation Rules (ver documentación oficial en inglés)
2. Sanitización de Entradas
-
Si se necesita incluir entradas del usuario directamente en consultas SQL (aunque no es recomendable), se debe utilizar las funciones de escape de Laravel para sanitizar los datos y evitar que se interpreten como código SQL. Referencia: Laravel Strings - Method e() (ver documentación oficial en inglés)
-
Se puede utilizar la función trim() para eliminar espacios en blanco. Referencia: Laravel Strings - Method trim() (ver documentación de PHP Function trim() (sitio web oficial en inglés))
-
Y adicional se puede utilizar la función strip_tags() para eliminar tags HTML (ver documentación de PHP Function strip_tags() (sitio web oficial en inglés)).
namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class LoginRequest extends FormRequest{ /* ... código oculto ... */ /** * Sanitización de entradas */ protected function passedValidation(): void { $this->merge([ 'name' => strip_tags(trim(e($this->name))), ]); }}
3. Use de Herramientas Seguras de Consulta SQL
3.1. Uso de Eloquent ORM
se debe utilizar Eloquent, el ORM de Laravel como constructor para parametrizar las consultas, Eloquent ayuda a evitar que las entradas maliciosas se interpreten como código SQL (ver documentación de Laravel Eloquent (sitio web oficial en inglés)).
namespace App\Http\Controllers; use App\Models\User;use App\Http\Requests\LoginRequest; class AuthController extends Controller{ public function login(LoginRequest $request) { $name = $request->input('name'); $password = $request->input('password'); // Uso de Eloquent ORM $user = User::where('name', $name) ->first(); }}
3.2. Uso de Query Builder
Si necesitas más flexibilidad que Eloquent, usa el Query Builder de Laravel, que también escapa los parámetros de forma segura (ver documentación de Laravel Queries (sitio web oficial en inglés)).
namespace App\Http\Controllers; use App\Models\User;use App\Http\Requests\LoginRequest; class AuthController extends Controller{ public function login(LoginRequest $request) { $name = $request->input('name'); // Uso de Query Builder $user = DB::table('users') ->where('name', $anme) ->get(); }}
3.3. Uso de Consultas Parametrizadas
Si debes construir consultas SQL dinámicas, utiliza parámetros con nombre o marcadores de posición (?) para evitar que el código malicioso se interprete como parte de la consulta, estas consultas se denominan consultas parametrizadas (ver documentación de Laravel Database (sitio web oficial en inglés)).
namespace App\Http\Controllers; use App\Models\User;use App\Http\Requests\LoginRequest; class AuthController extends Controller{ public function login(LoginRequest $request) { $name = $request->input('name'); // Uso de Consultas Parametrizadas $user = DB::select( 'SELECT name, password ". "FROM users ". "WHERE name = ?', $name ); }}
Para ilustrar la mitigación de la Inyección SQL en Laravel, se puede revisar el código de la función login() de un controlador utilizado para el proceso de inicio de sesión de una aplicación web:
namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class LoginRequest extends FormRequest{ public function authorize(): bool { return true; } /** * Validación de entradas */ public function rules(): array { return [ 'name' => ['required', 'string'], 'password' => ['required', 'string'], ]; } /** * Sanitización de entradas */ protected function passedValidation(): void { $this->merge([ 'name' => strip_tags(trim(e($this->name))), ]); }}
namespace App\Http\Controllers; use App\Http\Requests\LoginRequest;use App\Models\User;use Illuminate\Support\Facades\Hash; class AuthController extends Controller{ public function login(LoginRequest $request) { $name = $request->input('name'); $password = $request->input('password'); // Uso de Eloquent ORM $user = User::where('name', $name)->first(); if (Hash::check($password, $user->password)) { $request->session()->regenerate(); return redirect()->intended('dashboard'); } return back()->withErrors([ 'name' => 'las credenciales no coinciden.', ])->onlyInput('name'); }}