На Java пишется огромное количество серверного кода. Отсюда следует, что написанные на ней веб-приложения должны быть устойчивы к специальным уязвимостям. И эта небольшая статья как раз про один из способов борьбы с ними — SAST. И ещё про то, что такое taint-анализ и как он во всём этом участвует.
В рамках нашего Java-анализатора реализованы механизмы, которые позволят нам создавать диагностики, связанные с поиском использования потенциально опасных данных, попавших в программу извне. Анализаторы PVS-Studio для C++ и C# уже давно так умеют, вот и до Java добрались.
В этой статье (без закапывания в технические подробности) расскажу, что такое поиск использования недостоверных (потенциально опасных) данных, и почему уметь такое — круто. Если же, напротив, вы хотите ознакомиться с этим вопросом более подробно с точки зрения теории — тимлид нашего Java-отдела написал об этом статью. Если вам это интересно, то рекомендую почитать.
Здесь же мы поговорим про потенциальные уязвимости. Особенный уклон сделаем на уязвимости из десятки тех типов, которым чаще всего подвергаются веб-приложения.
Уязвимость — это проблемное место в программе, которое можно использовать для нарушения её работы.
Для их выявления используется большое количество различных тестов. Использовать их для обеспечения безопасности приложения — правильно и нужно, но осуществляются они на этапе тестирования. А если я скажу, что обнаружить уязвимости можно раньше?
Здесь нас дорога приводит к понятию SAST.
SAST (Static Application Security Testing) — это методология тестирования, при которой код программы анализируется на наличие потенциальных уязвимостей.
Под потенциальными уязвимостями подразумеваются "дефекты" в коде. Дефектами они называются потому, что при определённом стечении обстоятельств их могут использовать злоумышленники, чтобы нарушить работу ПО.
В случае, если они обнаружат, как можно использовать потенциальную уязвимость и сделают это, она перейдёт в разряд полноценной уязвимости.
И SAST-специализированное ПО как раз нацелено на выявление потенциальных уязвимостей. Одно из главных преимуществ использования SAST — их обнаружение прямо на этапе разработки приложения.
Почему это хорошо — расскажу далее.
Мы часто посещаем различные тематические конференции. И если тема доклада касается статического анализа, мы говорим об одном очень важном преимуществе его использования — значительном уменьшении стоимости разработки. Вернее, стоимости исправления ошибки. Но, поскольку оба этих понятия тесно взаимосвязаны, я позволил себе такое обобщение.
За счёт чего уменьшается стоимость?
Вопрос о цене исправления ошибки подняли в NIST (National Institute of Standards and Technology). В своём исследовании они рассмотрели, как увеличивается стоимость исправления уязвимости в зависимости от того, на каком этапе цикла разработки ПО она была обнаружена.
Как вы можете видеть, рост экспоненциальный. И это логично.
Исправление уязвимостей после релиза обходится дорого: разработчикам приходится отвлекаться от текущих задач, возвращаться к старому коду, что требует времени и ресурсов.
Но дела будут обстоять ещё хуже, если уязвимость, к примеру, позволяет манипулировать внутренними данными ПО — злоумышленники могут их удалить. Думаю, вы представляете, какие убытки понесёт компания, если они просто возьмут и снесут базу данных. Или обнародуют её. Ну и помимо финансовых потерь, не стоит забывать о репутационных рисках.
На этапе разработки устранение проблем требует меньше усилий и снижает затраты.
Различных уязвимостей сейчас очень много. Какие из них должно находить SAST-решение? "Те, которым чаще всего сейчас подвергаются приложения" — звучит довольно логично. Надеюсь, и вы так думаете.
OWASP (Open Worldwide Application Security Project) — это некоммерческий фонд, занимающийся повышением безопасности ПО. С 2004 года зарегистрирован в США как некоммерческая благотворительная организация.
Фонд публикует большое количество материалов на тему безопасной разработки ПО. Все они лежат в открытом доступе. Также он проводит конференции и встречи, оказывает поддержку Open Source.
OWASP Top Ten — проект OWASP, представляющий из себя отчёт о самых критичных уязвимостях веб-приложений. Он представлен в виде рейтинга, где каждая позиция объединяет группу схожих уязвимостей. Порядок в списке зависит от множества факторов: потенциального ущерба, частоты встречаемости и простоты эксплуатации злоумышленниками.
Особенность OWASP Top Ten в том, что он составляется на основе реально выявленных уязвимостей. Сведения о них собираются раз в несколько лет и поставляются из следующих источников:
Из-за того, что составлен он на основе реальных уязвимостей, тренды в рамках информационной безопасности отражаются в нём как нельзя точно. Для составления актуального сейчас OWASP Top Ten 2021 было рассмотрено свыше 500 тысяч реальных проектов. Отсюда и большое доверие со стороны компаний, заинтересованных в борьбе с уязвимостями в своих веб-приложениях.
По словам из пресс-релиза OWASP Top Ten 2021, за все годы работы топ стал "псевдостандартом", представляющим из себя базовый канон, которому необходимо соответствовать. И с этим тяжело не согласиться. Абсолютное большинство SAST-решений ориентируются именно на него.
У нас есть страница, на которой мы регулярно отмечаем, какие из наших диагностик соответствуют категориям OWASP Top Ten.
Давайте рассмотрим некоторые уязвимости из списка OWASP Top Ten.
Возьмём в пример SQL-инъекцию. Всё-таки в нашем Java-анализаторе диагностика вышла именно на неё :)
SQL-инъекция — это уязвимость, позволяющая злоумышленнику внедрять свой код в запрос, направленный в базу данных. Благодаря этому злоумышленник может манипулировать находящимися в ней данными, а в особых случаях — нарушить их конфиденциальность.
Реализация этой уязвимости возможна, когда пользовательский ввод используется в запросе напрямую и никак перед этим не обрабатывается и не проверяется.
Давайте рассмотрим на примере. У нас есть сайт со статьями. В нём есть форма поиска, чтобы было удобно искать публикации по названиям. Поиск по названию будет происходить так:
И в случае, если объединение данных извне с запросом произойдёт некорректно, в него может быть внедрён зловредный код.
Если вы хотите посмотреть на пример, при котором возможно осуществление SQL-инъекции, то далее будет код на Java с объяснениями.
Давайте взглянем на следующий код:
@Controller("/demo")
public class DemoController {
@Autowired
private JdbcTemplate jdbcTemplate;
@GetMapping("/demo_get")
public Optional<DemoObject> demoEndpoint(
@RequestParam("param") String param)
{
Optional<DemoObject> demoObj = demoExecute(param);
return demoObj;
}
private Optional<DemoObject> demoExecute(String name) {
String sql = "SELECT * FROM DEMO_TABLE WHERE field = '" + name + "'";
DemoObject result = jdbcTemplate.queryForObject(sql, DemoObject.class);
return Optional.ofNullable(result);
}
}
Пример синтетический, но он отражает вполне реальную проблему: данные извне используются в запросе непроверенными и необработанными. Такие ситуации могут быть и в реальных проектах.
Теперь поподробнее.
Нам извне приходит строка param
. Она передаётся в метод demoExecute
. В нём, в строке sql
, формируется запрос к базе данных. Происходит это путём сложения строки sql
с пришедшим извне параметром. После этого строка sql
передаётся в метод queryForObject
, который его исполняет.
Если данные извне будут поступать такими, какими хочет разработчик, проблем не возникнет. Однако стоит пользователю передать что-то более специфичное, станет немного грустно.
Что это за специфичное такое? Ну, например:
' drop table DEMO_TABLE; --
В таком случае строка sql
будет следующей:
SELECT * FROM DEMO_TABLE WHERE field = '' drop table DEMO_TABLE; --'
И что произойдёт? А произойдёт удаление нашей таблицы DEMO_TABLE
из базы данных.
Чтобы избежать такой серьёзной потери, нам нужно проверить/очистить данные извне перед формированием запроса.
Как это можно сделать? Например, удалить ненужные нам символы из строки, что пришла извне:
name = name.replaceAll("[;'%\"--]", "");
Однако стоит помнить, что защититься от SQL-инъекций полностью таким образом невозможно. Лучше использовать параметризированные запросы:
private Optional<DemoObject> demoExecute(String name) {
String sql = "SELECT * FROM DEMO_TABLE WHERE field = ?";
DemoObject result = jdbcTemplate.queryForObject(
sql,
new Object[]{name},
DemoObject.class
);
return Optional.ofNullable(result);
}
В таком случае все данные, передаваемые в запрос, обрабатываются как отдельные параметры. И что бы не передал пользователь, восприниматься это как SQL-команда не будет.
Объясняя вам этот пример, я использовал общие слова и формулировки. Однако в рамках этой проблематики существует специальная терминология:
param
— потенциально заражённые данные;demoEndpoint
— источник заражённых данных;queryForObject
;К слову, срабатывание анализатора на приведённый участок кода будет выглядеть следующим образом:
V5309 Possible SQL injection. Potentially tainted data in the 'sql' variable is used to create SQL command. DemoController.java 28, 20
Если обобщить суть уязвимости, то выглядеть она будет так: непроверенные данные извне при попадании в определённое место программы способны нарушить ход её выполнения.
Одна ли SQL-инъекция работает по такому принципу? Нет, уязвимостей с подобным паттерном много. Среди них:
И множество других. Все они входят в OWASP Top Ten.
Нам, как статическому анализатору, необходимо научиться такое ловить. Как это реализовать? Давайте я выделю основные моменты, которые нас интересуют:
И нам нужно проследить, как данные, пришедшие из источников, путешествуют по программе. И в случае, если они попали в стоки несанитизированными, сообщить об этом.
Так в игру и вступил taint-анализ.
У нас есть статья, где более подробно описывается реализация вышеописанных моментов. Если стало интересно, рекомендую ознакомиться.
Процесс, что я описал выше, называется анализом помеченных данных. Также его называют анализом заражённых данных, или, как я говорил выше, taint-анализом. Тут уж на ваш выбор.
Наши старшие братья, C# и C++, уже давно так умеют. Вот и мы решили, что нужно поспевать за ними. Для реализации этого мы, помимо добавления новых механизмов, правили старые. И впоследствии для добавления новых диагностик нам нужно будет ещё пройтись напильником и по нашему data-flow, и по многим другим вещам. Но фундамент заложен, чему мы несказанно рады.
Именно он позволит нам продвинуться в покрытии OWASP уязвимостей, чем мы и будет заниматься в рамках нескольких следующих релизов.
В 2024 году вышел ГОСТ 71207-2024, который описывает, как правильно использовать статический анализатор при разработке безопасного ПО. И в нём также есть информация о том, что используемый статический анализатор должен уметь. К слову, у нас есть статья, где на момент релиза 7.34 описывается, каким из этих требований PVS-Studio удовлетворяет.
В ГОСТовской терминологии taint-анализ называется "анализом помеченных данных". Обратимся к пункту 7.4, который располагается в главе "Требования к методам анализа, реализованным в статическом анализаторе". Приведу его содержание в упрощённом виде:
"Статический анализатор должен реализовывать следующие методы анализа: ...; межпроцедурный и межмодульный контекстно-чувствительный анализ помеченных данных;..."
Терминология для Java разработчика может показаться немного странной, но если перевести, то: межмодульный анализ — технология, выявляющая ошибки, которые возникают при взаимодействии процедур (методов), которые расположены в разных модулях (файлах).
И теперь это требование мы удовлетворяем. Благодаря этому наш анализатор стал намного лучше и интереснее: реализация taint-анализа для Java позволила нам закрыть один очень важный гештальт из ГОСТа.
Реализация taint-анализа в рамках нашего Java анализатора PVS-Studio приблизила нас к званию полноценного SAST-решения. Мы уже приступили к разработке диагностик, направленных на покрытие уязвимостей из классификации OWASP Top Ten (и не только оттуда). И с каждым релизом их будет становиться всё больше.
Также мы внимательно относимся к ГОСТу, и в следующих релизах продолжим реализовывать механизмы, которые продолжат увеличивать степень соответствия ему.
Ну и, конечно, оставляю ссылку, по которой вы можете попробовать анализатор PVS-Studio.
Пришло время прощаться. До скорых встреч!
0