В статье рас­ска­жу, как заг­ружать в Burp тар­геты и най­ден­ные уяз­вимос­ти из Acunetix. Покажу, как запус­тить новое ска­ниро­вание в Acunetix из рас­ширения Burp. Поделюсь лай­фха­ком, как находить недоку­мен­тирован­ные методы в Acunetix API. На выходе у тебя получит­ся пол­ноцен­ное рас­ширение, обес­печива­ющее двус­торон­нюю связь меж­ду прог­рамма­ми.

Burp иде­аль­но под­ходит для сбо­ра информа­ции по кон­крет­ному тар­гету. Acunetix шикар­но выпол­няет мас­совые ска­ниро­вания. У обо­их инс­тру­мен­тов раз­личные наборы ска­неров, и час­то они находят непере­сека­ющиеся уяз­вимос­ти. Идея в том, что­бы встро­ить в интерфейс Burp рас­ширение, которое даст воз­можность запус­тить допол­нитель­ное ска­ниро­вание в Acunetix и получить най­ден­ные уяз­вимос­ти в Burp.

Основа

В пре­дыду­щей статье я раз­бирал нюан­сы, которые при­годят­ся, если пла­ниру­ешь при­менять на прак­тике изло­жен­ное здесь. Мы под­готови­ли рабочую сре­ду, соз­дали и запус­тили рас­ширения на Jython + Burp Extender API. Затем реали­зова­ли кон­нект Acunetix API, интерфейс для вво­да дан­ных и хра­нения их меж­ду запус­ками Burp.

Го­товые ис­ходни­ки рас­ширения ты можешь ска­чать с моего GitHub.

Спи­сок фай­лов:

  • burp-acunetix.py — основной файл рас­ширения со все­ми нас­трой­ками и пре­дус­танов­ками;
  • acu_client.py — класс с метода­ми Acunetix API;
  • jtransport.py — класс, реали­зующий зап­росы по HTTP/HTTPS;
  • store.py — класс для хра­нения информа­ции о сос­тоянии рас­ширения;
  • run_api_task.py — класс для реали­зации мно­гопо­точ­ности;
  • menu.py — реали­зация пун­ктов кон­текс­тно­го меню Burp;
  • ui.py — файл с интерфей­сом рас­ширения.

В статье буду избе­гать объ­ясне­ний по час­ти интерфей­са, ина­че и без того боль­шой матери­ал прев­ратит­ся в гигант­ский.

 

Ищем и загружаем таргеты из Acunetix

Итак, пред­положим, что ты ска­чал перечис­ленные выше фай­лы. Откры­ваем про­ект и про­дол­жаем реали­зацию задуман­ного. Нач­нем с пра­вок в фай­ле ui.py. Он отве­чает за интерфейс рас­ширения. В текущей вер­сии интерфейс — это два тек­сто­вых поля для кред к Acunetix API и пара кно­пок.

Для поис­ка тар­гетов удоб­но добавить таб­лицу (ком­понент Java JTable), тек­сто­вое поле JTextField и кноп­ку поис­ка JButton. Раз­местить эле­мен­ты нуж­но на новой панели JPanel, которую прик­репим к основной панели. В фай­ле ui.py в фун­кцию _build_ui добав­ляем код:

# Найди эту строку
main_container.add(Box.createVerticalStrut(15))
# Добавь код ниже
self.dynamic_panel = JPanel(BorderLayout())
self._show_search_panel()
# Конец кода
main_container.add(self.dynamic_panel)

Соз­дай фун­кцию _show_search_panel, в которой про­пиши соз­дание ком­понен­тов для новой панели:

def _show_search_panel(self):
self.dynamic_panel.removeAll()
panel = JPanel(GridBagLayout())
panel.setBorder(BorderFactory.createTitledBorder("Search Targets"))
gbc = GridBagConstraints()
gbc.insets = Insets(6, 6, 6, 6)
gbc.anchor = GridBagConstraints.WEST
gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 0; gbc.fill = GridBagConstraints.NONE
panel.add(JLabel("Search:"), gbc)
gbc.gridx = 1; gbc.weightx = 1.0; gbc.fill = GridBagConstraints.HORIZONTAL
self.search_field = JTextField()
panel.add(self.search_field, gbc)
gbc.gridx = 2; gbc.weightx = 0; gbc.fill = GridBagConstraints.NONE
self.search_btn = JButton("Search", actionPerformed=self.on_search_targets)
self.search_btn.setPreferredSize(Dimension(100, 28))
panel.add(self.search_btn, gbc)
columns = ["ID", "Address", "Description"]
self.table_model = DefaultTableModel(columns, 0)
self.targets_table = JTable(self.table_model)
self.targets_table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
self.targets_table.getSelectionModel().addListSelectionListener(self.on_target_selected)
table_scroll = JScrollPane(self.targets_table)
table_scroll.setPreferredSize(Dimension(0, 180))
gbc.gridx = 0; gbc.gridy = 1; gbc.gridwidth = 3; gbc.weighty = 1.0
gbc.fill = GridBagConstraints.BOTH; gbc.insets = Insets(10, 0, 0, 0)
panel.add(table_scroll, gbc)
self.dynamic_panel.add(panel, BorderLayout.CENTER)
self.dynamic_panel.revalidate()
self.dynamic_panel.repaint()
def on_search_targets(self, event):
# Обработка события нажатия кнопки поиска
pass
def on_target_selected(self, event):
# Обработка события выбора таргета
pass
Обновленный вид интерфейса расширения
Об­новлен­ный вид интерфей­са рас­ширения

В фай­ле acu_client.py най­ди фун­кцию _get_targets. Она отве­чает за получе­ние тар­гетов из Acunetix. При­нима­ет два парамет­ра: query и normalize. Пер­вый — это стро­ка поис­ка. Вто­рой параметр ука­зыва­ет, что нужен объ­ект, под­готов­ленный к выводу в таб­лице. Зна­чение True зас­тавит фун­кцию уда­лить все лиш­ние поля.

Соз­дай фун­кцию‑обер­тку search_targets, это чис­то сти­лис­тичес­кая вещь, которая поз­волит сде­лать код чита­емым и удоб­ным для мас­шта­биро­вания. Фун­кция переда­ет управле­ние _get_targets, уста­нав­ливая normalize в True:

def search_targets(self, query):
return self._get_targets(query=query, normalize=True)

Вер­нись в файл интерфей­са ui.py. Что­бы ожи­вить кноп­ку Search, замени код фун­кции on_search_targets:

def on_search_targets(self, event):
# Получаем значение поисковой строки
self.query = self.search_field.getText().strip()
# Отключаем кнопку, чтобы избежать повторных нажатий
self.search_btn.setEnabled(False)
# Меняем надпись на кнопке, чтобы пользователь видел разницу
self.search_btn.setText("Searching...")
# Передаем управление функции поиска и вывода данных
self._search_targets()

Ког­да поль­зователь клик­нет по кноп­ке, мы изме­ним ее вид и переда­дим управле­ние в _search_targets. Внут­ри выпол­ним зап­рос к API, получив ответ, положим его в таб­лицу.

def _search_targets(self):
api_key, api_url = self.get_credentials_from_fields()
if not api_key or not api_url:
self._callbacks.issueAlert("API credentials are missing. Please fill in the fields and save the settings.")
JOptionPane.showMessageDialog(self.panel, "API access data is missing. Please enter the data in the text fields and save.", "Error", JOptionPane.ERROR_MESSAGE)
return
def do_search():
self.client.set_credentials(api_url, api_key)
self._callbacks.printOutput("[SEARCH] Searching targets: %s" % self.query)
return self.client.searc_targets(self.query)
def on_success(result):
success, message = result
if success:
self._update_search_table(message)
return
err = "Search failed: %s" % message
self._callbacks.issueAlert(err)
JOptionPane.showMessageDialog(self.panel, err, "Error", JOptionPane.ERROR_MESSAGE)
def on_error(error):
self._callbacks.printError("[SEARCH] Search error: %s" % str(error))
def on_finally():
self._callbacks.printOutput("[SEARCH] Finished")
self.search_btn.setText("Search")
self.search_btn.setEnabled(True)
self.task_runner.run("SEARCH", do_search, on_success, on_error, on_finally)

