4 de março de 2010

Php Security - Nem tudo que é publicado deve ser seguido

Eu estava lendo o livro: Pro PHP Security (Chris Snyder and Michael Southwell) - Apress. É no passado mesmo, eu não li o resto, pois analisando os códigos no começo do livro encontrei algumas falhas, incluindo problemas de design e arquitetura.

1 - Uso de variáveis Super Globals do Php sem sanitização - página 77
<form action="<? $_SERVER['SCRIPT_NAME']" method="post">
<p>
username: <input type="text" name="userName" size="32" /><br />
username: <input type="password" name="userPassword" size="16" /><br />
<input type="submit" name="submit" value="login" />
<p>
</form>

Estas variáveis como qualquer input podem ser manipuladas, o que torna este form vulnerável a XSS (Cross Site Scripting).

2 - Implementação inadequada de salt - página 78

O livro começou bem neste ponto, sugerindo a utilização de salt como recurso para fortalecer os hashs de senha. Mas, como geralmente acontece, a idéia é boa mas a implementação é ruim.
$salt = time();
$hashedPassword = sha1($userPassword . $salt);

O código apresentado sugere gerar um hash no momento da criação baseado em time(). Como comparar este resultado na hora de cada login se o time() tem um valor que não é fixo? Eles sugerem armazenar um hash em um campo na mesma tabela de usuários, junto com o hash da senha.
$query = 'INSERT INTO LOGIN VALUES (' . dbSafe($userName) . ', ' . dbSafe($hashedPassword) . ',' .dbSafe($salt) . ')';

Geralmente o comprometimento dos hashs de senhas acontece por falhas de SQL Injection, portanto se o usuário mal intencionado pega o hash e salt numa paulada só e consegue reverter a senha (óbvio que aqui estamos falando de senhas com baixa complexidade e que podem ser revertidas por base de hashs pré-compilados).

Este é o típico caso onde existe a exceção, se em código compilado e local não é recomendado a senha hard-coded, neste caso em linguagem interpretada como php, asp é mais seguro que quardar no banco, pois para comprometer o salt ele precisa ter acesso aos diretórios do servidor de aplicação.

As páginas seguintes são uma enrolação danada e nada de código, nem vulnerável. Ai fiquei curioso para ver quais eram as sugestões para controles de sessão,uma falha comum em aplicações.

3 - Controle inadequado contra CSRF/XSRF (Cross Site Request Forgery) - Página 402

<?php

$referrer = $_SERVER['HTTP_REFERER'];
if (!empty($referrer)) {
$uri = parse_url($referrer);
if ($uri['host'] != $_SERVER['HTTP_HOST']) {
exit("Form submissions from $referrer not allowed.");
}
}

else {
exit('Referrer not found. Please <a href="' . $_SERVER['SCRIP_NAME'] . '">try again</a>.');
}

?>

O referer é um recurso do HTTP que informa de onde a requisição está vindo, mas como é coletado usando uma variável Super Global, pode ser manipulado como analisamos no início do post na falha número 1. Controles efetivos para CSRF/XSRF são token de sessão com boa entropia e solicitar uma nova autenticação em operações críticas.

Para forjar um referer e bypassar este controle pode ser usado scripts que geram um referer, exemplo:
<META HTTP-EQUIV="refresh" CONTENT="0;url=[url a ser atacada];">

Não preciso nem falar que não li o livro todo. Não recomendo, o livro não acrescenta praticamente nada em segurança e ainda comete erros grosseiros.

36 comentários:

X disse...

Fala pra que magica vc faz para manipular o $_SERVER['SCRIPT_NAME'] huehueueu

Almir Neto disse...

Ótimo artigo.
Não li o livro mas concordo que nem tudo que é publicado deve ser seguido. Infelizmente livros com esse nível são publicados com falhas, isso prejudica muito o profissional inciante que quer crescer. Em que fonte confiar?

Marcelo M. Fleury disse...

Uma tristeza existirem publicações como essas... Ta mais para Php INsecurity, Deus me livre :/.
[]s

PHP Security – Nem tudo que é publicado deve ser seguido disse...

[...] Enviado por Wagner Elias (weliasΘconviso·com·br): “Uma análise de códigos publicados em um livro de segurança em php que possuem falhas. Os códigos possuem falhas que podem passar desapercebidos por programadores menos experientes, mas que são um prato cheio para usuários mal intencionados. Uma lição de que é necessário validar as fontes e questionar o que lhe apresentam como solução.” [referência: wagnerelias.com] [...]

