D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Автор petrinh1988
Источник https://xss.is
Мы уже успели поработать с разными видами сканирования, создавали точки инъекции и запускали новые процессы. В этой статье предлагаю поработать немного глобальнее. Итогом этой статьи будет расширение наподобие Hackvertor. Это одно из самых популярных расширений для BurpSuite. Оно позволяет преобразовывать строки в разные форматы при помощи XML-подобных теэгов. Выглядит это следующим образом (на примере все той же лабы):
Что произошло? Нужный кусок текста обернули в тэги <@hex_entities><@/hex_entities>, в итоге на сервер пришел запрос с закодированной инъекцией и атака не была обнаружена.
Это не весь функционал Hackvertor, есть мощный интерфейс для работы с кодированием/декодированием. Есть встроенные в интерфейс элементы, например, на скрине выше вкладка в Repeater и т.д. Но мы еще не добрались до построения интерфейсов, о них будет речь в следующих статьях, поэтому наш Hackvertor будет урезанным.
Вот, что будет уметь наше расширение по итогу:
Python: Скопировать в буфер обмена
В отличии от прошлого логгера, здесь все немного замороченнее. Во-первых, задействованы константы IBurpExtenderCallbacks. Для информативного вывода, чтобы корректно отображать название инструмента пославшего запрос, составлен словарь. Напомню, изначально tool имеет целочисленный тип. Другая особенность в том, что обрезается ответ. Смысла выводить кучу HTML-кода нет, поэтому полученное сообщение обрезается по индексу начала тела ответа.
Для реализации задуманного, нам нужно обработать messageInfo в который попадает объект IHttpResponseRequest, кстати про этот объект…
В отношении запросов и ответов, самым базовым является объект IHttpRequestResponse. Я неоднократно в статьях называл его по разному, чтобы формировать некое понимание. В сущности, это пара запрос(request) и ответ (response). Именно эту пару нам отдает Burp в сканерах, т.к. мы получаем данные об уже выполненном запросе. Получить отдельно запрос или ответ, можно при помощи getRequest() и getResponse() соответственно.
Важно! IHttpRequestResponse отработавшего запроса мы получаем в сканерах! В том же processHttpMessage интерфейса IHttpListener, при messageIsRequest в состоянии True, запрос еще не выполнен и мы можем повлиять на него через setRequest().
Отдельно запрос или ответ, в расширениях Burp, принято называть httpMessage. Получается, что у нас может быть, либо сообщение запроса либо сообщение ответа от сервера. Request и response это httpMessage, по сути своей массив байт, которые отличаются только направлением этого сообщения (или пакета). Соответственно, набор байт (или массив байт) для нас это текст составленный по определенным правилам, для машины нули и единички. Но это уже совсем основы компьютерной грамотности, поэтому их объяснение избегу)))
IHttpRequestResponse это объект, который не только содержит два набора байт, но так же комментарии к ним и цвет выделения. Управляется это методами getComment(), setComment() и getHighlight(), setHighlight(), соответственно. Кстати, сами запросы/ответы тоже можно прямо назначать через setRequest() и getRequest(). На этом моменте можно вернуться к первым статьям и посмотреть, где можно было работу с запросами перестроить.
Последней важной частью IHttpRequestResponse является объект IHttpService, который управляем соответствующими методами: getHttpService() и getHttpService(). По сути своей, объект хранит в себе информацию о хосте: имя или IP-адрес, порт и протокол.
Для удобного взаимодействия с объектами запросов и ответов, предусмотрены специальные классы IRequestInfo и IResponseInfo, соответственно. Никто не мешает написать свои обработчики и работать напрямую с массивами байт, но гораздо удобнее ввести getHeaders() и получить все заголовки удобным списком строк. Чтобы привести байты к этим объектам, в хелперах есть методы analyzeRequest() и analyzeResponse().
С большинством методов не должно возникнуть никаких вопроса, они интуитивно понятны. Единственное, могут возникнуть вопросы с методами связанными с Mime-type ответа. getStatedMimeType() возвращает, какой тип указан в ответе сервера, а getInferredMimeType() возвращает предполагаемый Mime-type на основе содержимого. В данном случае, Burp может помочь увидеть несоответствие между тем, что отдает сервер и тем, как он это представляет.
IParameter и ICookie два типа, которые хранят в себе соответствующие данные. IParameter уже разбирал в предыдущих материалах, ICookie достаточно прост:
На этом закончим с базой. В большинстве случаев, при построении запросов, дело придется иметь с HTTP-сообщением. Методы, которые позволяют удобно строить и модифицировать запросы, находятся в хелперах, которые мы получаем из коллбэков. Но к этому еще вернемся в будущем.
Меня интересует построение масштабируемого решения, чтобы можно было добавлять все новые и новые варианты преобразования строк. Поэтому, оптимально будет каждый вариант преобразования выполнить в виде объекта. Объекта содержащего открывающий тэг, закрывающий тэг, метод преобразования convert(), методы возвращающие имя name() и описание detail(), на случай если потребуется логгирование. Чтобы было по феншую, создам класс, который будет выступать родителем для остальных:
Python: Скопировать в буфер обмена
Раз уже работали с hex entities, создам соответствующий класс преобразования:
Python: Скопировать в буфер обмена
Самое время оживить наше расширение, добавив в него возможность делать первые преобразования:
Python: Скопировать в буфер обмена
Разберу по шагам, что происходит:
Как и писал выше, если запрос еще не выполнен, в processHttpMessage() мы можем повлиять на него, т.к. объект messageInfo передается по ссылке, а не по значению. Соответственно, финальный шаг — это обновление запроса. Самое время протестировать в Repeater
Отлично! Видим, что отправили пиписку и получили пиписку. Как говориться, что посеяли, то и пожали. А значит наше расширение работает исправно. Поэтому, предлагаю убрать под капот составление регулярок и добавить еще метод конвертации для разнообразия. После перейдем к работе с меню BurpSuite.
Python: Скопировать в буфер обмена
Бесхитростное преобразование строки в строку с рандомным регистром. Остается только добавить в список конвертеров и можно использовать.
Обновленный родительский класс выглядит следующим образом:
Python: Скопировать в буфер обмена
Теперь достаточно запросить паттерн для поиска или паттерн для замены.
from burp import IBurpExtender, IHttpListener, IBurpExtenderCallbacks, IIContextMenuFactory
from HexEntity import HexEntity
import re
Python: Скопировать в буфер обмена
Наш класс “MyContextMenu” должен реализовывать всего один метод createMenuItems(IContextMenuInvocation invocation). Как видим, он принимает загадочный аргумент с типом IContextMenuInvocation., который полностью нам объясняет где мы оказались и что вокруг происходит. А вернуть методо должен список объектов javax.swing.JMenuItem. Здесь стоит вспомнить, что расширения это не CPython, а Jython. Это значит, что мы можем использовать не только пакеты пайтен. Вернее их использовать мы можем не только лишь все, мало какие можем использовать. Зато можем спокойно юзать библиотеки Java. Самое время почитать подробнее про этот JMenuItem.
Да, чтиво длинное, но не будем впадать в панику. В конце-то концов, нам нужно совсем немногое оттуда. Надо понять, как добавлять надписи и привязывать события. Первое, что от нас требуется, это импортнуть пакет:
from javax.swing import JMenuItem
Импортнули, создадим наш супер-пункт меню и проверим:
Python: Скопировать в буфер обмена
Батюшки мои! Пункт меню добавился. Не знаю откуда у меня такая радость, когда выполняется обычный код, но всегда кайфую когда в существующем ПО появляется что-то от меня))))
Нужно заставить меню что-то делать. Создадим обработчик события и привяжем к нашему меню:
Python: Скопировать в буфер обмена
Пока просто распечатал информацию о событии при помощи метода toString(). Для понимания процесса, в event передался объект java.util.EventObject. Среди его методов есть toString().
В расширениях убираю и снова ставлю галочку на нашем расширении, чтобы перезагрузить. После кликаю по пункту меню и смотрю вывод:
Код: Скопировать в буфер обмена
Чтоже, достаточно информативно. Мы видим подробнейшую информацию о том где и на чем произошло событие. Но информация касается исключительно самого объекта меню. Нет тех прелестей, которые нам сулил передаваемый в фабрику объект IContextMenuInvocation. Есть несколько вариантов решить эту проблему. Первый - работать с меню внутри класса. Запомнить invocation в self и обращаться к нему. Тогда можно остаться в рамках указанной функции. Выглядеть работа будет следующим образом:
Python: Скопировать в буфер обмена
Перезагружаю расширение и в репитере кликаю по нашему контекстному меню.
Все четко работает. Но есть альтернатива, которая мне кажется более приемлемой — создать отдельный класс.
Итак, в целом, с меню разобрались. Но нам нужно связать его с нашим списком конвертеров. Для этого сделаем отдельный класс, который
Python: Скопировать в буфер обмена
Вынес меню в отдельный файл. Реализовал события, как это принято в Java. Теперь каждое событие это объект, в котором храниться вся нужная информация.
Чтобы все было красиво, нужно правильно организовать вставку данных. А именно, если пользователь просто кликнул в запросе по нашему пункту меню, мы просто вставляем тэги. Но, если пользователь выделил какой-то текст, наша задача обернуть выделенный текст в тэги. Ровно так же, как делает расширение Hackvertor. Для этого потребуется несколько методов из объекта invocation. А именно, getSelectedMessages(), который вернет IHttpRequestResponse[]. Так мы получим подробную информацию об запросе/ответе на котором кликнул пользователь. Второй метод это getSelectionBounds(), который покажет точные индексы выделения. Все, что нам останется сделать, это выполнить замену в нужных границах в нужном запросе/ответе. Звучит может и муторно, но реализуется без особых сложностей:
Python: Скопировать в буфер обмена
Получаем координаты начала и конца выделения, через getSelectionBounds(). На выходе массив из двух чисел, а раз знаем заране что получаем, можно воспользоваться деструктуризацией. Далее получаем выделенные сообщения и проходимся по ним циклом. Так как для тестов я использовал репитер, там будет всего одно значение, но не важно, пусть будет цикл.
Я знаю, что работать буду исключительно с Request, поэтому игнорирую респонс. Кстати, так как я забыл в конструкторе класса сохранить хелперы, использую для доступа к ним getHelpers().
Следующая строка просто пересобирает заново строковый реквест по частям. Если выделен какой-то текст, позиция start и end будут отличаться. Если нет выделения, обе позиции будут указывать на позицию курсора в запросе. В любом случае произойдет вставка. Ну и завершаю процесс через setRequest(0. Важно понимать, что в данном контексте, сообщение будет указывать на открытый в редакторе запрос.
Например, вы написали расширение, которое преобразует параметр запроса search в base64. Пока ничего не происходит. Внезапные непонятные ситуации могут начаться, если вы пустите трафик браузера через Burp. IHttpListener будет делать то, что ему скажут… сказали конвертить search и base64, значит будет. На такой случай, можно сделать простой фильтр:
Python: Скопировать в буфер обмена
Python: Скопировать в буфер обмена
Файл регистрирует коллбэки
Спойлер: http_listener.py
Python: Скопировать в буфер обмена
Файл отвечающий за IHttpListener
Спойлер: converter_list.py
Python: Скопировать в буфер обмена
Здесь хранится список конвертеров. При добавлении нового класса конвертера, сюда нужно добавить тоже. При этом, указав тэг.
Спойлер: HexEntity.py
Python: Скопировать в буфер обмена
Спойлер: RandomCase.py
Python: Скопировать в буфер обмена
Два конвертора
Спойлер: context_menu.py
Python: Скопировать в буфер обмена
Файл отвечающий за контекстное меню
Серия ширится и растет, это уже четвертая статья из серии (тыц, тыц и тыц). Если не читали, советую. Жду ваши вопросы, пожелания и замечания, а пока пойду писать про свинг… Нет, не про тот свинг, что в интересных роликах, а про Swing в Java . То, на базе чего строятся пользовательские интерфейсы. Сегодня мы коснулись его компонента JMenuItem, но пора переходить к полноценным решениям.
Надеюсь вам понравилось!
Источник https://xss.is
Мы уже успели поработать с разными видами сканирования, создавали точки инъекции и запускали новые процессы. В этой статье предлагаю поработать немного глобальнее. Итогом этой статьи будет расширение наподобие Hackvertor. Это одно из самых популярных расширений для BurpSuite. Оно позволяет преобразовывать строки в разные форматы при помощи XML-подобных теэгов. Выглядит это следующим образом (на примере все той же лабы):
Что произошло? Нужный кусок текста обернули в тэги <@hex_entities><@/hex_entities>, в итоге на сервер пришел запрос с закодированной инъекцией и атака не была обнаружена.
Это не весь функционал Hackvertor, есть мощный интерфейс для работы с кодированием/декодированием. Есть встроенные в интерфейс элементы, например, на скрине выше вкладка в Repeater и т.д. Но мы еще не добрались до построения интерфейсов, о них будет речь в следующих статьях, поэтому наш Hackvertor будет урезанным.
Вот, что будет уметь наше расширение по итогу:
- Перехватывать все исходящие запросы Burp, чтобы найти свои тэги и произвести замены
- Преобразовывать строки в несколько разных форматов
- Поддержка контекстного меню BurpSuite, для удобной работы
Перехват запросов
Чтобы ловить все запросы Burp, потребуется уже знакомый интерфейс IHttpListener. В первой статье использовал его для создания простого логгера. IHttpListener, который предполагает всего один метод processHttpMessage(). Простое расширение, которое логгирует все запросы, будет выглядеть следующим образом:Python: Скопировать в буфер обмена
Код:
from burp import IBurpExtender, IHttpListener, IBurpExtenderCallbacks
from datetime import datetime
class BurpExtender(IBurpExtender):
def registerExtenderCallbacks(self, cb):
cb.registerHttpListener(HttpListener(cb))
print('Logger loaded')
class HttpListener(IHttpListener):
tools = {
IBurpExtenderCallbacks.TOOL_COMPARER: 'COMPARER',
IBurpExtenderCallbacks.TOOL_DECODER: 'DECODER',
IBurpExtenderCallbacks.TOOL_EXTENDER: 'EXTENDER',
IBurpExtenderCallbacks.TOOL_INTRUDER: 'INTRUDER',
IBurpExtenderCallbacks.TOOL_PROXY: 'PROXY',
IBurpExtenderCallbacks.TOOL_REPEATER: 'REPEATER',
IBurpExtenderCallbacks.TOOL_SCANNER: 'SCANNER',
IBurpExtenderCallbacks.TOOL_SEQUENCER: 'SEQUENCER',
IBurpExtenderCallbacks.TOOL_SPIDER: 'SPIDER',
IBurpExtenderCallbacks.TOOL_SUITE: 'SUITE',
IBurpExtenderCallbacks.TOOL_TARGET: 'TARGET'
}
def __init__(self, cb):
self._cb = cb
self._helpers = cb.getHelpers()
def processHttpMessage(self, tool, isRequest, messageInfo):
messageTime = datetime.now()
direction = ''
if isRequest:
direction = 'Request'
httpMessage = self._helpers.bytesToString(messageInfo.getRequest())
else:
direction = ' Response'
response = messageInfo.getResponse()
responseInfo = self._helpers.analyzeResponse(response)
httpMessage = self._helpers.bytesToString(response)[:responseInfo.getBodyOffset()]
print('\n[' + messageTime.ctime() + '] ' + self.tools[tool] + direction)
print(httpMessage)
Как говориться - RTFM! Правда я бы передал на RTFMC - Read The Fucking Manual Carefully или Читай Чертову Документацию Внимательно. Во время написания статьи внимательно пересматривал документацию и нашел функцию getToolName(int toolFlag). Не нужно было городить конструкцию tools в примере выше… Но оставлю это здесь, чтобы был урок не только для меня. Продолжим.
Нажмите, чтобы раскрыть...
В отличии от прошлого логгера, здесь все немного замороченнее. Во-первых, задействованы константы IBurpExtenderCallbacks. Для информативного вывода, чтобы корректно отображать название инструмента пославшего запрос, составлен словарь. Напомню, изначально tool имеет целочисленный тип. Другая особенность в том, что обрезается ответ. Смысла выводить кучу HTML-кода нет, поэтому полученное сообщение обрезается по индексу начала тела ответа.
Для реализации задуманного, нам нужно обработать messageInfo в который попадает объект IHttpResponseRequest, кстати про этот объект…
Фундаментальные вещи
Думаю есть смысл остановиться и поговорить о базе. На самом деле, уже давно стоило обсудить, какие базовые объекты в Burp используются и как их принято называть в справке и расширениях. Но лучше поздно, чем никогда. Возможно, это уберет некую путаницу. Вещи могут показаться излишне очевидными, но сам часто плевался из-за того, что автор не раскрыл суть некоторых понятий, считая их очевидными.В отношении запросов и ответов, самым базовым является объект IHttpRequestResponse. Я неоднократно в статьях называл его по разному, чтобы формировать некое понимание. В сущности, это пара запрос(request) и ответ (response). Именно эту пару нам отдает Burp в сканерах, т.к. мы получаем данные об уже выполненном запросе. Получить отдельно запрос или ответ, можно при помощи getRequest() и getResponse() соответственно.
Важно! IHttpRequestResponse отработавшего запроса мы получаем в сканерах! В том же processHttpMessage интерфейса IHttpListener, при messageIsRequest в состоянии True, запрос еще не выполнен и мы можем повлиять на него через setRequest().
Отдельно запрос или ответ, в расширениях Burp, принято называть httpMessage. Получается, что у нас может быть, либо сообщение запроса либо сообщение ответа от сервера. Request и response это httpMessage, по сути своей массив байт, которые отличаются только направлением этого сообщения (или пакета). Соответственно, набор байт (или массив байт) для нас это текст составленный по определенным правилам, для машины нули и единички. Но это уже совсем основы компьютерной грамотности, поэтому их объяснение избегу)))
IHttpRequestResponse это объект, который не только содержит два набора байт, но так же комментарии к ним и цвет выделения. Управляется это методами getComment(), setComment() и getHighlight(), setHighlight(), соответственно. Кстати, сами запросы/ответы тоже можно прямо назначать через setRequest() и getRequest(). На этом моменте можно вернуться к первым статьям и посмотреть, где можно было работу с запросами перестроить.
Последней важной частью IHttpRequestResponse является объект IHttpService, который управляем соответствующими методами: getHttpService() и getHttpService(). По сути своей, объект хранит в себе информацию о хосте: имя или IP-адрес, порт и протокол.
Для удобного взаимодействия с объектами запросов и ответов, предусмотрены специальные классы IRequestInfo и IResponseInfo, соответственно. Никто не мешает написать свои обработчики и работать напрямую с массивами байт, но гораздо удобнее ввести getHeaders() и получить все заголовки удобным списком строк. Чтобы привести байты к этим объектам, в хелперах есть методы analyzeRequest() и analyzeResponse().
IRequestInfo | IResponseInfo | ||
Тип данных | Название метода | Тип данных | Название метода |
int | getBodyOffset() | int | getBodyOffset() |
byte | getContentType() | ICookie[] | getCookies() |
string[] | getHeaders() | string[] | getHeaders() |
string | getMethod() | string | getInferredMimeType() |
IParameter[] | getParameters() | string | getStatedMimeType() |
java.net.URL | getUrl() | short(int) | getStatusCode() |
С большинством методов не должно возникнуть никаких вопроса, они интуитивно понятны. Единственное, могут возникнуть вопросы с методами связанными с Mime-type ответа. getStatedMimeType() возвращает, какой тип указан в ответе сервера, а getInferredMimeType() возвращает предполагаемый Mime-type на основе содержимого. В данном случае, Burp может помочь увидеть несоответствие между тем, что отдает сервер и тем, как он это представляет.
IParameter и ICookie два типа, которые хранят в себе соответствующие данные. IParameter уже разбирал в предыдущих материалах, ICookie достаточно прост:
На этом закончим с базой. В большинстве случаев, при построении запросов, дело придется иметь с HTTP-сообщением. Методы, которые позволяют удобно строить и модифицировать запросы, находятся в хелперах, которые мы получаем из коллбэков. Но к этому еще вернемся в будущем.
Модификация сообщения
Возвращаясь к нашему расширению… Учитывая все вышесказанное, нас интересуют сугубо запросы. Если isRequest будет False, сразу выходим. В ином случае, берем getRequest(), и ищем в нем наши тэги. Причем, договоримся сразу, что должен быть и открывающий тэг и закрывающий. Если тэги найдены, на основе запроса, строим новый запрос и выполняем его через makeHttpRequest().Меня интересует построение масштабируемого решения, чтобы можно было добавлять все новые и новые варианты преобразования строк. Поэтому, оптимально будет каждый вариант преобразования выполнить в виде объекта. Объекта содержащего открывающий тэг, закрывающий тэг, метод преобразования convert(), методы возвращающие имя name() и описание detail(), на случай если потребуется логгирование. Чтобы было по феншую, создам класс, который будет выступать родителем для остальных:
Python: Скопировать в буфер обмена
Код:
class IConvert():
def __init__(self, tagName):
self._tagName = tagName
def getName(self): pass
def getTag(self):
return self._tagName
def getDetail(self): pass
def convert(self, payload): pass
def getOpenTag(self):
return '<@' + self.tagName + '>'
def getCloseTag(self):
return '</' + self.tagName + '>'
Раз уже работали с hex entities, создам соответствующий класс преобразования:
Python: Скопировать в буфер обмена
Код:
from IConvert import IConvert
class HexEntity(IConvert):
def getName(self):
return 'Hex entities'
def getDetail(self):
return 'Converts text to hex entities. Example: 1 => 1'
def convert(self, payload):
hex_payload = ';'.join([hex(ord(char)).replace('0x', '&#x') for char in payload]) + ';'
return hex_payload
Самое время оживить наше расширение, добавив в него возможность делать первые преобразования:
Python: Скопировать в буфер обмена
Код:
from burp import IBurpExtender, IHttpListener, IBurpExtenderCallbacks
from HexEntity import HexEntity
import re
class BurpExtender(IBurpExtender):
def registerExtenderCallbacks(self, cb):
cb.registerHttpListener(HttpListener(cb))
print('Logger loaded')
class HttpListener(IHttpListener):
converters = [
HexEntity('my-hex-entity')
]
def __init__(self, cb):
self._cb = cb
self._helpers = cb.getHelpers()
def processHttpMessage(self, tool, isRequest, messageInfo):
if not isRequest: return
modified = False
request = messageInfo.getRequest()
requestStr = self._helpers.bytesToString(request)
for converter in self.converters:
tag = converter.getTag()
pattern = '(?<=<@' + tag + '>).*[\n]*(?=<\/' + tag + '>)'
forReplace = re.findall(pattern, requestStr)
if len(forReplace):
for item in forReplace:
payload = converter.convert(item)
pattern = '<@' + tag + '>.*[\n]*<\/' + tag + '>'
requestStr = re.sub(pattern, payload, requestStr)
modified = True
if modified:
messageInfo.setRequest(self._helpers.stringToBytes(requestStr))
Разберу по шагам, что происходит:
- Если мы имеем дело с Response, выходим. Мы уже никак не можем повлиять на запрос, он выполнен.
- Устанавливаем флаг modified в False, чтобы понимать, нужно ли обновлять запрос.
- Получаем запрос и конвертируем его в строку
- Циклом проходим по всем конвертерам. Это позволит нам бесконечно добавлять их. Ну как бесконечно, главное не переусердствовать.
- Получаем тэг и формируем паттерн. Вообще, по хорошему, стоит убрать этот процесс под капот. Например, запихать подобные процедуры в отдельный класс и от него наследовать конверторы.
- Регулярным выражением ищем совпадения, если они есть проходим по ним. Плюс решения в том, что конвертироваться будут только полные пары тэгов открытия и закрытия. Минус — висящие тэги уйдут в запрос.
- Конвертируем пэйлоад и производим замену по регулярному выражению.
Как и писал выше, если запрос еще не выполнен, в processHttpMessage() мы можем повлиять на него, т.к. объект messageInfo передается по ссылке, а не по значению. Соответственно, финальный шаг — это обновление запроса. Самое время протестировать в Repeater
Отлично! Видим, что отправили пиписку и получили пиписку. Как говориться, что посеяли, то и пожали. А значит наше расширение работает исправно. Поэтому, предлагаю убрать под капот составление регулярок и добавить еще метод конвертации для разнообразия. После перейдем к работе с меню BurpSuite.
Python: Скопировать в буфер обмена
Код:
from IConvert import IConvert
import random
class RandomCase(IConvert):
def __init__(self, tagName):
self.tagName = tagName
def getName(self):
return 'RaNDom CaSE'
def getDetail(self):
return 'Randomize Letter Case in a String'
def changeSymb(self, simb):
if simb.isalpha():
rnd = random.randint(0,1)
print(rnd)
if rnd:
return simb.upper()
return simb.lower()
return simb
def convert(self, payload):
newPayload = ''
for i in range(len(payload)):
newPayload = newPayload + self.changeSymb(payload[i])
return newPayload
Бесхитростное преобразование строки в строку с рандомным регистром. Остается только добавить в список конвертеров и можно использовать.
Обновленный родительский класс выглядит следующим образом:
Python: Скопировать в буфер обмена
Код:
class IConvert():
tagName = ''
def getName(self): pass
def getTag(self):
return self.tagName
def getDetail(self): pass
def convert(self, payload): pass
def getOpenTag(self):
return '<@' + self.tagName + '>'
def getCloseTag(self):
return '</' + self.tagName + '>'
def getPatternFind(self):
return '(?<=<@' + self.tagName + '>).*[\n]*(?=<\/' + self.tagName + '>)'
def getPatternReplace(self):
return '<@' + self.tagName + '>.*[\n]*<\/' + self.tagName + '>'
Теперь достаточно запросить паттерн для поиска или паттерн для замены.
Добавляем свои пунктики в контекстное меню
Чтобы добавить свои пункты меню, нам потребуется Несколько важных ингредиентов. Как обычно, все работает через слушатели. BurpSuite надо сообщить о том, что у нас есть свой обработчик контекстного меню, который добавит свои пункты. Обработчиком будет выступать объект на основе интерфейса IContextMenuFactory. Ну и “по классике”, чтобы сообщить об этом Burp, достаточно вызвать подходящую функцию регистрации - registerContextMenuFactory() из коллбэков экстендера.from burp import IBurpExtender, IHttpListener, IBurpExtenderCallbacks, IIContextMenuFactory
from HexEntity import HexEntity
import re
Python: Скопировать в буфер обмена
Код:
class BurpExtender(IBurpExtender):
def registerExtenderCallbacks(self, cb):
cb.registerHttpListener(HttpListener(cb))
cb.registerContextMenuFactory(MyContextMenu(cb))
print('Logger loaded')
class MyContextMenu(IContextMenuFactory):
def __init__(self, cb):
self._cb = cb
self._helpers = cb.getHelpers()
Наш класс “MyContextMenu” должен реализовывать всего один метод createMenuItems(IContextMenuInvocation invocation). Как видим, он принимает загадочный аргумент с типом IContextMenuInvocation., который полностью нам объясняет где мы оказались и что вокруг происходит. А вернуть методо должен список объектов javax.swing.JMenuItem. Здесь стоит вспомнить, что расширения это не CPython, а Jython. Это значит, что мы можем использовать не только пакеты пайтен. Вернее их использовать мы можем не только лишь все, мало какие можем использовать. Зато можем спокойно юзать библиотеки Java. Самое время почитать подробнее про этот JMenuItem.
Да, чтиво длинное, но не будем впадать в панику. В конце-то концов, нам нужно совсем немногое оттуда. Надо понять, как добавлять надписи и привязывать события. Первое, что от нас требуется, это импортнуть пакет:
from javax.swing import JMenuItem
Импортнули, создадим наш супер-пункт меню и проверим:
Python: Скопировать в буфер обмена
Код:
class MyContextMenu(IContextMenuFactory):
def __init__(self, cb):
self._cb = cb
self._helpers = cb.getHelpers()
def createMenuItems(self, invocation):
menuItem1 = JMenuItem("Converter Super Menu")
return [menuItem1]
Батюшки мои! Пункт меню добавился. Не знаю откуда у меня такая радость, когда выполняется обычный код, но всегда кайфую когда в существующем ПО появляется что-то от меня))))
Нужно заставить меню что-то делать. Создадим обработчик события и привяжем к нашему меню:
Python: Скопировать в буфер обмена
Код:
def createMenuItems(self, invocation):
menuItem1 = JMenuItem("Converter Super Menu", actionPerformed=self.menuClick)
return [menuItem1]
def menuClick(self, event):
print('Our first event')
print(event.toString())
Пока просто распечатал информацию о событии при помощи метода toString(). Для понимания процесса, в event передался объект java.util.EventObject. Среди его методов есть toString().
В расширениях убираю и снова ставлю галочку на нашем расширении, чтобы перезагрузить. После кликаю по пункту меню и смотрю вывод:
Код: Скопировать в буфер обмена
Код:
Our first event
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=Converter Super Menu,when=1722440230590,modifiers=Button1] on javax.swing.JMenuItem[,1,4,180x24,invalid,alignmentX=0.0,alignmentY=0.0,border=com.formdev.flatlaf.ui.FlatMenuItemBorder@38dc8149,flags=256,maximumSize=,minimumSize=,preferredSize=,defaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=javax.swing.plaf.InsetsUIResource[top=3,left=6,bottom=3,right=6],paintBorder=true,paintFocus=false,pressedIcon=,rolloverEnabled=false,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,text=Converter Super Menu]
Чтоже, достаточно информативно. Мы видим подробнейшую информацию о том где и на чем произошло событие. Но информация касается исключительно самого объекта меню. Нет тех прелестей, которые нам сулил передаваемый в фабрику объект IContextMenuInvocation. Есть несколько вариантов решить эту проблему. Первый - работать с меню внутри класса. Запомнить invocation в self и обращаться к нему. Тогда можно остаться в рамках указанной функции. Выглядеть работа будет следующим образом:
Python: Скопировать в буфер обмена
Код:
class MyContextMenu(IContextMenuFactory):
def __init__(self, cb):
self._cb = cb
self._helpers = cb.getHelpers()
def createMenuItems(self, invocation):
self.invocation = invocation
menuItem1 = JMenuItem("Converter Super Menu", actionPerformed=self.menuClick)
return [menuItem1]
def menuClick(self, event):
toolFlag = self.invocation.getToolFlag()
toolName = self._cb.getToolName(toolFlag)
print(toolName)
Перезагружаю расширение и в репитере кликаю по нашему контекстному меню.
Все четко работает. Но есть альтернатива, которая мне кажется более приемлемой — создать отдельный класс.
Итак, в целом, с меню разобрались. Но нам нужно связать его с нашим списком конвертеров. Для этого сделаем отдельный класс, который
Python: Скопировать в буфер обмена
Код:
from burp import IContextMenuFactory
from javax.swing import JMenuItem
from java.awt.event import ActionListener
from converter_list import converters
class MyContextMenu(IContextMenuFactory):
def __init__(self, cb):
self._cb = cb
self._helpers = cb.getHelpers()
def createMenuItems(self, invocation):
self.invocation = invocation
menuItems = []
for converter in converters:
menuItem = JMenuItem(converter.getName())
menuItemClick = MenuItemListener(self._cb, invocation, converter)
menuItem.addActionListener(menuItemClick)
menuItems.append(menuItem)
return menuItems
class MenuItemListener(ActionListener):
def __init__(self, cb, invocation, converter):
self._cb = cb
self.invocation = invocation
self.converter = converter
def actionPerformed(self, event):
toolFlag = self.invocation.getToolFlag()
toolName = self._cb.getToolName(toolFlag)
print(toolName + ' ' + self.converter.getName())
Вынес меню в отдельный файл. Реализовал события, как это принято в Java. Теперь каждое событие это объект, в котором храниться вся нужная информация.
Чтобы все было красиво, нужно правильно организовать вставку данных. А именно, если пользователь просто кликнул в запросе по нашему пункту меню, мы просто вставляем тэги. Но, если пользователь выделил какой-то текст, наша задача обернуть выделенный текст в тэги. Ровно так же, как делает расширение Hackvertor. Для этого потребуется несколько методов из объекта invocation. А именно, getSelectedMessages(), который вернет IHttpRequestResponse[]. Так мы получим подробную информацию об запросе/ответе на котором кликнул пользователь. Второй метод это getSelectionBounds(), который покажет точные индексы выделения. Все, что нам останется сделать, это выполнить замену в нужных границах в нужном запросе/ответе. Звучит может и муторно, но реализуется без особых сложностей:
Python: Скопировать в буфер обмена
Код:
def actionPerformed(self, event):
start,end = self._invocation.getSelectionBounds()
httpMessages = self._invocation.getSelectedMessages()
for i in range(len(httpMessages)):
request = self._cb.getHelpers().bytesToString(httpMessages[i].getRequest())
newRequest = request[:start] + self._converter.getOpenTag() + request[start:end] + self._converter.getCloseTag() + request[end:]
httpMessages[i].setRequest(newRequest)
Получаем координаты начала и конца выделения, через getSelectionBounds(). На выходе массив из двух чисел, а раз знаем заране что получаем, можно воспользоваться деструктуризацией. Далее получаем выделенные сообщения и проходимся по ним циклом. Так как для тестов я использовал репитер, там будет всего одно значение, но не важно, пусть будет цикл.
Я знаю, что работать буду исключительно с Request, поэтому игнорирую респонс. Кстати, так как я забыл в конструкторе класса сохранить хелперы, использую для доступа к ним getHelpers().
Следующая строка просто пересобирает заново строковый реквест по частям. Если выделен какой-то текст, позиция start и end будут отличаться. Если нет выделения, обе позиции будут указывать на позицию курсора в запросе. В любом случае произойдет вставка. Ну и завершаю процесс через setRequest(0. Важно понимать, что в данном контексте, сообщение будет указывать на открытый в редакторе запрос.
Один важный момент
Есть нюанс, который очень важен, но остался за кадром. При взаимодействии с процессингом в IHttpListener нужно быть внимательным. В данном примере я не стал делать фильтрацию по используемому инструменту. В нашем случае это не имеет значения. Но бывает, что могут возникнуть неприятные ситуации.Например, вы написали расширение, которое преобразует параметр запроса search в base64. Пока ничего не происходит. Внезапные непонятные ситуации могут начаться, если вы пустите трафик браузера через Burp. IHttpListener будет делать то, что ему скажут… сказали конвертить search и base64, значит будет. На такой случай, можно сделать простой фильтр:
Python: Скопировать в буфер обмена
Код:
if tool == IBurpExtenderCallbacks.TOOL_PROXY:
return
Итоговые файлы расширения
Спойлер: main.pyPython: Скопировать в буфер обмена
Код:
from burp import IBurpExtender
from context_menu import MyContextMenu
from http_listeener import HttpListener
class BurpExtender(IBurpExtender):
def registerExtenderCallbacks(self, cb):
cb.registerHttpListener(HttpListener(cb))
cb.registerContextMenuFactory(MyContextMenu(cb))
print('Logger loaded')
Спойлер: http_listener.py
Python: Скопировать в буфер обмена
Код:
from burp import IHttpListener
from converter_list import converters
import re
class HttpListener(IHttpListener):
def __init__(self, cb):
self._cb = cb
self._helpers = cb.getHelpers()
def processHttpMessage(self, tool, isRequest, messageInfo):
if not isRequest: return
modified = False
request = messageInfo.getRequest()
requestStr = self._helpers.bytesToString(request)
for converter in converters:
tag = converter.getTag()
pattern = converter.getPatternFind()
forReplace = re.findall(pattern, requestStr)
if len(forReplace):
for item in forReplace:
payload = converter.convert(item)
pattern = converter.getPatternReplace()
requestStr = re.sub(pattern, payload, requestStr)
modified = True
if modified:
messageInfo.setRequest(self._helpers.stringToBytes(requestStr))
Спойлер: converter_list.py
Python: Скопировать в буфер обмена
Код:
from HexEntity import HexEntity
from RandomCase import RandomCase
converters = [
HexEntity('my-hex-entity'),
RandomCase('my-random-case')
]
Спойлер: HexEntity.py
Python: Скопировать в буфер обмена
Код:
from IConvert import IConvert
class HexEntity(IConvert):
def __init__(self, tagName):
self.tagName = tagName
def getName(self):
return 'Hex entities'
def getDetail(self):
return 'Converts text to hex entities. Example: 1 => 1'
def convert(self, payload):
hex_payload = ';'.join([hex(ord(char)).replace('0x', '&#x') for char in payload]) + ';'
return hex_payload
Спойлер: RandomCase.py
Python: Скопировать в буфер обмена
Код:
from IConvert import IConvert
import random
class RandomCase(IConvert):
def __init__(self, tagName):
self.tagName = tagName
def getName(self):
return 'RaNDom CaSE'
def getDetail(self):
return 'Randomize Letter Case in a String'
def changeSymb(self, simb):
if simb.isalpha():
rnd = random.randint(0,1)
print(rnd)
if rnd:
return simb.upper()
return simb.lower()
return simb
def convert(self, payload):
newPayload = ''
for i in range(len(payload)):
newPayload = newPayload + self.changeSymb(payload[i])
return newPayload
Спойлер: context_menu.py
Python: Скопировать в буфер обмена
Код:
from burp import IContextMenuFactory
from javax.swing import JMenuItem
from java.awt.event import ActionListener
from converter_list import converters
class MyContextMenu(IContextMenuFactory):
def __init__(self, cb):
self._cb = cb
self._helpers = cb.getHelpers()
def createMenuItems(self, invocation):
self.invocation = invocation
menuItems = []
for converter in converters:
menuItem = JMenuItem(converter.getName())
menuItemClick = MenuItemListener(self._cb, invocation, converter)
menuItem.addActionListener(menuItemClick)
menuItems.append(menuItem)
return menuItems
class MenuItemListener(ActionListener):
def __init__(self, cb, invocation, converter):
self._cb = cb
self._invocation = invocation
self._converter = converter
def actionPerformed(self, event):
start,end = self._invocation.getSelectionBounds()
httpMessages = self._invocation.getSelectedMessages()
for i in range(len(httpMessages)):
request = self._cb.getHelpers().bytesToString(httpMessages[i].getRequest())
newRequest = request[:start] + self._converter.getOpenTag() + request[start:end] + self._converter.getCloseTag() + request[end:]
httpMessages[i].setRequest(newRequest)
Подводим итоги
В результате, получилось довольно неплохое расширение, которое можно спокойно масштабировать, добавляя в него все новые конверторы. Свои, кастомные, уникальные и непревзойденные. При этом, умудрились захватить фундамент по разработке расширений, которую нужно было давно выдать. Плюс, ближе разобрались с IHttpListener и реализовали еще одну его возможность (подмену запросов). Разобрали, хоть и поверхностно, базовое взаимодействие с компонентами Java. Научились делать меню, привязывать к нему события и взаимодействовать с запросами/ответами в интерфейсе Burp.Серия ширится и растет, это уже четвертая статья из серии (тыц, тыц и тыц). Если не читали, советую. Жду ваши вопросы, пожелания и замечания, а пока пойду писать про свинг… Нет, не про тот свинг, что в интересных роликах, а про Swing в Java . То, на базе чего строятся пользовательские интерфейсы. Сегодня мы коснулись его компонента JMenuItem, но пора переходить к полноценным решениям.
Надеюсь вам понравилось!