Автор petrinh1988
Источник https://xss.pro
API Burp Extender не только позволяет строить произвольные пользовательские интерфейсы в отдельных вкладках, но и расширять интерфейс собственных инструментов: Proxy, Repeater и Target. Тех инструментов, которые взаимодействуют с объектами IHTTPRequestResponse, если точнее, то с HTTP-сообщениями (в этой статье есть разбор этого вопроса) . Мы можем добавлять свои вкладки в эти тулзы, создавая возможность работать с запросами/ответами в интерактивном режиме. Реализуется это через интерфейс IMessageEditorTabFactory. Более того, если не требуется сложных интерфейсов с кнопочками и фишечками, можно использовать базовый функционал Burp Extender. createTextEditor() создаст нам удобное поле для редактирования сообщения. Другими словами, совсем без заморочек.
Приведу несколько простых примеров, чтобы принцип работы был максимально понятным. В первом примере, поработаем с уже знакомой лабой PortSwigger, для решения которой, SQL Injection нужно кодировать в hex entity. Да, я уже заездил эту лабу вдоль и поперек — под неё делался Insertion Point, было два варианта автоматизации… но в данном случае речь идет про инструмент для интерактивной работы, поэтому проще показать на уже знакомой лабе. Просто она идеально подходит для демонстрации множества аспектов работы. А мы, все же, про расширения для Burp, а не решение лабораторных или машин. В компенсацию, во второй части, расширим функционал, добавив возможность выбора параметра, который будем кодировать. В третьей части статьи, доведу инструмент до универсальности, создав возможность добавлять бесконечное число вариантов обхода WAF. Думаю, что это максимально эффективный и понятный способ продемонстрировать, как можно писать универсальные масштабируемые расширения (возможно слишком громко…)))) ).
Как уже сказал, работать будем с HTTP-сообщениями, а именно - редактировать их. Это же отражено и в названии основного интерфейса, который будем регистрировать в коллбэках - IMessageEditorTabFactory. Хочу отдельно остановиться на слове Factory, мы уже сталкивались с интерфейсом его содержащим, когда добавляли пункт в контекстное меню. Там класс был IContextMenuFactory. Factory - это паттерн программирования. По сути своей, представляет фабрику объектов РАЗНЫХ классов, но являющихся потомками одного класса. Некий универсальный класс, который упрощает процесс. Уже приводил эту картинку в прошлой статье:
IMessageEditorTabFactory реализует один единственный метод createNewInstance(). Данный метод возвращает IMessageEditorTab, что в принципе, понятно из названия “фабрики”. Но где же здесь разные классы и причем тут картинка? Все просто, ключевой метод получившегося объекта getUiComponent(), который возвращает, логично, JComponent. Каким же будет этот компонент, решим уже мы с вами. Нам вообще ничего не мешает, забыть про то, что это редактор сообщения и, вместо текстового поля createTextEditor(), впилякать в интерфейс огромную кнопку:
Круто? Да! Но почему там чекбокс, а не кнопка? Потому что универсально! Если впилить кнопку, из-за стилистики будет непонятно что это кнопка. Поэтому я заменил JButton на JCheckBox и получил чекбокс, так как нашей фабрике плевать, что там выпускается, главное чтобы это было из серии JComponent. Абстрактная аналогия: заводу мороженного плевать на начинку, главное чтобы это было мороженое. Именно фабричность компонентов нам и позволит в третьем примере вернуть не текстовое окно, а мало-мальский интерфейс прилепленный на JPanel(), который тоже относиться к JComponent (см. рисунок выше).
Хотя, все же кнопку можно отобразить красиво, если стилизовать ее при помощи коллбэка customizeUiComponent()…
Плохо видно, но если присмотреться к уголкам, можно заметить скругления. Опять же, мать её, универсальность — customizeUiComponent() принимает JComponent и накручивает на нее шрифты, цвет и т.п.
Довольно вводной, пора браться за работу.
Все, что сделано — это сохранение ссылок на объекты и генерация текстового поля.
Загружаем, чтобы проверить. Все проходит успешно, никаких ошибок. Идем в Repeater и… ничего. Возвращаемся в расширение и видим, что насыпало Errors
А почему? Потому что созданное текстовое поле, из-за некоторых особенностей, не может самостоятельно быть приведено к JComponent и нужен явный вызов метода getComponent(). Да, подобные сюрпризы бывают. Но теперь-то мы о них знаем?
После корректировки перезагрузим расширение и увидем в Repeater свою вкладку, а на ней красивое текстовое поле в стили Burp
Пока это просто поле, которое вообще ничего не умеет и совершенно не умное. Для начала оживим методы, которые просто возвращают значения:
Что мы видим? В большинстве случаев, возвращаем стандартные методы нашего текстового поля. Название таба, соответственно, строковый литерал. Функция isEnsbled() должна понять, нужно ли поле ввода сделать рабочим или нет. Сначала смотрим, является ли сообщение сообщением запроса, так как в ответе нам редактировать ничего не нужно. Далее, при помощи метода getRequestParameter() пытаемся получить параметр запроса. Наше поле находится внутри свойства XML, но это не мешает Burp воспринимать его, как параметр запроса. Если этого параметра нет, то и редактировать нам нечего.
Осталось оживить два ключевых метода, которые позволят нам управлять самим HTTP-сообщением: getMessage() и setMessage(). Причем, по моей наивной логике, setMessage - должен устанавливать сообщение, а getMessage() получать его из потока. Но нет. Через setMessage() мы устанавливаем значение текстового поля (ну или других настроек расширения), а через getMessage() мы получаем обновленное сообщение из текстового поля.
Сначала напишу setMessage(), чтобы где надо, в поле выводилось значение параметра
Первым делом, проверяем есть что-то вообще в контенте. Если пусто, очищаем текстовое поле и делаем его нередактируемым. В ином случае, получаем наш параметр getRequestParameter() и его значение, через getValue(). Не забываем очистить от урл-кодирования. Далее просто назначаем текст и делаем поле редактируемым. Сам контент нам еще понадобиться для обработки второй функции. Запоминаю его в себя в _contentOld. Это не Old-контент, но чтобы не было неразберихи при чтении кода добавил суффикс.
Все четко. В текстовом поле лежит значение storeId. Обратите внимание не всего XML, а только указанного параметра, так как мы его получали через getRequestParameter(). Кстати, обратили внимание, что на скрине Proxy? Для разнообразия открыл подобным образом. Но интерактив, соответственно, будет только в Repeater. В Proxy и Target, текстовое поле будет “не едитабельное”.
Все работает, поэтому перехожу к getMessage(), чтобы изменения в текстовом поле были равны изменению в окне запроса.
Проверяю, было ли изменение в тексте. Если нет, возвращаю старый контент, т.к. Нам не нужно менять параметр. Далее, получаю значение текстового поля и кодирую его в url. Создаю на его основе параметр, далее произвожу замену в исходном запросе (contentOld) и возвращаю новый запрос, который мне выдаст updateParameter().
Воодушевленно иду тестировать — значение подставляется, меняю “1” на “12”, возвращаюсь в запрос и… глюки, Burp не хочет корректно работать, а в логе ошибок расширения:
Если у вас “не жадный” Burp и вместо ошибки все зависло до уровня белого окна, просто закройте BurpLoaderKeygen, тогда сам Burp отвиснет.
А почему? А потому. Ответа на этот вопрос у меня нет. Может быть, потому что в создании параметра указан PARAM_BODY? Логично, но нет. Подставляй туда хоть PARAM_XML, хоть PARAM_XML_ATTR, ничего не изменится. Не может Burp Extender нормально сбилдить параметр, если он часть XML. У меня нет ответа почему, если у вас есть — напишите. Я же в таких случаях все время колхозил и сейчас буду колхозить. А именно, возьму старый контет, сделаю в нем замену и сконвертирую в байт-массив, так как на выходе у нас должен быть byte[]
Без зазрения совести скопировал и чуток подправил код из второй статьи. Сразу скопировал и функцию кодирования в hex entity. После этих изменений, все ожидаемо работает. Интерактив появился, но пока без кодирования. Просто добавлю старую функцию _toHexEntity() и оберну в нее новое значение из текстового поля:
Остался один недочет. В функции setMessage() нам нужно детектить закодировано значение в hex entity или нет. В данном случае, достаточно будет банального isdigit()
Соответственно, функция обратной конвертации _hexToString():
Сейчас, в качестве основного компонента, используется текстовое поле Burp, что нам не подходит. Вместо этого, будем возвращать JPanel() с прикрепленными к ней элементами. Прикреплять будем все тоже текстовое поле и выпадающий список JComboBox.
Из интересных изменений - получение параметров запроса. Объект controller относиться к типу IMessageEditorController, который очень похож на IHttpRequestResponse. Чтобы получить параметры для выпадающего списка, получаем запрос методом getRequest() . Хелпером analyzeRequest, из набора байт реквеста, создаем удобный объект requestInfo с типом IRequestInfo. Cписок параметров предоставит метод getParameters(), который возвращает список объектов с типом IParameter. Конструктор выпадающего списка принимает массив строк, поэтому завершаем процесс подготовки циклом, который методом getName() из каждого параметра вытащит название. Осталось создать создать интерфейс и запомнить ссылки на компоненты.
Не забываем менять функцию getUiComponent(), именно она отвечает за то, что будет выводиться на нашей закладке. Если оставить текстовое поле, чтобы там мы не создавали, будет текствоое поле. Поэтому, меняет возврат на нашу панель:
Сохраню, протестирую:
Интерфейс изменится, но алгоритм работы остался старый. Нам нужно внести коррективы в работу текстового поля. Привязать параметр, который выводится и обрабатывается в текстовом поле, к новому JComboBox.
Событие к JComboBox привязывается при помощи метода addActionListener(), который принимает событие с типом ActionListener. Для этого, реализую класс ActionListener() с единственным методом actionPerformed(). В рамках задачи, нужно связать событие смены параметра в JComboBox с функцией setMessage(), которая необходимо для изменения текста внутри редактора. Передам в конструктор события объект METab, чтобы была возможность вызывать setMessage(), при этом избежав дублирования кода:
setMessage принимает два параметра: контент, с которым нужно работать и флаг, является ли контент запросом. В нашем случае, контент всегда будет объектом запроса, но правильнее будет сохранить isRequest в самом объекте и передавать его.
При попытке теста, все рухнет. Причина в том, что расширение жестко настроено на работу с storeId и приведением значения к hex entity. Внесу правки, сначала в setMessage(), чтобы расширение корректно выводило информацию:
Алгоритм изменился, но не радикально. Сначала получаем выбранное в комбо боксе значение, при помощи getSelectedItem(). Это название параметра, с ним можно выполнить запрос через хелпер getRequestParameter(). Получив значение, устанавливаем. Раньше была конвертация из hex entity, но сейчас она будет мешать. Обращаю внимание на то, что _contentOld превратился в _request. Мы работает только с реквестами, поэтому так логичнее и не будет путаницы.
Расширение прекрасно работает. При смене параметра, меняется и значение. Пора переделать возврат значения в Burp, через getMessage().
Описываю только изменения. Как и в setMessage(), сначала получаем название параметра из списка и IParameter из self._request. Напомню, что в self._requestу нас храниться запрос. Далее мы проверяем тип параметра.
Если это XML, то конвертируем в HEX Entity и создаем новый запрос через конкатенацию. Для получения позиций подстановки использую методы параметра: getValueStart() и getValueEnd(). Благодаря им, замена значения это легкая прогулка. Кстати, иногда могут потребоваться собратья этих функций: getNameStart() и getNameEnd().
Если параметр не XML, то формирование нового запроса происходит при помощи двух функций-хелмперов: buildParameter() и updateParameter. Первая создает новый объект IParameter, а вторая заменяет им старый в указанном запросе.
Теперь, можно спокойно менять значения у разных параметров. Вторая часть завершена. Время сделать расширение еще более универсальным, добавив декодирование на входе и кодирование на выходе.
Добавим два JComboBox: один будет отвечать за декодирование входных данных, второй за выходных. Этот процесс уже понятен. Вопрос в том, как организовать сам процесс кодирования и декодирования таким образом, чтобы можно было легко и просто добавлять новые и новые варианты. Я решил эту задачу через класс-провайдер, который аккумулирует в себе список добавленных вариантов кодирования/декодирования и просто передает управление выбранному:
Что внутри?
Сами конечные классы очень простые. Ниже два примера. NoneEnc нужен для ситуации, когда не требуется кодирование, ну а с Hex Entity вы уже давно знакомы:
В hex entity одно отличие - отдельные hex-значения выделяются при помощи регулярки. В заключении я про это еще напишу.
Что теперь? А теперь без проблем можно писать все новые и новые конверторы, достаточно создать класс с двумя функциями: decode и encode. После добавить в список encoders объекта EncoderProvider. После этого, конвертор появится в выпадающих списках…. Вернее реализуем это все следующим образом:
Создаем объект, с которым будем работать. Получаем список доступных конверторов и создаем два JComboBox, передавая в конструктор список конвертеров. Далее устанавливаем списки на None, так как изначально пользователь ничего не выбирал, попытка конверсии вызовет ошибку и ничего не произойдет.
Обратите внимание, что только к входящему конвертеру добавляю слушатель события выбора. Конвертер, который займется выходными данными, в любом случае будет выбран верно, так как мы перед отдачей будем проверять выбранный вариант кодирования.
Функция setMessage() изменилась не сильно. Теперь проверяем не только, с каким параметром работаем и какой декодер выбран. Через провайдер передаем значение параметра выбранному пользователем декодеру. Если декодер не сможет обработать значение, то просто возьмется сырое значение параметра.
Если я проверю значение, какой декодер использовать, то зачем надо было прикручивать к комбобоксу обработчик события? Все просто — setMessage() срабатывает только при открытии вкладки расширения. Без слушателя, пользователю пришлось бы кликать туда-сюда, чтобы сработал обработчик.
Здесь изменения примерно такие же. Разница в том, что запускаем encode, а не decode. Ну и на выходе наша задача не установить текстовое поле, а вернуть новый Request, которым Burp замени старый. Ну и проверка типа параметра. Думаю вы помните про особенность PARAM_XML и PARAM_XML_ATTR. С параметром типа PARAM_JSON и другими, думаю уже разберетесь. Если что, пишите, дополню.
Вроде все! Или нет? Давайте глянем на этот кусок кода, который решает работает ли наш поле или нет:
Что-то мне подсказывает, что это не совсем вариант для универсального решения. Вряд ли на каждом сайте есть параметр “storeid”. Правильнее будет так:
Теперь наше поле будет рабочим, если это запрос. .
Самый прикол в том, что это произошло сразу после другого затупа. У меня просто перестала открываться вкладка расширения. Код был тот же, он 10 минут назад работал, а сейчас нет. И никаких ошибок. Просто не открывается вкладка. Чтобы я не делал. Уже все закоментил, потом раскоментил. Думал это черная магия. Оказалось, что это… в этот раз это косяк Burp))))
Когда видна только часть вкладки, нет стрелочки чтобы появилось всплывающее меню и можно было бы выбрать вкладку расширения. При этом, Burp абсолютно не понимает, что вы кликаете на эту вкладку. Как решить? Нет, не как я, тратя полтора часа на правки кода… Достаточно сдвинуть разделитель влево или вправо, чтобы название вкладки было видно полностью или появилась стрелочка для всплывающего меню.
Вот так друзья. Не забывайте отдыхать. И, если что-то работает не так как вы ожидали, лучше отдохните и отвлекитесь на приятные вещи. Сексом что ли займитесь или с собакой погуляйте. После проблема решиться за считанные минуты.
Надеюсь вам понравилось! Впереди еще много крутых тем!
https://gist.github.com/ncoblentz/4420532
Источник https://xss.pro
API Burp Extender не только позволяет строить произвольные пользовательские интерфейсы в отдельных вкладках, но и расширять интерфейс собственных инструментов: Proxy, Repeater и Target. Тех инструментов, которые взаимодействуют с объектами IHTTPRequestResponse, если точнее, то с HTTP-сообщениями (в этой статье есть разбор этого вопроса) . Мы можем добавлять свои вкладки в эти тулзы, создавая возможность работать с запросами/ответами в интерактивном режиме. Реализуется это через интерфейс IMessageEditorTabFactory. Более того, если не требуется сложных интерфейсов с кнопочками и фишечками, можно использовать базовый функционал Burp Extender. createTextEditor() создаст нам удобное поле для редактирования сообщения. Другими словами, совсем без заморочек.
Приведу несколько простых примеров, чтобы принцип работы был максимально понятным. В первом примере, поработаем с уже знакомой лабой PortSwigger, для решения которой, SQL Injection нужно кодировать в hex entity. Да, я уже заездил эту лабу вдоль и поперек — под неё делался Insertion Point, было два варианта автоматизации… но в данном случае речь идет про инструмент для интерактивной работы, поэтому проще показать на уже знакомой лабе. Просто она идеально подходит для демонстрации множества аспектов работы. А мы, все же, про расширения для Burp, а не решение лабораторных или машин. В компенсацию, во второй части, расширим функционал, добавив возможность выбора параметра, который будем кодировать. В третьей части статьи, доведу инструмент до универсальности, создав возможность добавлять бесконечное число вариантов обхода WAF. Думаю, что это максимально эффективный и понятный способ продемонстрировать, как можно писать универсальные масштабируемые расширения (возможно слишком громко…)))) ).
Как уже сказал, работать будем с HTTP-сообщениями, а именно - редактировать их. Это же отражено и в названии основного интерфейса, который будем регистрировать в коллбэках - IMessageEditorTabFactory. Хочу отдельно остановиться на слове Factory, мы уже сталкивались с интерфейсом его содержащим, когда добавляли пункт в контекстное меню. Там класс был IContextMenuFactory. Factory - это паттерн программирования. По сути своей, представляет фабрику объектов РАЗНЫХ классов, но являющихся потомками одного класса. Некий универсальный класс, который упрощает процесс. Уже приводил эту картинку в прошлой статье:
IMessageEditorTabFactory реализует один единственный метод createNewInstance(). Данный метод возвращает IMessageEditorTab, что в принципе, понятно из названия “фабрики”. Но где же здесь разные классы и причем тут картинка? Все просто, ключевой метод получившегося объекта getUiComponent(), который возвращает, логично, JComponent. Каким же будет этот компонент, решим уже мы с вами. Нам вообще ничего не мешает, забыть про то, что это редактор сообщения и, вместо текстового поля createTextEditor(), впилякать в интерфейс огромную кнопку:
Python:
from burp import IBurpExtender, IMessageEditorTabFactory, IMessageEditorTab
from javax.swing import JButton
class BurpExtender(IBurpExtender, IMessageEditorTabFactory ):
def registerExtenderCallbacks(self, cb):
self._cb = cb
cb.setExtensionName('Test Factory')
cb.registerMessageEditorTabFactory(self)
def createNewInstance(self, controller, editable):
return METab(self, controller, editable)
class METab(IMessageEditorTab):
def __init__(self, extender, controller, editable):
self._extender = extender
self._editable = editable
self._cb = extender._cb
def getMessage(self): pass
def getSelectedData(self): pass
def getTabCaption(self): return 'TEST BUTTON'
def getUiComponent(self): return JButton("I'm a BUTTON")
def isEnabled(self, content, isRequest): return True
def isModified(self): return False
def setMessage(self, content, isRequest): pass
Круто? Да! Но почему там чекбокс, а не кнопка? Потому что универсально! Если впилить кнопку, из-за стилистики будет непонятно что это кнопка. Поэтому я заменил JButton на JCheckBox и получил чекбокс, так как нашей фабрике плевать, что там выпускается, главное чтобы это было из серии JComponent. Абстрактная аналогия: заводу мороженного плевать на начинку, главное чтобы это было мороженое. Именно фабричность компонентов нам и позволит в третьем примере вернуть не текстовое окно, а мало-мальский интерфейс прилепленный на JPanel(), который тоже относиться к JComponent (см. рисунок выше).
Хотя, все же кнопку можно отобразить красиво, если стилизовать ее при помощи коллбэка customizeUiComponent()…
Плохо видно, но если присмотреться к уголкам, можно заметить скругления. Опять же, мать её, универсальность — customizeUiComponent() принимает JComponent и накручивает на нее шрифты, цвет и т.п.
Довольно вводной, пора браться за работу.
Интерактивный декодер
Вводная все таже: SQLi лабораторная, где уязвимым является параметр storeId при POST-запросе остатков товара в магазине. Набросаем скелет нашего будущего расширения. Фабрика создает объект IMessageEditorTab, который методом getUiComponent() возвращает то, что будет в нашей закладке. А именно, там будет текстовое поле созданное стандартным коллбэком Burp Extender createTextEditor(). Собственно, весь полюбившийся нам порядок действий: импортируем интерфейсы API Burp, в функции регистрации коллбэков регистрируем фабрику и т.д.
Python:
from burp import IBurpExtender
from burp import IMessageEditorTabFactory
from burp import IMessageEditorTab
class BurpExtender(IBurpExtender, IMessageEditorTabFactory ):
def registerExtenderCallbacks(self, callbacks):
self._callbacks = callbacks
self._helpers = callbacks.getHelpers()
callbacks.setExtensionName('Test Factory')
callbacks.registerMessageEditorTabFactory(self)
def createNewInstance(self, controller, editable):
return METab(self, controller, editable)
class METab(IMessageEditorTab):
def __init__(self, extender, controller, editable):
self._extender = extender
self._editable = editable
self._controller = controller
self._callbacks = extender._callbacks
self._txtEditor = extender._callbacks.createTextEditor()
self._txtEditor.setEditable(editable)
def getMessage(self): pass
def getSelectedData(self): pass
def getTabCaption(self): return 'Hex Entity Decoder'
def getUiComponent(self): return self._txtEditor
def isEnabled(self, content, isRequest): return True
def isModified(self): return False
def setMessage(self, content, isRequest): pass
Все, что сделано — это сохранение ссылок на объекты и генерация текстового поля.
Загружаем, чтобы проверить. Все проходит успешно, никаких ошибок. Идем в Repeater и… ничего. Возвращаемся в расширение и видим, что насыпало Errors
А почему? Потому что созданное текстовое поле, из-за некоторых особенностей, не может самостоятельно быть приведено к JComponent и нужен явный вызов метода getComponent(). Да, подобные сюрпризы бывают. Но теперь-то мы о них знаем?
Python:
def getUiComponent(self): return self._txtEditor.getComponent()
После корректировки перезагрузим расширение и увидем в Repeater свою вкладку, а на ней красивое текстовое поле в стили Burp
Пока это просто поле, которое вообще ничего не умеет и совершенно не умное. Для начала оживим методы, которые просто возвращают значения:
Код:
def getSelectedData(self): return self._txtEditor.getSelectedText()
def getTabCaption(self): return 'Hex Entity Decoder'
def getUiComponent(self): return self._txtEditor.getComponent()
def isModified(self): return self._txtEditor.isTextModified()
def isEnabled(self, content, isRequest):
if isRequest:
stockId = self._extender._helpers.getRequestParameter(content, 'storeid')
if not stockId is None:
return True
return False
Что мы видим? В большинстве случаев, возвращаем стандартные методы нашего текстового поля. Название таба, соответственно, строковый литерал. Функция isEnsbled() должна понять, нужно ли поле ввода сделать рабочим или нет. Сначала смотрим, является ли сообщение сообщением запроса, так как в ответе нам редактировать ничего не нужно. Далее, при помощи метода getRequestParameter() пытаемся получить параметр запроса. Наше поле находится внутри свойства XML, но это не мешает Burp воспринимать его, как параметр запроса. Если этого параметра нет, то и редактировать нам нечего.
Осталось оживить два ключевых метода, которые позволят нам управлять самим HTTP-сообщением: getMessage() и setMessage(). Причем, по моей наивной логике, setMessage - должен устанавливать сообщение, а getMessage() получать его из потока. Но нет. Через setMessage() мы устанавливаем значение текстового поля (ну или других настроек расширения), а через getMessage() мы получаем обновленное сообщение из текстового поля.
Сначала напишу setMessage(), чтобы где надо, в поле выводилось значение параметра
Python:
def setMessage(self, content, isRequest):
if content is None:
self._txtEditor.setText(None)
self._txtEditor.setEditable(False)
else:
storeId = self._extender._helpers.getRequestParameter(content, "storeid")
storeIdValue = self._extender._helpers.urlDecode(storeId.getValue())
self._txtEditor.setText(storeIdValue)
self._txtEditor.setEditable(self._editable)
self._contentOld = content
Первым делом, проверяем есть что-то вообще в контенте. Если пусто, очищаем текстовое поле и делаем его нередактируемым. В ином случае, получаем наш параметр getRequestParameter() и его значение, через getValue(). Не забываем очистить от урл-кодирования. Далее просто назначаем текст и делаем поле редактируемым. Сам контент нам еще понадобиться для обработки второй функции. Запоминаю его в себя в _contentOld. Это не Old-контент, но чтобы не было неразберихи при чтении кода добавил суффикс.
Все четко. В текстовом поле лежит значение storeId. Обратите внимание не всего XML, а только указанного параметра, так как мы его получали через getRequestParameter(). Кстати, обратили внимание, что на скрине Proxy? Для разнообразия открыл подобным образом. Но интерактив, соответственно, будет только в Repeater. В Proxy и Target, текстовое поле будет “не едитабельное”.
Все работает, поэтому перехожу к getMessage(), чтобы изменения в текстовом поле были равны изменению в окне запроса.
Python:
def getMessage(self):
if self._txtEditor.isTextModified():
text = self._txtEditor.getText()
input = self._extender._helpers.urlEncode(text)
newParameter = self._extender._helpers.buildParameter("storeid", input, IParameter.PARAM_BODY)
return self._extender._helpers.updateParameter(self._contentOld, newParameter)
else:
return self._contentOld
Проверяю, было ли изменение в тексте. Если нет, возвращаю старый контент, т.к. Нам не нужно менять параметр. Далее, получаю значение текстового поля и кодирую его в url. Создаю на его основе параметр, далее произвожу замену в исходном запросе (contentOld) и возвращаю новый запрос, который мне выдаст updateParameter().
Воодушевленно иду тестировать — значение подставляется, меняю “1” на “12”, возвращаюсь в запрос и… глюки, Burp не хочет корректно работать, а в логе ошибок расширения:
Если у вас “не жадный” Burp и вместо ошибки все зависло до уровня белого окна, просто закройте BurpLoaderKeygen, тогда сам Burp отвиснет.
А почему? А потому. Ответа на этот вопрос у меня нет. Может быть, потому что в создании параметра указан PARAM_BODY? Логично, но нет. Подставляй туда хоть PARAM_XML, хоть PARAM_XML_ATTR, ничего не изменится. Не может Burp Extender нормально сбилдить параметр, если он часть XML. У меня нет ответа почему, если у вас есть — напишите. Я же в таких случаях все время колхозил и сейчас буду колхозить. А именно, возьму старый контет, сделаю в нем замену и сконвертирую в байт-массив, так как на выходе у нас должен быть byte[]
Python:
def getMessage(self):
if self._txtEditor.isTextModified():
text = self._extender._helpers.bytesToString(self._txtEditor.getText())
newValue = self._extender._helpers.urlEncode(text)
content = self._extender._helpers.bytesToString(self._contentOld)
newContent = content.replace("<storeId>" + str(self._storeId) + "</storeId>", "<storeId>" + newValue + "</storeId>")
return self._extender._helpers.stringToBytes(newContent)
else:
return self._contentOld
Без зазрения совести скопировал и чуток подправил код из второй статьи. Сразу скопировал и функцию кодирования в hex entity. После этих изменений, все ожидаемо работает. Интерактив появился, но пока без кодирования. Просто добавлю старую функцию _toHexEntity() и оберну в нее новое значение из текстового поля:
Python:
def getMessage(self):
if self._txtEditor.isTextModified():
text = self._extender._helpers.bytesToString(self._txtEditor.getText())
newValue = self._toHexEntity(text)
content = self._extender._helpers.bytesToString(self._contentOld)
newContent = content.replace("<storeId>" + str(self._storeId) + "</storeId>", "<storeId>" + newValue + "</storeId>")
return self._extender._helpers.stringToBytes(newContent)
else:
return self._contentOld
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
Остался один недочет. В функции setMessage() нам нужно детектить закодировано значение в hex entity или нет. В данном случае, достаточно будет банального isdigit()
Python:
if not str(storeIdValue).isdigit():
storeIdValue = self._hexToString(storeIdValue)
Соответственно, функция обратной конвертации _hexToString():
Python:
def _hexToString(self, payload):
return ''.join([chr(int(hex)) for hex in payload.replace('&#x', '').split[';']])
Долой storeId, даешь выбор параметра для работы!
База, с которой будем работать готова. Самое время перейти ко второй демонстрации и улучшить наш интерфейс, добавив возможность выбора параметра из списка параметров. Заодно, попрактикуемся с элементами JContainer. Но с начала, добавлю в конструктор METab присвоение хелперов переменной self._helpers и заменю все нелепые self._extender._helpers на self._helpers. Код растет, к хелперам обращаюсь все чаще, а укороченная запись лучше читается.Сейчас, в качестве основного компонента, используется текстовое поле Burp, что нам не подходит. Вместо этого, будем возвращать JPanel() с прикрепленными к ней элементами. Прикреплять будем все тоже текстовое поле и выпадающий список JComboBox.
Python:
class METab(IMessageEditorTab):
def __init__(self, extender, controller, editable):
self._extender = extender
self._editable = editable
self._callbacks = extender._callbacks
self._helpers = extender._helpers
self._controller = controller
self._request = controller.getRequest()
requestInfo = self._helpers.analyzeRequest(self._request)
self._params = requestInfo.getParameters()
paramNames = [param.getName() for param in self._params]
self._txtEditor = extender._callbacks.createTextEditor()
self._comboParams = JComboBox(paramNames)
self._txtEditor.setEditable(editable)
self._pnlMain = JPanel()
self._pnlMain.setLayout(BoxLayout(self._pnlMain, BoxLayout.Y_AXIS))
pnlTop = JPanel(FlowLayout(FlowLayout.LEFT, 10,10))
lblParams = JLabel('Parameter:')
pnlTop.add(lblParams)
pnlTop.add(self._comboParams)
self._pnlMain.add(pnlTop)
self._pnlMain.add(self._txtEditor.getComponent())
Из интересных изменений - получение параметров запроса. Объект controller относиться к типу IMessageEditorController, который очень похож на IHttpRequestResponse. Чтобы получить параметры для выпадающего списка, получаем запрос методом getRequest() . Хелпером analyzeRequest, из набора байт реквеста, создаем удобный объект requestInfo с типом IRequestInfo. Cписок параметров предоставит метод getParameters(), который возвращает список объектов с типом IParameter. Конструктор выпадающего списка принимает массив строк, поэтому завершаем процесс подготовки циклом, который методом getName() из каждого параметра вытащит название. Осталось создать создать интерфейс и запомнить ссылки на компоненты.
Не забываем менять функцию getUiComponent(), именно она отвечает за то, что будет выводиться на нашей закладке. Если оставить текстовое поле, чтобы там мы не создавали, будет текствоое поле. Поэтому, меняет возврат на нашу панель:
Python:
def getUiComponent(self): return self._pnlMain
Сохраню, протестирую:
Интерфейс изменится, но алгоритм работы остался старый. Нам нужно внести коррективы в работу текстового поля. Привязать параметр, который выводится и обрабатывается в текстовом поле, к новому JComboBox.
Событие к JComboBox привязывается при помощи метода addActionListener(), который принимает событие с типом ActionListener. Для этого, реализую класс ActionListener() с единственным методом actionPerformed(). В рамках задачи, нужно связать событие смены параметра в JComboBox с функцией setMessage(), которая необходимо для изменения текста внутри редактора. Передам в конструктор события объект METab, чтобы была возможность вызывать setMessage(), при этом избежав дублирования кода:
Python:
from java.awt.event import ActionListener
...
class ComboBoxListener(ActionListener):
def __init__(self, metab):
self._metab = metab
def actionPerformed(self, event):
print(event.toString())
self._metab.setMessage(self._metab._request, True)
setMessage принимает два параметра: контент, с которым нужно работать и флаг, является ли контент запросом. В нашем случае, контент всегда будет объектом запроса, но правильнее будет сохранить isRequest в самом объекте и передавать его.
При попытке теста, все рухнет. Причина в том, что расширение жестко настроено на работу с storeId и приведением значения к hex entity. Внесу правки, сначала в setMessage(), чтобы расширение корректно выводило информацию:
Python:
def setMessage(self, content, isRequest):
if content is None:
self._txtEditor.setText(None)
self._txtEditor.setEditable(False)
else:
paramName = str(self._comboParams.getSelectedItem())
param = self._helpers.getRequestParameter(content, paramName)
paramValue = self._helpers.urlDecode(param.getValue())
self._txtEditor.setText(paramValue)
self._txtEditor.setEditable(self._editable)
self._request = content
Алгоритм изменился, но не радикально. Сначала получаем выбранное в комбо боксе значение, при помощи getSelectedItem(). Это название параметра, с ним можно выполнить запрос через хелпер getRequestParameter(). Получив значение, устанавливаем. Раньше была конвертация из hex entity, но сейчас она будет мешать. Обращаю внимание на то, что _contentOld превратился в _request. Мы работает только с реквестами, поэтому так логичнее и не будет путаницы.
Расширение прекрасно работает. При смене параметра, меняется и значение. Пора переделать возврат значения в Burp, через getMessage().
Python:
def getMessage(self):
if self._txtEditor.isTextModified():
text = self._helpers.bytesToString(self._txtEditor.getText())
paramName = paramName = str(self._comboParams.getSelectedItem())
param = self._helpers.getRequestParameter(self._request, paramName)
if param.getType() in [IParameter.PARAM_XML, IParameter.PARAM_XML_ATTR]:
newValue = self._helpers.stringToBytes(self._toHexEntity(text))
newRequest = self._request[:param.getValueStart()] + newValue + self._request[param.getValueEnd():]
return newRequest
newParam = self._helpers.buildParameter(paramName, text, param.getType())
return self._extender._helpers.updateParameter(self._request, newParam)
else:
return self._request
Описываю только изменения. Как и в setMessage(), сначала получаем название параметра из списка и IParameter из self._request. Напомню, что в self._requestу нас храниться запрос. Далее мы проверяем тип параметра.
Если это XML, то конвертируем в HEX Entity и создаем новый запрос через конкатенацию. Для получения позиций подстановки использую методы параметра: getValueStart() и getValueEnd(). Благодаря им, замена значения это легкая прогулка. Кстати, иногда могут потребоваться собратья этих функций: getNameStart() и getNameEnd().
Если параметр не XML, то формирование нового запроса происходит при помощи двух функций-хелмперов: buildParameter() и updateParameter. Первая создает новый объект IParameter, а вторая заменяет им старый в указанном запросе.
Теперь, можно спокойно менять значения у разных параметров. Вторая часть завершена. Время сделать расширение еще более универсальным, добавив декодирование на входе и кодирование на выходе.
Кодировка на вход, кодировка на выход
В этой части, основная задача это создать универсальный и расширяемый механизм кодирования и декодирования. Так, чтобы можно было взять расширение и спокойно дописать в него свой собственный алгоритм, при этом без головной боли по поводу интеграции.Добавим два JComboBox: один будет отвечать за декодирование входных данных, второй за выходных. Этот процесс уже понятен. Вопрос в том, как организовать сам процесс кодирования и декодирования таким образом, чтобы можно было легко и просто добавлять новые и новые варианты. Я решил эту задачу через класс-провайдер, который аккумулирует в себе список добавленных вариантов кодирования/декодирования и просто передает управление выбранному:
Python:
from encoders.NoneEnc import NoneEnc
from encoders.HexEntity import HexEntity
class Encoder():
encoders = {
"Hex Entity": HexEntity,
"None": NoneEnc
}
string_types = ['unicode', 'str']
bite_arr_type = 'array.array'
def __init__(self, helpers):
self._helpers = helpers
def getAllEncodersName(self):
return [name for name in self.encoders]
def decode(self, encoderName, payload):
if not encoderName in self.encoders:
return payload
return self.encoders[encoderName].decode(self._preparePayload(payload))
def encode(self, encoderName, payload):
if not encoderName in self.encoders:
return payload
return self.encoders[encoderName].encode(self._preparePayload(payload))
def _preparePayload(self, payload):
if type(payload) == self.bite_arr_type:
return self._helpers.bytesToString(payload)
return payload
Что внутри?
- Словарь, в котором ключами перечислены доступные варианты, а значением классы кодирования/декодирования. При этом, каждый класс должен реализовывать, как кодирование, так и декодирование. Если нужно только кодирование или декодирование, просто делается функция-пустышка, как в NoneEnc. Просто возвращает то, что получила.
- При инициализации получаем хелперы, так как потребуется функция bytesToString(). В классах работа ведется со строками. Если захотите расширить варианты, без проблем. Но все же предполагается некая стандартизация.
- Функция getAllEncodersName() просто возвращает список строк с названиями видов кодирования/декодирования. Это нужно для заполнения выпадающих списокв.
- Братья близнецы decode/encode, отличия только в том, какая функция вызывается в итоговом классе. Предварительно, производится проверка, есть ли искомый вариант декодера в списке, если нет - возвращаем то что получили.
- _preparePayload() нужна для проверки, не отправили ли нам массив байт. Если отправили, вызываем тот самый хелпер.
Сами конечные классы очень простые. Ниже два примера. NoneEnc нужен для ситуации, когда не требуется кодирование, ну а с Hex Entity вы уже давно знакомы:
Python:
class NoneEnc():
@staticmethod
def encode(text): return text
@staticmethod
def decode(text): return text
Python:
import re
class HexEntity():
@staticmethod
def encode(payload):
return ';'.join([hex(ord(char)).replace('0x', '&#x') for char in payload]) + ';'
@staticmethod
def decode(payload):
return ''.join([chr(int(hex, 16)) for hex in re.findall('(?<=x)\w{2}', payload)])
В hex entity одно отличие - отдельные hex-значения выделяются при помощи регулярки. В заключении я про это еще напишу.
Что теперь? А теперь без проблем можно писать все новые и новые конверторы, достаточно создать класс с двумя функциями: decode и encode. После добавить в список encoders объекта EncoderProvider. После этого, конвертор появится в выпадающих списках…. Вернее реализуем это все следующим образом:
Python:
self.encoders = EncoderProvider(self._helpers)
self._allEncodes = self.encoders.getAllEncodersName()
self._comboFrom = JComboBox(self._allEncodes)
self._comboTo = JComboBox(self._allEncodes)
self._comboFrom.setSelectedItem('None')
self._comboTo.setSelectedItem('None')
listenerFrom = self.ComboBoxListener(self)
self._comboFrom.addActionListener(listenerFrom)
Создаем объект, с которым будем работать. Получаем список доступных конверторов и создаем два JComboBox, передавая в конструктор список конвертеров. Далее устанавливаем списки на None, так как изначально пользователь ничего не выбирал, попытка конверсии вызовет ошибку и ничего не произойдет.
Обратите внимание, что только к входящему конвертеру добавляю слушатель события выбора. Конвертер, который займется выходными данными, в любом случае будет выбран верно, так как мы перед отдачей будем проверять выбранный вариант кодирования.
Python:
def setMessage(self, content, isRequest):
if content is None:
self._txtEditor.setText(None)
self._txtEditor.setEditable(False)
else:
paramName = str(self._comboParams.getSelectedItem())
param = self._helpers.getRequestParameter(content, paramName)
selectedDecoder = str(self._comboFrom.getSelectedItem())
try:
paramValue = self.encoders.decode(selectedDecoder, self._helpers.urlDecode(param.getValue()))
except:
paramValue = self._helpers.urlDecode(param.getValue())
self._txtEditor.setText(paramValue)
self._txtEditor.setEditable(self._editable)
self._request = content
Функция setMessage() изменилась не сильно. Теперь проверяем не только, с каким параметром работаем и какой декодер выбран. Через провайдер передаем значение параметра выбранному пользователем декодеру. Если декодер не сможет обработать значение, то просто возьмется сырое значение параметра.
Если я проверю значение, какой декодер использовать, то зачем надо было прикручивать к комбобоксу обработчик события? Все просто — setMessage() срабатывает только при открытии вкладки расширения. Без слушателя, пользователю пришлось бы кликать туда-сюда, чтобы сработал обработчик.
Python:
def getMessage(self):
if self._txtEditor.isTextModified():
text = self._helpers.bytesToString(self._txtEditor.getText())
paramName = paramName = str(self._comboParams.getSelectedItem())
param = self._helpers.getRequestParameter(self._request, paramName)
selectedEncoder = str(self._comboTo.getSelectedItem())
try:
payload = self.encoders.encode(selectedEncoder, text)
except:
payload = text
if param.getType() in [IParameter.PARAM_XML, IParameter.PARAM_XML_ATTR]:
newValue = self._helpers.stringToBytes(payload)
newRequest = self._request[:param.getValueStart()] + newValue + self._request[param.getValueEnd():]
return newRequest
newParam = self._helpers.buildParameter(paramName, payload, param.getType())
return self._extender._helpers.updateParameter(self._request, newParam)
else:
return self._request
Здесь изменения примерно такие же. Разница в том, что запускаем encode, а не decode. Ну и на выходе наша задача не установить текстовое поле, а вернуть новый Request, которым Burp замени старый. Ну и проверка типа параметра. Думаю вы помните про особенность PARAM_XML и PARAM_XML_ATTR. С параметром типа PARAM_JSON и другими, думаю уже разберетесь. Если что, пишите, дополню.
Вроде все! Или нет? Давайте глянем на этот кусок кода, который решает работает ли наш поле или нет:
Python:
def isEnabled(self, content, isRequest):
if isRequest:
stockId = self._helpers.getRequestParameter(content, 'storeid')
if not stockId is None:
return True
return False
Что-то мне подсказывает, что это не совсем вариант для универсального решения. Вряд ли на каждом сайте есть параметр “storeid”. Правильнее будет так:
Python:
def isEnabled(self, content, isRequest):
if isRequest:
return True
return False
Теперь наше поле будет рабочим, если это запрос. .
Вместо заключения
Обычно в этом разделе я рассказываю, что изучили и какой классный инструмент получился. Но сейчас, я хочу напомнить — программировать нужно со свежей головой, отдохнувшим. Когда писал код для статьи, в какой-то момент словил жесткий затуп. Hex entity нормально переводил строку в &#x… А вот обратно творилась полная херня. Строка ‘1 UNION SELECT NULL’ конвертилась в “1 UNION SELECT NULL”, а вот обратно получалось что-то вроде “1 UI SEECT U”. Я убил часы. Я рвал на себе волосы. Пока до меня не дошло, что в hex не только 0-9, но и ABCDEF… Я даже не думал в эту сторону, пытаясь все списать на тупость Burp, тупость Java, Jython, что угодно, но не я. А достаточно было поменять регулярку с “\d{2}”, которая брала только цифры, на “(?<=x)\w{2}”.Самый прикол в том, что это произошло сразу после другого затупа. У меня просто перестала открываться вкладка расширения. Код был тот же, он 10 минут назад работал, а сейчас нет. И никаких ошибок. Просто не открывается вкладка. Чтобы я не делал. Уже все закоментил, потом раскоментил. Думал это черная магия. Оказалось, что это… в этот раз это косяк Burp))))
Когда видна только часть вкладки, нет стрелочки чтобы появилось всплывающее меню и можно было бы выбрать вкладку расширения. При этом, Burp абсолютно не понимает, что вы кликаете на эту вкладку. Как решить? Нет, не как я, тратя полтора часа на правки кода… Достаточно сдвинуть разделитель влево или вправо, чтобы название вкладки было видно полностью или появилась стрелочка для всплывающего меню.
Вот так друзья. Не забывайте отдыхать. И, если что-то работает не так как вы ожидали, лучше отдохните и отвлекитесь на приятные вещи. Сексом что ли займитесь или с собакой погуляйте. После проблема решиться за считанные минуты.
Надеюсь вам понравилось! Впереди еще много крутых тем!
https://gist.github.com/ncoblentz/4420532
Вложения
Последнее редактирование: