Но и в программах на других языках атаки на форматную строку могут стать источником серьезных неприятностей. Самая очевидная заключается в том, что пользователь не понимает, что происходит, однако при некоторых условиях противник может организовать атаку с кросс–сайтовым сценарием или внедрением SQL–команд, тем самым запортив или модифицировав данные.
Подверженные греху языки
Самыми опасными в этом отношении являются языки С и С++. Успешная атака приводит к исполнению произвольного кода и раскрытию информации. В программах на других языках произвольный код обычно выполнить не удается, но, как отмечено выше, возможны другие виды атак. С программой на Perl ничего не случится, если пользователь подсунет спецификаторы формата, но она может стать уязвимой, когда форматные строки считываются из ненадежного источника данных.
Как происходит грехопадение
Форматирование данных для вывода или хранения – это довольно сложное дело. Поэтому во многих языках программирования есть средства для решения этой задачи. Как правило, формат описывается так называемой форматной строкой. По существу, это мини–программа на очень специализированном языке, предназначенном исключительно для описания формата выходных данных. Однако многие разработчики допускают примитивную ошибку – позволяют задавать форматную строку пользователям, не заслуживающим доверия. В результате противник может подсунуть такую строку, при работе с которой возникнут серьезные проблемы.
В программах на языке C/C++ это особенно рискованно, поскольку обнаружить сомнительные места в форматной строке очень сложно, а кроме того, форматные строки в этих языках могут содержать некоторые опасные спецификаторы (и прежде всего %п), отсутствующие в других языках.
В C/C++ можно объявить функцию с переменным числом аргументов, указав в качестве последнего аргумента многоточие (…). Проблема в том, что при вызове такая функция не знает, сколько аргументов ей передано. К числу наиболее распространенных функций с переменным числом аргументов относятся функции семейства printf: printf, sprintf, snprintf, fprintf, vprintf и т. д. Та же проблема свойственна функциям для работы с широкими символами. Рассмотрим пример:
#include <stdio.h>
int main(int argc, char* argv[])
{
if(argc > 1)
printf(argv[1]);
return 0;
}
Исключительно простая программа. Однако посмотрим, что может произойти. Программист ожидает, что пользователь введет что–то безобидное, например Hello World. В ответ будет напечатано то же самое: Hello World. Но давайте передадим программе в качестве аргумента строку %х %х. Если запустить эту программу в стандартном окне команд (cmd.exe) под Windows ХР, то получим:
E:\projects\19_sins\format_bug>format_bug.exe «%x %x»
12ffc0 4011e5
В другой операционной системе или при использовании другого интерпретатора команд для ввода точно такой строки в качестве аргумента может потребоваться слегка изменить синтаксис, и результат, вероятно, тоже будет отличаться. Для удобства можете поместить аргументы в shell–сценарий или пакетный файл.
Что произошло□ Функции printf передана форматная строка, вместе с которой следовало бы передать еще два аргумента, то есть поместить их в стек перед вызовом функции. Встретив спецификатор %х, printf прочтет четыре байта из стека. Нетрудно представить себе, что при наличии более сложной функции, которая хранит в стеке некоторую секретную информацию, противник смог бы эту информацию распечатать. В данном же случае на выходе мы видим адрес кадра стека (0xl2ffc0), за которым следует адрес, по которому вернет управление функция main(). То и другое – важная информация, которую противник сумел несанционированно получить.
Теперь возникает вопрос: «Как противник может воспользоваться ошибкой при работе с форматной строкой для записи в память□» Существует довольно редко используемый спецификатор %п, который позволяет записать число выведенных к настоящему моменту байтов в переменную, адрес которой передан в качестве соответствующего ему аргумента. Вот предполагаемый способ его применения:
unsigned int bytes;
printf("%s%n\n", argv[1], &bytes);
printf("Длина входных составляла %d символов\n, bytes");
В результате было бы напечатано:
E:\projects\19_sins\format_bug>format_bug2.exe «Some random input»