Files
archived-doc-pt-br/security/database.xml
Leonardo Lara Rodrigues 3ee3071c6c fix and improve translations
2025-09-29 11:40:16 -03:00

489 lines
20 KiB
XML

<?xml version="1.0" encoding="UTF-8"?>
<!-- EN-Revision: 7204e2dbb9b484c8b67bb5ad4a93fa1369c5b317 Maintainer: ae Status: ready --><!-- CREDITS: rafaelbernard,narigone,ae,leonardolara -->
<chapter xml:id="security.database" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Segurança de Bancos de Dados</title>
<simpara>
Hoje em dia, bancos de dados são componentes cardinais de qualquer aplicação web
permitindo que websites forneçam conteúdo dinâmico variável. Uma vez que informações muito
sensíveis e/ou secretas podem ser guardadas em um banco de dados, proteger seus
bancos de dados é essencial.
</simpara>
<simpara>
Para retirar ou guardar qualquer informação, você precisa conectar-se ao banco
de dados, enviar uma consulta (query) legítima, pegar o resultado e fechar a conexão.
Atualmente, a linguagem mais usada para consulta nessa interação é a
Structured Query Language (SQL). Veja como um atacante pode <link
linkend="security.database.sql-injection">manipular uma consulta SQL</link>.
</simpara>
<simpara>
O <acronym>PHP</acronym> não pode proteger seu banco de dados sozinho. As
seções a seguir tentam ser uma introdução básica em relação a como
acessar e manipular banco de dados a partir de scripts <acronym>PHP</acronym>.
</simpara>
<simpara>
É preciso manter em mente essa regra simples: defesa em profundidade. Em quanto mais lugares
há ações para aumentar a proteção do banco de dados, menor a probabilidade
de um atacante ter sucesso em expor ou abusar de qualquer informação
guardada. Uma boa modelagem do esquema (schema) do banco de dados e da aplicação
para lidar com os maiores medos.
</simpara>
<sect1 xml:id="security.database.design">
<title>Desenhando Bancos de Dados</title>
<simpara>
O primeiro passo é sempre criar o banco de dados, a não ser que você queira
usar um de terceiros. Quando um banco de dados é criado, ele é
atribuído a um dono, o mesmo que executou os comandos de criação. Normalmente, só
o dono (ou um superusuário) pode fazer algo com os objetos naquele
banco de dados, e para permitir que outros usuários usem, privilégios devem
ser concedidos.
</simpara>
<simpara>
Aplicações nunca devem conectar-se ao banco de dados como seu dono ou um
superusuário, porque esses usuários podem executar qualquer consulta que
quiserem como, por exemplo, modificar o esquema (ex.: destruindo tabelas) ou
removendo seu conteúdo completamente.
</simpara>
<simpara>
Devem ser criados usuários de bancos de dados diferentes para cada aspecto
da sua aplicação com direitos bem limitados aos objetos do banco de dados.
Apenas os privilégios necessários devem ser concedidos, e deve-se evitar que o
mesmo usuário possa interagir com o banco de dados em casos de uso diferentes. Isso
significa que se invasores conseguirem acessar seu banco de dados usando credenciais da sua
aplicação, eles só podem afetar o banco tanto quanto sua aplicação poderia.
</simpara>
</sect1>
<sect1 xml:id="security.database.connection">
<title>Conectando com o Banco de Dados</title>
<simpara>
Deve-se estabelecer as conexões sobre SSL para criptografar as
comunicações cliente/servidor para aumentar a segurança, ou você pode usar ssh
para criptografar a conexão de rede entre clientes e o servidor de banco de dados.
Se uma dessas opções for usada, o monitoramento do seu tráfego e obtenção
de informação sobre seu banco de dados serão dificultados para um possível atacante.
</simpara>
<!--simpara>
If your database server has native SSL support, consider using <link
linkend="ref.openssl">OpenSSL functions</link> in communication between
<acronym>PHP</acronym> and database via SSL.
</simpara-->
</sect1>
<sect1 xml:id="security.database.storage">
<title>Modelo de Armazenamento Criptografado</title>
<simpara>
SSL/SSH protege dados transitando de um cliente para o servidor, mas
não protege os dados guardados em um banco de dados. SSL é um
protocolo on-the-wire.
</simpara>
<simpara>
Uma vez que o atacante ganhe acesso direto ao seu banco de dados (perpassando o
servidor web), os dados armazenados podem ser expostos ou usados inadequadamente, a não ser
que a informação seja protegida pelo banco em si. Criptografar os dados é
uma boa maneira de diminuir essa ameaça, mas poucos bancos de dados oferecem
esse tipo de criptografia de dados.
</simpara>
<simpara>
A maneira mais fácil de contornar esse problema é primeiro criar seu próprio
pacote de criptografia, e então usá-lo no seus scripts <acronym>PHP</acronym>. O <acronym>PHP</acronym> pode
ajudá-lo com várias extensões, tais como <link
linkend="book.openssl">OpenSSL</link> e <link
linkend="book.sodium">Sodium</link>, cobrindo uma grande variedade de algoritmos
de criptografia. O script criptografa os dados antes de inseri-los no banco de dados e
descriptografa quando os recupera. Veja as referências para outros exemplos de como
a criptografia funciona.
</simpara>
<sect2 xml:id="security.database.storage.hashing">
<title>Hashing</title>
<simpara>
No caso de dados realmente ocultos, se sua representação bruta não for necessária
(ou seja, não será exibido), o hashing deve ser levado em consideração.
O exemplo conhecido de hashing é armazenar o hash criptográfico de uma
senha em um banco de dados, em vez da própria senha.
</simpara>
<simpara>
As funções <link linkend="ref.password">password</link>
fornecem uma maneira conveniente para criar o hash de dados confidenciais e trabalhar com esses hashes.
</simpara>
<simpara>
<function>password_hash</function> é usado para criar o hash uma determinada string usando o
algoritmo mais forte atualmente disponível e <function>password_verify</function>
verifica se a senha fornecida corresponde ao hash armazenado no banco de dados.
</simpara>
<example>
<title>Usando campo de senha hasheado</title>
<programlisting role="php">
<![CDATA[
<?php
// guardando hash da senha
$query = sprintf("INSERT INTO users(name,pwd) VALUES('%s','%s');",
pg_escape_string($username),
password_hash($password, PASSWORD_DEFAULT));
$result = pg_query($connection, $query);
// consultando se o usuário enviou a senha correta
$query = sprintf("SELECT pwd FROM users WHERE name='%s';",
pg_escape_string($username));
$row = pg_fetch_assoc(pg_query($connection, $query));
if ($row && password_verify($password, $row['pwd'])) {
echo 'Bem-vindo, ' . htmlspecialchars($username) . '!';
} else {
echo 'Autenticação falhou para ' . htmlspecialchars($username) . '.';
}
?>
]]>
</programlisting>
</example>
</sect2>
</sect1>
<sect1 xml:id="security.database.sql-injection">
<title>Injeção de SQL</title>
<simpara>
A injeção de SQL é uma técnica onde o agressor explora falhas
no código da aplicação responsável em criar e povoar instruções SQL.
O agressor pode assim obter acesso privilegiado a partes da aplicação,
extrair todos os dados do banco de dados, alterar os dados,
e até mesmo executar comandos perigosos em nível do sistema onde o banco
de dados roda. A falha ocorre quando desenvolvedores concatenam ou
interpolam dados arbitrários em instruções SQL.
</simpara>
<para>
<example>
<title>
Dividindo o conjunto de resultados em páginas ... e criando super-usuários
(PostgreSQL)
</title>
<simpara>
No exemplo a seguir, dados de usuário são diretamente interpolados na
instrução SQL, permitindo ao agressor obter uma conta de superusuário no banco de dados.
</simpara>
<programlisting role="php">
<![CDATA[
<?php
$offset = $_GET['offset']; // Usando os dados sem validação!
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
$result = pg_query($conn, $query);
?>
]]>
</programlisting>
</example>
Usuários normais clicam nos links 'próxima' e 'anterior' onde <varname>$offset</varname>
é codificado na <acronym>URL</acronym>. O script espera que o valor de
<varname>$offset</varname> seja um número decimal. No entanto, e se alguém tentar
quebrar a instrução SQL, utilizando a seguinte <acronym>URL</acronym>:
<informalexample>
<programlisting role="sql">
<![CDATA[
0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
select 'crack', usesysid, 't','t','crack'
from pg_shadow where usename='postgres';
--
]]>
</programlisting>
</informalexample>
Se isso acontecesse, então o script daria de presente acesso de superusuário ao
atacante. Perceba que <literal>0;</literal> é para fornecer uma deslocamento válido
para a consulta original e terminá-la.
</para>
<note>
<para>
É uma técnica comum forçar o avaliador de SQL ignorar o resto da consulta
escrita pelo desenvolvedor com <literal>--</literal>, que é o sinal de
comentário no SQL.
</para>
</note>
<para>
Uma maneira de ganhar senhas é desviar suas páginas de resultado de busca.
A única coisa que o atacante precisa fazer é ver se alguma variável enviada
é usada em um comando SQL que não é tratado corretamente. Esses filtros podem ser
configurados de forma a personalizar cláusulas <literal>WHERE, ORDER BY,
LIMIT</literal> e <literal>OFFSET</literal> em cláusulas <literal>SELECT</literal>
Se seu banco de dados suporta o construtor <literal>UNION</literal>,
o atacante pode tentar adicionar uma consulta inteira à consulta original para
listar senhas de uma tabela arbitrária. É altamente recomendável gravar apenas
hashs criptográficos das senhas, ao invés de gravar a senha.
<example>
<title>
Listando artigos ... e algumas senhas (qualquer banco de dados)
</title>
<programlisting role="php">
<![CDATA[
<?php
$query = "SELECT id, name, inserted, size FROM products
WHERE size = '$size'";
$result = odbc_exec($conn, $query);
?>
]]>
</programlisting>
</example>
A parte estática da consulta pode ser combinada com outro comando
<literal>SELECT</literal> que revela todas as senhas:
<informalexample>
<programlisting role="sql">
<![CDATA[
'
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
--
]]>
</programlisting>
</informalexample>
</para>
<para>
Instruções <literal>UPDATE</literal> e <literal>INSERT</literal> também podem
ser abusadas em ataques.
<example>
<title>
De recuperando uma senha ... para ganhando mais privilégios (qualquer banco de dados)
</title>
<programlisting role="php">
<![CDATA[
<?php
$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
]]>
</programlisting>
</example>
Se um usuário malicioso envia o valor
<literal>' or uid like'%admin%</literal> para <varname>$uid</varname> para
mudar a senha do administrador, ou simplesmente configura <varname>$pwd</varname> para
<literal>hehehe', trusted=100, admin='yes</literal> (com um espaço
sobrando) para ganhar mais privilégios. Então, a consulta ficará retorcida:
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
// $uid: ' or uid like '%admin%
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%';";
// $pwd: hehehe', trusted=100, admin='yes
$query = "UPDATE usertable SET pwd='hehehe', trusted=100, admin='yes' WHERE
...;";
?>
]]>
</programlisting>
</informalexample>
</para>
<simpara>
Pode parecer que o agressor precisa saber alguma coisa
da arquitetura do banco para conduzir um ataque
efetivo, mas obter esse tipo de informação é geralmente bem simples. Por exemplo,
o código pode ser parte de um sistema open source e publicamente disponível.
Esse tipo de informação pode também pode ser obtido
em sistemas de código fechado -- mesmo no caso dele estar ofuscado ou compilado --
e mesmo através do seu próprio código, através de mensagens de erro.
Outros métodos incluem o uso de nomes típicos de tabelas e colunas. Por
exemplo, um formulário de login normalmente utiliza tabelas chamadas 'user' com
colunas chamadas 'id', 'username', e 'password'.
</simpara>
<para>
<example>
<title>Atacando o sistema de um banco de dados (MSSQL Server)</title>
<simpara>
Um exemplo assustador de como comandos do sistema operacional podem ser acessados
em alguns bancos de dados.
</simpara>
<programlisting role="php">
<![CDATA[
<?php
$query = "SELECT * FROM products WHERE id LIKE '%$prod%'";
$result = mssql_query($query);
?>
]]>
</programlisting>
</example>
Se o atacante enviar o valor
<literal>a%' exec master..xp_cmdshell 'net user test testpass /ADD' --</literal>
para <varname>$prod</varname>, então <varname>$query</varname> terá o valor:
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
$query = "SELECT * FROM products
WHERE id LIKE '%a%'
exec master..xp_cmdshell 'net user test testpass /ADD'--";
$result = mssql_query($query);
?>
]]>
</programlisting>
</informalexample>
O MSSQL Server executa comandos SQL em um lote incluindo um comando
para adicionar um novo usuário para o banco de dados de contas locais. Se essa aplicação
estiver sendo executada como <literal>sa</literal> e o serviço MSSQLSERVER estivesse
sendo executado com privilégios suficientes, o atacante teria agora uma
conta com a qual poderia acessar essa máquina.
</para>
<note>
<para>
Alguns dos exemplos acima estão ligados a bancos específicos. Isso não
significa que um ataque similar é impossível contra outros produtos.
Seu servidor de banco de dados pode ter uma vulnerabilidade similar de outra maneira.
</para>
</note>
<para>
<mediaobject>
<alt>Um exemplo humorado dos problemas relacionados à injeção de SQL</alt>
<imageobject>
<imagedata fileref="en/security/figures/xkcd-bobby-tables.png" format="PNG"/>
</imageobject>
<caption>
<simpara>
Imagem cortesia de <link xlink:href="&url.xkcd;327">xkcd</link>
</simpara>
</caption>
</mediaobject>
</para>
<sect2 xml:id="security.database.avoiding">
<title>Técnicas para evitar ataques</title>
<para>
A maneira recomendada de evitar ataques de injeção de SQL é informar todos os
dados em instruções preparadas. Usar instruções parametrizadas não é o suficiente
para evitar SQL injection, mas é a maneira mais rápida e segura de fornecer dados
a instruções SQL. Todos os dados dinâmicos em <literal>WHERE</literal>,
<literal>SET</literal>, e <literal>VALUES</literal> precisam ser
substituídos por âncoras. Os dados em si serão informados durante a
execução, e serão enviados separadamente do comando SQL.
</para>
<para>
Informar dados via parâmetros deve ser utilizado apenas para dados. Outras partes dinâmicas
de uma instrução SQL precisa ser filtrada por uma lista prévia e conhecida de valores válidos.
</para>
<para>
<example>
<title>Evitando SQL injection ao utilizar instruções preparadas PDO</title>
<programlisting role="php">
<![CDATA[
<?php
// A parte SQL dinâmica precisa é validada a partir de dados prévios
$sortingOrder = $_GET['sortingOrder'] === 'DESC' ? 'DESC' : 'ASC';
$productId = $_GET['productId'];
// O SQL é preparado utilizando âncoras
$stmt = $pdo->prepare("SELECT * FROM products WHERE id LIKE ? ORDER BY price {$sortingOrder}");
// O valor é informado, incluindo caracteres curinga
$stmt->execute(["%{$productId}%"]);
?>
]]>
</programlisting>
</example>
</para>
<simpara>
Instruções preparadas são fornecidas
<link linkend="pdo.prepared-statements">no PDO</link>,
<link linkend="mysqli.quickstart.prepared-statements">no MySQLi</link>,
e por outras bibliotecas de bancos de dados.
</simpara>
<simpara>
Ataques de injeção de SQL são principalmente baseados na exploração de código que não é
escrito pensando em segurança. Nunca confie em nenhum dado enviado pelo usuário,
e menos ainda em dados enviados pelo navegador, mesmo que o dado venha de um option box
ou um campo hidden, nem mesmo cookies. O primeiro exemplo mostra como
uma instrução SQL muito simples pode causar um dano desastroso.
</simpara>
<para>
Uma estratégia de defesa envolve várias boas práticas de codificação:
<itemizedlist>
<listitem>
<simpara>
Nunca se conecte ao banco de dados utilizando um usuário administrador ou dono
dos objetos do banco. Sempre utilize usuários com privilégios mínimos.
</simpara>
</listitem>
<listitem>
<simpara>
Sempre verifique se o dado enviado tem o tipo esperado. O <acronym>PHP</acronym> possui
várias funções de validação de dados, de coisas simples como
as encontradas em <link linkend="ref.var">funções de variável</link> e
em <link linkend="ref.ctype">funções de string</link>
(por exemplo, <function>is_numeric</function>, <function>ctype_digit</function>)
a coisas mais avançadas como
suporte a
<link linkend="ref.pcre">expressões regulares compatíveis com Perl</link>.
</simpara>
</listitem>
<listitem>
<simpara>
Se a aplicação espera dados numéricos, considere verificar os dados
com <function>ctype_digit</function>, ou modificar os dados utilizando
<function>settype</function>, ou ainda reformatar o dado
com <function>sprintf</function>.
</simpara>
</listitem>
<listitem>
<simpara>
Se o banco de dados não suportar enviar dados por parâmetros, então
é necessário escapar todos os dados de usuário não numéricos, passando
o dado para funções específicas de escape do banco (por exemplo
<function>mysql_real_escape_string</function>,
<function>sqlite_escape_string</function>, etc).
Funções genéricas como <function>addslashes</function> são úteis apenas
em contextos específicos (por exemplo, no MySQL é possível modificar o
comportamento das aspas com <varname>NO_BACKSLASH_ESCAPES</varname>), então
o escape específico é necessário.
</simpara>
</listitem>
<listitem>
<simpara>
Nunca imprima nenhum dado ou erro específico do banco de dados, especialmente
dados referentes a schema. Veja também <link
linkend="security.errors">exibição de erros</link> e <link
linkend="ref.errorfunc">funções de manipulação e log de erros</link>.
</simpara>
</listitem>
</itemizedlist>
</para>
<simpara>
Além disso, você ganha em relatar consultas ou dentro do script
ou no próprio banco de dados, se esse suportar. Obviamente, o relatório é incapaz
de prevenir qualquer tentativa danosa, mas pode ser útil para ajudar a
rastrear qual aplicação foi atacada. O relatório não é útil em si, mas
através da informação que ele contém. Mais detalhes geralmente é melhor que menos.
</simpara>
</sect2>
</sect1>
</chapter>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->