Совсем чуть-чуть о cgi

Введение в cgi

Популярность таких языков программирования, как php, используемых для создания веб-приложений, заставила нас позабыть о том, что для написания веб-софта можно использовать любой язык программирования, способный работать со стандартными устройствами ввода/вывода, будь то C, C++, Pascal, Delphi, Basic, Assembler (если постараться, можно и в машинных кодах написать). В чем же мы выиграем, если приложение будет написано, допустим, на C, а не на том же php? Всем известно, что C – это компилируемый язык, а php – интерпретируемый. Компилируемые языки работают в разы быстрее, мы сможем использовать это преимущество, когда нужно выполнить ресурсоемкие операции непосредственно на web-странице. Подобные приложения принято называть CGI-скриптами.

CGI — стандарт интерфейса, используемого для связи внешней программы с веб-сервером. Программу, которая работает по такому интерфейсу совместно с веб-сервером, принято называть шлюзом, хотя многие предпочитают названия «скрипт» (сценарий) или «CGI-программа».

Листинг: Пример GCI-программы на C:

#include <<>>
#include <<>>
int main()
{
printf("Content-Type: text/html\r\n\r\n"); //HTTP-заголовок
printf("<<>>Hello World<<>>"); // печатаем на экране сообщение Hello World
}

Для того чтобы сервер правильно обрабатывал наши данные, ему нужно послать заголовок с их фактическим указанием. Content-Type: text/html говорит о том, что данные текстовые, формата html; если же мы укажем Content-Type: image/png, то это будет означать рисунок формата png; если не послать серверу заголовок, то он выдаст вам ошибку с номером 500. Собственно, здесь все понятно. Но смысл cgi-скриптов не в статической печати кода, а в динамической; в зависимости от какого-либо условия html-код должен меняться, сам скрипт делать те или иные действия. В большинстве случаев выполнение или невыполнение условия строится на данных, полученных от пользователя.

Листинг: Пример GCI-программы на C, получаем данные от пользователя:

#include <<>>
#include <<>>
int main()
{
char *usedb = getenv("QUERY_STRING");
printf("Content-Type: text/html\r\n\r\n"); //HTTP-заголовок
printf("%s", usedb); //данные, которые ввел пользователь
}

Давайте откомпилируем данные, например, пускай наш cgi-скрипт называется script.c, откомпилируем его (gcc -o script script.c) и поместим в директорию для cgi-скриптов (cgi, cgi-bin) нашего web-сервера. Обратимся к скрипту следующим образом: « сайт сгенерируется web-страница с надписью «hello».

QUERY_STRING – это переменная окружения, помимо нее еще существуют и другие. В этой переменной хранится все, что мы передали серверу после знака «?». Функция getenv() возвращает нам указатель на QUERY_STRING, его мы и сохраняем в переменной типа указатель – usedb. Затем выводим значение по адресу в указателе на экран. Также данные от пользователя можно принять с помощью переменной PATH_INFO, только тогда обращаться к скрипту нужно иначе, допустим, вы используете вышеприведенный скрипт, только берете другую переменную окружения (PATH_INFO), тогда обращаемся так:
« сайт

вывод:

/peremennay1&peremennay2

В принципе, вышеперечисленного достаточно для программирования cgi-приложений, при условии, что вы уже знаете язык, на котором программируете. Кстати говоря, cgi-скрипты можно писать и на языке команд операционной системы.

Уязвимости cgi

Переполнение буфера:

Листинг: GCI-программа на C, переполнение буфера:

#include <<>>
#include <<>>
#include <<>>
int main()
{
char *usedb = getenv("QUERY_STRING");
char dann[10];
printf("Content-Type: text/html\r\n\r\n");
strcpy(dann, usedb);
printf("%s", dann);
}

Обратитесь к скрипту следующим образом сайт и сервер наградит вас ошибкой под номером 500. Но это меньшее, что может случиться; в лучшем (для взломщика) случае он получит доступ к серверу с правами уязвимого скрипта, а там и до root'а не далеко. Чтобы избежать подобных ошибок, используйте безопасный аналог функции strcpy – trlcpy, а также следите за границами массивов.
Уязвимость форматной строки (подробнее можно почитать в моей статье «Недостатки форматной строки»):

Листинг: GCI-программа на C, уязвимость форматной строки:

#include <<>>
#include <<>>
int main()
{
char *usedb = getenv("QUERY_STRING");
printf("Content-Type: text/html\r\n\r\n");
printf(usedb);
}

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

Произвольное чтение файлов:

Листинг: GCI-программа на C, чтение произвольных файлов:

#include <<>>
#include <<>>
#include <<>>
int main()
{ char *OpenFile(char *arg);
char *filename = getenv("QUERY_STRING");
printf("Content-Type: text/html\r\n\r\n");
printf("%s", OpenFile(filename));
}

char *OpenFile(char *filename)
{
int i=0;
char line[256], text[10240];
FILE *f;

f=fopen(filename,"r");

while(!feof(f))
{
fgets(line,256,f);
strcpy(&text[i], line); // Здесь лучше использовать безопасную функцию strlcpy
i=i+strlen(line);
}
fclose(f);
return &text[0];
}

Теперь обратитесь к скрипту примерно так:

сайт

На странице появится содержимое файла /etc/passwd. Перед использованием функций foren, open фильтруйте данные. Если ваш скрипт работает с БД, фильтруйте данные, дабы избежать уязвимости типа sql-injection, xss.

Сегодня мы разобрали типичные ошибки программистов, это относится не только к cgi-скриптам, но и к обычным программам. Большинство ошибок не поддается классификации, поэтому следите за логикой выполнения программы, стоит ей выполниться не так, как задумал разработчик, и вся безопасность сойдет на нет.

Kerny SASecurity gr. q@sa-sec.org


Компьютерная газета. Статья была опубликована в номере 01 за 2010 год в рубрике программирование

©1997-2024 Компьютерная газета