Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Иванов Разрушаюсчие программные воздействия 2011

.pdf
Скачиваний:
154
Добавлен:
16.08.2013
Размер:
2.56 Mб
Скачать

7

8 strcpy(buf,argv[1]);

Потенциальная ошибка кроется в строке 2. Функция strlen() возвращает в качестве результата целое без знака. Если длина аргумента, переданного программе, превышает максимальное положительное значение, представимое в формате «int» (целое со знаком), то значение, присвоенное переменной «size», будет интерпретироваться как отрицательное. В результате проверка, указанная в строке 5, не выявит ошибку, что может привести к переполнению буфера.

Пример использования уязвимости

Существуют два варианта атаки на уязвимую программу. В первом случае целью является аварийное завершение программы, во втором — выполнение внедренного кода.

Атаку, которая приводит к аварийному завершению программы, провести достаточно просто. Для этого необходимо при запуске программы передать ей заведомо изменяющее ход ее выполнения значение аргумента «argv[1]», например, заданное следующим образом:

`perl –e 'print "a"x4294967295'`.

Очевидно, что ожидаемым ответом программы будет аварийное завершение с формулировкой «Segmentation fault».

Если целью является выполнение собственного кода, в качестве «argv[1]» следует передать строку, имеющую вид:

NOP . . . Shell-код.

Количество значений NOP в данной строке должно быть достаточно большим, чтобы не только выполнить переполнение буфера, но и перезаписать адрес возврата. Передача указанной строки на вход уязвимой программе приведет к тому, что будет выполнен Shell-код. В данном случае Shell-код будет почти идентичен коду, разобранному ранее (см. подраздел «Переполнение буфера на стеке»).

261

Пример безопасного кода

Для того чтобы превратить уязвимую программу в безопасную, достаточно внести в текст программы минимальные изменения, которые касаются согласованного использования переменных со знаком и без знака (строка 2).

1#define BUFSIZE 100

2unsigned int size = strlen(argv[1]);

3char buf[BUFSIZE];

4

5if (size > BUFSIZE)

6exit(0);

7

8 strcpy(buf,argv[1]);

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

7.1.5. Уязвимость индексации массива

Суть уязвимости

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

С одной стороны, уязвимость данного класса аналогична уязвимости переполнения буфера, когда запись информации осуществляется в область памяти, находящейся непосредственно за массивом (по сути, буфер можно рассматривать как массив байтов). Но, с другой стороны, ошибку индексации массива можно применить для записи в произвольное место памяти произвольной информации. Подобная возможность может использоваться злоумышленником, для того чтобы передать управление на собственный код.

262

Пример уязвимого кода

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

1#include <stdio.h>

2#include <malloc.h>

3#include <stdlib.h>

5int* IntVector;

6void bar(void)

7{

8printf("Программа атакована!");

9}

10

11void Insertlnt(unsigned long index, unsigned long value )

12{

13printf("Запись в память по адресу %p\n", &(IntVector[index])};

14IntVector[index] = value;

15}

16

17bool InitVector(int size)

18{

19IntVector = (int*)malloc(sizeof(int)*size);

20printf("Адpec переменной IntVector: %p\n", IntVector);

21if(IntVector == NULL)

22return false;

23else

24return true;

25}

26

27int main(int argc, char* argv[])

28{

29unsigned long index, value;

30printf("Адрес функции bar %p\n", bar);

263

31if(!InitVector(Oxffff))

32{

33printf("He могу инициализировать вектор!\п");

34return -1;

3 }

36index = atol(argv[1]);

37value = atol(argv[2]);

38Insertlnt(index, value);

39free(IntVector);

40return 0;

41}

Следует обратить внимание на отсутствие проверки в функции Insertlnt() (строка 14). Программист уверен, что пользователь при запуске программы не введет значение индекса, выходящего за пределы массива размером 64 Кб. Значение 64 Кб появилось в связи с тем, что в строке 31 программного кода для массива жестко определяется размер в 64 Кб.

Пример использования уязвимости

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

