Предисловие. Некогда в июне 2001 года в журнале «Системный администратор» (Sys Admin Magazine, June 2001, Volume 10, Number 6) была опубликована статья Томаса Акина «Danger of SUID Shell Scripts», которая не теряет своей актуальности и сегодня. К сожалению, в конце лета 2007 года журнал перестал выходить. По неизвестным мне причинам сайт журнала также прекратил существование — точнее он сейчас переадресовывает посетителей на другой. Было бы здорово, если бы сайт просто «заморозили», сохранив архив всех накопившихся материалов, который бесспорно представил бы собой кладезь полезной практической информации для специалистов сферы ИТ. В Сети можно найти скудные копии статьи, о которой идет речь; у меня же нашелся бумажный вариант оригинала и я хочу представить вольный перевод с небольшим дополнением. Некоторые моменты статьи мне кажутся несколько глупыми (например, использование временных файлов), некоторые — непривычными (оболочки, которые использует автор), но в целом я уверен — в статье есть на что обратить внимание, взять на заметку и не забывать.
Уровень сложности: простой
Некоторые соглашения. SUID-программы, SUID-приложения — исполняемые файлы, имеющие атрибут setuid (в дополнение к атрибуту исполнения).
SUID-сценарии, SUID-скрипты — аналогично, сценарии командного интерпретатора, имеющие атрибут setuid в дополнение к атрибуту выполнения.
Unix-система — Unix или любая Unix-подобная операционная система.
Данная статья предпринимает попытку пройтись по тонкой границе между полным раскрытием и созданием эксплойта. Целью является иллюстрация работы SUID-приложений, чтобы помочь остальным избежать распространенных ошибок при написании своих программ. Примеры, приведенные в статье, достаточно разобраны для того, чтобы помочь понять каждую опасность, но нет гарантии, что все будет работать именно так, как показано, в случае попытки злонамеренного использования.
Обычно, сценарии и программы в Unix выполняются с правами пользователя, который их запустил. Вот почему простые пользователи не могут изменить свои пароли путем прямой правки файла /etc/passwd (Unix-системы не хранят более в данном файле пароли, а только информацию об учетных записях — прим.); у них нет прав на запись в /etc/passwd и ни одна команда, выполненная ими, не сможет этого сделать. Однако, SUID-программы перекрывают нормальные права доступа и всегда выполняются с правами владельца программы. Следовательно, пользователи могут с помощью команды /usr/bin/passwd менять свои пароли. Программа /usr/bin/passwd имеет атрибут SUID и пользователя root как владельца. Она всегда выполняется с правами пользователя root:
Когда начинающие администраторы открывают для себя атрибут SUID они видят его как панацею и незамедлительно начинают его использовать для программ и сценариев, чтобы упростить себе работу. К сожалению, очень часто они поступают неверно.
Работая с администраторами, которые недавно познакомились с атрибутом SUID, часто можно встретить сценарии, подобные этому:
Данный простенький скрипт призван разрешить службе поддержки (группа helpdesk) сбрасывать пароли пользователей, что является довольно частой задачей. Сценарию присвоен атрибут SUID и суперпользователь установлен в качестве владельца. Члены группы helpdesk могут читать и запускать данный сценарий на выполнение. который полон «дыр» безопасности подобно решету. В рамках статьи будут рассмотрены семь из них, а также варианты, как их избежать.
Первоочередной проблемой является использование C-shell. Сценарии этой оболочки уязвимы к манипуляциям с переменными окружения. Для того, чтобы использовать это как преимущество, взломщик может скомпроментировать учетную запись службы поддержки и получить оболочку с правами суперпользователя следующим образом:
Переписав скрипт с применением Korn shell помогает избежать характерной для C-shell проблемы, однако сценарий все еще уязвим к манипуляциям с переменной окружения PATH. Использование относительного пути к программе дает возможность злоумышленнику запустить свое приложение вместо штатного /usr/bin/passwd:
Переменная PATH была изменена и теперь команда change-pass вызовет /tmp/passwd вместо /usr/bin/passwd.
Теперь PATH в безопасности и используются абсолютные пути, но если присмотреться можно заметить, что сценарий может изменить пароль любого пользователя даже root. Мы ведь не хотим, чтобы кто-либо из службы поддержки (или взломщик) с помощью нашего скрипта изменить пароль суперпользователя.
Теперь сценарий завершит своб работу, если кто-то введет root в качестве аргумента. Однако что же произойдет, если не передать аргументов вовсе? Будет вызван passwd, соответственно, без аргументов. В таком случае программа меняет пароль текущего пользователя (инициировавшего запуск), то есть root (не забывайте, что сразу после запуска SUID сделал свое дело и изменил текущего пользователя на root — прим.). В контексте исполняемого файла с атрибутом SUID текущим пользователем всегда будет владелец этого файла. Таким образом, все еще существует возможность сбросить пароль суперпользователя, вызвав change-pass и не передав ни одного аргумента. Отсюда уточнение к третьему уроку — понимайте работу программ в сценарии, особенно то, как они обрабатывают аргументы.
Теперь мы никому не позволим изменить пароль пользователя root, но обратите внимание на использование временного файла (лично я этой необходимости понять не могу — прим.). Сценарий удаляет временный файл, создает его, заполняя именем пользователя, пароль которого следует сбросить, и в конце концов проверяет, не root ли этот пользователь. Что если злоумышленник очень точно отмеряет момент, когда файл будет удален, а новый еще не создан, и создаст пустой файл /tmp/.user? Будет ли он перезаписан? Возможно да, а возможно и нет… Зависит от настроек системы. Если созданный взломщиком /tmp/.user не будет перезаписан, проверки в скрипте будут пройдены и passwd предложит сменить пароль суперпользователя (случай с отсутствием аргументов — прим.). Для облегчения проведения подобной атаки злоумышленник может составить специальную программу для отслеживания активности (появления файла /tmp/.user в данном случае) и подмены необходимого файла.
Примечание. Такие типы атак основаны на временных задержках (далее будет еще один подобный пример).
В текущем варианте не используются временные файлы (сразу бы так — прим.), но по-прежнему атакующий может использовать трюк с точкой с запятой — символом-разделителем. С помощью «;» можно в одну строку записать несколько команд, которые будут выполнены по очереди. Зная это взломщик может написать:
Наш сценарий примет этот ввод и выполнит:
Каждая из этих комманд будет выполнена, предоставляя оболочку с правами суперпользователя. Для предотвращения подобных проблем нужно убедиться, что пользовательских ввод не содержит точек с запятой или любых других мета-символов.
Теперь из ввода будут убраны символы пробела, \, $, /, ;, (, ), |, >, <, & и табуляции.
Другая распространенная уязвимость связана с внутренним разделителем полей (internal field separator, IFS) командной оболочки. IFS определяет символ, разделяющий команды. Обычно, таким является символ пробела, табуляции или новой строки. Наш сценарий вызывает программу используя полный путь /usr/bin/passwd. Замена IFS на «/» командой
К сожалению, мы все еще не в безопасности. Сценариям командой оболочки присущи условия возникновения «гонок», которые мы не можем побороть даже написанием качественных скриптов. Проблема заключается в том, что выполнение сценария происходит в два этапа. Сначала система запускает еще один экземпляр оболочки. Затем новый процесс читает содержимое файла сценария и исполняет его. Подобно ситуации с временными файлами в четвертом уроке атакующий может эксплуатировать временные задержки и использовать момент между созданием новой оболочки и чтением скрипта. Создав символическую ссылку на SUID-сценарий
Даже после всей проделанной работы практически невозможно написать безопасный SUID-сценарий командной оболочки (это невозмножно на большинстве систем). Из-за указанных выше проблем некоторые системы (например, Linux) не поощряют установку атрибута SUID на командные сценарии. Существует три более безопасных способа получить функциональность SUID: программа-обертка на языке C, скрипт на Perl или программа подобная sudo. Начинающим в безопасном программировании следует использовать sudo или Perl-программу. Suidperl имеет втроенные механизмы защиты от ошибок программиста, описанных в статье. Дополнительную информацию о безопасном программировании с использованием атрибута SUID можно найти в книге «Practical UNIX & Internet Security» (O'Reilly & Associates) или статье «Writing Safe Setuid Programs».
Послесловие. Прототип программы-обертки для использования SUID:
Опасность использования SUID в сценариях командной оболочки
Уровень сложности: простой
Некоторые соглашения. SUID-программы, SUID-приложения — исполняемые файлы, имеющие атрибут setuid (в дополнение к атрибуту исполнения).
SUID-сценарии, SUID-скрипты — аналогично, сценарии командного интерпретатора, имеющие атрибут setuid в дополнение к атрибуту выполнения.
Unix-система — Unix или любая Unix-подобная операционная система.
Данная статья предпринимает попытку пройтись по тонкой границе между полным раскрытием и созданием эксплойта. Целью является иллюстрация работы SUID-приложений, чтобы помочь остальным избежать распространенных ошибок при написании своих программ. Примеры, приведенные в статье, достаточно разобраны для того, чтобы помочь понять каждую опасность, но нет гарантии, что все будет работать именно так, как показано, в случае попытки злонамеренного использования.
Обычно, сценарии и программы в Unix выполняются с правами пользователя, который их запустил. Вот почему простые пользователи не могут изменить свои пароли путем прямой правки файла /etc/passwd (Unix-системы не хранят более в данном файле пароли, а только информацию об учетных записях — прим.); у них нет прав на запись в /etc/passwd и ни одна команда, выполненная ими, не сможет этого сделать. Однако, SUID-программы перекрывают нормальные права доступа и всегда выполняются с правами владельца программы. Следовательно, пользователи могут с помощью команды /usr/bin/passwd менять свои пароли. Программа /usr/bin/passwd имеет атрибут SUID и пользователя root как владельца. Она всегда выполняется с правами пользователя root:
% ls -l `which passwd`
-rwsr-xr-x 1 root root 23688 Jan 6 2007 /usr/bin/passwd
Когда начинающие администраторы открывают для себя атрибут SUID они видят его как панацею и незамедлительно начинают его использовать для программ и сценариев, чтобы упростить себе работу. К сожалению, очень часто они поступают неверно.
Работая с администраторами, которые недавно познакомились с атрибутом SUID, часто можно встретить сценарии, подобные этому:
% ls change-pass
-rwsr-x--- 1 root helpdesk
37 Feb 26 16:35 change-pass
% cat change-pass
#!/bin/csh -b
set user = $1
passwd $user
Данный простенький скрипт призван разрешить службе поддержки (группа helpdesk) сбрасывать пароли пользователей, что является довольно частой задачей. Сценарию присвоен атрибут SUID и суперпользователь установлен в качестве владельца. Члены группы helpdesk могут читать и запускать данный сценарий на выполнение. который полон «дыр» безопасности подобно решету. В рамках статьи будут рассмотрены семь из них, а также варианты, как их избежать.
Первоочередной проблемой является использование C-shell. Сценарии этой оболочки уязвимы к манипуляциям с переменными окружения. Для того, чтобы использовать это как преимущество, взломщик может скомпроментировать учетную запись службы поддержки и получить оболочку с правами суперпользователя следующим образом:
% env TERM='`cp /bin/sh /tmp/sh;chown root /tmp/sh;chmod 4755/tmp/sh`' change-pass
Урок первый — никогда не используйте C-shell для SUID-сценариев
% cat change-pass
#!/bin/ksh
user=$1
passwd $user
Переписав скрипт с применением Korn shell помогает избежать характерной для C-shell проблемы, однако сценарий все еще уязвим к манипуляциям с переменной окружения PATH. Использование относительного пути к программе дает возможность злоумышленнику запустить свое приложение вместо штатного /usr/bin/passwd:
% export PATH='/tmp'
% echo "cp /bin/sh /tmp/sh;chown root /tmp/sh;chmod 4755/tmp/sh" > /tmp/passwd
% ./change-pass
Переменная PATH была изменена и теперь команда change-pass вызовет /tmp/passwd вместо /usr/bin/passwd.
Урок второй — необходимо всегда вручную устанавливать переменную окружения PATH и использовать абсолютные пути
% cat change-pass
#!/bin/ksh
PATH='/bin:/usr/bin'
user=$1
/usr/bin/passwd $user
Теперь PATH в безопасности и используются абсолютные пути, но если присмотреться можно заметить, что сценарий может изменить пароль любого пользователя даже root. Мы ведь не хотим, чтобы кто-либо из службы поддержки (или взломщик) с помощью нашего скрипта изменить пароль суперпользователя.
Урок третий — необходимо понимать работу задействованных программ
% cat change-pass
#!/bin/ksh
PATH='/bin:/usr/bin'
user=$1
rm /tmp/.user
echo "$user" > /tmp/.user
isroot='/usr/bin/grep -c root /tmp/.user'
[ "$isroot" -gt 0 ] && echo "You Can't change root's password!" && exit
/usr/bin/passwd $user
Теперь сценарий завершит своб работу, если кто-то введет root в качестве аргумента. Однако что же произойдет, если не передать аргументов вовсе? Будет вызван passwd, соответственно, без аргументов. В таком случае программа меняет пароль текущего пользователя (инициировавшего запуск), то есть root (не забывайте, что сразу после запуска SUID сделал свое дело и изменил текущего пользователя на root — прим.). В контексте исполняемого файла с атрибутом SUID текущим пользователем всегда будет владелец этого файла. Таким образом, все еще существует возможность сбросить пароль суперпользователя, вызвав change-pass и не передав ни одного аргумента. Отсюда уточнение к третьему уроку — понимайте работу программ в сценарии, особенно то, как они обрабатывают аргументы.
% cat change-pass
#!/bin/ksh
PATH='/bin:/usr/bin'
user=$1
[ -z $user ] && echo "Usage: change-pass username" && exit
rm /tmp/.user
echo "$user" > /tmp/.user
isroot='/usr/bin/grep -c root /tmp/.user'
[ "$isroot" -gt 0 ] && echo "You Can't change root's password!" && exit
/usr/bin/passwd $user
Теперь мы никому не позволим изменить пароль пользователя root, но обратите внимание на использование временного файла (лично я этой необходимости понять не могу — прим.). Сценарий удаляет временный файл, создает его, заполняя именем пользователя, пароль которого следует сбросить, и в конце концов проверяет, не root ли этот пользователь. Что если злоумышленник очень точно отмеряет момент, когда файл будет удален, а новый еще не создан, и создаст пустой файл /tmp/.user? Будет ли он перезаписан? Возможно да, а возможно и нет… Зависит от настроек системы. Если созданный взломщиком /tmp/.user не будет перезаписан, проверки в скрипте будут пройдены и passwd предложит сменить пароль суперпользователя (случай с отсутствием аргументов — прим.). Для облегчения проведения подобной атаки злоумышленник может составить специальную программу для отслеживания активности (появления файла /tmp/.user в данном случае) и подмены необходимого файла.
Примечание. Такие типы атак основаны на временных задержках (далее будет еще один подобный пример).
Урок четвертый — не используйте временный файлы или (в случае неизбежной необходимости их применения) не помещайте их в доступные на запись остальным места
% cat change-pass
#!/bin/ksh
PATH='/bin:/usr/bin'
user=$1
[ -z $user ] && echo "Usage: change-pass username" && exit
[ "$user" = root ] && echo "You can't change root's password!" && exit
/usr/bin/passwd $user
В текущем варианте не используются временные файлы (сразу бы так — прим.), но по-прежнему атакующий может использовать трюк с точкой с запятой — символом-разделителем. С помощью «;» можно в одну строку записать несколько команд, которые будут выполнены по очереди. Зная это взломщик может написать:
% change-pass "user;cp /bin/sh /tmp/sh;chown root /tmp/sh;chmod 4755 /tmp/sh"
Наш сценарий примет этот ввод и выполнит:
/usr/bin/passwd user;cp /bin/sh /tmp/sh;chown root /tmp/sh;chmod 4755 /tmp/sh
Каждая из этих комманд будет выполнена, предоставляя оболочку с правами суперпользователя. Для предотвращения подобных проблем нужно убедиться, что пользовательских ввод не содержит точек с запятой или любых других мета-символов.
% cat change-pass
#!/bin/ksh
PATH='/bin:/usr/bin'
user=${1##*[ \\$/;()|\>\<& ]}
[ -z $user ] && echo "Usage: change-pass username" && exit
[ "$user" = root ] && "You can't change root's password!" && exit
/usr/bin/passwd $user
Теперь из ввода будут убраны символы пробела, \, $, /, ;, (, ), |, >, <, & и табуляции.
Урок пятый — не доверяйте и проверяйте весь пользовательский ввод, исключайте мета-символы
Другая распространенная уязвимость связана с внутренним разделителем полей (internal field separator, IFS) командной оболочки. IFS определяет символ, разделяющий команды. Обычно, таким является символ пробела, табуляции или новой строки. Наш сценарий вызывает программу используя полный путь /usr/bin/passwd. Замена IFS на «/» командой
% export IFS='/'
заставит сценарий вызывать не /usr/bin/passwd, а вместо этого выполнить по порядку usr, bin и passwd. Теперь злоумышленник может создать скрипт с названием usr, который создает оболочку с правами root, а наш SUID-сценарий выполнит его.Урок шестой — всегда определяйте IFS вручную
% cat change-pass
#!/bin/ksh
PATH='/bin:/usr/bin'
IFS=' '
user=${1##*[ \\$/;()|\>\<& ]}
[ -z $user ] && echo "Usage: change-pass username" && exit
[ "$user" = root ] && "You can't change root's password!" && exit
/usr/bin/passwd $user
К сожалению, мы все еще не в безопасности. Сценариям командой оболочки присущи условия возникновения «гонок», которые мы не можем побороть даже написанием качественных скриптов. Проблема заключается в том, что выполнение сценария происходит в два этапа. Сначала система запускает еще один экземпляр оболочки. Затем новый процесс читает содержимое файла сценария и исполняет его. Подобно ситуации с временными файлами в четвертом уроке атакующий может эксплуатировать временные задержки и использовать момент между созданием новой оболочки и чтением скрипта. Создав символическую ссылку на SUID-сценарий
% cd /tmp
% ln -s change-pass rootme
вызывая сценарий по ссылке и быстро подменяя содержимое% ./rootme &
% rm rootme && echo "cp /bin/sh /tmp/sh;chown root /tmp/sh;chmod 4755 /tmp/sh" > rootme
возможно выполнить что угодно от имени root. Применяя такую технику шансы на успех крайне малы, но существуют методики и программы, которые повышают вероятность успеха и помогают автоматизировать процесс. Существует два способа защититься от подобного рода атак. Первый — не использовать SUID-сценарии командной оболочки. Вторым обладают некоторые системы (например, Solaris), который заключается в предотвращении возникновения условий «гонок» путем передачи описателя открытого файла сценария командной оболочке, избегая таким образом необходимости в переоткрытии и чтении файла SUID-скрипта.Урок седьмой — не используйте SUID-сценарии
Даже после всей проделанной работы практически невозможно написать безопасный SUID-сценарий командной оболочки (это невозмножно на большинстве систем). Из-за указанных выше проблем некоторые системы (например, Linux) не поощряют установку атрибута SUID на командные сценарии. Существует три более безопасных способа получить функциональность SUID: программа-обертка на языке C, скрипт на Perl или программа подобная sudo. Начинающим в безопасном программировании следует использовать sudo или Perl-программу. Suidperl имеет втроенные механизмы защиты от ошибок программиста, описанных в статье. Дополнительную информацию о безопасном программировании с использованием атрибута SUID можно найти в книге «Practical UNIX & Internet Security» (O'Reilly & Associates) или статье «Writing Safe Setuid Programs».
Послесловие. Прототип программы-обертки для использования SUID:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
// помещаем необходимые проверки
setuid(0); // устанавливаем id нужного пользователя (не обязательно root, как в примере)
//setgid(0); // можно использовать групповую политику (id группы) вместо смены текущего пользователя
system("/path/to/script.sh"); // выполняем нужный сценарий
return 0;
}