mirror of
https://github.com/php/doc-pt_br.git
synced 2026-03-23 22:52:12 +01:00
489 lines
20 KiB
XML
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
|
|
-->
|