Marcos disse...

A cara, na boa, quem deseja qualquer outra caracteristica que não seja "form funcionando", já abandonou o php

Carlos André Ferrari disse...

@Almir Neto

Este é o grande problema.. "Em quem confiar".. por isso que o PHP tem má fama.

Eu uso e confio na documentação do PHP e, com moderação, nos comentários que tem lá. tudo é uma questão de analisar os códigos.

E se o programador tem total conhecimento do Protocolo HTTP e dos métodos comuns de ataque, ele dificilmente terá problemas.

Júnio disse...

Muito obrigado pela colaboração. Estou iniciando nesta linguagem. Quais livros vocês recomendam sobre PHP?

Daniel disse...

Nao li o livro, mas voce tambem esta exagerando...

O _SERVER nao pode ser manipulado pelo usuario remotamente, sendo que ele eh gerado pelo web server:

http://php.net/manual/en/reserved.variables.server.php

O uso do SCRIPT_NAME eh recomendado pelo manual do PHP:

" 'SCRIPT_NAME'
Contains the current script's path. This is useful for pages which need to point to themselves. The __FILE__ constant contains the full path and filename of the current (i.e. included) file."


Sobre o salt, a implementacao deles eh correta. Inclusive eh desse modo que varios sistemas funcionam (como o proprio Linux e suas senhas).

Elias Wagner disse...

Olá Daniel,

não é proibido usar o _SERVER, desde que seja sanitizado e isto está claro no post. Quanto a _SERVER não ser manipulado, você está equivocado ou desconhece. Aqui você vai entender um pouco mais:
http://www.google.com/search?q=Exploiting%20%2B%24_SERVER

Sobre o Salt, realmente e;e deve ser usado, foi exatamente o que eu disse: a dica é certa mais a implementação é errada. O local onde se armazena o salt e a string de salt é essencial para sua eficácia.

Ficou mais claro?

Eusébio disse...

Concordo com o Daniel. Seria bom mostrar exemplos de como reproduzir o problema, se estás criticando.

Elias Wagner disse...

Olá Eusébio,

existem vários exemplos, conforme o link que enviei para o Daniel.

Recomendo principalmente a leitura do primeiro:
http://www.suspekt.org/2009/02/06/some-facts-about-the-phplist-vulnerability-and-the-phpbbcom-hack/

Este é o problema do livro. Quem não está focado em segurança de aplicação web pode passar despercebido.

Abs.

João disse...

Não sou especialista em segurança ou em criptografia, mas pelo que eu saiba hoje em dia o uso de um salt tem como objetivo maior tornar muito mais difícil ou talvez impraticável o uso de tabelas rainbow.

Ricardo disse...

Eu sempre tive a impressão de que os valores de $_SERVER fossem gerados pelo servidor sem interferência dos clientes (ótimas últimas palavras não é?). Imagina quantos sites php são vulneráveis a esta falha?
Obrigado pela informação, e sobre o que você disse sobre não acreditar em tudo o que é publicado me lembrou da falácia apelo à autoridade.

Elias Wagner disse...

João,

você tem razão, o salt é uma boa prática, como eu deixei bem claro:

"2 - Implementação inadequada de salt - página 78

O livro começou bem neste ponto, sugerindo a utilização de salt como recurso para fortalecer os hashs de senha. Mas, como geralmente acontece, a idéia é boa mas a implementação é ruim."

O problema é a implementação do salt sugerido por eles. O salt está junto com a senha, portando o comprometimento do hash via SQLi compromete o salt também.

Abs.

PHP Security – Nem tudo que é publicado deve ser seguido :Software Livre disse...

[...] de que é necessário validar as fontes e questionar o que lhe apresentam como solução.” [referência: wagnerelias.com] Share and [...]

X2 disse...

1 - Uso de variáveis Super Globals do Php sem sanitização - página 77

Estou descrente que NESSE exemplo isso seja uma vulnerabilidade ou que é necessário o programador gastar linha de código para sanitizar a varíavel $_SERVER['SCRIPT_NAME'].

Tem como mostrar um exemplo claro de como pode explorar esse exemplo?

Vinícius Sant'anna disse...

ótimo post, porém infeliz os autores desse livro.

