Segurança de Bando de Dados

Atualmente, bancos de dados são componentes primordiais de qualquer aplicação web, por permitir aos websites fornecer variedades de conteúdos dinâmicos. Visto que informações muito sensíveis ou secretas podem ser guardadas em bancos, você considera fortemente a proteção de seus bancos de dados.

Para resgatar ou guardar qualquer informação você conecta ao banco de dados, envia uma requisição legitimada, pega o resultado, e fecha a conexão. Atualmente, a linguagem de requisição mais usada nesta interação é a Linguagem de Requisição Estruturada Structured Query Language (SQL). Veja como um agressor pode se engajar com uma requisição SQL.

Como você pode pensar, o PHP não pode proteger seu banco de dados por si só. As seções seguintes fazem uma introdução nos muitos fundamentos de como acessar e manipular banco de dados com scripts PHP.

Mantenha em sua mente uma regra simples: defesa em completo. Em lugares que você toma mais ações para aumentar a proteção de seu banco tem menos probabilidade de que uma um invasor tenha sucesso, e exponha ou faça mau uso de qualquer informação secreta que esteja guardada. Esquema de banco de dados bem desenhado e as aplicações comerciais tratados com seus maiores receios.

Estruturando Banco de dados

O primeiro passo é sempre criar o banco de dados, a menos que você queira utilizar um já existente, terceirizado. Quando um banco de dados é criado, ele é designado para um proprietário, que executa a instrução de criação. Normalmente, apenas o proprietário (ou um superusuário) pode fazer qualquer coisa com os objetos no banco de dados, e para permitir que outros usuários o utilizem, privilégios devem ser concedidos.

Aplicações nunca conectariam ao banco de dados como sendo o proprietário ou um superusuário, porque estes usuários podem executar qualquer instrução à sua vontade, por exemplo, modificar o esquema (apagando tabelas) ou deletando todo o conteúdo.

Você pode criar diferentes usuários de banco de dados para cada aspecto de sua aplicação com muitos direitos limitados aos objetos do banco. A maioria dos privilégios requeridos seria concedido apenas, e evitaria que o mesmo usuário pudesse interagir com o banco de dados em diferentes situações. Isto significa que se os invasores conseguem acessar seu banco de dados usando uma destas credenciais de sua aplicação, eles podem apenas efetuar tantas mudanças quanto a sua aplicação puder.

Você é encourajado a não implementar toda a lógica de negócios na aplicação web (i.e em seu script), ao invés disso faça-o em um esquema de banco de dados usando views, triggers ou regras. Se o sistema evolui, novas portas serão planejadas para abrir o banco de dados, e você tem que re-implementar a lógica em cada cliente de banco de dados em separado. Acima ou abaixo, triggers podem ser usados para manipular campos automaticamente e com tranparência, que frequentemente oferece muita ajuda quando se está tirando os bugs de sua aplicação ou traçando transações anteriores.

Conectando ao Banco de Dados

Você pode querer estabelecer conexões com SSL para encriptar comunicações cliente/servidor para aumentar a segurança, ou você pode usar ssh para encriptar a conexão de rede entre clientes e o servidor do banco de dados. Se você quer estabelecer conexões com SSL para encriptar comunicações cliente/servidor para aumentar a segurança, ou você pode utilizar SSH para encriptar a conexão da rede entre clientes e servidor do banco de dados. Se uma das duas opções é usada, então monitorar seu tráfego e obter informação sobre seu banco de dados será difícil para um suposto invasor.

Modelo de Armazenamento Encripitado

SSL/SSH protege dados viajando do cliente para o servidos, SSL/SSH não protege os dados persistentes guardados no banco. SSL é um protocolo on-the-wire.

Uma vez que um invasor ganha acesso ao seu banco de dados diretamente (passando pelo servidor web), os dados sensíveis podem ser expostos ou receber um mal uso, a menos que a informação esteja protegida pelo próprio banco. Encriptar os dados é uma boa maneira de aliviar esta ameaça, mas pouquíssimos bancos de dados oferecem este tipo de encriptação de dados.

A maneira mais fácil de contornar este problema é primeiro criar o seu proprio pacote de encriptação, e então usá-lo de dentro de seus scripts PHP. O PHP pode auxiliar você nisto com suas várias extensões, tais como Mcrypt e Mhash, simulando uma ampla variedade de algorítimos de encriptação. O script encripta os dados antes de inserí-los em um banco de dados, e desencripta-os ao devolvê-los. Veja as referências para mais exemplos de como a encriptação trabalha.

No caso de dados verdadeiramente escondidos, se sua representação original não é necessária (i.e. não ser mostrada), embaralhamento (hashing) pode também ser levado em consideração. O exemplo bem conhecido para embaralhamento é guardando o a senha com o hash MD5 em um banco de dados, ao invés da própria senha. Veja também crypt() e md5().

Exemplo 15-5. Usando campo de senha embaralhado (hashed)

// guardando senha embaralhada
$query  = sprintf("INSERT INTO users(name,pwd) VALUES('%s','%s');",
            addslashes($username), md5($password));
$result = pg_exec($connection, $query);

// pesquisando se o usuário submeteu a senha correta
$query = sprintf("SELECT 1 FROM users WHERE name='%s' AND pwd='%s';",
            addslashes($username), md5($password));
$result = pg_exec($connection, $query);

if (pg_numrows($result) > 0) {
    echo "Bem vindo, $username!";
}
else {
    echo "Falha na autenticação para $username.";
}

Injeção de SQL (SQL Injection)

Muitos programadores de web não estão cientes de como solicitações SQL devem ser feitas com cuidado, e assumem que uma solicitação SQL é um comando confiável. Isso significa que solicitações SQL estão aptas a driblar controles de acesso, através de passagens de sistemas de autenticação padrão e vericações de autorização, e alguma vezes solicitações SQL podem até permitir acesso a comandos do sistema operacional do host.

Injeção de comandos SQL direto é uma técnica onde um agressor cria ou altera comandos SQL existentes para expor dados escondidos, ou cancelar dados valiosos, ou até executar comandos de sistema operacional no host do banco de dados. Isto é efetuado por uma aplicação que pega entradas de usuários e combinando-a com parâmetros estáticos para construir uma solicitação SQL. Os seguintes exemplos são baseados em histórias verdadeiras, infelizmente.

A falta de uma validação na entrada, leva a uma conexão ao banco de dados como superusuário ou aquele que pode criar usuários, o agressor pode criar um superusuário em seu banco de dados.

Exemplo 15-6. Dividindo o conjunto de resultados em páginas ... e fazendo superusuários (PostgreSQL e MySQL)

$offset = argv[0]; // tome cuidado, sem validação de entrada!
$query  = "SELECT id, nome FROM produtos ORDER BY nome LIMIT 20 OFFSET $offset;";
// com PostgreSQL
$resultado = pg_exec($conn, $query);
// com MySQL
$resultado = mysql_query($query);
Usuários comuns clicam nos links 'próximo', 'anterior' onde o $offset está codificada na URL. O script espera que a entrada de $offset é um número decimal. No entando, o que acontece se alguém tenta quebrar anexando uma forma de urlencode() seguindo para a URL.

// no caso do PostgreSQL
0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
    select 'crack', usesysid, 't','t','crack'
    from pg_shadow where usename='postgres';
--

// no caso do MySQL
0;
UPDATE user SET Password=PASSWORD('crack') WHERE user='root';
FLUSH PRIVILEGES;

Se isso aconteceu, então o script concederia um acesso como superusuário para ele. Note que 0; é para fornecer um offset válido para a solicitação original e terminá-la.

Nota: Está é uma técnica comum para forçar o interpretador SQL a ignorar o resto da solicitação escrita pelo desenvolvedor com -- que é o sinal de comentário em SQL.

Uma forma acessível de ganhar senhas é driblar suas páginas de resultados de pesquisa. A única coisa que o invasor precisa fazer é ver se há quaisquer variáveis submetidas usadas na instrução SQL que não é manipulada adequadamente. Estes filtros podem ser definidos, geramente em um formulário precedente para customizar instruções WHERE, ORDER BY, LIMIT e OFFSET cláusulas em SELECT. Se seu banco de dados suporta o construtor UNION, o agressor pode tentar anexar uma solicitação inteira para o original para listar senhas de uma tabela arbitrária. Usar campos de senhas criptografados é altamente recomendado.

Exemplo 15-7. Listando artigos ... e algumas senhas (qualquer servidor de banco de dados)

$query  = "SELECT id, name, inserted, size FROM products
                  WHERE size = '$size'
                  ORDER BY $order LIMIT $limit, $offset;";
$result = odbc_exec($conn, $query);
A parte estática da solicitação (query) pode ser combinada com uma outra instrução SELECT que revela todos as senhas:

'
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
--

Se esta solicitação (agindo com o ' e --) foi atribuida para uma das variáveis usadas na $query, a solicitação perversa é despertada.

SQL UPDATE's tambémes suscepatíveis a ataques. Estas solicitações estão também ameaçadas em ser cortadas e anexadas a elas uma nova solicitação inteira. Mas o agressor pode brincar com a cláusula SET. Neste caso alguma informação de esquema deve ser tomado para manipuar a solicitação com sucesso. Isto pode ser adquirido pela examinação dos nomes das variáveis do formulário, ou apenas pela força bruta. Não há, então, muitas convenções de nomeação para campos guardando senhas e logins.

Exemplo 15-8. Recriando uma senha ... para obter mais privilégios (qualquer banco de dados)

$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
Mas um usuário malicioso submete o valor ' or uid like'%admin%'; -- para $uid afim de mudar a senha do administrador, ou simplesmente definir $pwd para "'hehehe', admin='yes', trusted=100 " (with a trailing space) para obter mais privilégios. Então, a solicitação será deformada:

// $uid == ' or uid like'%admin%'; --
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%'; --";

// $pwd == "hehehe', admin='yes', trusted=100 "
$query = "UPDATE usertable SET pwd='hehehe', admin='yes', trusted=100 WHERE ...;"

Um exemplo terrível de como comandos de sistema operacional podem ser acessados em alguns hosts de banco de dados.

Exemplo 15-9. Atacando o sistema operacional de hosts de banco de dados (MSSQL Server)

$query  = "SELECT * FROM products WHERE id LIKE '%$prod%'";
$result = mssql_query($query);
Se o atacante submete o valor a%' exec master..xp_cmdshell 'net user test testpass /ADD' -- para $prod, então a $query será:

$query  = "SELECT * FROM products
                    WHERE id LIKE '%a%'
                    exec master..xp_cmdshell 'net user test testpass /ADD'--";
$result = mssql_query($query);

MSSQL Server executa o lote de instruções SQL que inclui um comando para adicionar um novo usuário para a conta local do banco de dados. Se esta aplicação estava rodando como sa e o serviço de MSSQLSERVER está rodando com privilégios suficientes, o agressor agora teria uma conta para acessar está máquina.

Nota: Alguns dos exemplos acima diz respeito a um banco de dados específico. Isto não significa que um ataque similar é impossível contra outros produtos. Seu banco de dados pode estar similarmente vulnerável de outra forma.

Técnicas de Prevenção

Você pode alegar que o agressor deve possuir uma peça de informação sobre o esquema do banco de dados na maioria dos exemplos. Você está certo, mas você nunca sabe quando e como ele pode se relacionar, e se isso acontece, seu banco de dados pode estar exposto. Se você está usando manipulador de banco de dados open source (código aberto), ou um públicamente disponível, que pode fazer parte de um sistema de gerenciamento de conteúdo ou forum, o intruso pode fácilmente produzir uma cópia de uma peça de seu código. Isso pode ser, também, um risco na segurança se ele está pobremente desenvolvido.

Estes ataques podem ser baseados na exploração de códigos que, ao serem escritos não consideraram a segurança. Nunca confie em nenhum tipo de entrada, especialmente as que vem do lado do cliente, mesmo que ela venha de uma caixa de seleção, de um campo hidden ou um cookie. O primeiro exemplo mostra que um solicitação (query) inocente pode causar disastres.

  • Nunca conecte ao banco de dados como superusuário ou como proprietário. Utilize sempre usuários, ajustados com privilégios bastante limitados.

  • Verifique se uma dada entrada tem o tipo de dado esperado. O PHP tem uma vasta área de funções de validação de formulários, a mais simples destas é Funções de Variáveis e in Character Type Functions (ex is_numeric(), ctype_digit() respectivamente) e mais adiante temos suporte a Expressões Regulares Perl compatible.

  • Se a aplicação espera por uma entrada numérica, considere a verificação dos dados com is_numeric(), ou mudando o seu tipo usandos settype(), ou utilize sua representação numérica por sprintf().

    Exemplo 15-10. Uma forma mais segura de compor uma solicitação para paginar

    settype($offset, 'integer');
    $query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";

    // por favor note que %d no formato de string, usando %s seria insignificante
    $query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;",
                     $offset);

  • Ponha entre aspas cada entrada não numérica do usuário que é passada para o banco de dados com addslashes() ou addcslashes(). Veja O primeiro exemplo. Conforme mostrado no exemplo, por aspas dentro da parte estática da solicitação não é o bastante, e pode ser facilmente crackeado.

  • Não imprima nenhuma informação específica do banco de dados, especialmente sobre o esquema, através de todos os recursos. Veja Também Do not print out any database specific information, especially about the schema, by fair means or foul. See also Reportando Erro e Manipulação de Erro e Funções de Logging (registrar atividades ocorridas no computador).

  • Você pode utilizar procedimentos guardados e cursores definidos previamente para resumir acessos a dados assim que os usuários não acessem as tabelas ou views diretamente, mas esta solução tem outros impactos.

Além destes, você se beneficia de solicitações de logging, ou dentro de seu script ou pelo próprio banco de dados, se ele suportar logging. Obviamente, o logging está indisponível para impedir qualquer tentativa prejudicial, mas ele pode ser útil para rastrear qual aplicação está sendo driblada. O log não é util por si só, mas sim as informações que ele contém. Quanto mais detalhes melhor.