Как избежать уязвимости с открытым переадресацией и безопасно перенаправить при успешном входе в систему (код 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, но мы разработчики - единственное, что защищает наших клиентов от плохих парней - все, что мы можем сделать, чтобы сделать плохая работа парней - победа в моей книге.
Спасибо, Брэд
Ответы
Ответ 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 или любым его параметром (поскольку вы в противном случае управляете всем кодом сервера и браузера).