Главная » Безопасность » Безопасность ASP.NET
Безопасность ASP.NET

Безопасность ASP.NET

ASP.NET MVC — не самый трендовый, но довольно популярный стек в среде веб-разработчиков. С точки зрения противодействия хакерам, его стандартный функционал дает вам кое-какой базовый уровень безопасности, но для защиты от абсолютного большинства хакерских атак нужен дополнительная уровень защиты. В этой статье мы рассмотрим основы, которые должен знать о безопасности ASP.NET любой разработчик (будь то Core, MVC, MVC Razor или Web Forms).

Начнем со всем известных видов атак.

SQL Injection

Как ни странно, но в 2017 году injection и, в частности, SQL injection находятся на первом месте среди «Toп-10 рисков безопасности OWASP» (Open Web Application Security Project). Данный тип атаки подразумевает, что введенные пользователем данные используются на серверной стороне в качестве параметров запроса.

Пример классической SQL-инъекции скорее характерен именно для приложений Web Forms. От атак помогает защититься использование параметров в качестве значений запроса:

Если вы разрабатываете MVC-приложение, то Entity Framework прикрывает некоторые уязвимости. Получить сработавшую в MVC/EF-приложении SQL-инъекцию нужно умудриться. Однако это возможно, если вы выполнните SQL-код с помощью ExecuteQuery или вызовите плохо написанные хранимые процедуры.

Несмотря на то что ORM позволяет избежать SQL-инъекции (за исключением приведенных выше примеров), рекомендуется ограничивать атрибутами значения, которые могут принимать поля модели, а значит, и формы. Например, если подразумевается, что в поле может быть введен только текст, то с помощью Regex укажи диапазон ^[a-zA-Z]+$. А если в поле должны быть введены цифры, то укажите это как требование:

В Web Forms ограничить значения вы можете с помощью валидаторов. Пример:

Начиная с .NET 4.5 Web Forms используют Unobtrusive Validation. А это значит, что не требуется писать какой-то дополнительный код для проверки значения формы.

Валидация данных, в частности, может помочь защититься от еще одной всем известной уязвимости под названием cross-site scripting (XSS).

XSS

Типичный пример XSS — добавление скрипта в комментарий или запись в гостевую книгу. Выглядеть он может так:

Как вы понимааете, в данном примере куки с вашего сайта передаются в качестве параметра на какой-то хакерский ресурс.

В Web Forms можно совершить ошибку с помощью примерно такого кода:

Понятно, что вместо username может быть скрипт. Чтобы избежать выполнения скрипта, можно как минимум использовать другое ASP.NET-выражение: <%: username %>, которое энкодит свое содержимое.

Если мы используем Razor, то строки автоматически энкодируются, что сводит возможность реализации XSS к минимуму — злоумышлений сможет ее провернуть, только если вы грубо ошибетесь, например используя @Html.Raw(Model.username) или юзая в своей модели MvcHtmlString вместо string.

Для дополнительной защиты от XSS данные кодируются еще и в коде C#. В .NET Core можно использовать следующие кодеры из пространства имен System.Text.Encodings.Web: HtmlEncoder, JavaScriptEncoder и UrlEncoder.

Следующий пример вернет строку <script>:

В классическом .NET используется HttpUtility.HtmlEncode. А начиная с .NET 4.5 можно сделать AntiXssEncoder энкодером по умолчанию. Делается это добавлением в тег httpRuntime файла web.config одного атрибута:

Таким образом, сохранив старый код 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

Будь осторожен с редиректами! Следующий код очень опасен:

Атакующий может добавить ссылку на свой сайт. А пользователь же, увидев, что URL начинается с хорошего сайта, может не рассмотреть адрес полностью (особенно если он длинный) и кликнуть ссылку, таким образом перейдя на вредоносный сайт с вашего, хорошего сайта. Эта уязвимость может использоваться, в частности, для фишинга. Пример фишинговой ссылки:

Многие пользователи, получив e-mail со ссылкой, смотрят, совпадает ли домен, и совсем не ожидают переброски на злодейский ресурс. А если по редиректу откроется страница с таким же дизайном, то многие пользователи не задумываясь введут свои логин и пароль (решив, что случайно вышли из аккаунта). После чего, кстати, могут быть перенаправлены злоумышленниками обратно на настоящий сайт.

Этот вид атак касается и MVC. В следующем примере происходит проверка на то, является ли ссылка локальной:

Для защиты от этого типа атак можно использовать и вспомогательный метод LocalRedirect:

В общем, старайся никогда не доверять полученным данным.

Mass assignment

Разберем эту уязвимость на примере. Допустим, в нашем веб-сайте есть простая модель с двумя свойствами:

И есть обычная и довольно простая вьюшка:

С помощью этой вьюшки можно редактировать только имя пользователя, не так ли?

А теперь давай перейдем к столь же простому коду:

Все ли здесь нормально? Как оказывается, нет. А все из-за того, что экшен помечен как HttpPost. Чтобы убедится в этом, достаточно открыть утилиту вроде Postman или Fiddler и отправить POST-запрос на адрес с указанием параметров id и IsAdmin. Если мы тестируем локально, то адрес может быть таким: localhost:51696/Home/Vulnerable?id=34&IsAdmin=true.

Безопасность ASP.NET
Безопасность ASP.NET. Результат выполнения POST-запроса

Как видно на скриншоте, получен доступ к секретной информации (в HTML-коде есть строка You are an admin).

Как защититься от этого типа атаки?

Самый простой вариант — не попадать в ситуацию, когда с HttpPost передается какой-нибудь объект. А если этого не избежать, нужно быть готовым к тому, что передано может быть все что угодно. Один из вариантов — это создать какой-то отдельный класс для передачи его через HttpPost. Это может быть как базовый класс текущего класса с общедоступными параметрами, так и класс-двойник. В этом классе важные поля можно пометить атрибутом Editable со значением false:

Header’ы против атак

Защититься от хакеров помогает установка определенных значений в заголовок запроса. Заголовки поддерживаются большинством современных браузеров (да-да, старые версии идут лесом), поэтому сейчас мы рассмотрим некоторые популярные виды атак, избежать которых поможет установка Header’ов.

XSS

Для дополнительной защиты можно использовать заголовок content-security-policy. Он позволит загружать контент лишь с определенных ресурсов. Например, можно разрешить запуск скриптов только с текущего сайта:

Есть еще возможность указать доверенные сайты, доступ к содержимому которых разрешен. Следующий заголовок тоже помогает защититься от XSS, хотя, как правило, он включен браузерами по умолчанию: x-xss-protection. Пример:

Clickjacking

Сайт может показать пользователю окошко, ссылку или баннер, поверх которого добавлена скрытая кнопка/ссылка внутри прозрачного iframe. И получается, что пользователь кликает на что-то, на что он хочет кликнуть, но при этом фактически он щелкает на скрытый объект против своей воли.

Установка заголовка X-FRAME-OPTIONS со значением DENY запретит помещать страницы твоего сайта в iframe. Если у вас на сайте нет фреймов, то это хороший вариант. Если же вы используете iframe для отображения страниц, то значение SAMEORIGIN разрешит отображать страницы сайта во фрейме, но только на других страницах того же самого вашего сайта.

MIME sniffing

Зачастую хакер может загрузить вредоносный код в виде файла с совершенно безобидным расширением. Допустим, используя в качестве тега video. И может случиться так, что браузер распознает файл как код и выполнит его. Чтобы этого не произошло, может использоваться установка заголовка X-Content-Type-Options: nosniff. При получении этого заголовка браузер будет проверять, соответствует ли формат содержимого файла тому, который указан (эта проверка и называется MIME sniffing).

Referrer-Policy

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

Для того чтобы скрыть ссылку при переходе на чужой сайт, можно установить заголовок со значением Referrer-Policy: no-referrer.

Каким образом можно добавить на сайт заголовки запросов?

Заголовки запросов можно установить как с помощью настроек IIS, так и из кода приложения. Настройку IIS рассматривать не будем, а поговорим про варианты установки заголовков из кода.

Чтобы добавить заголовок в ASP.NET Core, можно создать Middleware. Middleware, как можно понять из названия, — это промежуточный код, находящийся посередине цепочки процесса запросов и ответов. Вот пример пары классов, позволяющих добавить заголовок X-Frame-Options:DENY:

Зарегистрировать получившийся Middleware можно в методе Configure файла Startup.cs одной строкой:

Теперь среди списка заголовков, полученных от сервера, мы сможем увидеть наш недавно добавленный X-Frame-Options.

Безопасность ASP.NET
Безопасность ASP.NET. Список заголовков

Можно даже не использовать Middleware, а добавить заголовок сразу в метод Config файла Startup.cs, заменив

на

Этот способ выглядит проще. Кроме того, с его помощью можно установить заголовки как для всего контента, так и только для динамического (соответственно, добавить код до строки app.UseStaticFiles() и после).

Динамический контент — это файлы, которые содержат в себе динамические данные модели. Статический контент — это такие файлы, как HTML, CSS, JS, JPG и подобные.

В классическом ASP.NET заголовок добавляется немного иначе. Есть два варианта. Первый — это добавить в секцию system.webServer файла web.config теги. Например, такие:

Заметьте, что можно не только добавлять, но и удалять теги. В примере удаляется заголовок X-Powered-By. Чем меньше информации мы раскрываем, тем лучше, не так ли? Результат на скриншоте.

Безопасность ASP.NET
Безопасность ASP.NET. Список заголовков

Кроме заголовка X-Powered-By, вполне можно убрать еще и заголовки Server и X-AspNet-Version.

Второй вариант добавления заголовков — это добавить метод Application_BeginRequest в файл Global.asax:

nwebsec

Для добавления заголовка можно воспользоваться и довольно популярным NuGet-пакетом под названием NWebsec. Его автор — Андре Клингсхейм (André N. Klingsheim).
NWebsec можно использовать как с обычными ASP.NET-приложениями, так и с Core 1.1. В приложении ASP.NET после установки пакета в web.config появятся следующие теги:

В качестве их содержимого можно добавить установку заголовков. Скажем, такой вариант:

Если вы используете ASP.NET Core, то рекомендуемый вариант добавления заголовков выглядит так:

перед

и после

Один большой минус NWebsec — версия .NET Core 2.0 пока что не поддерживается.

Самые страшные ошибки конфигурирования

Хранение строки подключения к базе данных

Если вы работаете в полноценном ASP.NET, то лучший вариант хранения строки подключения — это файл web.config. Причем храните строку не в открытом виде, а в зашифрованном. Сделать это можно с помощью утилиты aspnet_regiis.exe. Самый простой вариант — запустить Developer Command Prompt в режиме администратора и выполнить команду:

Два параметра команды — это раздел, который необходимо зашифровать (в данном случае — connectionStrings1), и путь к директории, в которой находится файл web.config.

Если вы работаете в ASP.NET Core, то можете использовать Secret Manager tool для хранения строк во время разработки. Никакого готового варианта для продакшена .NET Core пока что нет. Но если вы хостите приложение в Azure, то конфиденциальную информацию можно хранить в параметрах приложения.

Безопасность ASP.NET
Безопасность ASP.NET. Параметры Azure Web App

При этом саму строку подключения лучше вынести в отдельный файл. Из соображений безопасности этот файл неплохо бы исключить из системы контроля версий.

Таким же образом можно вынести и конфиденциальные параметры:

В самом файле необходимо просто указать то содержимое, которое было бы использовано в качестве содержимого тегов.

Скрытие сообщений об ошибке

Видели когда-нибудь «желтый экран смерти» с текстом кода, в котором возникла ошибка? Уверен, что никогда. 🙂 Шутка. Я не случайно поместил эту рекомендацию сразу после «строки подключения». Тем нагляднее будет пример, в котором злоумышленник может искусственным образом создать ошибку и получить полезную для себя информацию. В идеальном случае это может быть строка подключения. Иногда даже мелочь может сократить время поиска уязвимостей сайта! Если у нас классическое приложение ASP.NET, то в web.config режим CustomErrors обязательно оставляем On или хотя бы RemoteOnly:

В ASP.NET Core можно разделить отображение для режима разработки и для продакшена с помощью NuGet-пакета Microsoft.AspNetCore.Diagnostics. Например, для настройки отображения сообщения об ошибке в метод Configure класса StartUp можно добавить:

Еще несколько ошибок конфигурирования web.config

Если вдруг у вас в web.config случайно попали настройки трейсинга или дебага, то на продакшен-сервере обязательно поставьте значения false.

Чтобы взломщик не смог получить доступ к файлу куки (скажем, с помощью XSS), значение следующего параметра должно быть true:

Broken Authentication and Session Management

Для хранения паролей и другой конфиденциальной информации используйте только стойкие хеши с salt. OWASP рекомендует Argon2, PBKDF2, scrypt и bcrypt.

Используй Forms authentication только для интранет-сайтов. Для веба лучше юзать Identity. И кстати, не забудьте в Identity ограничить число попыток ввода пароля, добавив в метод ConfigureServices файла Startup.cs следующий код:

Если вы разрешаете пользователю редактировать свои данные, то проверяйте, действительно ли он меняет именно их (помним о том, что полученным данным не стоит доверять):

Заключение

В этой статье я постарался собрать все, что может пригодиться разработчику на ASP.NET в плане безопасности. «Все» не получилось. 🙂 Из моего поля зрения выпали, например, ошибки конфигурирования IIS… Но надеюсь, что получилось многое: этого материала вам хватит, чтобы усвоить основные правила безопасной разработки и не совершать грубых ошибок.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *