Nette Documentation Preview

syntax
Riscos de segurança
*******************

.[perex]
Os bancos de dados geralmente contêm dados confidenciais e permitem operações perigosas. O Nette Database oferece vários recursos de segurança. No entanto, é fundamental entender a diferença entre APIs seguras e inseguras.


Injeção de SQL .[#toc-sql-injection]
====================================

A injeção de SQL é o risco de segurança mais grave quando se trabalha com bancos de dados. Ela ocorre quando uma entrada de usuário não verificada se torna parte de uma consulta SQL. Um invasor pode injetar seus próprios comandos SQL, obtendo ou modificando dados no banco de dados.

```php
// CÓDIGO INSEGURO - vulnerável à injeção de SQL
$database->query("SELECT * FROM users WHERE name = '$_GET[name]'");

// O invasor pode inserir algo como: ' OR '1'='1
// A consulta resultante será:
// SELECT * FROM users WHERE name = '' OR '1'='1'
// Isso retorna todos os usuários!
```

O mesmo se aplica ao Database Explorer:

```php
// CÓDIGO INSEGURO
$table->where('name = ' . $_GET['name']);
$table->where("name = '$_GET[name]'");
```


Consultas parametrizadas seguras .[#toc-safe-parameterized-queries]
===================================================================

A maneira segura de inserir valores em consultas SQL é por meio de consultas parametrizadas. O Nette Database oferece várias maneiras de usá-las.


Marcadores de interrogação de espaço reservado .[#toc-placeholder-question-marks]
---------------------------------------------------------------------------------

O método mais simples é usar pontos de interrogação de espaço reservado:

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

// Condição de segurança no Explorer
$table->where('name = ?', $_GET['name']);
```

O mesmo se aplica a todos os outros métodos do Database Explorer que permitem a inserção de expressões com pontos de interrogação e parâmetros de espaço reservado.

.[warning]
Os valores devem ser do tipo escalar (`string`, `int`, `float`, `bool`) ou `null`. Se, por exemplo, `$_GET['name']` for uma matriz, o Nette Database incluirá todos os seus elementos na consulta SQL, o que pode ser indesejável.


Matrizes de valores .[#toc-value-arrays]
----------------------------------------

Nas cláusulas `INSERT`, `UPDATE` ou `WHERE`, podemos usar matrizes de valores:

```php
// INSERT seguro
$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']);
```

O Nette Database escapa automaticamente de todos os valores passados por meio de consultas parametrizadas. No entanto, é preciso garantir o tipo de dados correto dos parâmetros.


As chaves de matriz não são uma API segura .[#toc-array-keys-are-not-a-safe-api]
================================================================================

Embora os valores em matrizes sejam seguros, o mesmo não se pode dizer das chaves:

```php
// CÓDIGO INSEGURO - as chaves podem conter injeção de SQL
$database->query('INSERT INTO users', $_GET);
$database->query('SELECT * FROM users WHERE', $_GET);
$table->where($_GET);
```

Para os comandos `INSERT` e `UPDATE`, essa é uma falha de segurança crítica - um invasor pode inserir ou modificar qualquer coluna no banco de dados. Por exemplo, ele poderia definir `is_admin = 1` ou inserir dados arbitrários em colunas confidenciais.

Nas condições do `WHERE`, isso é ainda mais perigoso porque permite a **enumeração do SQL**, uma técnica para recuperar gradualmente informações sobre o banco de dados. Um invasor poderia tentar explorar os salários dos funcionários injetando em `$_GET` dessa forma:

```php
$_GET = ['salary >', 100000];   // começa a determinar as faixas salariais
```

O principal problema, entretanto, é que as condições do `WHERE` suportam expressões SQL nas chaves:

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

// INSEGURO: o invasor pode injetar seu próprio SQL
$_GET = ['1) UNION SELECT name, salary FROM users WHERE (is_admin = ?' => 1];
$table->where($_GET); // permite que o invasor obtenha salários de administrador
```

Isso é mais uma vez **injeção de SQL**.


Colunas de lista branca .[#toc-whitelisting-columns]
----------------------------------------------------

Se quiser permitir que os usuários escolham colunas, use sempre uma lista de permissões:

```php
// Processamento seguro - somente colunas permitidas
$allowedColumns = ['name', 'email', 'active'];
$values = array_intersect_key($_GET, array_flip($allowedColumns));

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


Identificadores dinâmicos .[#toc-dynamic-identifiers]
=====================================================

Para nomes dinâmicos de tabelas e colunas, use o espaço reservado `?name`:

```php
// Uso seguro de identificadores confiáveis
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name', $column, $table);

// UNSAFE - nunca use a entrada do usuário
$database->query('SELECT ?name FROM users', $_GET['column']);
```

O símbolo `?name` deve ser usado somente para valores confiáveis definidos no código do aplicativo. Para valores fornecidos pelo usuário, use uma lista branca novamente.

Riscos de segurança

Os bancos de dados geralmente contêm dados confidenciais e permitem operações perigosas. O Nette Database oferece vários recursos de segurança. No entanto, é fundamental entender a diferença entre APIs seguras e inseguras.

Injeção de SQL

A injeção de SQL é o risco de segurança mais grave quando se trabalha com bancos de dados. Ela ocorre quando uma entrada de usuário não verificada se torna parte de uma consulta SQL. Um invasor pode injetar seus próprios comandos SQL, obtendo ou modificando dados no banco de dados.

// CÓDIGO INSEGURO - vulnerável à injeção de SQL
$database->query("SELECT * FROM users WHERE name = '$_GET[name]'");

// O invasor pode inserir algo como: ' OR '1'='1
// A consulta resultante será:
// SELECT * FROM users WHERE name = '' OR '1'='1'
// Isso retorna todos os usuários!

O mesmo se aplica ao Database Explorer:

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

Consultas parametrizadas seguras

A maneira segura de inserir valores em consultas SQL é por meio de consultas parametrizadas. O Nette Database oferece várias maneiras de usá-las.

Marcadores de interrogação de espaço reservado

O método mais simples é usar pontos de interrogação de espaço reservado:

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

// Condição de segurança no Explorer
$table->where('name = ?', $_GET['name']);

O mesmo se aplica a todos os outros métodos do Database Explorer que permitem a inserção de expressões com pontos de interrogação e parâmetros de espaço reservado.

Os valores devem ser do tipo escalar (string, int, float, bool) ou null. Se, por exemplo, $_GET['name'] for uma matriz, o Nette Database incluirá todos os seus elementos na consulta SQL, o que pode ser indesejável.

Matrizes de valores

Nas cláusulas INSERT, UPDATE ou WHERE, podemos usar matrizes de valores:

// INSERT seguro
$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']);

O Nette Database escapa automaticamente de todos os valores passados por meio de consultas parametrizadas. No entanto, é preciso garantir o tipo de dados correto dos parâmetros.

As chaves de matriz não são uma API segura

Embora os valores em matrizes sejam seguros, o mesmo não se pode dizer das chaves:

// CÓDIGO INSEGURO - as chaves podem conter injeção de SQL
$database->query('INSERT INTO users', $_GET);
$database->query('SELECT * FROM users WHERE', $_GET);
$table->where($_GET);

Para os comandos INSERT e UPDATE, essa é uma falha de segurança crítica – um invasor pode inserir ou modificar qualquer coluna no banco de dados. Por exemplo, ele poderia definir is_admin = 1 ou inserir dados arbitrários em colunas confidenciais.

Nas condições do WHERE, isso é ainda mais perigoso porque permite a enumeração do SQL, uma técnica para recuperar gradualmente informações sobre o banco de dados. Um invasor poderia tentar explorar os salários dos funcionários injetando em $_GET dessa forma:

$_GET = ['salary >', 100000];   // começa a determinar as faixas salariais

O principal problema, entretanto, é que as condições do WHERE suportam expressões SQL nas chaves:

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

// INSEGURO: o invasor pode injetar seu próprio SQL
$_GET = ['1) UNION SELECT name, salary FROM users WHERE (is_admin = ?' => 1];
$table->where($_GET); // permite que o invasor obtenha salários de administrador

Isso é mais uma vez injeção de SQL.

Colunas de lista branca

Se quiser permitir que os usuários escolham colunas, use sempre uma lista de permissões:

// Processamento seguro - somente colunas permitidas
$allowedColumns = ['name', 'email', 'active'];
$values = array_intersect_key($_GET, array_flip($allowedColumns));

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

Identificadores dinâmicos

Para nomes dinâmicos de tabelas e colunas, use o espaço reservado ?name:

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

// UNSAFE - nunca use a entrada do usuário
$database->query('SELECT ?name FROM users', $_GET['column']);

O símbolo ?name deve ser usado somente para valores confiáveis definidos no código do aplicativo. Para valores fornecidos pelo usuário, use uma lista branca novamente.