Если вы когда-либо программировали на языке программирования 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-доступа.