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

Что такое уязвимость строки формата?

Эксплойт Format String возникает, когда отправленные данные входной строки оцениваются приложением как команда.

С технической точки зрения, когда строка данных, переданная программе, передается в функцию printf в виде строки формата, это делает программу уязвимой для уязвимости строки формата.

Но в чем проблема?

Прежде чем понять, почему передача строки данных в printf в качестве строки формата делает ее уязвимой, нам нужно понять, как необязательные аргументы работают в языке программирования C.

Давайте посмотрим на сигнатуру функции printf.

int printf(const char *format, …);

Список аргументов printf содержит:
1. Строка формата
2. Ноль или более необязательных аргументов.

‘…’ — это специальный оператор в C, который позволяет передавать в программу любое количество аргументов.

Но как это вообще работает?

Спойлер: указатели

Следующий фрагмент кода показывает реализацию необязательных аргументов:

va_list (строка 6) — это указатель, который обращается к необязательным аргументам.
va_start() (строка 8) — это макрос, который вычисляет начальную позицию указателя va_list на основе последнего аргумента, переданного функции, которая не является необязательным параметром (Narg в приведенном выше примере).
va_arg() (строки 10, 11) — это макрос, используемый для перемещения указателя va_list на основе типа данных.
va_end() (строка 13) вызывается после доступ ко всем аргументам.

Таким образом, все сводится к тому, чтобы иметь указатель, указывающий на начало необязательных аргументов в стеке, и продвигать указатель на основе типа данных этих аргументов.

Примечание. Напоминаем, что аргументы помещаются в стек в обратном порядке.

printf также получает доступ к своему необязательному аргументу точно так же, но в отличие от приведенного выше примера, вместо явного получения количества аргументов, как мы сделали с параметром аргумента Narg, он обращается к спецификаторам формата для перемещения указателя va_list.

Если вы все еще запутались, просто взгляните на пример ниже:

Здесь мы просто печатаем отформатированную строку с помощью функции printf. Теперь взгляните на структуру стека в этом примере.

Как вы можете ясно видеть, для каждого спецификатора формата указатель va_list перемещается вверх по стеку.

К вашему сведению

Спецификатор формата доступен в C

Что делать, если какой-то необязательный параметр отсутствует?

Макрос va_arg() не имеет механизма для проверки того, достиг ли он конца списка необязательных аргументов. Таким образом, он продолжает извлекать данные из стека, перемещая указатель va_list, что приводит к утечке памяти.

Ну вот, теперь вы понимаете эту уязвимость.

Определите, что уязвимо

Начнем с передачи аргументов в printf.

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

Как и в приведенном выше фрагменте, мы передаем входную переменную (str) второму вызову printf, что делает эту часть уязвимой.

Как предотвратить уязвимость строки формата?

  • Укажите строки формата как часть программы, а не как входную переменную.
  • Если возможно, используйте литеральную константу в качестве строк формата в вашей программе.
  • Никогда не печатайте строки в C, просто передавая строку в printf, вместо этого используйте спецификатор "%s”format в строке формата, а затем передайте входную строку в качестве аргумента в printf.

Вывод

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

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

В следующем сообщении в блоге мы увидим, как можно использовать уязвимости строк форматирования для получения root-доступа.