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.