Думаю, для многих не секрет, что уязвимости в проекте могут оказать на него крайне негативное влияние. Существует ряд способов по борьбе с уязвимостями, начиная с ручного поиска и заканчивая использованием специализированных инструментов. Об одном из таких инструментов пойдёт речь в статье.
Пара вводных слов
Сегодня мы поговорим о механизме пользовательских аннотаций в статическом анализаторе PVS-Studio и о том, как он может помочь в борьбе с уязвимостями. Стоит отметить, что идея добавить пользовательскую разметку в анализатор не нова. Её уже успешно внедрили в C++ часть PVS-Studio. О предпосылках появления C++ аннотаций, а также о их возможностях можно узнать в отдельной статье.
Команда разработки C# анализатора решила не отставать от своих коллег и принялась реализовывать пользовательские аннотации для своей части анализатора.
Уже неоднократно я употребил выражение "пользовательские аннотации". Если с пониманием первой части этого выражения не должно возникать проблем, то на второй следует заострить внимание. Под "аннотациями" в данном контексте подразумевается некоторая разметка, позволяющая анализатору получить дополнительную информацию об исходном коде. Например, разметив метод, мы можем подсказать анализатору, что этот метод всегда возвращает непроверенные внешние данные. Если подобные данные не обработать соответствующим образом, то при попадании в определённые места программы, они могут привести к появлению потенциальной уязвимости.
Что такое потенциальная уязвимость? Если вкратце, то это дефект, содержащийся в коде программы, который при некотором стечении обстоятельств может эксплуатировать злоумышленник. Более развёрнуто о потенциальных уязвимостях можно почитать здесь.
Как же защитить свой код от появления в нём потенциальных уязвимостей? Одним из способов, помогающих в решении этой проблемы, может стать технология taint-анализа. Она позволяет отслеживать распространение потенциально небезопасных данных по программе.
Реализация taint-анализа в PVS-Studio
В PVS-Studio taint-анализ реализован посредством анализа потока данных (data-flow), разметки источников и приёмников заражённых данных. Во время анализа просчитываются пути распространения заражённых данных. Если такие данные без проверки попадут в т.н. приёмник, то анализатор выдаст предупреждение о возможном дефекте безопасности. Это была демоверсия описания работы taint-анализа в PVS-Studio, но есть и полная :)
На данный момент в анализаторе существует 16 taint-диагностик, каждая из которых отвечает за конкретных дефект безопасности:
V5608 — SQL injection;
V5609 — Path traversal vulnerability;
V5610 — XSS vulnerability;
V5611 — Insecure deserialization vulnerability;
V5614 — XXE vulnerability;
V5615 — XEE vulnerability;
V5616 — Command injection;
V5618 — Server-side request forgery;
V5619 — Log injection;
V5620 — LDAP injection;
V5622 — XPath injection;
V5623 — Open redirect vulnerability;
V5624 — Configuration vulnerability;
V5626 — ReDoS vulnerability;
V5627 — NoSQL injection;
V5628 — Zip Slip vulnerability.
Рассмотрим пример потенциальной уязвимости, которую находит анализатор:
void ProcessRequest(HttpRequest request)
{
string name = request.Form["name"];
string sql = $"SELECT * FROM Users WHERE name='{name}'";
ExecuteReaderCommand(sql);
....
}
void ExecuteReaderCommand(string sql)
{
using (var command = new SqlCommand(sql, _connection))
{
using (var reader = command.ExecuteReader()) { .... }
}
....
}
В переменную name будет записано имя пользователя, полученное из стороннего источника. Впоследствии значение этой переменной выступает частью SQL-запроса.
Данный код уязвим к SQL-инъекциям, так как в нём непроверенные внешние данные используются для формирования запроса к базе данных.
К примеру, в параметре name может быть записано следующее значение:
'; DELETE FROM Users WHERE name != '
При подстановке этой строки в шаблон запроса получится следующая SQL-команда:
SELECT * FROM Users WHERE name='';
DELETE FROM Users WHERE name != ''
Выполнение такого запроса приведёт к удалению всех записей из таблицы Users (при условии, что для каждого пользователя задано значение столбца name).
Подобная потенциальная уязвимость будет найдена с помощью PVS-Studio.
Пользовательские аннотации
Примечание. На момент написания статьи в C# части анализатора реализованы пользовательские аннотации только для taint-анализа. Подобный способ разметки требуется для ГОСТ Р 71207–2024. Теперь PVS-Studio предоставляет пользователям возможность размечать источники (процедуры-источники) и приёмники (процедуры-стоки) чувствительных данных. Стоит отметить, что в будущем мы планируем расширить возможности пользовательских C# аннотаций (аннотации не только для taint-анализа).
Ранее я уже писал о том, что понимается под "аннотацией" в текущем контексте, но на всякий случай повторюсь. Под аннотацией подразумевается некоторая разметка, позволяющая анализатору получить дополнительную информацию об исходном коде.
В PVS-Studio начиная с версии 7.33 появился механизм, который позволит пользователям размечать источники, передатчики и приёмники небезопасных данных для C# проектов. Таким образом, анализатор сможет точнее искать потенциальные уязвимости для конкретного проекта.
Стоит уточнить, что в анализаторе уже есть разметка для большинства популярных библиотечных методов/конструкторов/свойств. Например, анализатор уже "из коробки" знает, что при передаче результата выполнения метода System.Console.ReadLine в конструктор System.Data.SqlClient.SqlCommand возможно возникновение SQL injection.
Если вкратце, то работает это за счёт того, что в анализаторе есть аннотации, которые говорят, что System.Console.ReadLine — источник taint-данных, а System.Data.SqlClient.SqlCommand — приёмник. Если в него попадают taint-данные, может возникнуть уязвимость SQL injection.
Вернёмся непосредственно к пользовательским аннотациям. Для большей наглядности давайте рассмотрим пример их использования.
Допустим, у нас в проекте имеется следующий класс:
namespace MyNamespace
{
public class MyClass
{
public string GetUserInput()
{
....
}
public string ModifyCommand(string command, string commandAddition)
{
....
}
public void ExecuteCommand(string command)
{
....
}
}
}
Методы этого класса выполняют следующие действия:
GetUserInput возвращает некоторый пользовательский ввод;
ModifyCommand добавляет к уже существующей SQL-команде строку;
ExecuteCommand выполняет SQL-команду.
Также рассмотрим возможный вариант использования этих методов:
public static void ProcessUserInput(MyClass test)
{
string userInput = test.GetUserInput();
string modifiedCommand = test.ModifyCommand("I'm a sql command",
userInput);
test.ExecuteCommand(modifiedCommand);
}
В данном случае, пользовательский ввод без проверки попадает в метод, выполняющий SQL-команду. Как мы уже упоминали выше, это может привести к возникновению SQL Injection.
Чтобы анализатор отслеживал подобные случаи, нужно добавить ряд аннотаций методов. Содержимое файла с аннотациями будет выглядеть следующим образом:
{
"version": 1,
"language": "csharp",
"annotations": [
{
"type": "method",
"namespace_name": "MyNamespace",
"type_name": "MyClass",
"method_name": "GetUserInput",
"returns": {
"attributes": [
"always_taint"
],
"namespace_name": "System",
"type_name": "String"
}
},
{
"type": "method",
"namespace_name": "MyNamespace",
"type_name": "MyClass",
"method_name": "ModifyCommand",
"params": [
{
"namespace_name": "System",
"type_name": "String"
},
{
"namespace_name": "System",
"type_name": "String",
"attributes": [
"transfer_annotation_to_return_value"
]
}
]
},
{
"type": "method",
"namespace_name": "MyNamespace",
"type_name": "MyClass",
"method_name": "ExecuteCommand",
"params": [
{
"namespace_name": "System",
"type_name": "String",
"attributes": [
"sql_injection_target"
]
}
]
}
]
}
Разберём, зачем нужна каждая из аннотаций:
Аннотация метода GetUserInput содержит информацию о том, что этот метод возвращает taint-данные.
Аннотация метода ModifyCommand содержит информацию о том, что при попадании taint-данных во второй аргумент, метод также вернёт taint.
Аннотация метода ExecuteCommand подсказывает анализатору, что при попадании taint-данных в первый аргумент возможно возникновение SQL Injection.
Таким образом, анализатор понимает, что taint-данные из GetUserInput передаются в ModifyCommand. После этого возвращаемое значение ModifyCommand также становится заражённым. Это значение передаётся в метод ExecuteCommand, что в свою очередь может привести к возникновению SQL Injection.
Если добавить аннотации в проект, который содержит использование ProcessUserInput, а потом проанализировать этот проект, получим следующее предупреждение:
V5608 [OWASP-5.3.4, OWASP-5.3.5] Possible SQL injection. Potentially tainted data in the 'modifiedCommand' variable is used to create SQL command.
Стоит отметить, что в файл с аннотациями можно добавить поле $schema. Благодаря этому полю, современные текстовые редакторы и IDE могут проводить валидацию, а также подсказывать возможные значения во время редактирования файла. О том, какие значения принимает поле $schema можно узнать в документации.
Более детально о возможностях пользовательских C# аннотаций можно почитать в документации.
Заключение
Вот мы и разобрались, как аннотации могут помочь в выявлении потенциальных уязвимостей. Если у вас появилось желание разметить свой проект и посмотреть есть ли в нём потенциальные уязвимости, то предлагаю попробовать PVS-Studio.
Если же составлять аннотации нет желания, то всегда можно положиться на разработчиков PVS-Studio. Как мы уже упоминали выше, множество аннотаций библиотечных методов, конструкторов и свойств доступны сразу после установки. А потому остаётся только проверить свой проект :)
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Nikita Panevin. Catch vulnerability on your own: user annotations for C# code.
Комментарии (4)
mvv-rus
25.10.2024 12:32Рацпредложение.
В C# есть возможность помечать методы и др. объекты атрибутами. Вы не думали над возможностью сделать аннотации с помощью атрибутов?
При этом, во-первых, уменьшится объем писанины, так как имя класса, метода и т.д. будет ясно из контекста, а, во-вторых, уменьшится вероятность ошибок при подготовке к повторной проверке: например, при переименовании метода его аннотация а птрибуте точно не потеряется, а в JSON имя метода надо поправлять отдельно. Недостаток, правда тоже есть: в коде появляется в зависимость от сборки с классами метаданных, которые совершенно не нужны в готовом продукте.
NikitaPanevin Автор
25.10.2024 12:32У нас были подобные мысли, но мы столкнулись с рядом трудностей и в итоге остановились на аннотациях в формате JSON. Кроме того, для C++ части PVS-Studio аннотации уже реализованы в формате JSON, и мы решили сохранить некоторое единообразие.
a-tk
Представим себе автора библиотеки, который публикует её в nuget пакета.
Как распространять такие аннотации?
NikitaPanevin Автор
На данный момент функционала для распространения аннотаций в описанном вами сценарии не предусмотрено. К тому же сложно представить, что разработчики библиотеки будут специально адаптировать её под конкретный статический анализатор, используемый её пользователями.
Тем не менее, если пользователь библиотеки применяет PVS-Studio для анализа, он может самостоятельно разметить необходимые методы, конструкторы и свойства библиотеки.