Перед тем, как начать статью, скажу что тема очень объемная. Если честно, я сильно обалдел, когда увидел сколько получилось материала. Но из песни слов не выкинуть, хоть я очень старался… Что вы узнаете из этой темы:
Настало время для создания взрослых расширений. До этого, мы дополняли сканирование своими чекерами, создавали точки инъекции и т.д. Но вся мощь расширений включается в тот момент, когда у расширений появляется свой собственный интерфейс пользователя, возможность взаимодействия с пользователем и автономной работы в рамках инфраструктуры Burp.
Burp написан на Java, интеграция с API происходит через систему листенеров и коллбэков. По факту, расширение плотно встраивается в процесс работы, в отличии от работы через тот же REST API. Поэтому, написанный нами код на Python, при помощи jython-standalone-2.7.3.jar, проходит прекомпиляцию в Java Сlass. После, Burp взаимодействует с нашим расширением, как с обычным объектом Java. Соответственно, этот позволяет нам импортировать и использовать Java-библиотеки, в том числе Java.Swing для создания пользовательских интерфейсов.
Для справки. Расширения написанные на Ruby также компилируются при помощи Jruby.
Не знаю, есть ли альтернативы по GUI в Java, мы будем использовать Java Swing. Но я не являюсь Java-программистом, возможно что-то упускаю. Поэтому, если будет у статьи легкий душок дилетантства, не удивляйтесь. Если вдруг здесь окажется Java-программист, прости пожалуйста)))
В этой статье, рассмотрим некоторые компоненты и построим свой первые интерфейс для расширения в Burp. Итогом статьи будет полноценное рабочее решение. За основу возьму оповещатель в Telegram из предыдущих статей и соберем более-менее адекватный интерфейс, предоставив возможность выбирать когда стоит присылать расширение, а когда не нужно.
Код телеграм нотификатора
Почему вообще важна вся эта история с иерархией классов? Java, на мой взгляд, это наиболее приближенный к принципам SOLID язык программирования даже на уровне самой философии языка. Поэтому, вниз по иерархии объекты обрастают свойствами и методами. Таким образом, есть некие универсальные доступные методы, доставшиеся от родителей, которые свойственны всем компонентам. Есть и специфические уникальные возможности, которые появляются на одной из веток потомков. Например, JCheckbox обладает, как специфическими функциями для управления выбором(isSelected), так и общими с тем же JLabel (например, getHeight), доставшимися от JComponent.Соответственно, видя иерархию наследования, мы можем понимать, какие методы и свойства нам будут доступны. Ориентироваться, где искать те или иные нужные нам компоненты, например, события. Если же мы потерялись и заблудились, понимая структуру объектов, сильно быстрее сможем найти нужную информацию в справке.
Что нам важно знать? Java Swing построен на базе Java Abstract Widget Toolkit. У нас будут импорты из обоих библиотек. Если точнее, компоненты будут импортироваться из javax.swing, события из java.awt.event.
Чтобы реализовать класс нашего таба, достаточно в него добавить две функции: getTabCaption(), возвращающую ничто иное, как название; и getUiComponent(), которая возвращает содержимое нашей вкладки в виде компонента java.awt.Component. Для добавления вкладки, просто регистраторе коллбэков передаем компонент методу addSuiteTab()/
Реализуем простой интерфейс состоящий основной панели (JPanel), как основного компонента отображения, и нескольких кнопок для того, чтобы у нас хотя бы что-то уже было в интерфейсе:
Возвращаясь к вопросу о иерархии. Как видно, GUI тоже строиться иерархично: на родительский компонент помещаются дети. В целом, построение интерфейсов в Java так и происходит. Сделали контейнер, в него поместили еще несколько контейнеров, на каждый контейнер разместили какие-то управляющие компоненты. Некоторые контейнеры создаются для того, чтобы просто была возможность разбить область экрана на части.
Загружаем в Burp расширение и видим такую замечательную картинку:
Собственно, что мы сделали? Создали основную панель к которой прикрутили несколько кнопок.Основной панелью, я называю окно на котором все выводится. Для тех, кто писал оконные интерфейсы, это что-то типа формы. Просто формы встроенной в другой интерфейс. Если хотите, <body></body> в HTML. Движок Java должен понимать, что и где рисовать, вот мы ему и даем пространство.
Кнопки представляют собой JButton объекты. В конструктор мы передали просто надпись. Точно так же, можно создать надпись (Jlabel), чекбокс (JCheckbox) и т.д. Есть разные варианты, можно передавать в конструктор событие. Собственно, в зависимости от задачи, будем использовать, как вариант с передачей события, так и с привязкой. Позже станет понятно почему.
Надписи на кнопках выбраны не просто так, они потребуются для следующей демонстрации. Как видите, интерфейсы строятся путем добавления дочерних компонентов, при этом управление расположением несколько своеобразное. Сейчас расположение идет просто потоком. Для изменения этого поведения необходимо использовать LayoutManager.Который будет помогать нам распределять компоненты по доступномному пространству (нашей основной панели). Конструктор JPanel принимает менеджер пространства как аргумент.
Подключим BorderLayout — это компонент, который позволяет управлять пространством внутри интерфейса. Менеджер полностью соответствует названию, предлагая прижимать компоненты к краям. По факту, разбивая плоскость на пять основных частей: PAGE_START, PAGE_END, LINE_START, LINE_END, CENTER. Чтобы понять смысловую нагрузку BorderLayout, достаточно запустить расширение с ним и без него.
Это все те же пять кнопок, но расположены они под управлением BorderLayout. Как видно,они по-прижимались к границам, центральный компонент растянулся и занял основное пространство. Это классическое представление оконного интерфейса: вверху меню, внизу статусбар, основная рабочая область в центре и два возможных сайдбара. Еще раз обращаю внимание на то, что кнопки растянулись и заняли все предоставляемое менеджером пространство.
К слову сказать, BorderLayout это не единственный менеджер пространства. Есть еще такие варианты (с коротким описанием):
То, что я очень поверхностно описал каждый из менеджеров, не означает что к каким-то из них стоит относиться легкомысленно и т.п. Они все созданы под конкретные задачи и прекрасно с ними справляются. Главное понимать, когда и какой использовать. Но это прям очень серьезная и большая тема и, по сути, здесь излишняя. Это арсенал Java-программистов, а значит для нас достаточно специфичен, кому надо есть смысл покопаться в справке и попробовать создать разные интерфейсы. Вся информация есть в справке по Swing,
В целях демонстрации и построения боль-мень удобного интерфейса, воспользуемся несколькими менеджерами: BorderLayout, как способ огранизовать общее пространство (сделать вверху красивую шапочку); BoxLayout, как способ удобно организовать некоторые отдельные пространства; неявно FloatLayout и, в основном, AbsoluteLayout. Важно понимать, что каждый из них, работает по разному. Это касается не только возможностей в указании координат, отступов и других параметров пложения на плоскости, но и размеров компонент. Помните пример BorderLayout с кнопками? Они растянулись и заняли все пространство. Подобное поведение может быть, как огромным плюсом (не нужно пересчитывать размеры и положение), так и минусом, если не понимать, что делаешь.
К слову сказать, некоторые другие Layout Managers рассмотрим в следующих статьях. Здесь я исходил из того, чтобы максимально быстро и максимально просто можно было собрать интерфейс. Именно по этой причине, практически все с четким позиционированием. Минус один - на разных мониторах, совершенно по разному будет отображаться. Где-то может и не влезть в размерную сетку.
Тренировки ради, заставим “жить” центральную кнопку. Вернее, прикрутим к ней событие. Например, при нажатии на кнопку пусть выскакивает сообщение. Для этого используем коллбэк issueAlert(String message), который выводит в лог Burp сообщение. Как понятно из объявления, метод принимает строку.
Как видно, изменения в коде небольшие. Для генерации события, использовали параметр конструктора JButton actionPerformed, которому передали функцию обрабатывающую событие. Ровно тоже самое было в предыдущей статье, когда оживляли собственный пункт меню. Как и писал выше, благодаря иерархии компонентов, появляется универсальность, в т.м. универсальные события. .
В “Event log” появилось сообщение. Как видно по счетчику [7], кликнул по кнопке семь раз. Отлично! Мы умеем создавать элементы GUI и присваивать обработчики событий. Причем, кто читал прошлую статью, умеют работать с событиями двумя способами. Если не читали, рекомендую прочитать.
В общем, в идеале хотелось бы видеть что-то типа такого:
Сразу предполагаю наличие основного файла, смысл которого в регистрации необходимым коллбэков и связки разных частей проекта.
Так как у нас будет работать бот оповещений в Телеграм, логично реализовать весь функционал касающийся работы с апи TG отдельным объектом. Задача объекта — просто пересылать в чат, полученные сообщения. Ну и механизм чтения/хранения токена и идентификатора чата.
Чтобы реагировать на возникающие уязвимости, нам нужен слушатель их появления. Как вы помните, для этого в Burp есть IScannerListener. Соответственно, во избежание нагромождения, выведем в отдельный объект. Хотя, момент спорный. Сам код объекта небольшой и размещение его в основном классе сильно не напрягает. Более того, выше у нас возник объект бота оповещений, который нужен как в листенере уязвимостей, так и в настройках. Мы же должны организовать сохранение из интерфейса… в итоге, реализация в одном классе избавляет нас от необходимости передавать объект бота и как-то его контролить. Если у вас возникнет желание переписать объект слушателя в основной класс, это неплохой повод для тренировки. Я же реализую в отдельном классе и отдельным файлом, мне так удобнее и понятно что куда относится.
Интерфейс, конечно же, тоже отдельный класс в отдельном файле. Стоит сказать, что самый большой файл.
Интерфейс — это визуальное отображение настроек. Помимо отображения, нужны и данные с которыми можно работать. Настройки должны где-то храниться, а значит загружаться и сохраняться. Мы же не хотим пользователя заставлять каждый раз заново настраивать уведомления? Кроме того, опрашивать элементы интерфейса, во время сканирования, не очень удобно. Гораздо лучше, если у нас в памяти есть объект с настройками и функция чекер оперативно может определить, надо ли отправлять уведомление. В итоге, у нас еще один класс, который будет обрабатывать данные со всеми настройками, кроме ТГ-бота.
Интерфейс вертикально разбит на три основных блока, при помощи BorderLayout(): вверху у нас красивый хедер, внизу кнопка сохранения настроек, а по центру основное рабочее пространство. В коде это выглядит просто и уже знакомо:
Понятное дело, что сейчас на этот скелет будем натягивать мясо. Просто разделяю, чтобы порционно выдавать информацию. Из нового, что здесь есть, это FlowLayout(FlowLayout.CENTER, 10,20), в первую очередь. Данная строчка явно задает менеджер пространства Flow с центральным позиционированием и отступами 10 и 20. Другими словами, внутри панели pnlHead, элементы будут центрироваться по горизонтали. Отступы по горизонтали будут 10 пикселей, по вертикали 20 пикселей. Отступы между ЭЛЕМЕНТАМИ, а не просто паддинги внутри блока.
Строка pnlBody.setLayout(None) тоже встречается впервые. Как из нее понятно, мы вызываем функцию назначенную LayoutManager, но передаем туда ничего. Подобным образом, мы сбрасываем менеджер пространства, назначенный по умолчанию. Если помните, выше я говорил, что по умолчанию назначается FlowLayout без параметров. Передав None, мы переключились в режим AbsoluteLayout. Причем, это режим только для панели pnlBody. В итоге, верх и низ у нас полностью управляется BorderLayout(), а центральную часть будем рисовать вручную, указывая точные координаты каждого элемента. Давайте нарисуем красивую шапку:
Первым делом, используя Java-класс Color, создаем объект цвета. Он нам потребуется, чтобы создать красивый задний фон. Назначается фон, соответственно, функцией setBackground(Color). Важный момент, при назначении цвета объектам JLabel() нужно назначить непрозрачность через setOpaque(True). Если этого не сделать и менеджер растянет нашу надпись, получится вот так:
Чтобы вывести логотип, потребуется объект ImageIcon из javax.swing. Далее все просто — передаем в конструктор путь к файлу картинки. Для этого прямо здесь импортирую “os” и стандартными способами формирую полный путь к файлу. Завершающие этапы, это передать объект картинки в конструктор JLabel и прикрепить его к нужной панели.
Для заголовка и описания создал два JLabel и объединил их в одну панель. Причина в том, чтобы разместить их друг под другом. В итоге, появляется панель с менеджером BoxLayout и направлением Y_AXIS (один под другим). Назначение менеджера происходит через setLayout с передачей в конструктор объекта, которому назначаем и нужной осью направления.
Шрифт для заголовка создается при помощи Font(), которому передается название шрифта, стиль и размер. Font импортируется из java.awt.
Каждый элемент для вывода, в итоге прикрепляется к родительскому элементу через метод add(). Например, pnlHead.add(pnlTitle)
В конструктор JButton передаем событие actionPerformed=self.onClickSaveSettings, само событие опишу позднее. Пока только про визуал. Из нового здесь только назначение цвета текста, через setForeground() и использование константы из класса Color. Да, есть стандартный набор цветов, которыми можно пользоваться. Думаю, здесь все уже знакомо и понятно.
Функцию тела будет играть компонент с именем pnlBody, который в самом конце прикреплю к pnlMain.
Координаты x и y нужны как точка отсчета, относительно которой будут располагаться элементы. Пример добавления текстового поля для ввода токена:
В конструктор JTextField передается размерность, хотя в данном случае это скорее для примера, так как ниже задаю ширину. Текстовое поле создается без границ, просто белый прямоугольник в который можно тыкнуть, но это как-то неприлично… при помощи setBorder() задаю рамки. В конструктор передается специальный объект BorderFactory, вернее вызов его статического метода createLineBorder(). Из названия понятно, какой именно тип бордера будет создан. Больше вариантов границ можно найти в справке по BorderFactory. Из интересного, создание пустой границы - при помощи такого подхода можно изменить позиционирование, например, создав пустую границу слева, толщиной в 50 пикселей.
Думаю, что с параметрами createLineBorder все понятно. Разве что True, это флаг означающий, что нужно скругление границ.
setBounds(), логично, задает координаты и размер. Параметры: x, y, width, height. После остается только добавить на панельку. Таким образом, почти, со всеми элементами.
Как писал выше, предполагается огромное количество чекбоксов. При этом, каждый чекбокс это один и тот же набор операций. От примера выше отличие только в том, что ссылки на чекбоксы сохраняются в классе. Поэтому, написал простую функцию generateCheckboxed():
Функция получает панельку, к которой необходимо добавить новые чекбоксы. Начальные координаты, для размещения на панельке. Название свойства глобального объекта propName и является ли название подсвойством.
Касаемо глобального объекта — это объект с настройками нашего расширения. Уже касался его в части “Структура проекта”, подробнее опишу позже. Пока стоит предполагать, что будет некий глобальный объект, с которым и будет происходить взаимодействие.
Исптория с именем в виде субсвойства, это тоже про глобальный объект. Но чтобы было понятнее, данная особенность внесена из-за типа добавляемой уязвимости. В слушателе IScannerListener мы можем получить идентификатор уязвимости, вызвав getIssueType(). Тип возвращается в виде целого числа. Можно получить название уязвимости, вызывав специальный коллбэк Burp и по названию сравнивать, надо ли отправлять уведомления в телеграм. Но я выбрал вариант, когда сравнение ведется по числовому типу уязвимости, от чего эта часть объекта выглядит так:
При этом, все что касается остальных галочек, выглядит так:
Отсюда костыль в виде того, как получить надпись для чекбокса. Либо это само название свойства, либо название свойства это число, а нужная надпись находится в свойстве “name” объекта.
Сам цикл прост, как три копейки. Каждую итерацию смещаем позицию y, которая для удобства храниться в pos. Создаем новый чекбокс, вызвав конструктор JCheckBox() и передав ему надпись и состояние чекбокса. Состояние, соответственно, берется из глобального объекта, свойство “state”. Таким образом, уже при создании интерфейса, расставляются галочки сохраненные пользователем.
Все чекбоксы помещаю в список elems, который сохраняю в одноименном объекте нашего класса интерфейса. И тут же возвращаю. Сохранение в классе производится в определенное свойство, которое относится к конкретному разделу интерфейса:
Это потребуется на этапе реализации логики. Return значения делается изначально в связи с реализацией поиска и к этому мы вернемся ниже. Второй смысл в том, что получив обратно список элементов, мы можем получить их количество и рассчитать позиции где надо располагать следующие части интерфейса. Правда я этого не стал делать, схитрив — количество галочек, в двух блоках из трех, известно заранее и можно сразу их прописать.
Да, возврат значения, это дублирование. Но мне комфортнее было написать именно так. В конце-концов, когда мы пишем код, мы должны понимать, что через время будем его снова читать. И очень важно писать так, чтобы мы его могли понять.
Последний интересный момент, в отношении интерфейса, это использование JScrollPane. Это элемент, который содержит в себе панель и имеет возможность скролла. По аналогии с iframe в HTML. Если мы не знаем итоговую размерность или она слишком большая, выделяем ограниченную область в JScrollPane (без L на конце) и запихиваем в нее наш JPanel (c L на конце). В нашем случае, данный компонент прекрасно решает задачу с размещением все типов уязвимостей.
Чтобы обернуть панель в JScrollPane надо передать панель, которая уже собрана, в конструктор JScrollPane. После установить размеры панели с прокруткой и прикрепить ее к нужному элементу вывода через add.
Выше создание скролл-панели. Сначала создал JPanel, далее вызывал функцию генерации чекбоксов, указав, что название уязвимости лежит в свойстве “name”. Добавление события рассмотрю ниже. Далее создаю JScrollPane, передав нашу панельку. Назначаю координаты и прикрепляю к нашей центральной части pnlBody. Ниже весь код файла интерфейса.
Это основной класс интерфейса. При инициализации объект сохраняет ссылки на коллбэки, объект с настройками оповещений и ссылку на объект реализующий все связанное с телеграм. Функция getTabCaption() нужна чтобы задать название вкладки в интерфейсе. getUiComponent() является провайдером, создавая объект нашего интерфейса. Можно было все выполнить внутри одного объекта. Но потом, когда нужно будет вернуться к этому коду, будет гораздо удобнее понять смысл файла по его началу. getUiComponent(), как и писал выше, должна возвращать JPanel с интерфейсом, что и делает функция createGUI(). Самое время поговорить о событиях в нашем интерфейсе, подарить ему жизнь.
Привязка обработчика происходит через параметр actionPerformed конструктора. Внутри самой функции происходит вызов метода сохранения глобального объекта. Все просто, как три копейки. Тоже происходит и при сохранении токена и chatID телеграм:
Все кнопки привязаны подобным образом. Небольшие отличия только в том, что конкретно они делают. Ниже три кнопки, которые относятся к поиску: выделить все, снять выделение, очистить поиск. Кстати, особенность кнопок, отвечающих за выделение, в том, что они работают только с видимыми чекбоксами. Другими словами, если вы вбили в поле фильтра “sql”, галочки проставятся/снимутся только для подходящих уязвимостей. Если нужно включить или выключить все чекбоксы, нужно очистить поле фильтра.
showAllVulnsCheckbox() - функция атавизм, ранее она нужна была чтобы избежать дублированного кода, теперь оставил “на всякий случай”. Интерфейс может поменяться, да и в целом, вынос действий в отдельные функции это хороший тон.
Чтобы функция заработала, её достаточно добавить в класс class MainPanel(). При этом, не нужно создавать новое событие, достаточно в конструктор JTextField для txtVulnsSearch передать на неё ссылку через actionPerformed=self.changeSearch. Сначала функция получает ссылку на текстовое поле при помощи функции getSource(). По цепочке, получает текст и приводит его к нижнему регистру. Если текст пустой, то выводятся все чекбоксы. Если нет, проходит по списку, неподходящие прячет, подходящие показывает. Единственный недостаток функции в том, что событие передаваемое через actionPerformed относиться к ActionListener, а там нет обработки для ввода или нажатий клавиш. В результате, поле работать будет, но нужно нажать Enter, чтобы случилась фильтрация.
Более универсальный и корректный способ, это обрабатывать пользовательский ввод с клавиатуры. Для этого нужно импортировать DocumentListener из javax.swing. Для этого пришлось использовать хук:
Импортировал объект с событиями, как SingEvent, а дальше уже на его основе создал класс реализующий обработчик событий типа DocumentListener. Именно здесь находится все необходимое для того, чтобы можно было обрабатывать текстовый ввод “на лету”. Если конкретнее, внутри класса нужно реализовать следующие функции: changedUpdate(event), insertUpdate(event), removeUpdate(event).
Нам не нужно какое-то специфическое поведение для каждого отдельного случая, поэтому я просто в каждой из функций передал управление третьей функции:
Что происходит? В конструкторе получаю ссылки на чекбоксы уязвимостей. Внутри событий:
В принципе, за исключением того, что обрабатывается другой тип событий и через класс, отличий никаких. Разве что добавлен обработчик следующим образом:
txtVulnsSearch.getDocument().addDocumentListener(DocumentSearchListener(vulnsCheckboxes))
Код класса обработчика:
Фишка в том, чтобы запомнить для каждого отдельного чекбокса, к какому конкретно свойству настроек он привязан. Дальше, когда пользователь кликает на чекбокс, actionPerformed вызывает метод updateParam() глобального объекта настроек. Кстати, думаю теперь наглядно видно, почему в конструктор объекта обработчик передается как actionPerformed. Просто это единственный необходимый метод для реализации события ActionListener.
Хранить токен будем в файле “.env”, скорее как дань моде. Для сохранения в файл, нам ничего не нужно. Все реализуется стандартным способом через open(). Файл будем хранить прямо в папке с расширением. Хотя ничто не мешает вам, при желании, закинуть файл куда угодно, os прекрасно работает.
Единственный момент, как хранить данные в памяти? Наиболее простой вариант, это сделать отдельный класс с методами для сохранения и загрузки. При загрузке расширения создавать объект класса и передавать ссылку на него в слушатель уязвимостей. Причем, раз уж разделил, то написать два схожих класса. Настройки, к слову, оптимально хранить в JSON. Загружать его в словарь по которому потом будет удобно работать со списком уязвимостей.
Это те самые глобальные настройки, которые хранятся глобально и ссылка на которые передается от объекта к объекту. Вообще, чтобы избежать путаницы, уже назрел момент разобраться с общей структурой объектов и их взаимодействия. Я подготовил картинку:
Так путешествуют данные и события. Сначала BurpExtender создает объект бота TgBotNotify и настроек ExtSettings. При создании, они получают данные из файлов, после чего готовы передать все в интерейс и слушатель. Следующим шагом, создается интерфейс GUITab, куда передается объект settings типа ExtSettings. На основе settings устанавливаются галочки (см. выше в функция генерации галочек). Последний этап, это создание листенера IScannerListener. В него передаются настройки и телеграм бот. Настройки нужны для проверки, надо ли отправлять уведомление по конкретной уязвимости. За это отвечает функция checkNeedToNotify(). Если надо отправить сообщение, листенер передает данные в send_message() бота с типом TgBotNotify. Надеюсь, что теперь отпадет большинство вопросов.
Важный момент, что все объекты передаются по ссылке. Это значит, что все объекты имеющие ссылку на объект, имеют доступ к одному и тому же участку памяти. Другими словами, изменение галочек в интерфейсе напрямую влияет на работу листенера уязвимостей. Даже без сохранения настроек, листенер будет принимать во внимание установленные галочки.
Начнем реализацию.
Здсь все, как и описал на схеме. Создаем все объекты, перекидывая “себя” в качестве параметра. Почему себя? Чтобы не дробить на части, а получить все из одного класса. Установили название, создали интерфейс, зарегистрировали слушатель новых уязвимостей.
Чтение файла производится стандартным для пайтона методом. Далее регуляркой парсится на token и chat_id. На этом работа бота, пока, прекращена. До момента, когда по-требуется сохранение данных или же расширение примет решение, что пора отправить уведомление. Весь код бота:
Если я выше не забыл написать)))) При загрузке данных, токен в текстовом поле не отображается. Отображаются звездочки. Поэтому, перед сохранением, проверяется надо ли обновлять токен.
Соответственно, первые две функции это загрузка и сохранение данных. Раз настройки у нас хранятся в JSON-объекте, то и загрузка сохранение у них соответствующие:
Функция обновления параметров updateParam() — параметр меняется прямо в памяти, без записи. Сначала функция понимает, имеет ли она дело с вложенным параметром и обновляет соответствующим образом. Сделано это по из-за наличия опции “Notify me of vulnerabilities found that are added by extensions”, которая единственная храниться не в виде массива, а единичным значением:
Сама функция обновления параметра:
Наверное, одна из самых главных функций - checkNeedToNotify(). Она проверяет, нужно ли уведомлять пользователя. Как писал выше, проверка ведется по актуальным настройкам. Если пользователь сменил настройки и не сохранил, в файле какие-то галочки будут снятыми, а в памяти поставленными.
Все просто. На вход функция получает данные из найденной уязвимости: уверенность, уровень риска и тип уязвимости. Далее проходит по серии if’ов. Если хоть один не совпадает, функция возвращает False и оповещение не отсылается. Тип уязвимости проверяется по номеру. При этом, если установлена галочка “Оповещать о пользовательских уязвимостях”, проверяется есть присланный тип уязвимости в списке или нет. Соответственно, пользовательских уязвимостей не может быть в стандартном списке Burp.
Настройки нотификатора выглядели так:
Для второго теста потребуется расширение из второй статьи, а именно — добавление точки инъекции. Возьмем уже замученный нами пример с SQL Injection. В настройках расширения, отправляющего уведомления в телеграм, убираю все уязвимости и выбираю только то, что относиться к SQLi.. В настройках “Уверенности” (Confidence) добавляю галочку на “Firm”, ведь именно такой уровень уверенности вернет сканер. Все, приготовления закончены, как и в прошлом примере захожу в магазин, который создала лаба и делаю запрос остатков по любому товару. Запускаю обычное активное сканирование.
Через полторы-две минуты вижу, что уязвимость обнаружена, но в бот ничего не прилетело. Хм… неприятненько. Хотелось как по маслу, но нет. Добавляю вывод параметров перед отправкой сообщения, а также распечатываю код ответа и текст ответа.
Упс… вот чего не предусмотрел. Телеграм принимает далеко не все тэги. Как минимум, с <BR> у нас проблемы. Как в анекдоте, у нас два путя: сделать подмену <br> на “\n” и это будет работать. Либо, составить белый список тэгов и сносить все не относящееся к делу. Учитывая, что нам нужно просто оповещение и каких-то жестких требований к красоте оповещения мы не задаем, можно действовать в лоб: заменить все <br> на перенос строки (все же для читаемости стоит оставить), после заменить все тэги, кроме <b> на пустоту:
Запускаю новую проверку и получаю соответствующее уведомление.
Но главное, что все поставленные вопросы были разобраны, а на выходе получилось действительно полезное расширение. Надеюсь вам понравилось.
P.S.
Когда пишите расширение и часто его заново загружаете Burp, контролируйте память! У меня в какой-то момент за 15Gb перевалило. Burp не очищает память, когда вы перезагружаете расширение.
- Как сделать свою вкладку в Burp с вашим интерфейсом
- Как строятся интерфейсы в Jython на базе Java.Swing, в частности для Burp
- Что такое менеджеры пространства и какие отличия
- Как создавать разные элементы интерфейса (label, button, checkbox etc.)
- Разные виды событий элементов и разные способы прикрепления
- Сохранение и чтение файлов из расширения Burp, включая загрузку картинок
- Сделаете вполне рабочее и полезное расширение для Burp.
Настало время для создания взрослых расширений. До этого, мы дополняли сканирование своими чекерами, создавали точки инъекции и т.д. Но вся мощь расширений включается в тот момент, когда у расширений появляется свой собственный интерфейс пользователя, возможность взаимодействия с пользователем и автономной работы в рамках инфраструктуры Burp.
Burp написан на Java, интеграция с API происходит через систему листенеров и коллбэков. По факту, расширение плотно встраивается в процесс работы, в отличии от работы через тот же REST API. Поэтому, написанный нами код на Python, при помощи jython-standalone-2.7.3.jar, проходит прекомпиляцию в Java Сlass. После, Burp взаимодействует с нашим расширением, как с обычным объектом Java. Соответственно, этот позволяет нам импортировать и использовать Java-библиотеки, в том числе Java.Swing для создания пользовательских интерфейсов.
Для справки. Расширения написанные на Ruby также компилируются при помощи Jruby.
Не знаю, есть ли альтернативы по GUI в Java, мы будем использовать Java Swing. Но я не являюсь Java-программистом, возможно что-то упускаю. Поэтому, если будет у статьи легкий душок дилетантства, не удивляйтесь. Если вдруг здесь окажется Java-программист, прости пожалуйста)))
В этой статье, рассмотрим некоторые компоненты и построим свой первые интерфейс для расширения в Burp. Итогом статьи будет полноценное рабочее решение. За основу возьму оповещатель в Telegram из предыдущих статей и соберем более-менее адекватный интерфейс, предоставив возможность выбирать когда стоит присылать расширение, а когда не нужно.
Код телеграм нотификатора
Java.Swing
Это набор компонентов графического интерфейса (GUI). Вдаваться в подробности не вижу смысла, главное что в Swing есть все для создания интерфейса вне зависимости от используемой платформы. Все, как принято в Java - лишь бы была установлена Java-машина. Вот стырил картинку с иерархией объектов. Нас будет интересовать все или почти все, что входит в JComponent.
Почему вообще важна вся эта история с иерархией классов? Java, на мой взгляд, это наиболее приближенный к принципам SOLID язык программирования даже на уровне самой философии языка. Поэтому, вниз по иерархии объекты обрастают свойствами и методами. Таким образом, есть некие универсальные доступные методы, доставшиеся от родителей, которые свойственны всем компонентам. Есть и специфические уникальные возможности, которые появляются на одной из веток потомков. Например, JCheckbox обладает, как специфическими функциями для управления выбором(isSelected), так и общими с тем же JLabel (например, getHeight), доставшимися от JComponent.Соответственно, видя иерархию наследования, мы можем понимать, какие методы и свойства нам будут доступны. Ориентироваться, где искать те или иные нужные нам компоненты, например, события. Если же мы потерялись и заблудились, понимая структуру объектов, сильно быстрее сможем найти нужную информацию в справке.
Что нам важно знать? Java Swing построен на базе Java Abstract Widget Toolkit. У нас будут импорты из обоих библиотек. Если точнее, компоненты будут импортироваться из javax.swing, события из java.awt.event.
Первый GUI
Начнем потихоньку собирать простые интерфейсы. Для этого нам потребуется интерфейс ITab, который предоставляет API Burp Extender. Благодаря ему, мы можем создать полноценную вкладку для своего расширения в BurpSuite. В целом, вам уже должен быть привычным процесс: берем интерфейс и пишем к нему класс-потомок, после в функции регистрации коллбэков…Чтобы реализовать класс нашего таба, достаточно в него добавить две функции: getTabCaption(), возвращающую ничто иное, как название; и getUiComponent(), которая возвращает содержимое нашей вкладки в виде компонента java.awt.Component. Для добавления вкладки, просто регистраторе коллбэков передаем компонент методу addSuiteTab()/
Реализуем простой интерфейс состоящий основной панели (JPanel), как основного компонента отображения, и нескольких кнопок для того, чтобы у нас хотя бы что-то уже было в интерфейсе:
Python:
from burp import IBurpExtender, ITab
from javax.swing import JPanel, JButton
class BurpExtender(IBurpExtender):
def registerExtenderCallbacks(sefl, callbacks):
callbacks.addSuiteTab(GUITab(callbacks))
class GUITab(ITab):
def __init__(self, callbacks):
self._callbacks = callbacks
self._helpers = callbacks.getHelpers()
def getTabCaption(self):
return 'Telegram Notify'
def getUiComponent(self):
mainPanel = JPanel()
button1 = JButton("PAGE_START")
button2 = JButton("CENTER")
button3 = JButton("PAGE_END")
button4 = JButton("LINE_START")
button5 = JButton("LINE_END")
mainPanel.add(button1)
mainPanel.add(button2)
mainPanel.add(button3)
mainPanel.add(button4)
mainPanel.add(button5)
return mainPanel
Возвращаясь к вопросу о иерархии. Как видно, GUI тоже строиться иерархично: на родительский компонент помещаются дети. В целом, построение интерфейсов в Java так и происходит. Сделали контейнер, в него поместили еще несколько контейнеров, на каждый контейнер разместили какие-то управляющие компоненты. Некоторые контейнеры создаются для того, чтобы просто была возможность разбить область экрана на части.
Загружаем в Burp расширение и видим такую замечательную картинку:
Собственно, что мы сделали? Создали основную панель к которой прикрутили несколько кнопок.Основной панелью, я называю окно на котором все выводится. Для тех, кто писал оконные интерфейсы, это что-то типа формы. Просто формы встроенной в другой интерфейс. Если хотите, <body></body> в HTML. Движок Java должен понимать, что и где рисовать, вот мы ему и даем пространство.
Кнопки представляют собой JButton объекты. В конструктор мы передали просто надпись. Точно так же, можно создать надпись (Jlabel), чекбокс (JCheckbox) и т.д. Есть разные варианты, можно передавать в конструктор событие. Собственно, в зависимости от задачи, будем использовать, как вариант с передачей события, так и с привязкой. Позже станет понятно почему.
Надписи на кнопках выбраны не просто так, они потребуются для следующей демонстрации. Как видите, интерфейсы строятся путем добавления дочерних компонентов, при этом управление расположением несколько своеобразное. Сейчас расположение идет просто потоком. Для изменения этого поведения необходимо использовать LayoutManager.Который будет помогать нам распределять компоненты по доступномному пространству (нашей основной панели). Конструктор JPanel принимает менеджер пространства как аргумент.
Подключим BorderLayout — это компонент, который позволяет управлять пространством внутри интерфейса. Менеджер полностью соответствует названию, предлагая прижимать компоненты к краям. По факту, разбивая плоскость на пять основных частей: PAGE_START, PAGE_END, LINE_START, LINE_END, CENTER. Чтобы понять смысловую нагрузку BorderLayout, достаточно запустить расширение с ним и без него.
Python:
from burp import IBurpExtender, ITab
from javax.swing import JPanel, JButton
from java.awt import BorderLayout
class BurpExtender(IBurpExtender):
def registerExtenderCallbacks(sefl, callbacks):
callbacks.addSuiteTab(GUITab(callbacks))
class GUITab(ITab):
def __init__(self, callbacks):
self._callbacks = callbacks
self._helpers = callbacks.getHelpers()
def getTabCaption(self):
return 'Telegram Notify TEST'
def getUiComponent(self):
mainPanel = JPanel(BorderLayout())
button1 = JButton("PAGE_START")
button2 = JButton("CENTER")
button3 = JButton("PAGE_END")
button4 = JButton("LINE_START")
button5 = JButton("LINE_END")
mainPanel.add(button1, BorderLayout.PAGE_START)
mainPanel.add(button2, BorderLayout.CENTER)
mainPanel.add(button3, BorderLayout.PAGE_END)
mainPanel.add(button4, BorderLayout.LINE_START)
mainPanel.add(button5, BorderLayout.LINE_END)
return mainPanel
Это все те же пять кнопок, но расположены они под управлением BorderLayout. Как видно,они по-прижимались к границам, центральный компонент растянулся и занял основное пространство. Это классическое представление оконного интерфейса: вверху меню, внизу статусбар, основная рабочая область в центре и два возможных сайдбара. Еще раз обращаю внимание на то, что кнопки растянулись и заняли все предоставляемое менеджером пространство.
К слову сказать, BorderLayout это не единственный менеджер пространства. Есть еще такие варианты (с коротким описанием):
- AbsoluteLayout (он же Null) - менеджер не управляет пространством, давая нам возможность лепить элементы куда угодно, указывая их координаты и размеры.
- BoxLayout - складывает “коробки” рядами или друг на дружку. Коробки, это компоненты, а аллегория больше для понимания. При привязке этого менеджера, вы заранее определяете, как размещать компоненты - по оси X или по оси Y.
- CardLayout - чтобы понять его работу, проще представить работу с закладками. Не теми, что ищет веселый наркоман в кустах под окном, а закладками в браузере. В один момент времени, отображается только одна карточка.
- FlowLayout - стандартный вывод. Если не указали, то компоненты просто идут друг за другом в ряд, пока не закончится место. Если следующий компонент не влазит, менеджер поместит его на уровень вниз, начиная таким образом новый ряд.
- GridLayout - табличка, в которой мы заранее задаем количество строк и столбцов, а потом по ячейкам раскладываем наши элементы управления.
- GridBagLayout - тоже, что и выше, но более гибкое. Другое управление размерностью сетки, управление занимаемым пространством и т.д. Многие говорят, что это самый крутой и удобный менеджер.
- GroupLayout - как и следует из названия, пространство разбивается на группы, какие-то группы объединяются, добавляются новые… если вы видите интерфейс, где есть GroupLayout, скорее всего он был построен в конструкторе. Хотя, можно и руками. Мощная штука для строения GUI, но если его пытаться читать с непривычки, фляга может потечь. По крайней мере у меня…
- SpringLayout - при построении пространства вы задаете набор ограничений для каждого контейнера.
То, что я очень поверхностно описал каждый из менеджеров, не означает что к каким-то из них стоит относиться легкомысленно и т.п. Они все созданы под конкретные задачи и прекрасно с ними справляются. Главное понимать, когда и какой использовать. Но это прям очень серьезная и большая тема и, по сути, здесь излишняя. Это арсенал Java-программистов, а значит для нас достаточно специфичен, кому надо есть смысл покопаться в справке и попробовать создать разные интерфейсы. Вся информация есть в справке по Swing,
В целях демонстрации и построения боль-мень удобного интерфейса, воспользуемся несколькими менеджерами: BorderLayout, как способ огранизовать общее пространство (сделать вверху красивую шапочку); BoxLayout, как способ удобно организовать некоторые отдельные пространства; неявно FloatLayout и, в основном, AbsoluteLayout. Важно понимать, что каждый из них, работает по разному. Это касается не только возможностей в указании координат, отступов и других параметров пложения на плоскости, но и размеров компонент. Помните пример BorderLayout с кнопками? Они растянулись и заняли все пространство. Подобное поведение может быть, как огромным плюсом (не нужно пересчитывать размеры и положение), так и минусом, если не понимать, что делаешь.
К слову сказать, некоторые другие Layout Managers рассмотрим в следующих статьях. Здесь я исходил из того, чтобы максимально быстро и максимально просто можно было собрать интерфейс. Именно по этой причине, практически все с четким позиционированием. Минус один - на разных мониторах, совершенно по разному будет отображаться. Где-то может и не влезть в размерную сетку.
Немного о событиях
Тренировки ради, заставим “жить” центральную кнопку. Вернее, прикрутим к ней событие. Например, при нажатии на кнопку пусть выскакивает сообщение. Для этого используем коллбэк issueAlert(String message), который выводит в лог Burp сообщение. Как понятно из объявления, метод принимает строку.
Python:
...
button2 = JButton("CENTER", actionPerformed=self.onBtnClick)
...
return mainPanel
def onBtnClick(self, event):
print('Yo, bro!')
self._callbacks.issueAlert("Yo, bro!")
Как видно, изменения в коде небольшие. Для генерации события, использовали параметр конструктора JButton actionPerformed, которому передали функцию обрабатывающую событие. Ровно тоже самое было в предыдущей статье, когда оживляли собственный пункт меню. Как и писал выше, благодаря иерархии компонентов, появляется универсальность, в т.м. универсальные события. .
В “Event log” появилось сообщение. Как видно по счетчику [7], кликнул по кнопке семь раз. Отлично! Мы умеем создавать элементы GUI и присваивать обработчики событий. Причем, кто читал прошлую статью, умеют работать с событиями двумя способами. Если не читали, рекомендую прочитать.
Планирование интерфейса Telegram Notify
Какие параметры я хотел бы иметь возможность настраивать? У нас есть две составляющие: параметры доступные в объекте IScanIssue, т.е. Информация о найденной уязвимости; параметры самого телеграм-бота. В итоге, список получился такой:- Токен бота. Нам нужно где-то хранить токен, чтобы расширение не имело жесткой привязки. Соответственно, нужно поле для ввода и организация хранения, мы же не будем каждый раз вводить токен?
- Уровень риска. Смысл получать уведомления обо всех уязвимостях? Но ситуации бывают разные, поэтому стоило бы дать возможность пользователю выбирать. Само значение, доступно через getSeverity() объекта newIssue
- Вероятность наличия уязвимости. Доступно через getConfidence().
- Тип уязвимости. В идеале, мне бы хотелось иметь возможность выбирать о каких уязвимостях сообщать пользователю.
- И конечно же, учитывая, что список большой, всевозможные компоненты должны генерироваться по списку. У Burp почти 200 типов уязвимостей. Если все это запихивать руками, крыша съедет))))
В общем, в идеале хотелось бы видеть что-то типа такого:
Структура проекта
Расширения с пользовательскими интерфейсами, обычно, достаточно объемные. Нужно реализовать не только функционал, но и описать все рабочее пространство, реализовать обработку всех необходимых событий. Поэтому, лепить все в один файл нет никакого. Будем разделять и разделять максимально логично.Сразу предполагаю наличие основного файла, смысл которого в регистрации необходимым коллбэков и связки разных частей проекта.
Так как у нас будет работать бот оповещений в Телеграм, логично реализовать весь функционал касающийся работы с апи TG отдельным объектом. Задача объекта — просто пересылать в чат, полученные сообщения. Ну и механизм чтения/хранения токена и идентификатора чата.
Чтобы реагировать на возникающие уязвимости, нам нужен слушатель их появления. Как вы помните, для этого в Burp есть IScannerListener. Соответственно, во избежание нагромождения, выведем в отдельный объект. Хотя, момент спорный. Сам код объекта небольшой и размещение его в основном классе сильно не напрягает. Более того, выше у нас возник объект бота оповещений, который нужен как в листенере уязвимостей, так и в настройках. Мы же должны организовать сохранение из интерфейса… в итоге, реализация в одном классе избавляет нас от необходимости передавать объект бота и как-то его контролить. Если у вас возникнет желание переписать объект слушателя в основной класс, это неплохой повод для тренировки. Я же реализую в отдельном классе и отдельным файлом, мне так удобнее и понятно что куда относится.
Интерфейс, конечно же, тоже отдельный класс в отдельном файле. Стоит сказать, что самый большой файл.
Интерфейс — это визуальное отображение настроек. Помимо отображения, нужны и данные с которыми можно работать. Настройки должны где-то храниться, а значит загружаться и сохраняться. Мы же не хотим пользователя заставлять каждый раз заново настраивать уведомления? Кроме того, опрашивать элементы интерфейса, во время сканирования, не очень удобно. Гораздо лучше, если у нас в памяти есть объект с настройками и функция чекер оперативно может определить, надо ли отправлять уведомление. В итоге, у нас еще один класс, который будет обрабатывать данные со всеми настройками, кроме ТГ-бота.
Разобьем интерфейс на части
Начнем с рисования интерфейса. В процессе написания статьи, проект постоянно претерпевал изменения и итоговый вид отличается от схемы выше. Но решил оставить, как напоминание, что ничто не вечно и все меняется. Тем более, отличия не радикальные.Интерфейс вертикально разбит на три основных блока, при помощи BorderLayout(): вверху у нас красивый хедер, внизу кнопка сохранения настроек, а по центру основное рабочее пространство. В коде это выглядит просто и уже знакомо:
Python:
...
pnlMain = JPanel(BorderLayout())
pnlHead = JPanel(FlowLayout(FlowLayout.CENTER, 10,20))
pnlMain.add(pnlHead, BorderLayout.PAGE_START)
pnlBody = JPanel()
pnlBody.setLayout(None)
pnlMain.add(pnlBody, BorderLayout.CENTER)
pnlSave = JPanel()
btnSaveSettings = JButton("Save Settings", actionPerformed=self.onClickSaveSettings)
pnlSave.add(btnSaveSettings)
return pnlMain
Понятное дело, что сейчас на этот скелет будем натягивать мясо. Просто разделяю, чтобы порционно выдавать информацию. Из нового, что здесь есть, это FlowLayout(FlowLayout.CENTER, 10,20), в первую очередь. Данная строчка явно задает менеджер пространства Flow с центральным позиционированием и отступами 10 и 20. Другими словами, внутри панели pnlHead, элементы будут центрироваться по горизонтали. Отступы по горизонтали будут 10 пикселей, по вертикали 20 пикселей. Отступы между ЭЛЕМЕНТАМИ, а не просто паддинги внутри блока.
Хедер
Строка pnlBody.setLayout(None) тоже встречается впервые. Как из нее понятно, мы вызываем функцию назначенную LayoutManager, но передаем туда ничего. Подобным образом, мы сбрасываем менеджер пространства, назначенный по умолчанию. Если помните, выше я говорил, что по умолчанию назначается FlowLayout без параметров. Передав None, мы переключились в режим AbsoluteLayout. Причем, это режим только для панели pnlBody. В итоге, верх и низ у нас полностью управляется BorderLayout(), а центральную часть будем рисовать вручную, указывая точные координаты каждого элемента. Давайте нарисуем красивую шапку:
Python:
pnlHead = JPanel(FlowLayout(FlowLayout.CENTER, 10,20))
clrHead = Color(47,66,85)
pnlHead.setBackground(clrHead)
import os
path = os.path.dirname(os.path.abspath(__file__))
imgLogo = ImageIcon(os.path.join(path, 'logo_xss.png'))
lblLogo = JLabel(imgLogo)
pnlHead.add(lblLogo)
pnlTitle = JPanel()
pnlTitle.setLayout(BoxLayout(pnlTitle, BoxLayout.Y_AXIS))
pnlTitle.setBackground(clrHead)
lblTitle = JLabel('Telegram Notifier')
lblTitle.setFont(Font("Serif", Font.BOLD, 22))
lblLogoText = JLabel("The extension was developed specifically for an article on the xss.pro forum")
lblTitle.setOpaque(True)
lblTitle.setBackground(clrHead)
lblTitle.setForeground(Color.WHITE)
pnlTitle.add(lblTitle)
lblLogoText.setOpaque(True)
lblLogoText.setBackground(clrHead)
lblLogoText.setForeground(Color.WHITE)
pnlTitle.add(lblLogoText)
pnlHead.add(pnlTitle)
Первым делом, используя Java-класс Color, создаем объект цвета. Он нам потребуется, чтобы создать красивый задний фон. Назначается фон, соответственно, функцией setBackground(Color). Важный момент, при назначении цвета объектам JLabel() нужно назначить непрозрачность через setOpaque(True). Если этого не сделать и менеджер растянет нашу надпись, получится вот так:
Чтобы вывести логотип, потребуется объект ImageIcon из javax.swing. Далее все просто — передаем в конструктор путь к файлу картинки. Для этого прямо здесь импортирую “os” и стандартными способами формирую полный путь к файлу. Завершающие этапы, это передать объект картинки в конструктор JLabel и прикрепить его к нужной панели.
Для заголовка и описания создал два JLabel и объединил их в одну панель. Причина в том, чтобы разместить их друг под другом. В итоге, появляется панель с менеджером BoxLayout и направлением Y_AXIS (один под другим). Назначение менеджера происходит через setLayout с передачей в конструктор объекта, которому назначаем и нужной осью направления.
Python:
pnlTitle = JPanel()
pnlTitle.setLayout(BoxLayout(pnlTitle, BoxLayout.Y_AXIS))
Шрифт для заголовка создается при помощи Font(), которому передается название шрифта, стиль и размер. Font импортируется из java.awt.
Каждый элемент для вывода, в итоге прикрепляется к родительскому элементу через метод add(). Например, pnlHead.add(pnlTitle)
Футер
Центр большой, поэтому сначала поговорим про футер. С ним все просто: панелька, прикрепленная как BorderLayout.PAGE_END и кнопка, но оранжевая. Надо ведь соответствовать некому стилю:
Python:
pnlSave = JPanel()
btnSaveSettings = JButton("Save Settings", actionPerformed=self.onClickSaveSettings)
btnSaveSettings.setBackground(Color(255, 102, 51))
btnSaveSettings.setForeground(Color.WHITE)
pnlSave.add(btnSaveSettings)
pnlMain.add(pnlSave, BorderLayout.PAGE_END)
В конструктор JButton передаем событие actionPerformed=self.onClickSaveSettings, само событие опишу позднее. Пока только про визуал. Из нового здесь только назначение цвета текста, через setForeground() и использование константы из класса Color. Да, есть стандартный набор цветов, которыми можно пользоваться. Думаю, здесь все уже знакомо и понятно.
Основная часть интерфейса
Описывать каждый компонент в отдельности, нет никакого смысла. Большая часть, это просто создать объект, указать координаты и добавить на панель. Поэтому, приведу один пример и объясню принцип работы. Далее выделю те части, которые мне кажется наиболее интересными и важными. Весь код будет прикреплен в конце раздела, поэтому прочитать все получится в любом случае, а если будут вопросы — всегда их можно задать в теме или в личке.Функцию тела будет играть компонент с именем pnlBody, который в самом конце прикреплю к pnlMain.
Код:
pnlBody = JPanel()
...
pnlBody.setLayout(None)
x = 50
y = 10
...
Координаты x и y нужны как точка отсчета, относительно которой будут располагаться элементы. Пример добавления текстового поля для ввода токена:
Python:
txtToken = JTextField(30)
txtToken.setBorder(BorderFactory.createLineBorder(Color.GRAY, 1, True))
txtToken.setBounds(x + 150, y, 250, 20)
pnlBody.add(lblToken)
В конструктор JTextField передается размерность, хотя в данном случае это скорее для примера, так как ниже задаю ширину. Текстовое поле создается без границ, просто белый прямоугольник в который можно тыкнуть, но это как-то неприлично… при помощи setBorder() задаю рамки. В конструктор передается специальный объект BorderFactory, вернее вызов его статического метода createLineBorder(). Из названия понятно, какой именно тип бордера будет создан. Больше вариантов границ можно найти в справке по BorderFactory. Из интересного, создание пустой границы - при помощи такого подхода можно изменить позиционирование, например, создав пустую границу слева, толщиной в 50 пикселей.
Думаю, что с параметрами createLineBorder все понятно. Разве что True, это флаг означающий, что нужно скругление границ.
setBounds(), логично, задает координаты и размер. Параметры: x, y, width, height. После остается только добавить на панельку. Таким образом, почти, со всеми элементами.
Как писал выше, предполагается огромное количество чекбоксов. При этом, каждый чекбокс это один и тот же набор операций. От примера выше отличие только в том, что ссылки на чекбоксы сохраняются в классе. Поэтому, написал простую функцию generateCheckboxed():
Python:
def generateCheckboxed(self, panelTarget, x, y, propName, nameFromSubporp=False):
elems = {}
row = y
for item in self._settings[propName]:
row += 30
if nameFromSubporp:
chkBox = JCheckBox(self._settings[propName][item]['name'], self._settings[propName][item]['state'])
chkBox.addActionListener(CheckBoxAction(self._settingsObj, item, propName))
else:
chkBox = JCheckBox(item, self._settings[propName][item]['state'])
chkBox.addActionListener(CheckBoxAction(self._settingsObj, item, propName))
elems[item] = chkBox
if x and y:
chkBox.setBounds(x, row, 150, 20)
panelTarget.add(chkBox)
self._elems[propName] = elems
return elems
Функция получает панельку, к которой необходимо добавить новые чекбоксы. Начальные координаты, для размещения на панельке. Название свойства глобального объекта propName и является ли название подсвойством.
Касаемо глобального объекта — это объект с настройками нашего расширения. Уже касался его в части “Структура проекта”, подробнее опишу позже. Пока стоит предполагать, что будет некий глобальный объект, с которым и будет происходить взаимодействие.
Исптория с именем в виде субсвойства, это тоже про глобальный объект. Но чтобы было понятнее, данная особенность внесена из-за типа добавляемой уязвимости. В слушателе IScannerListener мы можем получить идентификатор уязвимости, вызвав getIssueType(). Тип возвращается в виде целого числа. Можно получить название уязвимости, вызывав специальный коллбэк Burp и по названию сравнивать, надо ли отправлять уведомления в телеграм. Но я выбрал вариант, когда сравнение ведется по числовому типу уязвимости, от чего эта часть объекта выглядит так:
JSON:
"2097960": {"name": "Path-relative style sheet import", "state": false}
При этом, все что касается остальных галочек, выглядит так:
JSON:
"Information": {"state": false}
Отсюда костыль в виде того, как получить надпись для чекбокса. Либо это само название свойства, либо название свойства это число, а нужная надпись находится в свойстве “name” объекта.
Python:
if nameFromSubporp:
chkBox = JCheckBox(self._settings[propName][item]['name'], self._settings[propName][item]['state'])
chkBox.addActionListener(CheckBoxAction(self._settingsObj, item, propName))
else:
chkBox = JCheckBox(item, self._settings[propName][item]['state'])
chkBox.addActionListener(CheckBoxAction(self._settingsObj, item, propName))
Сам цикл прост, как три копейки. Каждую итерацию смещаем позицию y, которая для удобства храниться в pos. Создаем новый чекбокс, вызвав конструктор JCheckBox() и передав ему надпись и состояние чекбокса. Состояние, соответственно, берется из глобального объекта, свойство “state”. Таким образом, уже при создании интерфейса, расставляются галочки сохраненные пользователем.
Все чекбоксы помещаю в список elems, который сохраняю в одноименном объекте нашего класса интерфейса. И тут же возвращаю. Сохранение в классе производится в определенное свойство, которое относится к конкретному разделу интерфейса:
Python:
self._elems = {
"ext_vulns": None,
"confidence": None,
"severity": None,
"vulns": None
}
Это потребуется на этапе реализации логики. Return значения делается изначально в связи с реализацией поиска и к этому мы вернемся ниже. Второй смысл в том, что получив обратно список элементов, мы можем получить их количество и рассчитать позиции где надо располагать следующие части интерфейса. Правда я этого не стал делать, схитрив — количество галочек, в двух блоках из трех, известно заранее и можно сразу их прописать.
Да, возврат значения, это дублирование. Но мне комфортнее было написать именно так. В конце-концов, когда мы пишем код, мы должны понимать, что через время будем его снова читать. И очень важно писать так, чтобы мы его могли понять.
Последний интересный момент, в отношении интерфейса, это использование JScrollPane. Это элемент, который содержит в себе панель и имеет возможность скролла. По аналогии с iframe в HTML. Если мы не знаем итоговую размерность или она слишком большая, выделяем ограниченную область в JScrollPane (без L на конце) и запихиваем в нее наш JPanel (c L на конце). В нашем случае, данный компонент прекрасно решает задачу с размещением все типов уязвимостей.
Чтобы обернуть панель в JScrollPane надо передать панель, которая уже собрана, в конструктор JScrollPane. После установить размеры панели с прокруткой и прикрепить ее к нужному элементу вывода через add.
Python:
pnlVulns = JPanel()
pnlVulns.setLayout(BoxLayout(pnlVulns, BoxLayout.Y_AXIS))
vulnsCheckboxes = self.generateCheckboxed(pnlVulns, 0, 0, 'vulns', True)
txtVulnsSearch.getDocument().addDocumentListener(DocumentSearchListener(vulnsCheckboxes))
scrlVuln = JScrollPane(pnlVulns)
scrlVuln.setBounds(x, 400, 650, 380)
pnlBody.add(scrlVuln)
Выше создание скролл-панели. Сначала создал JPanel, далее вызывал функцию генерации чекбоксов, указав, что название уязвимости лежит в свойстве “name”. Добавление события рассмотрю ниже. Далее создаю JScrollPane, передав нашу панельку. Назначаю координаты и прикрепляю к нашей центральной части pnlBody. Ниже весь код файла интерфейса.
Моменты, которые остались за кадром
Python:
class GUITab(ITab):
def __init__(self, parentObj):
self._callbacks = parentObj._callbacks
self._helpers = parentObj._callbacks.getHelpers()
self._settings = parentObj._settings
self._bot = parentObj._bot
def getTabCaption(self):
return 'Telegram Notify'
def getUiComponent(self):
GUI = MainPanel(self._settings, self._bot)
return GUI.createGUI()
Это основной класс интерфейса. При инициализации объект сохраняет ссылки на коллбэки, объект с настройками оповещений и ссылку на объект реализующий все связанное с телеграм. Функция getTabCaption() нужна чтобы задать название вкладки в интерфейсе. getUiComponent() является провайдером, создавая объект нашего интерфейса. Можно было все выполнить внутри одного объекта. Но потом, когда нужно будет вернуться к этому коду, будет гораздо удобнее понять смысл файла по его началу. getUiComponent(), как и писал выше, должна возвращать JPanel с интерфейсом, что и делает функция createGUI(). Самое время поговорить о событиях в нашем интерфейсе, подарить ему жизнь.
Оживляем кнопочки и галочки
В проекте реализовано несколько типов событий и несколько вариантов назначения событий. Связано это с тем, что некоторые события привязываются к спискам объектов. Например, текстовое поле фильтра типов уязвимостей жестко привязано к чекбоксам с названиями. Другие события, например, клик по кнопке “Сохранить настройки”, не требует каких-то дополнительных манипуляций и может быть реализовано передачей функции обработки через конструктор. Рассмотрим подробнее.
Python:
btnSaveSettings = JButton("Save Settings", actionPerformed=self.onClickSaveSettings)
...
def onClickSaveSettings(self, event):
self._settingsObj.saveSettings(self._settings)
Привязка обработчика происходит через параметр actionPerformed конструктора. Внутри самой функции происходит вызов метода сохранения глобального объекта. Все просто, как три копейки. Тоже происходит и при сохранении токена и chatID телеграм:
Python:
btnSaveToken = JButton("Save Token", actionPerformed=self.onClickSaveToken)
...
def onClickSaveToken(self, event):
token = self._txtToken.getText()
chat_id = self._txtChatId.getText()
self._bot.saveData(token, chat_id)
Все кнопки привязаны подобным образом. Небольшие отличия только в том, что конкретно они делают. Ниже три кнопки, которые относятся к поиску: выделить все, снять выделение, очистить поиск. Кстати, особенность кнопок, отвечающих за выделение, в том, что они работают только с видимыми чекбоксами. Другими словами, если вы вбили в поле фильтра “sql”, галочки проставятся/снимутся только для подходящих уязвимостей. Если нужно включить или выключить все чекбоксы, нужно очистить поле фильтра.
Python:
def onClickCheckAll(self, event):
for item in self._elems['vulns']:
if self._elems['vulns'][item].isVisible():
self._elems['vulns'][item].setSelected(True)
self._settingsObj.updateParam(item, True, 'vulns')
def onClickUncheckAll(self, event):
for item in self._elems['vulns']:
if self._elems['vulns'][item].isVisible():
self._elems['vulns'][item].setSelected(False)
self._settingsObj.updateParam(item, False, 'vulns')
def onClearSearch(self, event):
self._txtVulnsSearch.setText('')
return self.showAllVulnsCheckbox()
def showAllVulnsCheckbox(self):
for item in self._elems['vulns']:
chkVuln = self._elems['vulns'][item]
chkVuln.setVisible(True)
showAllVulnsCheckbox() - функция атавизм, ранее она нужна была чтобы избежать дублированного кода, теперь оставил “на всякий случай”. Интерфейс может поменяться, да и в целом, вынос действий в отдельные функции это хороший тон.
Реализация фильтрации
У меня есть два разных способа работы фильтра. Начну с простого, который и был причиной создания функции-атавизма showAllVulnsCheckbox().
Python:
def changeSearch(self, event):
searchText = event.getSource().getText().lower()
if not len(searchText):
return self.showAllVulnsCheckbox()
for item in self._elems['vulns']:
chkVuln = self._elems['vulns'][item]
text = chkVuln.getText().lower()
if text.find(searchText) == -1:
chkVuln.setVisible(False)
else:
chkVuln.setVisible(True)
Чтобы функция заработала, её достаточно добавить в класс class MainPanel(). При этом, не нужно создавать новое событие, достаточно в конструктор JTextField для txtVulnsSearch передать на неё ссылку через actionPerformed=self.changeSearch. Сначала функция получает ссылку на текстовое поле при помощи функции getSource(). По цепочке, получает текст и приводит его к нижнему регистру. Если текст пустой, то выводятся все чекбоксы. Если нет, проходит по списку, неподходящие прячет, подходящие показывает. Единственный недостаток функции в том, что событие передаваемое через actionPerformed относиться к ActionListener, а там нет обработки для ввода или нажатий клавиш. В результате, поле работать будет, но нужно нажать Enter, чтобы случилась фильтрация.
Более универсальный и корректный способ, это обрабатывать пользовательский ввод с клавиатуры. Для этого нужно импортировать DocumentListener из javax.swing. Для этого пришлось использовать хук:
Python:
from javax.swing import event as SwingEvent
...
class DocumentSearchListener(SwingEvent.DocumentListener):
...
Импортировал объект с событиями, как SingEvent, а дальше уже на его основе создал класс реализующий обработчик событий типа DocumentListener. Именно здесь находится все необходимое для того, чтобы можно было обрабатывать текстовый ввод “на лету”. Если конкретнее, внутри класса нужно реализовать следующие функции: changedUpdate(event), insertUpdate(event), removeUpdate(event).
Нам не нужно какое-то специфическое поведение для каждого отдельного случая, поэтому я просто в каждой из функций передал управление третьей функции:
Python:
class DocumentSearchListener(SwingEvent.DocumentListener):
def __init__(self, vulnsElems):
self._elems = vulnsElems
def changedUpdate(self, event):
self.updateSearch(event)
def insertUpdate(self, event):
self.updateSearch(event)
def removeUpdate(self, event):
self.updateSearch(event)
def updateSearch(self, event):
document = event.getDocument()
len = document.getLength()
searchText = document.getText(0, len).lower()
# print('Search text' + searchText)
for item in self._elems:
chkVuln = self._elems[item]
text = chkVuln.getText().lower()
# print('Vuln text' + text)
if text.find(searchText) == -1:
chkVuln.setVisible(False)
else:
chkVuln.setVisible(True)
Что происходит? В конструкторе получаю ссылки на чекбоксы уязвимостей. Внутри событий:
- Получить ссылку на документ. Именно так мы получим доступ к содержимому текстового поля
- Получить длину текста, т.к. getText() документа возвращает символы “с”-”по”
- Получен текст, теперь можно пройтись по всем чекбоксам в поиске текста.
В принципе, за исключением того, что обрабатывается другой тип событий и через класс, отличий никаких. Разве что добавлен обработчик следующим образом:
txtVulnsSearch.getDocument().addDocumentListener(DocumentSearchListener(vulnsCheckboxes))
Остался последний обработчик
Единственное, что в интерфейсе сейчас не работает (если не учитывать, что кроме интерфейса ничего еще нет), это чекбоксы. Выше в коде вы видели добавление события в функции generateCheckboxed:
Python:
chkBox.addActionListener(CheckBoxAction(self._settingsObj, item, propName))
Код класса обработчика:
Python:
from java.awt.event import ActionListener
class CheckBoxAction(ActionListener):
def __init__(self, settingsObj, nodeName, parentNode = None):
self.settingsObj = settingsObj
self._nodeName = nodeName
self._parentNode = parentNode
def actionPerformed(self, event):
self.settingsObj.updateParam(self._nodeName, event.getSource().isSelected(), self._parentNode)
Фишка в том, чтобы запомнить для каждого отдельного чекбокса, к какому конкретно свойству настроек он привязан. Дальше, когда пользователь кликает на чекбокс, actionPerformed вызывает метод updateParam() глобального объекта настроек. Кстати, думаю теперь наглядно видно, почему в конструктор объекта обработчик передается как actionPerformed. Просто это единственный необходимый метод для реализации события ActionListener.
Сохранение и загрузка данных
Токен сохраняется отдельно от основных настроек бота, не просто так. По хорошему, если исходить из безопасности, токен не должен отображаться в интерфейсе. По крайней мере до момента пока не нажмешь кнопку “Показать”. В нашем случае это мелочь, но почему бы для практики не писать, хотя бы эту часть, в хорошем тоне? Сделаю следующим образом: при загрузке расширения, если токен есть, то устанавливаем значение текстового поля в “*****”; при сохранении токена, после записи в файл, так же устанавливаем значение в звездочки; перед сохранением, соответственно, заменяем звездочки на пустоту и смотрим есть ли что-то в тексте.Хранить токен будем в файле “.env”, скорее как дань моде. Для сохранения в файл, нам ничего не нужно. Все реализуется стандартным способом через open(). Файл будем хранить прямо в папке с расширением. Хотя ничто не мешает вам, при желании, закинуть файл куда угодно, os прекрасно работает.
Единственный момент, как хранить данные в памяти? Наиболее простой вариант, это сделать отдельный класс с методами для сохранения и загрузки. При загрузке расширения создавать объект класса и передавать ссылку на него в слушатель уязвимостей. Причем, раз уж разделил, то написать два схожих класса. Настройки, к слову, оптимально хранить в JSON. Загружать его в словарь по которому потом будет удобно работать со списком уязвимостей.
Это те самые глобальные настройки, которые хранятся глобально и ссылка на которые передается от объекта к объекту. Вообще, чтобы избежать путаницы, уже назрел момент разобраться с общей структурой объектов и их взаимодействия. Я подготовил картинку:
Так путешествуют данные и события. Сначала BurpExtender создает объект бота TgBotNotify и настроек ExtSettings. При создании, они получают данные из файлов, после чего готовы передать все в интерейс и слушатель. Следующим шагом, создается интерфейс GUITab, куда передается объект settings типа ExtSettings. На основе settings устанавливаются галочки (см. выше в функция генерации галочек). Последний этап, это создание листенера IScannerListener. В него передаются настройки и телеграм бот. Настройки нужны для проверки, надо ли отправлять уведомление по конкретной уязвимости. За это отвечает функция checkNeedToNotify(). Если надо отправить сообщение, листенер передает данные в send_message() бота с типом TgBotNotify. Надеюсь, что теперь отпадет большинство вопросов.
Важный момент, что все объекты передаются по ссылке. Это значит, что все объекты имеющие ссылку на объект, имеют доступ к одному и тому же участку памяти. Другими словами, изменение галочек в интерфейсе напрямую влияет на работу листенера уязвимостей. Даже без сохранения настроек, листенер будет принимать во внимание установленные галочки.
Начнем реализацию.
Python:
from burp import IBurpExtender
from TgBotNotyfy import TgBotNotyfy
from ExtSettings import ExtSettings
from ScannerListener import ScannerListener
from GUITab import GUITab
class BurpExtender(IBurpExtender):
def registerExtenderCallbacks(self, callbacks):
callbacks.setExtensionName('Telegram Notify')
self._bot = TgBotNotyfy()
self._settings = ExtSettings()
self._callbacks = callbacks
callbacks.addSuiteTab(GUITab(self))
callbacks.registerScannerListener(ScannerListener(self))
Здсь все, как и описал на схеме. Создаем все объекты, перекидывая “себя” в качестве параметра. Почему себя? Чтобы не дробить на части, а получить все из одного класса. Установили название, создали интерфейс, зарегистрировали слушатель новых уязвимостей.
TgBotNotyfy
При инициализации нам нужно загрузить данные из файла “.env” и положить их в “себя”. Вот как выглядит нужный функционал:
Python:
class TgBotNotyfy():
_bot_url_stat = 'https://api.telegram.org/bot%token%/sendMessage'
_token_filename = '.env'
def __init__(self):
self._token, self._chat_id = self._loagBotParams()
self._bot_url = self._bot_url_stat.replace('%token%', self._token)
def _loagBotParams(self):
with open('.env', 'r') as f:
data = f.read()
token = re.findall('(?<=token=).*', data)
if len(token[0]):
chat_id = re.findall('(?<=chat_id=).*', data)
if len(chat_id[0]):
return [token[0], chat_id[0]]
return [False, False]
Чтение файла производится стандартным для пайтона методом. Далее регуляркой парсится на token и chat_id. На этом работа бота, пока, прекращена. До момента, когда по-требуется сохранение данных или же расширение примет решение, что пора отправить уведомление. Весь код бота:
Python:
import requests
import re
class TgBotNotyfy():
_bot_url_stat = 'https://api.telegram.org/bot%token%/sendMessage'
_token_filename = '.env'
def __init__(self):
self._token, self._chat_id = self._loagBotParams()
self._bot_url = self._bot_url_stat.replace('%token%', self._token)
def _loagBotParams(self):
with open('.env', 'r') as f:
data = f.read()
token = re.findall('(?<=token=).*', data)
if len(token[0]):
chat_id = re.findall('(?<=chat_id=).*', data)
if len(chat_id[0]):
return [token[0], chat_id[0]]
return [False, False]
def getToken(self):
return self._token
def getChatId(self):
return self._chat_id
def getParams(self):
return [self._token, self._chat_id]
def saveData(self, token, chat_id):
if not len(token.replace('*', '')) or not len(chat_id):
print('Toke not updated)
else:
self._token = token
self._chat_id = chat_id
with open('.env', 'w') as f:
f.write('token=' + token + '\n')
f.write('chat_id=' + chat_id + '\n')
return True
def _send_message(self, name, details, url):
if not self._token or not self._chat_id:
return
text = '<b>' + str(name) + '</b>\n' + str(details) + '\nURL: ' + str(url)
data = {
'chat_id': self._chat_id,
'text': text,
'parse_mode': 'HTML'
}
response = requests.post(self._bot_url, data=data)
Если я выше не забыл написать)))) При загрузке данных, токен в текстовом поле не отображается. Отображаются звездочки. Поэтому, перед сохранением, проверяется надо ли обновлять токен.
Python:
if not len(token.replace('*', '')) or not len(chat_id):
print('Toke not updated)
else:
self._token = token
ExtSettings
Пройдемся по ключевым функциям, которые работают с настройками бота: _loadSettings, saveSettings, updateParam, checkNeedToNotify.Соответственно, первые две функции это загрузка и сохранение данных. Раз настройки у нас хранятся в JSON-объекте, то и загрузка сохранение у них соответствующие:
Python:
def _loadSettings(self):
with open("./settings.json", "r") as f:
self._settings_obj = json.loads(f.read())
def saveSettings(self, new_settings):
self._settings_obj = new_settings
with open("./settings.json", "w") as f:
f.write(json.dumps(self._settings_obj))
print('Saved')
return
Функция обновления параметров updateParam() — параметр меняется прямо в памяти, без записи. Сначала функция понимает, имеет ли она дело с вложенным параметром и обновляет соответствующим образом. Сделано это по из-за наличия опции “Notify me of vulnerabilities found that are added by extensions”, которая единственная храниться не в виде массива, а единичным значением:
JSON:
"ext_vulns": {"state": true}
Сама функция обновления параметра:
Python:
def updateParam(self, param_name, value, property_name):
if property_name:
self._settings_obj[property_name][param_name]['state'] = value
else:
self._settings_obj[param_name]['state'] = value
Наверное, одна из самых главных функций - checkNeedToNotify(). Она проверяет, нужно ли уведомлять пользователя. Как писал выше, проверка ведется по актуальным настройкам. Если пользователь сменил настройки и не сохранил, в файле какие-то галочки будут снятыми, а в памяти поставленными.
Python:
def checkNeedToNotify(self, confidence, severity, issue_type):
if self._settings_obj['confidence'][confidence]['state']:
if self._settings_obj['severity'][severity]['state']:
if self._settings_obj['vulns'][str(issue_type)]["state"]:
return True
elif self._settings_obj['ext_vulns']['state']:
if not str(issue_type) in self._settings_obj['vulns']:
return True
return False
Все просто. На вход функция получает данные из найденной уязвимости: уверенность, уровень риска и тип уязвимости. Далее проходит по серии if’ов. Если хоть один не совпадает, функция возвращает False и оповещение не отсылается. Тип уязвимости проверяется по номеру. При этом, если установлена галочка “Оповещать о пользовательских уязвимостях”, проверяется есть присланный тип уязвимости в списке или нет. Соответственно, пользовательских уязвимостей не может быть в стандартном списке Burp.
Python:
import json
class ExtSettings():
_settings_obj = {}
def __init__(self):
self._loadSettings()
def _loadSettings(self):
with open("./settings.json", "r") as f:
self._settings_obj = json.loads(f.read())
def saveSettings(self, new_settings):
print('Start save')
self._settings_obj = new_settings
with open("./settings.json", "w") as f:
f.write(json.dumps(self._settings_obj))
print('Saved')
return
def getSettings(self):
return self._settings_obj
def updateParam(self, param_name, value, property_name):
if property_name:
print('Update Param:' + param_name + ' property name: ' + property_name + ' Value: ' + str(value))
self._settings_obj[property_name][param_name]['state'] = value
else:
print('Update Param:' + param_name + ' Value: ' + str(value))
self._settings_obj[param_name]['state'] = value
def checkNeedToNotify(self, confidence, severity, issue_type):
print(confidence, severity, issue_type)
if self._settings_obj['confidence'][confidence]['state']:
if self._settings_obj['severity'][severity]['state']:
if self._settings_obj['vulns'][str(issue_type)]["state"]:
return True
elif self._settings_obj['ext_vulns']['state']:
if not str(issue_type) in self._settings_obj['vulns']:
return True
return False
ScannerListener
В слушателе все просто. Получил объекты, запомнил ссылки. Когда появляется новая уязвимость, берет информацию о ней и отправляет в ExtSettings. Если приходит False, забываем про уязвимость и ждем следующую. Если пришло True, передаем данные в TgBotNotyfy на отправку пользователю. Все! Круг замкнулся!
Python:
from burp import IScannerListener
import requests
import re
class ScannerListener(IScannerListener):
def __init__(self, parentObj):
self._callbacks = parentObj._callbacks
self._help = parentObj._callbacks.getHelpers()
self._bot = parentObj._bot
self._settings = parentObj._settings
def newScanIssue(self, newIssue):
name = newIssue.getIssueName()
confidence = newIssue.getConfidence()
issue_type = newIssue.getIssueType()
severity = newIssue.getSeverity()
details = newIssue.getIssueDetail()
url = newIssue.getUrl().toString()
if self._settings.checkNeedToNotify(confidence, severity, issue_type):
self._bot._send_message(name, details, url)
Тестирование
Настал долгожданный момент! Все написано и самое время проверить работу. Чтобы не заморачиваться, снова обращаюсь к академии PortSwigger, которую организовали разработчики BurpSuite. Выбираю первую попавшуюся лабу с XSS уязвимостью и запускаю на нее сканер.
Настройки нотификатора выглядели так:
Для второго теста потребуется расширение из второй статьи, а именно — добавление точки инъекции. Возьмем уже замученный нами пример с SQL Injection. В настройках расширения, отправляющего уведомления в телеграм, убираю все уязвимости и выбираю только то, что относиться к SQLi.. В настройках “Уверенности” (Confidence) добавляю галочку на “Firm”, ведь именно такой уровень уверенности вернет сканер. Все, приготовления закончены, как и в прошлом примере захожу в магазин, который создала лаба и делаю запрос остатков по любому товару. Запускаю обычное активное сканирование.
Через полторы-две минуты вижу, что уязвимость обнаружена, но в бот ничего не прилетело. Хм… неприятненько. Хотелось как по маслу, но нет. Добавляю вывод параметров перед отправкой сообщения, а также распечатываю код ответа и текст ответа.
Упс… вот чего не предусмотрел. Телеграм принимает далеко не все тэги. Как минимум, с <BR> у нас проблемы. Как в анекдоте, у нас два путя: сделать подмену <br> на “\n” и это будет работать. Либо, составить белый список тэгов и сносить все не относящееся к делу. Учитывая, что нам нужно просто оповещение и каких-то жестких требований к красоте оповещения мы не задаем, можно действовать в лоб: заменить все <br> на перенос строки (все же для читаемости стоит оставить), после заменить все тэги, кроме <b> на пустоту:
Python:
details = details.replace('<br>', '')
details = re.sub('<(?!\/{0,1}b)[^>]*?>', '', details)
text = '<b>' + str(name) + '</b>\n' + str(details) + '\nURL: ' + str(url)
Запускаю новую проверку и получаю соответствующее уведомление.
Вместо заключения
Когда я начинал писать статью, думал она будет довольно небольшой. Ну что там, рассказать про интерфейсы… но, как видите… Word показывает 36 страниц… При этом, многие вопросы остались за кадром. Мы встроились только в одну часть Burp, а возможностей гораздо больше. Касаемо построения самих интерфейсов, тоже есть много нюансов. Например, как сделать интерфейс “резиновым” на базе комбинирования менеджеров лэйаутов. В любом случае, если вы прочитали и попробовали все предыдущие статьи и осилили эту, вы уже в состоянии писать довольно мощные расширения, как для себя, так и на заказ.Но главное, что все поставленные вопросы были разобраны, а на выходе получилось действительно полезное расширение. Надеюсь вам понравилось.
P.S.
Когда пишите расширение и часто его заново загружаете Burp, контролируйте память! У меня в какой-то момент за 15Gb перевалило. Burp не очищает память, когда вы перезагружаете расширение.