Пусть массив начинается по адресу 0x00510048. Значение, которое злоумышленник хочет перезаписать — адрес возврата в стеке, который расположен по адресу 0x0012FF84. Ниже показано, как вычисляется адрес элемента массива, исходя из базового адреса массива, номера элемента и размера элементов массива:

адрес_элемента = базовый_адрес_массива+

+номер_элемента * размер_элемента

Вданном примере нужно получить адрес элемента стека, в котором находится код возврата (значение 0x0012FF84), зная базовый адрес массива (значение 0x00510048) и размер элемента (размер типа «int»). Подставляя эти значения, получаем следующее:

264

0x10012FF84 = 0x00510048 + номер_элемента * 4

Вместо требуемого адреса 0x0012FF84 в формуле используется 0xl0012FF84. В результате вычислений старший разряд будет отброшен из-за переполнения разрядной сетки. Выполнив необходимые вычисления, получим номер элемента (индекс), равный 0x3FF07F0F или 1072725967 в десятичном представлении. Адрес функции «bar» — 0x00401000 или 4198400 в десятичном представлении.

Вызов рассмотренной уязвимой программы, который приведет к запуску произвольного кода (в нашем случае это функция этой же программы – «bar»), имеет вид

./massive_index_attack 1072725967 4198400.

Ниже приведен вывод данной программы:

Адрес функции bar 00401000

Адрес переменной IntVector 00510048

Запись в память по адресу 0012FF84

Программа атакована!

Пример безопасного кода

Для того чтобы исключить возможность атаки на систему через уязвимость индексации массива, необходимо при написании программ контролировать правильность задания индексов.

1#include <stdio.h>

2#include <malloc.h>

3#include <stdlib.h>

5int* IntVector;

6void bar(void)

7{

8printf("Программа атакована!");

9}

10

11 void Insertlnt(unsigned long index, unsigned long value )

265

12{

13printf("Запись в память по адресу %p\n", &(IntVector[index])};

14IntVector[index] = value;

15}

16

17bool InitVector(int size)

18{

19IntVector = (int*)malloc(sizeof(int)*size);

20printf("Адpec переменной IntVector: %p\n", IntVector);

21if(IntVector == NULL)

22return false;

23else

24return true;

25}

26

27int main(int argc, char* argv[])

28{

29unsigned long index, value;

30printf("Адрес функции bar %p\n", bar);

31if(!InitVector(1000))

32{

33printf("He могу инициализировать вектор!\п");

34return -1;

35}

36if (argv[1]>1000)

37return 1;

38index = atoi(argv[1]);

39value = atoi(argv[2]);

40Insertlnt(index, value);

41Free(InitVector);

42return 0;

43}

266

7.1.6. Состязания

Состязания (англ. race condition) – ошибка программирования многозадачной системы, при которой работа системы зависит от того, в каком порядке поступают на обработку различные процессы, параллельно выполняющиеся в системе.

Состязание возникает, когда различные процессы исполняются в системе одновременно в режиме разделения времени процессора и используют один и тот же ресурс (например, файл или устройство). При этом каждый процесс «полагает», что имеет монопольный доступ к ресурсу. Данная ситуация приводит к появлению уязвимости.

Казалось бы, операционные системы семейства UNIX защищены от подобных ошибок: каждый процесс, существующий в системе, исполняется от имени и с полномочиями пользователя, инициировавшего данный процесс. При этом неважно, какому пользователю принадлежит исполняемый файл; важно, какой пользователь инициирует процесс, т.е. запускает файл на исполнение. И если у обычного пользователя недостаточно прав доступа к какому-либо ресурсу, он не сможет ничего сделать.

Исключение из этого правила составляет суперпользователь – администратор системы (root), обладающий неограниченными полномочиями. Процессы, инициированные администратором системы, обладают полномочиями «root». Соответственно, атаки на подобные процессы могут привести к серьезным проблемам.

Некоторые процессы могут приобрести полномочия «root» и в том случае, если они были инициированы обычными, непривилегированными пользователями. Классическим примером является команда UNIX «passwd» — изменить пароль пользователя. Обычный пользователь не имеет права модификации системного файла паролей. Однако, выполняя команду «passwd», обычный, непривилегированный пользователь инициирует процесс, обладающий полномочиями «root», который успешно модифицирует файл паролей. Для этого исполняемый файл должен принадлежать администратору системы (root) и иметь установленный SU-

267

ID бит (напомним, что установленный SUID бит отображается символом s вместо x — исполняемый файл). И если необходимо разработать некоторую инсталляционную программу, которая должна иметь доступ к системным файлам и, следовательно, исполняться с полномочиями «root», необходимо гарантировать, что в подобной программе будут отсутствовать какие-либо уязвимости, допускающие атаки на нее (Cowan C., Beattie S., Wright C., Kroah-Hartman G. Kernel Protection From Temporary File Race Vulnerabilities. URL: http://www.usenix.org/events/sec01/ cowanbeattie.html]).

Суть уязвимости

Основная причина, создающая предпосылки для появления состязаний, заключается в следующем. Некоторый процесс, обладающий соответствующими полномочиями, желает получить доступ к некоторому ресурсу. Перед непосредственным получением доступа процесс выполняет необходимые проверки, например, проверяет условия, необходимые для успешного занятия ресурса. Условие состязания возникает, когда другой процесс пытается использовать ресурс в промежуток времени между проверкой и использованием ресурса первым процессом. Потенциальная угроза безопасности таится именно в наличии некоторого (пусть даже очень малого) промежутка времени между проверкой и использованием ресурса. Причем этот промежуток может быть обусловлен разными причинами.

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

268

ления своей работы, процесс, естественно, не выполняет повторную проверку параметров файла, считая их не изменившимися (или уже истинными), а продолжает свою работу.

Еще одним условием для возникновения состязания является открытие временного файла, если оно не выполнено должным образом.

Обычно создание временного файла происходит в каталоге /tmp, хотя, в принципе, временный файл может быть создан где угодно. Системный каталог /tmp часто выбирается создателями программ по той причине, что этот каталог имеет установленный бит Sticky-Bit (отображается символом t вместо x). Только владелец каталога (root) и владелец файла, находящегося в этом каталоге, могут удалить этот файл. Каталог имеет полные права для записи, любой пользователь может разместить в нем свои файлы, будучи уверенным, что они защищены — как минимум, до следующей чистки.

Программы, модифицирующие некоторые файлы, часто в процессе своей работы создают временные файлы. Например, такие программы временно сохраняют копию исходного файла во временном файле и модифицируют этот временный файл. Далее, в случае успешной модификации, с помощью системного вызова unlink() исходный файл удаляется, и с помощью системного вызова rename() модифицированный временный файл переносится на место исходного. Если такому процессу послать сигнал о немедленном завершении работы (например, SIGTERM), временные файлы могут быть сохранены до следующего запуска программы. И если перед повторным запуском программы созданный временный файл заменить новым файлом, имеющим то же имя и определенным как ссылка на другой, возможно системный, файл, тогда появляется возможность корректировки системного файла. Опять же следует напомнить, что такие угрозы имеют смысл для программных файлов, у которых установлен SUID бит.

269

Пример уязвимого кода

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

1#include <stdio.h>

2#include <stdlib.h>

3#include <unistd.h>

4#include <sys/stat.h>

5#include <sys/types.h>

8int main (int argc, char * argv [])

9{

10struct stat st;

11FILE * fp;

12setuid(0);

13if (argc != 3) {

14fprintf (stderr, "Использование: %s запись\n",

argv [0]);

15return 1;

16}

17if (stat (argv [1], & st) < 0) {

18fprintf (stderr, "не найден %s\n", argv [1]);

19return 1;

20}

21if (st . st_uid != getuid ()) {

22fprintf (stderr, "доступ запрещен :) %s \n",

argv [1]);

23return 1;

24}

25if (! S_ISREG (st . st_mode)) {

26fprintf (stderr, "%s не является поддерживаемым фай-

лом\n", argv[1]);

27return 1;

28}

29

270

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]