Nos últimos meses, na empresa onde trabalho, estive um pouco atarefado com problemas de invasão do tipo SQL Injection. Eu já tinha avisado meus superiores quando a vulnerabilidade do sistema que estava em utilização, porém, minhas sugestões para tornar o sistema mais seguro, só foram colocadas em prática quando o pesadelo virou realidade. A partir de então, o aspecto segurança passou a ser visto com mais atenção.
Após ganhar algumas horas em pesquisa sobre SQL Injection, descobri que o principal fator que possibilita o sucesso de um ataque deste tipo é a famosa técnica que a grande maioria de administradores inexperientes conhece, chamada NNF (next, next, finish). Achou engraçado? Mas é isso mesmo. A pessoa instala um servidor com as configurações padrão e logo disponibiliza este servidor em produção sem aplicar nenhum procedimento de segurança. Infelizmente isso é o que mais acontece e aconteceu comigo (não sou administrador de redes nem especialista em segurança, mas pra variar, em TI é sempre bom conhecer um pouco de tudo).
A instalação padrão de um servidor IIS exibe os erros HTTP 500;100 para o usuário que acessa um site. Esta configuração é tudo que um cracker precisa para desenvolver seu código malicioso. As formas mais comuns, através de formulários e até mesmo query strings, propositalmente o invasor vai provocando erros no código que fornecem as informações que ele precisa.
Sendo assim a melhor forma de tentar impedir o sucesso desse tipo de ataque é não exibir os erros http 500;100. Então você cria uma página de erro personalizada e direciona o erro para página criada na guia Http Custom Errors das propriedades do site.
Esta não é a solução definitiva, porém, somente esta ação aparentemente insignificante, reduz drasticamente a possibilidade de sucesso de um SQL Injection. Quer saber? Muito raramente alguém faz isso (com a exceção de empresas especializadas no assunto, ou empresas que foram vítimas de ataques).
Outra medida a ser tomada, e não menos importante, é a correção das falhas de segurança do código que em conjunto com os erros exibidos na tela, permitiram o sucesso da invasão. Mas isso eu vou mostrar falar em outro artigo.
Acontece que com a customização do erro, eu eliminei a única forma de debug que eu e os outros desenvolvedores que trabalham comigo tínhamos. Nesta hora de desespero total, eu percebi que ainda existia uma luz no fim do túnel: estou falando do comando Server.GetLastError().
Com esse comando é possível criar uma página de erro personalizada. Só que mostrar os erros na tela voltaria a fornecer informações preciosas aos invasores. Então tive a idéia de enviar os erros por e-mail. Pensei em algumas informações que os desenvolvedores precisam saber para resolver o problema e desenvolvi um script que reúne as seguintes informações: url de origem, url de destino, página do erro, código e descrição do erro, variáveis de formulário (post), variáveis de query string (get), variáveis de sessão do usuário e todos as ServerVariables. Adicionei este script a pagina de erro customizado e, acreditem ou não, uma série de melhorias começaram a acontecer:
- os desenvolvedores passaram a ter informações antes mesmo de o usuário abrir um chamado junto à equipe de suporte;
- conhecimento de bugs no sistema, antes desconhecidos;
- redução do número de abertura de chamados;
- usuários mais satisfeitos com o sistema;
- um sistema mais estável e seguro (porque as tentativas de invasão mal sucedidas também chegam por e-mail), entre outras coisas;
A principal desvantagem é a quantidade de e-mails recebidos inicialmente, porém, na medida que os problemas são solucionados, obviamente os e-mails diminuem.
Os famosos robôs de indexação de páginas (os crawlers como Googlebot, msnbot…), na tentativa de atualizar seu cache de página antigas aumentavam bastante o número de e-mails. Por isso existe no script um filtro dos principais robôs que acessam as páginas do meu sistema.
Segue o código do script que salvei com o nome my500.asp (esta página deve ser configurada no IIS) :
my500.asp
<%LANGUAGE="VBSCRIPT" CODEPAGE="1252"%>
<%
' TRATAMENTO DE ERROS
Option Explicit
On Error Resume Next
Response.Clear
Dim objError, sKey, Texto
Set objError = Server.GetLastError()
' EXPRESSÃO REGULAR PARA IDENTIFICAR SE O AGENTE É UM ROBÔ (CRAWLERs)
Dim reBots
Set reBots = new RegExp
reBots.IgnoreCase = True
reBots.Global = True
reBots.Pattern = "(msnbot|Slurp|Googlebot)"
Texto = Request.ServerVariables("HTTP_USER_AGENT")
' SE NÃO FOR UM ROBÔ, ENVIA O E-MAIL
If Not reBots.Test( Texto ) Then
' LÊ O ARQUIVO CORPO DO E-MAIL (MODELO)
Dim fso, HtmlFile, HtmlText
Set fso = Server.CreateObject("Scripting.FileSystemObject")
Set HtmlFile = fso.OpenTextFile(Server.MapPath("/erros/my500.html"), 1, False)
HtmlText = HtmlFile.ReadAll
HtmlFile.Close
Set HtmlFile = Nothing
Set fso = Nothing
' SUBSTITUI VARIÁVEIS DO CORPO DO E-MAIL
Dim re, sd
Set sd = Server.CreateObject("Scripting.Dictionary")
Set re = new RegExp
re.IgnoreCase = True
re.Global = True
' ARRAY DE VALORES QUE SERÃO ADICIONADOS AO CORPO DO TEXTO
Texto = "http://" & Request.ServerVariables("HTTP_HOST") & Request.ServerVariables("URL")
If Len(Trim( Request.ServerVariables("QUERY_STRING") )) <> 0 Then Texto = Texto "?" Request.ServerVariables("QUERY_STRING")
sd.Add "host", Texto
Texto = Request.ServerVariables("HTTP_REFERER")
If Len(Trim(Texto)) = 0 Then Texto = "Acesso Direto"
sd.Add "referer", Texto
sd.Add "iis.err", objError.ASPCode
sd.Add "com.err", objError.Number & " (0x" & Hex(objError.Number) & ")"
sd.Add "err.src", objError.Source
sd.Add "filename", objError.File
sd.Add "err.line", objError.Line
sd.Add "err.desc", objError.Description
sd.Add "asp.desc", objError.ASPDescription
Texto = ""
For Each sKey In Request.QueryString
Texto = Texto & sKey & " = " & Request.QueryString(sKey) & "<br />" & VbCrLf
Next
sd.Add "request.querystring", Texto
Texto = ""
For Each sKey In Request.Form
Texto = Texto & sKey & " = " & Request.Form(sKey) & "<br />" VbCrLf
Next
sd.Add "request.form", Texto
Texto = ""
For Each sKey In Request.ServerVariables
Texto = Texto & sKey & " = " & Request.ServerVariables(sKey) & "<br />" VbCrLf
Next
sd.Add "Request.ServerVariables", Texto
Texto = ""
For Each sKey In Session.Contents
Texto = Texto & sKey & " = " & Session.Contents(sKey) & "<br />" & VbCrLf
Next
sd.Add "sessions", Texto
' SUBSTITUI AS VARIÁVEIS NO CORPO DO E-MAIL QUE SERÁ ENVIADO
For Each sKey In sd
re.Pattern = "\{\{" & sKey & "\}\}"
HtmlText = re.Replace( HtmlText, sd( sKey ) )
Next
sd.RemoveAll
Set sd = Nothing
Set re = Nothing
' ENVIA UM E-MAIL PARA O ADMINISTRADOR DO SISTEMA
Dim objCDOSYSMail
Dim objCDOSYSCon
Set objCDOSYSMail = Server.CreateObject("CDO.Message")
Set objCDOSYSCon = Server.CreateObject("CDO.Configuration")
objCDOSYSMail.From = "email_from@seusite.com.br"
objCDOSYSMail.To = "dev@seusite.com.br"
objCDOSYSMail.Subject = "Erro ASP 500"
objCDOSYSMail.HtmlBody = HtmlText
objCDOSYSMail.Send
' DESTRÓI OS OBJETOS
Set objCDOSYSMail = Nothing
Set objCDOSYSCon = Nothing
End If
' DESTRÓI OS OBJETOS
Set reBots = Nothing
Set objError = Nothing
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Erro ASP 500</title>
</head>
<body>
Mensagem de erro que você achar melhor.
</body>
</html>
O script utiliza uma página modelo (my500.html) com variáveis que são substituidas pelo devido conteúdo.
Segue o código da página my500.html:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Erro ASP 500</title>
<style>
body { FONT-FAMILY: Arial; FONT-SIZE: 10pt; BACKGROUND: ffffff; COLOR: 000000; MARGIN: 15px; }
H2 { FONT-SIZE: 16pt; COLOR: ff0000; }
TABLE { BACKGROUND: FFF; PADDING: 5px; }
</style>
</head>
<body>
<h2 align="center">Erro ASP 500</h2>
<p align="center">Paacute;gina de origem: {{referer}}<br />
Paacute;gina com erro: {{host}}</p>
<div align="center"><center>
<table width="90" border="0" cellpadding="2" cellspacing="2">
<tr>
<th width="180" align="left" valign="top" nowrap>Nuacute;mero do Erro IIS</th>
<td align="left" valign="top">{{iis.err}}</td>
</tr>
<tr>
<th width="180" align="left" valign="top" nowrap>Nuacute;mero do Erro COM </th>
<td align="left" valign="top">{{com.err}}</td>
</tr>
<tr>
<th width="180" align="left" valign="top" nowrap>Coacute;digo Fonte do erro </th>
<td align="left" valign="top">{{err.src}}</td>
</tr>
<tr>
<th width="180" align="left" valign="top" nowrap>Nome do Arquivo </th>
<td align="left" valign="top">{{filename}}</td>
</tr>
<tr>
<th width="180" align="left" valign="top" nowrap> Linha </th>
<td align="left" valign="top">{{err.line}}</td>
</tr>
<tr>
<th width="180" align="left" valign="top" nowrap>Breve Descricao </th>
<td align="left" valign="top">{{err.desc}}</td>
</tr>
<tr>
<th width="180" align="left" valign="top" nowrap>Descriccedil;atilde;o Completa </th>
<td align="left" valign="top">{{asp.desc}}</td>
</tr>
<tr>
<th colspan="2" align="left" valign="top" nowrap>nbsp;</th>
</tr>
</table>
<table width="90" border="0" cellpadding="2" cellspacing="2">
<tr>
<th colspan="2" align="left" valign="top" nowrap>Query String</th>
</tr>
<tr>
<td colspan="2" align="left" valign="top" nowrap>
{{request.querystring}}</td>
</tr>
<tr>
<th colspan="2" align="left" valign="top" nowrap>nbsp;</th>
</tr>
<tr>
<th colspan="2" align="left" valign="top" nowrap>Formulaacute;rio </th>
</tr>
<tr>
<td colspan="2" align="left" valign="top" nowrap> {{request.form}}</td>
</tr>
<tr>
<td colspan="2" align="left" valign="top" nowrap>nbsp;</td>
</tr>
<tr>
<th colspan="2" align="left" valign="top" nowrap>Variaacute;veis de Sessatilde;o </th>
</tr>
<tr>
<td colspan="2" align="left" valign="top" nowrap>{{sessions}}</td>
</tr>
<tr>
<td colspan="2" align="left" valign="top" nowrap>nbsp;</td>
</tr>
<tr>
<th colspan="2" align="left" valign="top" nowrap>Variaacute;veis do Servidor </th>
</tr>
<tr>
<td colspan="2" align="left" valign="top" nowrap>{{Request.ServerVariables}}</td>
</tr>
</table>
</center>
</div>
</body>
</html>
Espero que gostem e que o scirpt ajude a muitos, porque a mim tem ajudado bastante.
Abraços.