跳到内容

未经验证的重定向和转发速查表

简介

当Web应用程序接受不可信输入,且该输入可能导致Web应用程序将请求重定向到其中包含的URL时,就可能发生未经验证的重定向和转发。通过将不可信的URL输入修改为恶意站点,攻击者可以成功发起网络钓鱼诈骗并窃取用户凭据。

由于修改后的链接中的服务器名称与原始站点相同,钓鱼尝试可能看起来更具可信度。未经验证的重定向和转发攻击还可以用于恶意构造URL,使其通过应用程序的访问控制检查,然后将攻击者转发到他们通常无法访问的特权功能。

安全的URL重定向

当我们想要自动将用户重定向到另一个页面(无需访问者点击超链接等操作)时,您可能会实现以下代码:

Java

response.sendRedirect("http://www.mysite.com");

PHP

<?php
/* Redirect browser */
header("Location: http://www.mysite.com");
/* Exit to prevent the rest of the code from executing */
exit;
?>

ASP .NET

Response.Redirect("~/folder/Login.aspx")

Rails

redirect_to login_path

Rust actix web

  Ok(HttpResponse::Found()
        .insert_header((header::LOCATION, "https://mysite.com/"))
        .finish())

在上述示例中,URL在代码中被明确声明,攻击者无法对其进行操纵。

危险的URL重定向

以下示例演示了不安全的重定向和转发代码。

危险的URL重定向示例 1

以下Java代码从名为url的参数中接收URL(GET 或 POST),并重定向到该URL。

response.sendRedirect(request.getParameter("url"));

以下PHP代码从查询字符串中获取URL(通过名为url的参数),然后将用户重定向到该URL。此外,此header()函数之后的PHP代码将继续执行,因此如果用户将浏览器配置为忽略重定向,他们可能能够访问页面的其余部分。

$redirect_url = $_GET['url'];
header("Location: " . $redirect_url);

一个类似的C# .NET易受攻击代码示例

string url = request.QueryString["url"];
Response.Redirect(url);

以及在Rails中

redirect_to params[:url]

Rust actix web

  Ok(HttpResponse::Found()
        .insert_header((header::LOCATION, query_string.path.as_str()))
        .finish())

如果不对URL进行验证或应用额外的控制方法来验证其确定性,上述代码就容易受到攻击。这种漏洞可能被用作网络钓鱼诈骗的一部分,通过将用户重定向到恶意网站。

如果没有进行验证,恶意用户可能会创建一个超链接,将您的用户重定向到一个未经验证的恶意网站,例如:

 http://example.com/example.php?url=http://malicious.example.com

用户看到链接指向原始的可信站点(example.com),并未意识到可能发生的重定向。

危险的URL重定向示例 2

ASP .NET MVC 1 和 2 网站特别容易受到开放重定向攻击。为了避免此漏洞,您需要应用MVC 3。

ASP.NET MVC 2 应用程序中LogOn操作的代码如下所示。成功登录后,控制器会将重定向返回到returnUrl。您可以看到未对returnUrl参数执行任何验证。

AccountController.cs中的ASP.NET MVC 2 LogOn操作(请参阅上面提供的Microsoft Docs链接以获取上下文)

[HttpPost]
 public ActionResult LogOn(LogOnModel model, string returnUrl)
 {
   if (ModelState.IsValid)
   {
     if (MembershipService.ValidateUser(model.UserName, model.Password))
     {
       FormsService.SignIn(model.UserName, model.RememberMe);
       if (!String.IsNullOrEmpty(returnUrl))
       {
         return Redirect(returnUrl);
       }
       else
       {
         return RedirectToAction("Index", "Home");
       }
     }
     else
     {
       ModelState.AddModelError("", "The user name or password provided is incorrect.");
     }
   }

   // If we got this far, something failed, redisplay form
   return View(model);
 }

危险的转发示例

当应用程序允许用户输入在网站不同部分之间转发请求时,应用程序必须检查用户是否被授权访问该URL,执行其提供的功能,并且这是一个合适的URL请求。

如果应用程序未能执行这些检查,攻击者精心构造的URL可能会通过应用程序的访问控制检查,然后将攻击者转发到通常不允许的管理员功能。

示例

http://www.example.com/function.jsp?fwd=admin.jsp

以下代码是一个Java Servlet,它将接收一个带有URL参数(名为fwd)的GET请求,用于转发到URL参数中指定的地址。Servlet将从请求中检索URL参数值,并在响应浏览器之前完成服务器端转发处理。

public class ForwardServlet extends HttpServlet
{
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
                    throws ServletException, IOException {
    String query = request.getQueryString();
    if (query.contains("fwd"))
    {
      String fwd = request.getParameter("fwd");
      try
      {
        request.getRequestDispatcher(fwd).forward(request, response);
      }
      catch (ServletException e)
      {
        e.printStackTrace();
      }
    }
  }
}

防止未经验证的重定向和转发

安全地使用重定向和转发可以通过多种方式实现:

  • 简单地避免使用重定向和转发。
  • 如果使用,不要将URL作为用户输入来指定目标。
  • 在可能的情况下,让用户提供一个短名称、ID或令牌,这些在服务器端映射到完整的目标URL。
    • 这提供了最高程度的保护,防止攻击篡改URL。
    • 请注意,这不会引入枚举漏洞,即用户可以通过循环ID来查找所有可能的重定向目标。
  • 如果无法避免用户输入,请确保提供的对于应用程序是有效、合适的,并且对用户是授权的。
  • 通过创建可信URL列表(主机列表或正则表达式)来清理输入。
    • 这应该基于白名单(allow-list)方法,而不是黑名单(denylist)。
  • 强制所有重定向首先通过一个页面,通知用户他们即将离开您的站点,并清楚地显示目标,然后让他们点击链接进行确认。

验证URL

验证和清理用户输入以确定URL是否安全并非易事。有关如何实现URL验证的详细说明,请参阅服务器端请求伪造防御速查表

参考资料