ideud8ef disse...

1) Pessoas que ativam register_globals na configuração do PHP deveriam apanhar. Eu simplesmente faria o meu script não funcionar se essa configuração estivesse ativa, assim os exemplos com $_SERVER do livro não poderiam ser injetados mesmo em versões antigas do PHP.

2) O código de salt do livro não apresenta a vulnerabilidade que você disse. É preciso sim armazenar o salt, todo mundo faz isso. Se você pegar o formato de senha encriptado do /etc/shadow por exemplo, ele vai ter primeiro um identificador do tipo de hash, depois o salt, e depois o hash, concatenados numa mesma string.

O problema no código do livro é outro. O salt é muito previsível, por ser baseado somente no tempo, e isso reduz a complexidade de um ataque por rainbow table, pois é necessário armazenar uma pequena faixa de salts. Se o salt tivesse uma boa aleatoriedade, o espaço de salts possíveis seria bem maior, aumentando a complexidade do ataque.

É claro que é sempre recomendável usar a função crypt do PHP para encriptar senhas, que aliás é capaz de usar hash baseado em blowfish como algoritmo, que é computacionalmente mais complexo que o hash sha1 que ele usou.

Elias Wagner disse...

Register Globals habilitado realmente é uma falha grave, inclusive as novas versões já vem desabilitada por default. Mas um código deve ser escrito independente das configurações do ambiente.

Para quem acha que não é necessário validar o $_SERVER['SCRIPT_NAME'] segue aqui uma série de falhas explorando SCRIPT_NAME:

http://osvdb.org/show/osvdb/35502
http://drupal.org/node/324875
http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2007-2431
http://www.milw0rm.com/exploits/3816
http://www.derkeiler.com/Mailing-Lists/Full-Disclosure/2010-02/msg00385.html

Muito gente acha que por ser recomendado o SCRIPT_NAME como opção mais segura para o PHP_SELF ele não deve ser validado e isto é incorreto.

Sobre o Salt, concordo que além de armazenar errado a entropia do Salt é baixa, previsível. Agora eu discordo que é correto armazenar na mesma tabela junto com o hash, como eu disse um SQLi você pega o hash também, por exemplo um SQLi como este: http://www.otavioribeiro.com/videos/MSSQL.html

O mais adequado nestes casos é armazenar uma string no código ou em um arquivo no server, como o wordpress faz por exemplo. Óbvio que quem configura deve alterar a string padrão.

Abs.

Elias Wagner disse...

Nas versões recentes e bem configurado de php não é possível explorar isto, mas como respondi em outro comentário, um código deve ser escrito independente do ambiente e da configuração que ele irá rodar.

Não acho uma boa prática sugerir um variável global em uma situação onde ele só precisa informar o nome do arquivo.

Vou tentar montar o ambiente certo para fazer um exemplo da exploração e publico.

João disse...

Elias, não concordo com a sua afirmação sobre o salt. Inserir o salt juntamente com a senha é uma prática comum. Afinal, se alguém obtém o hash, é porque teve acesso ao banco, e certamente conseguirá obter o salt.

ideud8ef disse...

Originally Posted By Elias WagnerRegister Globals habilitado realmente é uma falha grave, inclusive as novas versões já vem desabilitada por default. Mas um código deve ser escrito independente das configurações do ambiente.

Se eu ainda escrevesse coisas em PHP, não sentiria dó de colocar um if(ini_get('register_globals')) die('Desligue o register_globals seu admin burro'); no começo de cada arquivo .php :)

Agora eu discordo que é correto armazenar na mesma tabela junto com o hash, como eu disse um SQLi você pega o hash também

Ué, e qual o problema de pegar o hash e o salt com um SQL injection? Se a entropia do salt for boa (salt grande com boa aleatoricidade - que pode ser gerado com mcrypt por exemplo), a probabilidade de encontrar a senha precomputada é ínfima.

De maneira nenhuma concordo com o texto do seu artigo que diz que uma senha hardcoded é mais segura que uma senha encriptada com esquema de salt só porque ela está em um banco de dados e as estatísticas dizem que ataques SQL injection são mais encontrados que LFI e outros. É preciso utilizar criptografia decente sempre, seja onde a a senha estiver armazenada.

O mais adequado nestes casos é armazenar uma string no código ou em um arquivo no server, como o wordpress faz por exemplo. Óbvio que quem configura deve alterar a string padrão.

O que você quis dizer com isso? Armazenar um código em um arquivo no servidor e usar como salt? Se for isso, é extremamente inseguro. Um bom salt é um salt aleatório e totalmente independente para cada usuário diferente. Você poderia fazer hash do código do arquivo no servidor com o nome de usuário, mas mesmo assim não seria tão bom quanto um salt aleatório, pois todos os salts estão sendo tirados da mesma função e não são independentes entre si. Formalmente, teríamos uma grande quantidade de informação mútua entre salts diferentes.

Uma abordagem interessante seria armazenar um pre-salt no banco de dados junto com os demais dados do usuário e aplicar um hmac com uma senha contida em arquivo no servidor a esse pre-salt para gerar o salt verdadeiro.

Mas, sinceramente? Acho desnecessário tanta complicação se o salt usado for realmente bom, segundo os critérios que já citei. É mais provável a incidência de uma falha que permita acesso aos arquivos do servidor que a ocorrência de um salt repetido, dependendo do tamanho do salt.

Elias Wagner disse...

@João - Este é o ponto João, se armazenar o hash em outro local que não seja junto com o hash fica mais difícil de o cara ter acesso a senha. Ter acesso aos dados é problema sério sem dúvida, não estamos discutindo isto. Mas não é porque eu tive acesso ao banco que devo ter acesso a senha, se este for o raciocínio, para que hash? Coloca a seja em texto claro em tão.

Elias Wagner disse...

@ideud8ef - hahaha, maldoso você.

Concordo que o uso de um bom salt é essencial e se for complexo o suficiente vai quase impossível reverter a senha com um hash pré-computado.

Em php eu acho mais interessante um salt hard-coded do que em banco de dados.

Esta sim é uma boa abordagem:

"Uma abordagem interessante seria armazenar um pre-salt no banco de dados junto com os demais dados do usuário e aplicar um hmac com uma senha contida em arquivo no servidor a esse pre-salt para gerar o salt verdadeiro."

elias disse...

o salt serve pra inviabilizar um dicionario pre-computado, nao pra inviabilizar um ataque de força bruta. entre ter o hash e o salt e ter a senha, existem muitos ciclos de maquina..

o interessante de um salt por senha é que as hashes calculadas pra quebrar a senha dos outros não vai servir pra _sua_ senha. e também não fica trivial verificar se duas pessoas tem a mesma senha, que é um indício de senha fraquíssima

o ataque que voce parece supor é que as hashes estarão comprometidas, mas o salt estaria em um lugar seguro e inacessível. nesse caso você nem precisa de salt: basta armazenar logo as hashes no suposto lugar seguro e inacessível. :)

ps: desculpa, mas php security é quase um oxímoro..

Magno disse...

Muito obrigado pelas dicas.

PHP Security – Nem tudo que é publicado deve ser seguido « Fernando Martini disse...

[...] “Uma análise de códigos publicados em um livro de segurança em php que possuem falhas. Os códigos possuem falhas que podem passar desapercebidos por programadores menos experientes, mas que são um prato cheio para usuários mal intencionados. Uma lição de que é necessário validar as fontes e questionar o que lhe apresentam como solução.” Fonte: http://wagnerelias.com/2010/03/04/php-security-nem-tudo-que-e-publicado-deve-ser-seguido/ [...]

Rubens Takiguti Ribeiro disse...

1 - Nem todas variáveis super globais podem ser modificadas pelo cliente ($_SESSION, $_ENV e alguns valores de $_SERVER são alguns exemplos) e não precisam ser sanitizadas dependendo do contexto. No exemplo apresentado, se o cliente conseguir mudar o $_SERVER['SCRIPT_NAME'] para algo malicioso, será algo malicioso apenas para ele mesmo. Qualquer cliente consegue modificar o HTML com plugins como "Web Developer" para Firefox, por exemplo, isso não tem como evitar. Ele pode trocar o action de qualquer form, fazendo besteira, mas só afetaria ele mesmo. Isso é uma falha? Entendo que o importante é o sistema só aceitar valores válidos vindos de clientes, mas, no exemplo, o valor vindo do cliente só está sendo usado para montar uma página para ele mesmo.

2 - De fato, time não é a melhor função para se gerar o salt. Mas acho que esta é a única coisa inadequada. As principais falhas de segurança relacionadas à autenticação seriam: (i) alguém conseguir descobrir a senha de outra pessoa; (ii) alguém conseguir acessar como outra pessoa sem informar as credenciais adequadas; ou (iii) alguém conseguir testar um pacote de logins/senhas no sistema em busca de um par de valores válidos. O salt está indiretamente ligado apenas ao item (i). Mas, gerando a senha criptografada, já fica complicado obter a senha original, seja com salt ou não. O item (ii) e (iii) são tratados por outros mecanismos. O salt, pra mim, é um detalhe. Por mim pode até intercalar as soluções apresentadas: deixar um pedaço do salt no BD e parte dele ser estático em código. Enfim, é preciso se concentrar nas possiveis falhas, e não apenas no que as pessoas dizem que precisa se preocupar.

3 - Realmente neste ponto o livro errou feio. Referer é definido (opcionalmente) pelo navegador e pode ser burlável. Mas você dizer que todas super globais são editáveis pelo cliente, você se engana também.

Uma coisa eu concordo com você: não podemos seguir tudo aquilo que é publicado. Porém, tenho que admitir que isso também vale para parte de seu texto.

Rubens Takiguti Ribeiro disse...

Originally Posted By Elias WagnerRegister Globals habilitado realmente é uma falha grave, inclusive as novas versões já vem desabilitada por default[...]

Elias, não considero register globals uma falha grave. A utilização de register globals PROPICIA o aparecimento de falhas de segurança. E é por este motivo que ele será removido do PHP. Que fique claro que é possível criar código seguro com register globals ativada.

Elias Wagner disse...

Rubens,

obrigado pelos comentários.

O post e nem eu me considero verdade absoluta, mas eu continuo não acreditando que usar uma variável global para definir um nome de arquivo e não sanitizar não é uma boa prática. Existem várias referências de falhas que usam do recurso de globals não validadas.

Abs.

Elias Wagner disse...

Sim, é possível fazer código sem falhas mesmo com Globals habilitada, mas não deixa de ser uma falha o Globals habilitado. Um deslize e a aplicação se torna vulnerável devido a uma falha de configuração.

Abs.

Elias Wagner disse...

Você tem toda razão, é para evitar ataque de dicionário pre-computado!

Abs.

Rubens Takiguti Ribeiro disse...

Originally Posted By Elias WagnerRubens,

obrigado pelos comentários.

O post e nem eu me considero verdade absoluta, mas eu continuo não acreditando que usar uma variável global para definir um nome de arquivo e não sanitizar não é uma boa prática. Existem várias referências de falhas que usam do recurso de globals não validadas.

Abs.

Elias, na verdade eu não acho que não seja uma boa prática. Aliás, você está de parabéns em querer sanitizar tudo. Afinal, é muito melhor sanitizar tudo do que não sanitizar nada (e isso acontece aos montes).

Eu, particularmente, procuro refletir sobre as falhas e utilizar tais recursos onde considero realmente necessários. Especialmente por uma questão de processamento desnecessário.

O que soa estranho é quando você critica algo com uma argumentação: "super globais precisam ser sanitizadas sempre". Com certeza existem muitos casos onde elas precisam, mas outros não.

Acho que um bom livro sobre segurança é aquele que se concentra mais em apresentar a essência das falhas de segurança. E mostra algumas "soluções conhecidas" apenas para ilustrar. Afinal, existem muitas formas para não deixar brechas e todo programador precisa ser critico e ter flexibilidade para avaliar falhas pouco conhecidas também.

Por exemplo: toda linguagem acessada por outra linguagem precisa de tratamentos especiais. SQL é a mais comum, com as SQL injections, mas poucos sabem sobre Mail injection, por exemplo. Enfim, se algum dia eu usar PHP para acessar outra linguagem, tenho certeza que terei que fazer um tratamento especial sobre as variáveis usadas para "montar" o código da outra linguagem. Talvez essa "linguagem" nem exista ainda e a falha não tem um nome popular como "SQL injection" ou "XSS", mas o importante é já saber como se deparar com ela. A falha é conceitual, não específica.

Tropeçando 23 | Rafael Bernard Araujo disse...

[...] Php Security – Nem tudo que é publicado deve ser seguido – Wagner Elias – Think Security ... [...]

Guilherme disse...

Alguém ter a segunda versão desse livro ?

Elias Wagner disse...

Eu desconheço uma outra versão.