Книга «Безопасно by design»

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

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

У читателя должен быть опыт проектирования приложений на Java, C#, .NET или другом подобном языке.

В этой книге:

  • Концепции безоговорочно безопасного проектирования.
  • Выявление скрытых проблем с безопасностью.
  • Безопасные конструкции в коде.
  • Оценка безопасности путем обнаружения распространенных изъянов в проектировании.
  • Обеспечение безопасности унаследованных и микросервисных архитектур.
  • Концепции программирования, способствующие безопасности

    В этой главе

  • Как неизменяемость решает проблемы с безопасностью.
  • Как контракты с быстрым прекращением работы делают архитектуру безопасной.
  • Виды проверок корректности и порядок их проведения.
  • Разработчикам постоянно напоминают о приоритетах и крайних сроках. Различные грязные трюки и обход правил иногда становятся частью реальности, с которой приходится мириться. Но можно ли без этого обойтись? На самом деле решения о том, какой синтаксис использовать, с какими алгоритмами работать и как организовывать процесс выполнения, принимаете вы сами. Если вы действительно понимаете, чем одни концепции программирования лучше других, их применение становится второй натурой и занимает не больше времени, чем написание плохого кода. То же самое относится и к безопасности. Злоумышленников не заботят ваши крайние сроки и приоритеты — слабо защищенную систему можно взломать независимо от того, почему и в каких обстоятельствах она создавалась.

    Мы все несем ответственность за проектирование безопасного программного обеспечения. Из данной главы вы узнаете, почему для этого не требуется дополнительное время по сравнению с разработкой слабо защищенного уязвимого ПО. В связи с этим мы разделили материал на три части, где обсуждаются разные стратегии решения проблем с безопасностью, которые вам могут встретиться в повседневной работе (табл. 4.1).

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

    4.1. Неизменяемость

    Проектируя объект, вы должны определиться с тем, каким он должен быть: изменяемым или неизменяемым. В первом случае его состояние может меняться, а во втором — нет. Это может показаться несущественным, но с точки зрения безопасности этот аспект очень важен. Неизменяемые объекты можно безопасно разделять между потоками выполнения, с их помощью данные можно сделать высокодоступными, что очень значимо для защиты системы от DoS-атак (denial of service — «отказ в обслуживании»). А вот изменяемые объекты рассчитаны на обновление, что может привести к внесению несанкционированных изменений. Поддержка изменяемости зачастую привносится в систему из-за того, что ее требуют фреймворки, или потому, что на первый взгляд она делает код проще. Но это опасный подход, за который, возможно, придется дорого заплатить. Чтобы это проиллюстрировать, рассмотрим пример того, как использование изменяемости в архитектуре веб-магазина вызывает проблемы с безопасностью, которые можно легко решить за счет неизменяемости.

    4.1.1. Обыкновенный веб-магазин

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

    В целом система работала нормально — до недавних пор. Во время последней рекламной кампании на сайт магазина заходило много людей. Система плохо справлялась с нагрузкой, и клиенты жаловались на истечение времени ожидания заказа, большие задержки и нелогичные варианты оплаты. Последняя проблема казалась несущественной, но затем финансовый отдел сообщил о том, что у многих клиентов с низким кредитным рейтингом появились неоплаченные счета-фактуры. Началось полномасштабное расследование — безопасность системы под угрозой! Главным подозреваемым, естественно, был код для вычисления кредитного рейтинга, но, к всеобщему удивлению, проблема оказалась куда более серьезной — внутреннее устройство объекта Customer.

    Внутреннее устройство объекта Customer

    Объект Customer, показанный в листинге 4.1, имеет две интересные особенности. Первая состоит в том, что все поля инициализируются с помощью методов-сеттеров. Из этого следует, что после создания объекта его внутреннее состояние можно изменять. Это может стать источником проблем, так как мы не можем гарантировать, что объект инициализирован правильно. Еще одно наблюдение заключается в том, что каждый метод помечен ключевым словом synchronized, которое должно предотвращать конкурентное изменение полей, что, в свою очередь, может привести к конфликту потоков (это когда потоки вынуждены останавливаться и ждать, пока какой-то другой поток не снимет одну или несколько блокировок).

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

    Классификация проблем как нарушение целостности или доступности

    Под целостностью данных подразумевается их согласованность на протяжении всего жизненного цикла, доступность данных — это гарантия того, что их можно получить с соблюдением ожидаемого уровня производительности в системе. Обе концепции являются ключом к пониманию причины проблем, возникших в веб-магазине. Например, невозможность извлечь данные — это проблема с доступностью, которая часто сводится к тому, что какой-то код мешает параллельному или конкурентному доступу. Аналогично анализ проблем целостности следует начинать с кода, позволяющего вносить изменения. В табл. 4.2 показаны проблемы веб-магазина, классифицированные как нарушение доступности и целостности.

    Эти категории дают общее представление о том, какие участки класса Customer заслуживают особого внимания. Начнем с того, как неявное блокирование может ухудшить доступность.

    Неявное блокирование ухудшает доступность

    Вопрос о том, нужно ли запрещать конкурентный и параллельный доступ, зачастую становится компромиссом между производительностью и согласованностью. Если состояние всегда должно оставаться согласованным, а обновления чередуются с операциями чтения, имеет смысл прибегнуть к механизмам блокирования. Но если данные преимущественно читают, блокирование может привести к излишним конфликтам между потоками выполнения. В конфликтах, вызванных конкурентным доступом, как правило, легче разобраться, чем в тех, причина которых — параллельный доступ. Возьмем, к примеру, метод synchronized из листинга 4.1: в любой момент его может выполнять только один поток, так как для доступа к нему необходимо получить блокировку, встроенную в его объект. Все остальные потоки, пытающиеся конкурентно обратиться к этому методу, должны ждать, пока эта блокировка не будет снята, что может привести к конфликтам.

    Использование ключевого слова synchronized на уровне метода также может вызвать конфликты между потоками при параллельном обращении к двум и более методам. Оказывается, чтобы получить доступ ко всем методам, объекты, помеченные как synchronized, должны получить одну и ту же встроенную блокировку. Это означает, что потоки, вызывающие эти методы параллельно, неявно блокируют друг друга и подобные конфликты иногда сложно обнаружить.

    Если вернуться к нашему веб-магазину и проанализировать соотношение между операциями чтения и записи, окажется, что чтение данных о клиенте происходит намного чаще, чем их обновление. Дело в том, что изменением данных в основном занимается алгоритм вычисления кредитного рейтинга, а операции чтения выполняются в рамках многочисленных клиентских запросов, в том числе со стороны системы создания отчетов, принадлежащей отделу финансов. Это указывает на то, что параллельное и конкурентное чтение безопасны. Так почему бы и вовсе не избавиться от механизма блокирования (synchronized)?

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

    С полным содержанием статьи можно ознакомиться на сайте “Хабрахабр”:

    https://habr.com/ru/company/piter/blog/566730/

    Источник



    Leave A Reply

    Your email address will not be published.