Думаю, для многих не секрет, что уязвимости в проекте могут оказать на него крайне негативное влияние. Существует ряд способов по борьбе с уязвимостями, начиная с ручного поиска и заканчивая использованием специализированных инструментов. Об одном из таких инструментов пойдёт речь в статье.
Сегодня мы поговорим о механизме пользовательских аннотаций в статическом анализаторе PVS-Studio и о том, как он может помочь в борьбе с уязвимостями. Стоит отметить, что идея добавить пользовательскую разметку в анализатор не нова. Её уже успешно внедрили в C++ часть PVS-Studio. О предпосылках появления C++ аннотаций, а также о их возможностях можно узнать в отдельной статье.
Команда разработки C# анализатора решила не отставать от своих коллег и принялась реализовывать пользовательские аннотации для своей части анализатора.
Уже неоднократно я употребил выражение "пользовательские аннотации". Если с пониманием первой части этого выражения не должно возникать проблем, то на второй следует заострить внимание. Под "аннотациями" в данном контексте подразумевается некоторая разметка, позволяющая анализатору получить дополнительную информацию об исходном коде. Например, разметив метод, мы можем подсказать анализатору, что этот метод всегда возвращает непроверенные внешние данные. Если подобные данные не обработать соответствующим образом, то при попадании в определённые места программы, они могут привести к появлению потенциальной уязвимости.
Что такое потенциальная уязвимость? Если вкратце, то это дефект, содержащийся в коде программы, который при некотором стечении обстоятельств может эксплуатировать злоумышленник. Более развёрнуто о потенциальных уязвимостях можно почитать здесь.
Как же защитить свой код от появления в нём потенциальных уязвимостей? Одним из способов, помогающих в решении этой проблемы, может стать технология taint-анализа. Она позволяет отслеживать распространение потенциально небезопасных данных по программе.
В PVS-Studio taint-анализ реализован посредством анализа потока данных (data-flow), разметки источников и приёмников заражённых данных. Во время анализа просчитываются пути распространения заражённых данных. Если такие данные без проверки попадут в т.н. приёмник, то анализатор выдаст предупреждение о возможном дефекте безопасности. Это была демоверсия описания работы taint-анализа в PVS-Studio, но есть и полная :)
На данный момент в анализаторе существует 16 taint-диагностик, каждая из которых отвечает за конкретных дефект безопасности:
Рассмотрим пример потенциальной уязвимости, которую находит анализатор:
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)
{
....
}
}
}
Методы этого класса выполняют следующие действия:
Также рассмотрим возможный вариант использования этих методов:
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"
]
}
]
}
]
}
Разберём, зачем нужна каждая из аннотаций:
Таким образом, анализатор понимает, что 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. Как мы уже упоминали выше, множество аннотаций библиотечных методов, конструкторов и свойств доступны сразу после установки. А потому остаётся только проверить свой проект :)