Что­бы интерфейс не зависал, зап­росы к Acunetix API дела­ем в отдель­ном про­цес­се. Для это­го у нас есть класс ApiTaskRunner, в котором лежит метод run(). Алго­ритм любой фун­кции, работа­ющей с кли­ентом API (класс AcunetixClient), выг­лядит так:

def _search_targets(self):
# Какие-то действия
def do_search():
# Выполнение запроса к API через клиент AcunetixClient
return self.client.<some_function>()
def on_success(result):
# Функция выполняется в случае успешного запроса
def on_error(error):
# Выполняется в случае ошибки
# Обычно: self._callbacks.printError("[<SERVICE-NAME>] <описание>: %s" % str(error))
def on_finally():
# Необязательная функция. Если указана, выполняется независимо от результата
self.task_runner.run("<SERVICE-NAME>", do_search, on_success, on_error, on_finally)

От­лично! Твое рас­ширение уме­ет зап­рашивать спи­сок тар­гетов по поис­ковой стро­ке. Оста­лось сде­лать вывод резуль­татов в таб­лицу. Соз­дай фун­кцию:

def _update_search_table(self, targets):
self.table_model.setRowCount(0)
if not targets:
self._callbacks.printOutput("[UI] No targets to display")
return
for t in targets:
addr = t.get("address", "N/A")
desc = (t.get("description") or "")[:120]
if len(desc) == 120:
desc += "..."
tid = t.get("id", "N/A")
self.table_model.addRow([tid, addr, desc])
self._callbacks.printOutput("[UI] Updated table with %d targets" % len(targets))

Эта фун­кция очис­тит таб­лицу через setRowCount(0), а пос­ле добавит най­ден­ные цели. Обно­ви рас­ширение в Burp и про­верь работу поис­ка.

Результаты поиска
Ре­зуль­таты поис­ка
 

Особенности пагинации Acunetix

Ра­но или поз­дно ты стол­кнешь­ся со слиш­ком боль­шим объ­емом дан­ных. Это могут быть, нап­ример, сот­ни уяз­вимос­тей на сла­бом тар­гете. При­дет­ся исполь­зовать механизм пагина­ции Acunetix, которая доволь­но спе­цифич­на.

Нель­зя прос­то ука­зать стра­ницу и получить нуж­ный срез. API в парамет­ре ?c= ждет кур­сор стра­ницы. Кур­сор мож­но получить, выпол­нив зап­рос к API, в отве­те будет свой­ство pagination. Внут­ри свой­ства — мас­сив cursors:

"pagination": {
"count": 1322,
"cursor_hash": "9192d370043f1e7f14729e9c6fb143fb",
"cursors": [
null,
"100",
"200"
],
"sort": null
}

Пер­вое зна­чение null ука­зыва­ет, что дан­ные получе­ны с пер­вой стра­ницы, мы в начале спис­ка. Сле­дующие два зна­чения — это ука­затель на сле­дующую стра­ницу и стра­ницу «через одну» (текущая + 2). При зап­росе я уста­новил параметр &l= (количес­тво зап­рашива­емых объ­ектов) в 100. Пер­вая стра­ница выводит объ­екты 0–99, вто­рая — 100–199, третья — 200–299...

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

Что­бы понять, что ты в кон­це спис­ка, про­веряй количес­тво эле­мен­тов cursors. Пред­послед­няя стра­ница вер­нет толь­ко два эле­мен­та: текущую и сле­дующую стра­ницу. Пос­ледняя стра­ница вер­нет один кур­сор, ука­зыва­ющий на текущую стра­ницу.

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии