O que é abordado
Abordar a vulnerabilidade de segurança do banco de dados
Para aumentar a segurança no Dreamweaver 8.0.2 e CS3, removemos a capacidade de usar parâmetros SQL dinâmicos dentro dos conjuntos de registros padrão. O código original expôs os bancos de dados a injeções de SQL. O uso de instruções preparadas para modelos de servidor ASP_VBS e ColdFusion abordou a vulnerabilidade de segurança. Foi necessário adicionar uma função à página para quando o primeiro conjunto de registros fosse aplicado e, em seguida, chamar essa função de todos os conjuntos de registros na página para PHP_MySQL. Essa técnica é semelhante às instruções preparadas de ASP_VBS e ColdFusion.
Em um esforço de fechar a vulnerabilidade de segurança, trocamos um pouco de flexibilidade por segurança. Queremos oferecer aos desenvolvedores que já possuem ou planejam desenvolver extensões que precisam lidar com consultas SQL dinâmicas, assim como aos usuários finais que desejam personalizar seus conjuntos de registros para aceitar parâmetros dinâmicos, a capacidade de editar esses conjuntos de registros personalizados no Dreamweaver.
Injeções de SQL e métodos de prevenção
A injeção de SQL é uma técnica que explora uma vulnerabilidade de segurança que ocorre na camada de banco de dados de um aplicativo. Há vulnerabilidade quando os caracteres de escape literais de sequência de caracteres incorporados nas instruções SQL não são filtrados corretamente na entrada do usuário ou a entrada do usuário não está fortemente tipada e, portanto, é executada inesperadamente. Trata-se, na verdade, de uma instância de uma classe mais geral de vulnerabilidades que pode ocorrer sempre que uma linguagem de programação ou script é incorporada a outra. Visite a Wikipedia para obter informações adicionais.
Exemplo de consulta SQL padrão:
SELECT * FROM company_com WHERE name_com = '$companyName'
No exemplo acima, a variável $companyName é lida em um campo de formulário de entrada, para que o usuário tenha o controle total de qual valor será enviado. Na melhor das hipóteses, quando o usuário digitar “Adobe” (sem as aspas) na entrada correspondente ao nome da empresa, a consulta SQL fornecida terá a seguinte aparência:
SELECT * FROM company_com WHERE name_com = 'Adobe'
Mas há casos nos quais os usuários podem querer violar o site e roubar informações confidenciais e/ou destruir o banco de dados. Considerando o exemplo acima, é fácil obter uma injeção de SQL digitando a seguinte sequência de caracteres “blabla' OR '1'='1” (sem as aspas). Ao fornecer essa sequência de caracteres, o usuário terá acesso a todas as empresas, em vez de uma única empresa. O SQL real que é executado é:
SELECT * FROM company_com WHERE name_com = 'blabla' OR '1'='1'
Além disso, se o usuário quiser excluir toda a tabela company_com, ele pode fazer isso simplesmente passando a seguinte sequência de caracteres “x'; DROP TABLE company_com; --” (sem as aspas circundantes). Aqui está a consulta SQL completa:
SELECT * FROM company_com WHERE name_com = 'x'; DROP TABLE company_com; --'
Use uma das seguintes medidas para proteger seu site contra injeções de SQL:
- Restrinja a entrada a um conjunto mínimo de caracteres permitidos removendo automaticamente todos os outros caracteres que estiverem fora do intervalo especificado.
- Evite todos os caracteres que podem causar problemas quando usados em uma consulta SQL (por exemplo, aspas simples).
- Use instruções preparadas.
- Use direitos de acesso ao banco de dados.
- Use procedimentos armazenados.
Recomendamos as instruções preparadas descritas com mais detalhes em Usar instruções preparadas. O Dreamweaver 8.0.2 e o CS3 usam a abordagem de instruções preparadas para os modelos de servidor ASP_VBS, ASP_JavaScript, Cold Fusion e JSP e evitam a abordagem de entrada do usuário para o modelo de servidor PHP_MySQL. Consulte Ataques de injeção de SQL como exemplo para obter informações adicionais sobre os ataques de injeção de SQ.
Usar instruções preparadas
O Dreamweaver 8.0.2 e o CS usam instruções preparadas, pois esta solução nos garante que a substituição dos parâmetros SQL reais por determinados valores (possivelmente enviados via URL) será feita no servidor e não na página. As instruções preparadas também garantem que o valor passado para o próprio SQL tenha o tipo de dados apropriado e use o escape apropriado (se um parâmetro for considerado do tipo int, então o usuário não poderá enviar uma letra ou se um parâmetro for do tipo texto, toda a sequência de caracteres transmitida será evitada corretamente e o usuário não terá a chance de executar uma injeção de SQL).
Instruções preparadas para os modelos de servidor ASP_VBS e ASP_JS
Para ASP_VBS e ASP_JS, contamos com a camada de banco de dados ADODB. O exemplo a seguir mostra um conjunto de registros ASP_VBS simples que usa um parâmetro SQL dinâmico para filtrar os resultados:
... <% Dim Recordset1__MMColParam Recordset1__MMColParam = "1" If (Request.QueryString("id_com") <> "") Then Recordset1__MMColParam = Request.QueryString("id_com") End If %> <% Dim Recordset1 Dim Recordset1_cmd Dim Recordset1_numRows Set Recordset1_cmd = Server.CreateObject ("ADODB.Command") Recordset1_cmd.ActiveConnection = MM_connContacts_STRING Recordset1_cmd.CommandText = "SELECT * FROM company_com WHERE id_com = ?" Recordset1_cmd.Prepared = true Recordset1_cmd.Parameters.Append Recordset1_cmd.CreateParameter("param1", 5, 1, -1, Recordset1__MMColParam) ' adDouble Set Recordset1 = Recordset1_cmd.Execute Recordset1_numRows = 0 %> ...Instruções preparadas para o modelo de servidor ColdFusion
O ColdFusion oferece suporte interno para instruções preparadas. O exemplo a seguir mostra um conjunto de registros ColdFusion simples que usa um parâmetro SQL dinâmico para filtrar os resultados:
... <cfparam name="URL.id_com" default="1"> <cfquery name="Recordset1" datasource="company_employee"> SELECT * FROM company_com WHERE id_com = <cfqueryparam value="#URL.id_com#" cfsqltype="cf_sql_numeric"> </cfquery> ...Instruções preparadas simuladas para o modelo de servidor PHP_MySQL
As instruções preparadas para PHP foram disponibilizadas no MySQL 4.1, mas como também queríamos oferecer suporte a versões anteriores do MySQL, decidimos implementar uma função personalizada que faz praticamente a mesma coisa. O exemplo a seguir mostra um conjunto de registros PHP simples que usa um parâmetro SQL dinâmico para filtrar os resultados:
... <?php if (!function_exists("GetSQLValueString")) { function GetSQLValueString($theValue, $theType, $theDefinedValue = "", $theNotDefinedValue = "") { $theValue = get_magic_quotes_gpc() ? stripslashes($theValue) : $theValue; $theValue = function_exists("mysql_real_escape_string") ? mysql_real_escape_string($theValue) : mysql_escape_string($theValue); switch ($theType) { case "text": $theValue = ($theValue != "") ? "'" . $theValue . "'" : "NULL"; break; case "long": case "int": $theValue = ($theValue != "") ? intval($theValue) : "NULL"; break; case "double": $theValue = ($theValue != "") ? "'" . doubleval($theValue) . "'" : "NULL"; break; case "date": $theValue = ($theValue != "") ? "'" . $theValue . "'" : "NULL"; break; case "defined": $theValue = ($theValue != "") ? $theDefinedValue : $theNotDefinedValue; break; } return $theValue; } } $colname_Recordset1 = "-1"; if (isset($_GET['id_com'])) { $colname_Recordset1 = $_GET['id_com']; } mysql_select_db($database_connContacts, $connContacts); $query_Recordset1 = sprintf("SELECT * FROM company_com WHERE id_com = %s", GetSQLValueString($colname_Recordset1, "int")); $Recordset1 = mysql_query($query_Recordset1, $connContacts) or die(mysql_error()); $row_Recordset1 = mysql_fetch_assoc($Recordset1); $totalRows_Recordset1 = mysql_num_rows($Recordset1); ?> ...Permitir parâmetros dinâmicos em conjuntos de registros padrão
Então, como podemos nos proteger contra injeções de SQL, permitindo parâmetros dinâmicos e mantendo o código resultante editável através do painel Comportamentos do servidor no Dreamweaver? A solução é inserir os parâmetros dinâmicos na própria consulta SQL, em vez de passá-los como parâmetros clássicos do SQL (que seriam evitados ou anexados ao SQL por meio de instruções preparadas).
Importante: Os usuários não podem gerar esse código usando os Objetos e os Comportamentos do servidor fornecidos com o Dreamweaver 8.0.2 ou CS3 (exemplo: inserir um conjunto de registros através do painel Comportamentos do servidor ou da barra Inserir). Esta solução requer que o usuário edite o código manualmente ou instale uma extensão de terceiros que crie esse código. A responsabilidade de escrever código seguro recai sobre o usuário ou desenvolvedor de software de terceiros.
O objetivo deste método é permitir que desenvolvedores experientes criem consultas SQL dinâmicas. O uso indevido desta solução oferece ao hacker a possibilidade de obter privilégios indesejados e/ou interromper as páginas da Web e o banco de dados. Consulte Injeções de SQL e métodos de prevenção para minimizar o risco de injeções de SQL.
Os principais casos que identificamos nos quais essa solução poderia ser usada são:
- Criar/atualizar manualmente uma consulta SQL para que aceite parâmetros dinâmicos.
- Criar/atualizar extensões existentes para gerar parâmetros dinâmicos.
Ambos os casos de uso serão detalhados nos parágrafos seguintes.
Criar/atualizar manualmente uma consulta SQL para aceitar parâmetros dinâmicos, mantendo o conjunto de registros ainda editável no Dreamweaver
No exemplo a seguir, o usuário deseja adicionar a funcionalidade de classificação a uma tabela dinâmica. Ele decide recarregar a página enquanto passa os arquivos e a direção de classificação a ser usada (crescente ou decrescente) no URL. Um exemplo inicial de URL é:
http://www.mydomain.com/index.php?sortCol=name_com&sortDir=ascA codificação manual é necessária para realizar esta tarefa no Dreamweaver usando os Comportamentos do servidor. Os exemplos de código a seguir mostram as alterações que devem ser feitas em cada modelo de servidor.
Importante: Observe que nenhum dos exemplos abaixo tem validação contra injeções de SQL. Seu papel é puramente didático e não deve ser usado na produção como está. Os exemplos não estão blindados para que sejam o mais simples possível. Você deve se proteger contra injeções de SQL. Para obter informações adicionais, consulte Injeções de SQL e métodos de prevenção.
ASP_VBS
Antes:
... <% Dim Recordset1 Dim Recordset1_cmd Dim Recordset1_numRows Set Recordset1_cmd = Server.CreateObject ("ADODB.Command") Recordset1_cmd.ActiveConnection = MM_connContacts_STRING Recordset1_cmd.CommandText = "SELECT * FROM company_com" Recordset1_cmd.Prepared = true Set Recordset1 = Recordset1_cmd.Execute Recordset1_numRows = 0 %> ...Depois:
... <% Dim orderBy: orderBy = "" If (Request.QueryString("sortCol") <> "") Then orderBy = "ORDER BY " & Request.QueryString("id_com") If (Request.QueryString("sortDir") <> "") Then orderBy = orderBy & " " & Request.QueryString("sortDir") End If End If %> <% Dim Recordset1 Dim Recordset1_cmd Dim Recordset1_numRows Set Recordset1_cmd = Server.CreateObject ("ADODB.Command") Recordset1_cmd.ActiveConnection = MM_connContacts_STRING Recordset1_cmd.CommandText = "SELECT * FROM company_com " & orderBy & "" Recordset1_cmd.Prepared = true Set Recordset1 = Recordset1_cmd.Execute Recordset1_numRows = 0 %> ...ColdFusion
Antes:
... <cfquery name="Recordset1" datasource="company_employee"> SELECT * FROM company_com </cfquery> ...Depois:
... <cfparam name="URL.sortCol" default=""> <cfparam name="URL.sortDir" default="ASC"> <cfset orderBy=""> <cfif (#URL.sortCol# NEQ "")> <cfset orderBy="ORDER BY #URL.sortCol# #URL.sortDir#"> </cfif> <cfquery name="Recordset1" datasource="company_employee"> SELECT * FROM company_com #orderBy# </cfquery> ...PHP_MySQL
Antes:
... <?php mysql_select_db($database_connContacts, $connContacts); $query_Recordset1 = "SELECT * FROM company_com"; $Recordset1 = mysql_query($query_Recordset1, $connContacts) or die(mysql_error()); $row_Recordset1 = mysql_fetch_assoc($Recordset1); $totalRows_Recordset1 = mysql_num_rows($Recordset1); ?> ...Depois:
... <?php $orderBy = ""; if (isset($_GET['sortCol'])) { $orderBy = "ORDER BY " . $_GET['sortCol']; if (isset($_GET['sortDir'])) { $orderBy .= " " . $_GET['sortDir']; } } ?> <?php mysql_select_db($database_connContacts, $connContacts); $query_Recordset1 = "SELECT * FROM company_com " . $orderBy . ""; $Recordset1 = mysql_query($query_Recordset1, $connContacts) or die(mysql_error()); $row_Recordset1 = mysql_fetch_assoc($Recordset1); $totalRows_Recordset1 = mysql_num_rows($Recordset1); ?> ...Criar/atualizar extensões existentes para gerar parâmetros dinâmicos, mantendo o conjunto de registros ainda editável no Dreamweaver
Agora considere o mesmo cenário acima, exceto que o usuário deseja criar uma extensão que gere o código apropriado, em vez de depender de uma combinação de funcionalidade interna e edição manual. A nova extensão deve gerar o código correto para os modelos de servidor ASP_VBS, ColdFusion e PHP_MySQL.
Importante: Nas seções a seguir, focaremos apenas na geração de código que utilizará os valores dos parâmetros de URL para criar uma consulta SQL dinâmica. O foco principal é gerar o código para que ele ainda seja reconhecido pelo Dreamweaver e seja editável nas interfaces padrão do Dreamweaver. Este tutorial não manipula a validação dos dados de entrada (por meio de parâmetros de URL) nem protege o SQL final contra qualquer tentativa de injeção de SQL porque a complexidade desse código fica fora do escopo deste tutorial. O desenvolvedor é o único responsável por proteger o SQL final contra injeções de SQL.
Fornecemos uma extensão de demonstração que usa uma conexão e uma tabela como entrada e gera uma tabela dinâmica exibindo todos os registros. Ele também atualiza o SQL gerado para conter a variável orderBy, conforme mostrado nos exemplos acima. A extensão foi projetada para o Dreamweaver 8.0.2 e CS3. O código gerado não está blindado, porque o objetivo é criar conjuntos de registros que contêm parâmetros dinâmicos que permanecem editáveis usando as interfaces padrão do Dreamweaver. O código fonte da extensão está localizado na pasta de configuração do usuário.
Em todos os exemplos de código abaixo, as seções destacadas foram adicionadas para ativar a funcionalidade de classificação através dos parâmetros de URL sortCol e sortDir.
ASP_VBS
O código que gera a consulta SQL apropriada para o modelo de servidor ASP_VBS está localizado no arquivo “[PASTA_CONFIGURAÇÃO_USUÁRIO]/Commands/My Dynamic Table.js” da pasta de configuração do usuário. A alteração notável está na linha 130:
... 130: paramObj.encodedSQL = "SELECT * FROM " + paramObj.table + " \" & orderBy & \""; ...O arquivo de participantes do comportamento do servidor MyDynamicTable ASP_VBS inclui um participante extra que adiciona a definição da variável “orderBy” à página:
<group name="MyDynamicTable" version="9.0"> <groupParticipants> <groupParticipant name="connectionref_statement" /> <groupParticipant name="MyDynamicTable_orderBy" /> <groupParticipant name="recordset_main" /> <groupParticipant name="repeatedRegion_init2" /> <groupParticipant name="DynamicTable_main" /> <groupParticipant name="recordset_close" /> </groupParticipants> </group>ColdFusion
O mesmo “[PASTA_CONFIGURAÇÃO_USUÁRIO]/Commands/My Dynamic Table.js” na pasta de configuração do usuário também contém o código do modelo de servidor ColdFusion. A alteração relevante neste caso está na linha 138:
... 138: paramObj.SQLStatement = "SELECT * FROM " + paramObj.table + " #orderBy#"; ...O arquivo de participantes do comportamento do servidor MyDynamicTable ColdFusion inclui um participante extra que adiciona a definição da variável “orderBy” à página:
<group name="MyDynamicTable" version="9.0"> <groupParticipants> <groupParticipant name="MyDynamicTable_orderBy" /> <groupParticipant name="Recordset_main" /> <groupParticipant name="DynamicTable_main" /> <groupParticipant name="RepeatedRegion_pageNum" /> <groupParticipant name="RepeatedRegion_maxRows" /> <groupParticipant name="RepeatedRegion_startRow" /> <groupParticipant name="RepeatedRegion_endRow" /> <groupParticipant name="RepeatedRegion_totalPages" /> </groupParticipants> </group>PHP_MySQL
O último modelo de servidor na extensão é PHP_MySQL. A alteração relevante no arquivo "[PASTA_CONFIGURAÇÃO_USUÁRIO]/Commands/My Dynamic Table.js” da pasta de configuração do usuário está na linha 122:
... 122: paramObj.SQLStatement = "SELECT * FROM " + paramObj.table+ " \" . $orderBy . \""; ...O arquivo de participantes do comportamento de servidor MyDynamicTable PHP_MySQL inclui um participante extra que adiciona a definição da variável “orderBy” à página:
<group name="MyDynamicTable" version="9.0"> <groupParticipants> <groupParticipant name="Connection_include" /> <groupParticipant name="MyDynamicTable_orderBy" /> <groupParticipant name="EditOps_SQLValueString" /> <groupParticipant name="Recordset_main" /> <groupParticipant name="DynamicTable_main" /> <groupParticipant name="Recordset_close" /> </groupParticipants> </group>Prós
O método descrito acima tem várias vantagens, incluindo:
- Os desenvolvedores mais avançados podem tirar proveito da vulnerabilidade de injeção de SQL para construir SQLs mais complexos, dinamicamente.
- Os conjuntos de registros criados ainda serão editáveis no Dreamweaver.
- Não é difícil integrar às extensões existentes do Dreamweaver que geram conjuntos de registros com parâmetros de SQL dinâmicos.
- O código padrão gerado pelo Dreamweaver permanece inalterado; os desenvolvedores que não exigem consultas dinâmicas (e seus clientes) podem ter certeza de que o código escrito pelo Dreamweaver é seguro.
Contras
Como o Dreamweaver não foi projetado por padrão para oferecer suporte a esse método, ele também tem alguns inconvenientes, incluindo:
- O código gerado usando o método descrito neste documento é vulnerável a injeções de SQL e, portanto, precisa ser protegido de outras maneiras. A lista desses métodos de proteção inclui, entre outros:
- Verifique se o usuário final não pode usar o método de injeção de SQL para compor a página da Web e/ou banco de dados;
- Evite os argumentos antes que eles atinjam o SQL real;
- Certifique-se de que, mesmo que o usuário consiga violar seu código, ele não roubará informações confidenciais e/ou não comprometerá o banco de dados.
- Há dois problemas conhecidos que não podem ser corrigidos até a próxima versão do Dreamweaver:
- ColdFusion: a interface de usuário simples do conjunto de registros remove os hashes em torno dos nomes de variáveis da consulta SQL quando editados e depois aplicados novamente
- ColdFusion, ASP_VBS, ASP_JS: o botão Testar da interface de usuário avançada do conjunto de registros não funciona quando a consulta SQL contém nomes de variáveis
Localização da pasta Configuração do usuário
- Windows XP
C:\Documents and Settings\[nomedousuário]\Application Data\Adobe\Dreamweaver 9\Configuration
- Windows Vista
C:\Users\[nomedousuário]\AppData\Roaming\Adobe\Dreamweaver 9\Configuration
- Mac OS 10.4.x
/Users/[nomedousuário]/Library/Application\ Support/Adobe/Dreamweaver\ 9/Configuration