Подтвердить что ты не робот

Как избежать уязвимости с открытым переадресацией и безопасно перенаправить при успешном входе в систему (код HINT: ASP.NET MVC 2 по умолчанию уязвим)

Обычно, когда сайт требует, чтобы вы вошли в систему, прежде чем вы сможете получить доступ к определенной странице, вы попадаете на экран входа в систему и после успешной аутентификации вы перенаправляетесь обратно на первоначально запрошенную страницу. Это отлично подходит для удобства использования - но без тщательного изучения эта функция может легко стать уязвимостью open redirect.

К сожалению, для примера этой уязвимости смотрите не дальше, чем действие LogOn по умолчанию, предоставляемое ASP.NET MVC 2:

[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); // open redirect vulnerability HERE
            } else {
                return RedirectToAction("Index", "Home");
            }

        } else {
            ModelState.AddModelError("", "User name or password incorrect...");
        }
    }

    return View(model);
}

Если пользователь успешно аутентифицирован, он перенаправляется на "returnUrl" (если он был предоставлен с помощью формы входа в систему).

Вот простой пример атаки (один из многих, фактически), который использует эту уязвимость:

  • Атакующий, притворяясь банком-жертвой, отправляет по электронной почте жертве, содержащую ссылку, например: http://www.mybank.com/logon?returnUrl=http://www.badsite.com
  • Будучи обученным проверять имя домена ENTIRE (например, google.com = GOOD, google.com.as31x.example.com = BAD), жертва знает, что ссылка в порядке - нет никаких сложных подкатегорий, фишинг домена продолжается.
  • Потребитель щелкает ссылку, видит свой фактический знакомый банковский сайт и просят войти в систему
  • Жертва регистрируется и впоследствии перенаправляется на http://www.badsite.com, которая сделана так, чтобы выглядеть как сайт банка жертвы, поэтому жертва не знает, что он сейчас находится на другом сайте.
  • http://www.badsite.com говорит что-то вроде "Нам нужно обновить наши записи", пожалуйста, введите некоторые личную информацию ниже: [ssn], [адрес], [номер телефона] и т.д. "
  • Жертва, все еще думающая, что находится на своем банковском веб-сайте, попадает на уловку и предоставляет злоумышленнику информацию.

Любые идеи о том, как поддерживать эту функцию переадресации при успешном подключении, все же избегают уязвимости с открытым перенаправлением?

Я склоняюсь к варианту разделения параметра returnUrl на элементы управления/действия и используйте "RedirectToRouteResult" вместо простого "Перенаправления". Открывает ли этот подход какие-либо новые уязвимости?

Обновление

Ограничиваясь маршрутами контроллера/действий, я не могу перенаправить на пользовательские маршруты (например, /backend/calendar/2010/05/21). Я знаю, что, передавая больше параметров в действие LogOn, я мог бы заставить его работать, но я чувствую, что всегда буду пересматривать этот метод - постоянно обновляя нашу схему маршрутизации. Таким образом, вместо того, чтобы разбивать returnUrl на его элементы управления/действия, я сохраняю returnUrl as-is и анализирую его, чтобы убедиться, что он содержит только относительный путь (например, /users/1), а не абсолютный путь (например, http://www.badsite.com/users/1). Вот код, который я использую:

private static bool CheckRedirect(string url) {
    try {
        new Uri(url, UriKind.Relative);
    }
    catch (UriFormatException e) {
        return false;
    }

    return true;
}

Боковое примечание: я знаю, что этот open-redirect может показаться не таким большим, как XSS и CSRF, но мы разработчики - единственное, что защищает наших клиентов от плохих парней - все, что мы можем сделать, чтобы сделать плохая работа парней - победа в моей книге.

Спасибо, Брэд

4b9b3361

Ответ 1

Джон Галлоуэй написал статью с решением для MVC 2 (и 1).

Вот фрагмент, который должен помочь в вашей проблеме:

private bool IsLocalUrl(string url)
{
    if (string.IsNullOrEmpty(url))
    {
        return false;
    }

    Uri absoluteUri;
    if (Uri.TryCreate(url, UriKind.Absolute, out absoluteUri))
    {
        return String.Equals(this.Request.Url.Host, absoluteUri.Host, 
                    StringComparison.OrdinalIgnoreCase);
    }
    else
    {
        bool isLocal = !url.StartsWith("http:", StringComparison.OrdinalIgnoreCase)
            && !url.StartsWith("https:", StringComparison.OrdinalIgnoreCase)
            && Uri.IsWellFormedUriString(url, UriKind.Relative);
        return isLocal;
    }
}

Ответ 2

Да, это уязвимость. Перед перенаправлением вам необходимо проверить строковый параметр returnUrl, передав его объекту Uri и убедитесь, что целевой домен является как и запрашивающий домен. Вы также должны учитывать случай, когда returnUrl является относительным адресом, например /admin. Нет проблем в этом случае, поскольку перенаправление будет в том же приложении.

Ответ 3

Вы всегда можете сохранить запись предыдущей страницы с помощью TempData, когда пользователь не аутентифицирован, и использовать это для перенаправления на предыдущую страницу вместо параметра url.

Ответ 4

Пока вы используете один из вариантов Redirect, который использует параметры контроллера и действия, или имя маршрута, вы должны быть в порядке, если у вас есть адекватные средства контроля безопасности для ваших методов контроллера.

Концепция заключается в том, что все, что вы используете для перенаправления, должно проходить через механизм маршрутизации и проверяться путем сопоставления маршрута.

Но я подозреваю, что реальной уязвимостью является Cross-Site Scripting. Если ваш злоумышленник не может вставить какой-либо Javascript на страницу, они не могут манипулировать возвращаемым Url или любым его параметром (поскольку вы в противном случае управляете всем кодом сервера и браузера).