Riesgos de seguridad

Las bases de datos contienen a menudo datos sensibles y permiten operaciones peligrosas. Nette Database proporciona una serie de características de seguridad. Sin embargo, es crucial entender la diferencia entre API seguras y no seguras.

Inyección SQL

La inyección SQL es el riesgo de seguridad más grave cuando se trabaja con bases de datos. Se produce cuando una entrada de usuario no verificada pasa a formar parte de una consulta SQL. Un atacante puede inyectar sus propios comandos SQL, obteniendo o modificando datos en la base de datos.

// ❌ CÓDIGO INSEGURO - vulnerable a la inyección SQL.
$database->query("SELECT * FROM users WHERE name = '$_GET[name]'");

// El atacante puede introducir algo como ' OR '1'='1
// La consulta resultante será:
// SELECT * FROM usuarios WHERE nombre = '' OR '1'='1'
// Esto devuelve todos los usuarios.

Lo mismo ocurre con Database Explorer:

// ❌ CÓDIGO DE SEGURIDAD
$table->where('name = ' . $_GET['name']);
$table->where("name = '$_GET[name]'");

Consultas parametrizadas seguras

La forma segura de insertar valores en consultas SQL es a través de consultas parametrizadas. Nette Database proporciona varias formas de utilizarlas.

Signos de interrogación

El método más sencillo consiste en utilizar signos de interrogación:

// ✅ Consultas parametrizadas seguras.
$database->query('SELECT * FROM users WHERE name = ?', $_GET['name']);

// ✅ Condición segura en el Explorador
$table->where('name = ?', $_GET['name']);

Lo mismo se aplica a todos los demás métodos de Database Explorer que permiten insertar expresiones con signos de interrogación de marcador de posición y parámetros.

Los valores deben ser de tipo escalar (string, int, float, bool) o null. Si, por ejemplo, $_GET['name'] es una matriz, Nette Database incluirá todos sus elementos en la consulta SQL, lo que puede resultar indeseable.

Arrays de valores

Para las cláusulas INSERT, UPDATE, o WHERE, podemos utilizar matrices de valores:

// ✅ Safe INSERT
$database->query('INSERT INTO users', [
	'name' => $_GET['name'],
	'email' => $_GET['email'],
]);

// ✅ UPDATE seguro
$database->query('UPDATE users SET', [
	'name' => $_GET['name'],
	'email' => $_GET['email'],
], 'WHERE id = ?', $_GET['id']);

Nette Database escapa automáticamente todos los valores pasados a través de consultas parametrizadas. Sin embargo, debemos asegurarnos de que el tipo de datos de los parámetros es correcto.

Las claves de array no son una API segura

Mientras que los valores de las matrices son seguros, no puede decirse lo mismo de las claves:

// ❌ CÓDIGO INSEGURO - las claves pueden contener inyección SQL.
$database->query('INSERT INTO users', $_GET);
$database->query('SELECT * FROM users WHERE', $_GET);
$table->where($_GET);

Para los comandos INSERT y UPDATE, se trata de un fallo de seguridad crítico: un atacante podría insertar o modificar cualquier columna de la base de datos. Por ejemplo, podrían establecer is_admin = 1 o insertar datos arbitrarios en columnas sensibles.

En condiciones WHERE, esto es aún más peligroso porque permite la enumeración SQL – una técnica para recuperar gradualmente información sobre la base de datos. Un atacante podría intentar explorar los salarios de los empleados inyectando en $_GET de esta manera:

$_GET = ['salary >', 100000];   // empieza a determinar los rangos salariales

El principal problema, sin embargo, es que las condiciones de WHERE admiten expresiones SQL en las claves:

// Uso legítimo de operadores en claves
$table->where([
    'age > ?' => 18,
    'ROUND(score, ?) > ?' => [2, 75.5],
]);

// ❌ INSEGURO: el atacante puede inyectar su propio SQL
$_GET = ['1) UNION SELECT name, salary FROM users WHERE (is_admin = ?' => 1];
$table->where($_GET); // permite al atacante obtener salarios de administrador

Esto es una vez más inyección SQL.

Lista blanca de columnas

Si desea permitir a los usuarios elegir columnas, utilice siempre una lista blanca:

// ✅ Tratamiento seguro - sólo columnas permitidas
$allowedColumns = ['name', 'email', 'active'];
$values = array_intersect_key($_GET, array_flip($allowedColumns));

$database->query('INSERT INTO users', $values);

Identificadores dinámicos

Para los nombres dinámicos de tablas y columnas, utilice el marcador de posición ?name:

// ✅ Uso seguro de identificadores de confianza
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name', $column, $table);

// ❌ INSEGURO: no utilizar nunca la entrada del usuario.
$database->query('SELECT ?name FROM users', $_GET['column']);

El símbolo ?name sólo debe utilizarse para valores de confianza definidos en el código de la aplicación. Para los valores proporcionados por el usuario, vuelva a utilizar una lista blanca.

versión: 4.0