Главная страница   /   8.3. Подделка межсайтовых запросов (ASP.NET MVC 4 в действии

ASP.NET MVC 4 в действии

ASP.NET MVC 4 в действии

Джеффри Палермо

8.3. Подделка межсайтовых запросов

Межсайтовая подделка запросов (XSRF) – это атака, где сайт-взломщик представляет пользователю форму, которая после отправки посылает запрос к уязвимому приложению. Уязвимое приложение обрабатывает запрос в обычном режиме, потому что чаще всего обманутый пользователь остается аутентифицированным на уязвимом сайте.

В этой ситуации уязвимый сайт не имеет возможности узнать, пришел ли представленный запрос от него самого, что является нормальным поведением, или от стороннего сайта. Проблема, заключенная в ASP.NET MVC, состоит в том, как предоставить метку для безопасного сайта, с помощью которой он сможет гарантировать, что запросы генерируются только с тех страниц, которыми он управляет.

XSRF в действии

В пример кода для этой главы мы включили рабочую демонстрацию XSRF. Опять же, в решении есть два сайта: уязвимый и атакующий. Уязвимый сайт принимает простую отправку формы.

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

Наш атакующий сайт показан на рисунке 8-7. Эта кнопка просто напрашивается на клик.

Рисунок 8-7: Побуждаем пользователя нажать на кнопку

За кулисами, в недрах HTML-источника, рассказывается совсем другая история, как показано в листинге 8-1.

Листинг 8-1: Этот пример XSRF-страницы может быть использован для нарушения безопасности
<form method="post" action="http://localhost:8082/home/save">
	<input id="Name" name="Name" type="hidden" value="gotcha!" />
	<button type="submit">Free!!</button>
</form>

Строка 1: Отправка формы на другой сайт

Когда пользователь нажимает кнопку, форма отправляется. Сейчас спасти нас не может даже AuthorizeAttribute, мы уже вошли в систему! Результат показан на рисунке 8-8.

Рисунок 8-8: Форма отправляется на уязвимый сайт

Смышленый взломщик использовал JavaScript для отправки запроса, подавляя ответ от браузера, так что мы никогда не узнаем, что это произошло, пока не станет слишком поздно. Опять же ASP.NET MVC предоставляет простой механизм для борьбы с этой уязвимостью.

Предотвращение XSRF

Применение к действию ValidateAntiForgeryTokenAttribute требует, чтобы входные данные сопровождались специальным маркером, который гарантирует, что они поступают только от отвечающего приложения. За атрибутом обязательно должен следовать специальный вспомогательный метод HTML, который выводит маркер в форме в HTML-источнике.

В следующем коде показан атрибут, примененный к нашему уязвимому действию:

[ValidateAntiForgeryToken]
public ViewResult Save(InputModel form)
{
	return View(form);
}

В нашем представлении мы можем использовать вспомогательный метод AntiForgeryToken:

<form method="post" action="/home/save">
	@Html.AntiForgeryToken()
	<label for="Name">Name:</label>
	@Html.TextBox("Name")
	<button type="submit">Submit</button>
</form>

Когда установлены маркер и атрибут, отправленные с сайта данные, имеющие и маркер, и атрибут, будут приняты, но взломщики не смогут формулировать XSRF-атаки. Если они попробуют, появится исключение, как показано на рисунке 8-9.

Рисунок 8-9: Если запрос не сопровождается специальным маркером, выводится исключение

Теперь настало подходящее время, чтобы применить ValidateAntiForgeryTokenAttribute к действиям, которые принимают данные от формы. И общедоступные, и интранет-сайты уязвимы для XSRF, так что это необходимо для разработки безопасных приложений.

В следующем разделе мы рассмотрим атаку JSON hijacking, которая также требует от разработчиков использования ASP.NET MVC для определенных мер предосторожности.

Атака JSON hijacking

Атака JSON hijacking похожа на XSRF во многом, за исключением того, что она ориентирована на запрос защищенных данных в формате JSON из уязвимых приложений, доступ к которым осуществляется со старых браузеров. Атака JSON hijacking включает в себя несколько этапов:

  • Сайт-взломщик с помощью JavaScript сообщает браузеру жертвы запросить некоторые защищенные данные в формате JSON с другого сайта
  • Вредоносный JavaScript получает данные JSON
  • Если JSON отформатирован в виде массива, вредоносный скрипт может использовать JavaScript браузера, обрабатывающий данные, для чтения данных JSON и передачи их обратно атакующему сайту

Эта атака работает, только если конечная точка JSON, представленная вашим сайтом, возвращает конфиденциальные данные, и они доступны через запрос HTTP GET. Если пользователь был обманом приведен на вредоносный сайт, то на страницу может быть добавлен скрипт, который запрашивает конфиденциальные данные с вашего сайта. Используя динамическую природу языка JavaScript, настройки свойств объектов JSON могли быть переопределены, что позволит вредоносному сайту прочитать данные.

Примечание

Современные браузеры (такие как Firefox 4, Chrome 12, и Internet Explorer 9), не уязвимы для этих типов атак, но пользователи, работающие со старыми версиями Firefox и Chrome потенциально могут быть подвержены таким атакам.

Чтобы предотвратить вероятность такой атаки от вредоносного сайта, вы должны гарантировать, что конечные точки JSON, которые возвращают конфиденциальные данные, не отвечают на запросы GET.

Разрешите доступ к JSON только через POST

Решение для этой уязвимости, предлагаемое ASP.NET MVC, состоит в том, чтобы принимать в качестве запросов на данные JSON только HTTP POST, а не GET. Это решение заключено и реализовано в стандартном результате действия JsonResult, который поставляется с платформой. Если бы вы запросили данные, который будут возвращены в JsonResult, через запрос GET, вы не получили бы данных JSON.

Листинг 8-2 показывает, как нужно использовать метод POST в коде JavaScript для запроса данных JSON.

Листинг 8-2: Запрос данных JSON через POST
<script type="text/javascript">
$.postJSON = function(url, data, callback) {
	$.post(url, data, callback, "json");
};
$(function() {
	$.postJSON('/post/getsecurejsonpost',
	function(data) {
		var options = '';
		for (var i = 0; i < data.length; i++) {
			options += '<option value="' +
			data[i].Id + '">' + data[i].Title +
			'</option>';
		}
		$('#securepost').html(options);
	});
});
</script>
<h2>Secure Json (Post)</h2>
<div>
	<select id="securepost"/>
</div>

Строки 2-4: Вспомогательная функция для JSON POST

Строки 6-14: Скрипт, который заполняет опции выборки

Строка 20: Целевой элемент выборки

В предыдущем листинге для создания специального запроса POST на данные JSON используется JavaScript библиотека JQuery. После возвращения результатов функция наполняет ими список выборки.

Переопределите настройки по умолчанию для доступа через GET

Проблема с этим подходом не техническая, он работает и предотвращает атаку JSON hijacking. Но это решение не всегда является необходимым и может вызвать противоречия в системах, разработанных по архитектурному стилю REST.

Если оно вызывает проблемы, у вас есть дополнительные опции. Во-первых, вы можете явно включить запросы JSON из методов GET с помощью следующего кода:

[HttpGet]
public JsonResult GetInsecureJson()
{
	object data = GetData();
	return Json(data, JsonRequestBehavior.AllowGet);
}

Это позволит вашему действию отвечать на обычные JSON-запросы GET.

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

Модификация ответа JSON

Код в листинге 8-3 показывает особый результат действия, который заключает уязвимые данные JSON в переменную d.

Листинг 8-3: Создание SecureJsonResult для инкапсуляции логики сериализации
public class SecureJsonResult : ActionResult
{
	public string ContentType { get; set; }
	public Encoding ContentEncoding { get; set; }
	public object Data { get; set; }
	public override void ExecuteResult(ControllerContext context)
	{
		if (context == null)
		{
			throw new ArgumentNullException("context");
		}
		HttpResponseBase response = context.HttpContext.Response;
		if (!string.IsNullOrEmpty(ContentType))
		{
			response.ContentType = ContentType;
		}
		else
		{
			response.ContentType = "application/json";
		}
		if (ContentEncoding != null)
		{
			response.ContentEncoding = ContentEncoding;
		}
		if (Data != null)
		{
			var enumerable = Data as IEnumerable;
			if (enumerable != null)
			{
				Data = new {d = enumerable};
			}
			var serializer = new JavaScriptSerializer();
			response.Write(serializer.Serialize(Data));
		}
	}
}

Строки 13-24: Устанавливаем правильное кодирование

Строка 30: Заключает уязвимые данные JSON в переменной

Этот результат действия инкапсулирует сложный код для вывода защищенных данных JSON, и он работает хорошо. Недостатком такого подхода является то, что мы должны использовать переменную d в коде JavaScript. Листинг 8-4 показывает обработку сериализованных данных с помощью JQuery.

Листинг 8-4: Обработка SecureJsonResult с помощью JQuery
$(function () {
	$.getJSON('/post/getsecurejson',
	function (data) {
		var options = '';
		for (var i = 0; i < data.d.length; i++) {
			options += '<option value="' +
			data.d[i].Id + '">' + data.d[i].Title +
			'</option>';
		}
		$('#secure').html(options);
	});
});

Строка 7: Использует переменную d

С этим решением мы можем использовать GET для извлечения данных JSON, но теперь они являются безопасными, потому что никогда не представляют собой просто массив - любой массив заключен в переменной d. Мы просто должны получать значения через переменную d.

Этот нетрадиционный код может привести к путанице. Мы рекомендуем использовать поведение по умолчанию для получения данных JSON с помощью запросов HTTP POST. Если это станет проблемой, вы можете переключиться на это решение.