Статьи

Насколько глубоки ваши защиты?

Итак, вы создали защищенное веб-приложение. Вы включили удобные функции аутентификации и авторизации в ASP.NET. Но достаточно ли вы сделали? Нет, совсем нет. Что произойдет, если вы забудете развернуть web.config, управляющий доступом к административной папке приложения? Или, если злоумышленник получит доступ к блоку, используя вашу базу данных и ссылаясь на уровни бизнес-логики? Или если злоумышленник найдет SQL-инъекцию и начнет запись непосредственно в базу данных? Во многих случаях короткий ответ «плохие вещи», часто ведущий к безработице.

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

Как отмечалось выше, упрочнение поверхности является простым — ASP.NET предоставляет некоторые очень надежные и гибкие функции аутентификации и авторизации. А с версией 2.0 он даже предоставляет некоторые ограниченные сквозные решения в MembershipProviders и RoleProviders . Но это просто защищает поверхность — так же, как яичная скорлупа, ваше приложение твердое снаружи, но очень легко проникающее за пределы оболочки. Вместо того чтобы полагаться на оболочку для обеспечения безопасности, вы должны глубоко внедрить защиту, чтобы смягчить любые последствия взлома оболочки.

Погружение в бизнес-логику

Самым простым способом защиты уровня бизнес-логики является использование функции безопасности .NET Framework, в частности, PrincipalPermissionAttribute . Этот атрибут может быть применен к любому классу, методу, свойству. Если проверка основного разрешения не пройдена, будет выдано исключение SecurityException . Например, допустим, у вас есть объект Customer, к которому не должен получить доступ неаутентифицированный пользователь. Вы можете просто украсить объект с основным разрешением следующим образом:

[PrincipalPermission(SecurityAction.Demand, Authenticated=true)] public class Customer { //code for your customer object. } 

Или, допустим, у вас есть приложение, в котором согласно требованиям, только администраторы могут добавлять пользователей в роли. Функция, которую вы инкапсулировали в метод AddRole (string). Вы можете быть уверены, что ни один пользователь, скажем, не сможет добавить себя в группу администраторов с помощью кода, подобного следующему:

[PrincipalPermission(SecurityAction.Demand, Role="Admins")] public void AddRole(string role) { //code to add the role }
[PrincipalPermission(SecurityAction.Demand, Role="Admins")] public void AddRole(string role) { //code to add the role } 

В дополнение к простому способу защиты всей безопасности интерфейс IPrincipal очень полезен для таких ситуаций, как ведение журнала и аудит действий пользователя. Просто перехватите ссылку на главный объект, где это необходимо, и используйте свойство IPrincipal.Identity.Name, чтобы определить, кто что делает, с чем.

Есть еще одно огромное преимущество использования методов, подобных приведенным выше, для встраивания безопасности в ваши библиотеки бизнес-логики: безопасность становится намного более проверяемой. Тестирование веб-интерфейса обычно выполняется вручную, в то время как для .NET доступно несколько платформ модульного тестирования, таких как NUnit или MbUnit, которые позволяют очень быстро и легко тестировать PrincipalPermissions. Все, что вам нужно сделать, это заменить System.Threading.Thread.CurrentPrincipal на правильно сгенерированный GenericPrincipal и искать SecurityExceptions в зависимости от ситуации. И с NUnit GUI вы даже можете сделать красивый скриншот для босса, чтобы доказать это.

Помимо бизнес-логики и данных

Последняя линия защиты в большинстве веб-приложений заключается в базовом хранилище данных, обычно в какой-то СУБД. В ASP.NET чаще всего этим хранилищем данных является Sql Server. В одном из этих случаев истории Sql Server может быть очень, очень безопасным, но часто шлюзы остаются открытыми из-за ошибки администратора и разработчика. И, особенно в отношении Sql Server 2000, это может привести к очень, очень плохим вещам. Посмотрите на это видео [56k Осторожно], что может сделать для вас комбинация неправильно настроенной установки Sql Server и плохо закодированного веб-сайта.

Поскольку для обеспечения безопасности Sql Server существует несколько основных шагов, которые необходимо выполнить при установке. Главное — убедиться, что база данных работает не в контексте LOCAL SYSTEM, а как ограниченная учетная запись пользователя. Это ограничивает ущерб, который может быть нанесен в худшем случае. Но эта задача часто выходит за рамки ответственности разработчика, и на нее нельзя полностью положиться.

С точки зрения разработки приложений, есть много вещей, которые могут быть предприняты для предотвращения сценариев внедрения Sql в ваших приложениях ASP.NET. Прежде всего, убедитесь, что ограничены учетные записи пользователей вашего веб-приложения. Не существует абсолютно никаких сценариев, когда учетной записи пользователя веб-приложения требуются права db_owner; если требуется полный доступ на чтение / запись для всех таблиц, используйте db_datareader и db_datawriter, хотя можно утверждать, что все операции INSERT / UPDATEs / DELETE должны выполняться с помощью хранимых процедур. Но ни при каких обстоятельствах, которые я видел, не было оправданным предоставление прав веб-приложения для контроля доступа к базе данных. Не берите в голову некоторые сценарии, когда лень разработчика требовала, чтобы веб-приложение запускалось с полными правами системного администратора.

Во-вторых, и, возможно, более важно, всегда следует использовать параметры ADO.NET для передачи данных в базу данных. Например, это плохо:

public void badSql(SqlConnection conn, string input) { string sql = "SELECT * FROM Beers WHERE Type='{0}'"; SqlCommand cmd = conn.CreateCommand(); cmd.CommandText = string.Format(sql, input); //execute command }
public void badSql(SqlConnection conn, string input) { string sql = "SELECT * FROM Beers WHERE Type='{0}'"; SqlCommand cmd = conn.CreateCommand(); cmd.CommandText = string.Format(sql, input); //execute command } 

И вот как должен быть закодирован тот же метод:

public void goodSql(SqlConnection conn, string input) { string sql = "SELECT * FROM Beers WHERE Type=@Type"; SqlParameter param = new SqlParameter("@Type", SqlDbType.VarChar); param.Value = input; DbCommand cmd = conn.CreateCommand(); cmd.CommandText = sql; cmd.Parameters.Add(param); //execute command }
public void goodSql(SqlConnection conn, string input) { string sql = "SELECT * FROM Beers WHERE Type=@Type"; SqlParameter param = new SqlParameter("@Type", SqlDbType.VarChar); param.Value = input; DbCommand cmd = conn.CreateCommand(); cmd.CommandText = sql; cmd.Parameters.Add(param); //execute command } 

В дополнение к защите от внедрения Sql, использование параметризованных операторов упрощает переваривание и кэширование сервером базы данных.

Наконец, есть еще один вариант защиты базы данных: использование отдельных контекстов безопасности для доступа. Например, я разрабатываю много приложений с тремя внешними интерфейсами: общедоступным веб-приложением, которому нужно много ВЫБРАТЬ, и очень редко вводить. Приложение для управления сайтом, которому необходим полный доступ к базе данных. И, наконец, приложение веб-службы, которое должно изрядно выполнять как ВЫБОР, так и ВСТАВКУ, но только в ограниченных количествах. На сервере базы данных я назначаю каждому приложению свою роль и разрешаю каждому из них доступ к хранимым процедурам и таблицам, которые им необходимы. Это означает, что, если я оставлю открытым метод доступа к данным, который не должен быть открыт, он все равно потерпит неудачу, потому что не может получить доступ к хранилищу данных.

Сделал это так далеко? Сначала зайдите и защитите свои веб-приложения от начала до конца. Во-вторых, не забудьте пнуть этот пост . И наконец, обязательно ознакомьтесь с полезными ресурсами безопасности ASP.NET, опубликованными Скоттом Гатри (прокрутите вниз до маркированных списков).