Saltar contenido

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 credenciales vamos a tomar como ejemplo un Formulario de Inicio de Sesión, donde se solicitan las credenciales de acceso mediante Usuario y Contraseña

Formulario de Inicio de Sesión

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 vulnerable
SELECT name, password
FROM users
WHERE 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 vulnerable
SELECT name, password
FROM users
WHERE 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 credenciales vamos a tomar como ejemplo un Buscador de Productos que mediante el criterio de busqueda por código del producto se muestran los productos encontrados

Buscador de Productos

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 vulnerable
SELECT code, product
FROM products
WHERE 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 vulnerable
SELECT code, product
FROM products
WHERE 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 maliciosamente vamos a tomar como ejemplo una Suscripción de noticias que mediante el ingreso de un correo electrónico se registra al boletín de noticias

Suscripció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 vulnerable
INSERT 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 vulnerable
INSERT INTO subscribers (email)
VALUES ('[email protected]'); DROP TABLE users --'

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.

app/Http/Controllers/AuthController.php
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:

app/Http/Requests/LoginRequest.php
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

app/Http/Requests/LoginRequest.php
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)).

app/Http/Controllers/AuthController.php
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)).

app/Http/Controllers/AuthController.php
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)).

app/Http/Controllers/AuthController.php
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:

app/Http/Requests/LoginRequest.php
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))),
]);
}
}
app/Http/Controllers/LoginController.php
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');
}
}