BASH must die
Иван Скляров
Xakep, номер #061, стр. 061-060-2
Сначала в функции main() вызовом signal() отключается реакция на некоторые стандартные сигналы. Далее вызывается функция pam_test(), не принимающая и не передающая никаких аргументов. Собственно, эта функция, отсутствующая в любой нормальной оболочке, и осуществляет аутентификацию пользователей. Я специально вынес сюда код этой функции, т.к. именно он предназначен для инжектирования в любую нормальную оболочку, если нужно защититься от shellcode:
Код для защиты
pam_handle_t* pamh;
struct pam_conv pamc;
pamc.conv = &misc_conv;
pamc.appdata_ptr = NULL;
pam_start("passwd", getenv("LOGNAME"), &pamc, &pamh);
for (;;) {
if (pam_authenticate(pamh, 0) == PAM_SUCCESS)
break;
}
pam_end(pamh, 0);
Конечно, аутентификацию можно реализовать разными способами, но, наверное, правильнее всего использовать так называемые «подключаемые модули аутентификации» (PAM - pluggable authentication modules), что и делает pam_test(). В pam_test() используется стандартная диалоговая функция misc_conv(), осуществляющая терминальный ввод-вывод. Вызовом pam_start() инициализируется библиотека PAM (подробнее о PAM можно узнать по адресу www.citforum.ru/operating_systems/articles/pam.shtml). Первый аргумент pam_start() – это имя сервиса. Для наших целей удобно использовать сервис passwd (возможно, это и не самый лучший вариант). Хочу обратить внимание на второй параметр getenv(“LOGNAME”), определяющий имя пользователя из стандартной переменной среды LOGNAME. Если второй параметр сделать нулевым значением, то функция будет запрашивать не только пароль, но и логин пользователя. В нашем случае это станет дырой в безопасности, т.к. для доступа к оболочке (даже с root-правами) достаточно будет ввести имя и пароль совершенно любого пользователя, зарегистрированного в системе. Использование этого параметра открывает к тому же еще одну приятную особенность. Обычно демоны не устанавливают переменную среды LOGNAME, а это значит, что, даже имея пароль и логин рута (!), хакер не сможет проникнуть через шеллкод в систему с нашей защитой. Это будет возможно только в том случае, если хакер установит переменную LOGNAME в нужное значение.
Поэтому xsh запрашивает только пароль пользователя, автоматически определяя при этом логин. Далее в функции pam_test() запускается бесконечный цикл, и он останавливается только в случае правильно набранного пароля. После успешной аутентификации и возвращения из pam_test() открывается доступ к командной строке. Для этого в main() запускается еще один бесконечный цикл, где строкой приглашения является функция printf, выводящая на экран имя пользователя и текущую директорию (функция get_current_dir_name()). Прием и редактирование строки ввода осуществляется библиотекой readline (напомню, что bash тоже работает с этой библиотекой). Если пользователь вводит exit, то происходит выход из оболочки. Функция parser, с помощью стандартной функции strtok, разбивает введенную пользователем командную строку на лексемы. Далее реализуется возможность смены текущей директории командой cd (замечу, что cd является внутренней командой любой оболочки). Функция fork_cmd() выполняет командную строку. Для этого она с помощью вызова fork() создает новый процесс, после чего дочерний процесс выполняет команду с помощью execvp, а родительский ждет завершения дочернего (с помощью waitpid()). Вот и все. Подобным образом работают практически все известные оболочки.