# -*- coding: utf-8 -*-
from burp import IBurpExtender, IHttpListener, ITab
from javax.swing import (JPanel, JButton, JTextField, JLabel, JComboBox,
JTable, JScrollPane, JTextArea, JSplitPane,
BoxLayout, ListCellRenderer, JTree, JTabbedPane)
from javax.swing.table import DefaultTableModel
from javax.swing.tree import DefaultMutableTreeNode, DefaultTreeModel
from javax.swing.border import EmptyBorder, TitledBorder
from java.awt import (BorderLayout, Dimension, Insets, Color, EventQueue,
Font)
from java.awt.event import ItemEvent, ItemListener, MouseAdapter
import re
# MouseAdapter subclass for tree click events
class TreeClickListener(MouseAdapter):
def __init__(self, outer):
self.outer = outer
def mouseClicked(self, event):
self.outer.on_discovered_tree_click(event)
class HostSelectionListener(ItemListener):
def __init__(self, outer):
self.outer = outer
def itemStateChanged(self, event):
if event.getStateChange() == ItemEvent.SELECTED:
self.outer.refresh_info(None)
class ColorCellRenderer(JLabel, ListCellRenderer):
def getListCellRendererComponent(self, list, value, index, isSelected, cellHasFocus):
self.setText(value)
self.setOpaque(True)
self.setBackground(Color.white if not isSelected else Color.lightGray)
color_map = {
"red": Color.red, "orange": Color.orange, "yellow": Color.yellow,
"green": Color.green, "cyan": Color.cyan, "blue": Color.blue,
"pink": Color.pink, "magenta": Color.magenta, "gray": Color.gray
}
self.setForeground(color_map.get(value, Color.black))
return self
class BurpExtender(IBurpExtender, IHttpListener, ITab):
def registerExtenderCallbacks(self, callbacks):
self.callbacks = callbacks
self.helpers = callbacks.getHelpers()
callbacks.setExtensionName("7illight")
callbacks.registerHttpListener(self)
self.cached_keywords = []
self.discovered_map = {}
self.init_gui()
callbacks.addSuiteTab(self)
print("[+] 7illight extension with JS URL discovery loaded.")
def init_gui(self):
self.tabbed_panel = JTabbedPane()
# Discovered URLs Tree
self.disc_root_node = DefaultMutableTreeNode("Discovered JS URLs")
self.disc_tree_model = DefaultTreeModel(self.disc_root_node)
self.disc_tree = JTree(self.disc_tree_model)
self.disc_tree.setRootVisible(True)
self.disc_tree.addMouseListener(TreeClickListener(self))
disc_scroll = JScrollPane(self.disc_tree)
disc_scroll.setPreferredSize(Dimension(350, 500))
disc_scroll.setBorder(TitledBorder("Discovered from JavaScript"))
# Highlight Input (Horizontal layout)
self.scope_selector = JComboBox(["Both", "Request", "Response"])
self.color_selector = JComboBox(["red", "orange", "yellow", "green", "cyan", "blue", "pink", "magenta", "gray"])
self.color_selector.setRenderer(ColorCellRenderer())
self.keyword_field = JTextField(15)
self.color_preview = JLabel(" ")
self.color_preview.setOpaque(True)
self.color_preview.setBackground(Color.red)
def update_color_preview(event):
color_map = {
"red": Color.red, "orange": Color.orange, "yellow": Color.yellow,
"green": Color.green, "cyan": Color.cyan, "blue": Color.blue,
"pink": Color.pink, "magenta": Color.magenta, "gray": Color.gray
}
selected = self.color_selector.getSelectedItem()
self.color_preview.setBackground(color_map.get(selected, Color.black))
self.color_selector.addItemListener(update_color_preview)
self.add_button = JButton("Add", actionPerformed=self.add_keyword)
self.update_button = JButton("Update", actionPerformed=self.update_keyword)
self.delete_button = JButton("Delete", actionPerformed=self.delete_keyword)
input_row = JPanel()
input_row.setLayout(BoxLayout(input_row, BoxLayout.X_AXIS))
input_row.add(JLabel("Scope:"))
input_row.add(self.scope_selector)
input_row.add(JLabel("Color:"))
input_row.add(self.color_selector)
input_row.add(self.color_preview)
input_row.add(JLabel("Keyword:"))
input_row.add(self.keyword_field)
input_row.add(self.add_button)
input_row.add(self.update_button)
input_row.add(self.delete_button)
self.table_model = DefaultTableModel(["Scope", "Color", "Keyword"], 0)
self.table = JTable(self.table_model)
self.table.getSelectionModel().addListSelectionListener(lambda e: self.load_selected_row())
keyword_scroll = JScrollPane(self.table)
keyword_scroll.setPreferredSize(Dimension(500, 150))
keyword_panel = JPanel(BorderLayout())
keyword_panel.setBorder(TitledBorder("Keyword Highlighting"))
keyword_panel.add(input_row, BorderLayout.NORTH)
keyword_panel.add(keyword_scroll, BorderLayout.CENTER)
# Info Panel
self.info_area = JTextArea(10, 50)
self.info_area.setEditable(False)
self.info_area.setMargin(Insets(6, 6, 6, 6))
self.info_area.setFont(Font("Monospaced", Font.PLAIN, 12))
self.info_area.setBackground(Color(245, 245, 245))
info_scroll = JScrollPane(self.info_area)
info_scroll.setPreferredSize(Dimension(500, 150))
info_panel = JPanel()
info_panel.setLayout(BoxLayout(info_panel, BoxLayout.Y_AXIS))
info_panel.setBorder(TitledBorder("Info (Selected URL)"))
info_panel.add(info_scroll)
right_panel = JPanel()
right_panel.setLayout(BoxLayout(right_panel, BoxLayout.Y_AXIS))
right_panel.add(keyword_panel)
right_panel.add(info_panel)
self.split_discovered = JSplitPane(JSplitPane.HORIZONTAL_SPLIT, disc_scroll, right_panel)
self.split_discovered.setResizeWeight(0.35)
self.tabbed_panel.addTab("Discovered URLs", self.split_discovered)
def getTabCaption(self):
return "7illight"
def getUiComponent(self):
return self.tabbed_panel
def load_selected_row(self):
row = self.table.getSelectedRow()
if row != -1:
self.scope_selector.setSelectedItem(self.table_model.getValueAt(row, 0))
self.color_selector.setSelectedItem(self.table_model.getValueAt(row, 1))
self.keyword_field.setText(self.table_model.getValueAt(row, 2))
def add_keyword(self, event):
scope = self.scope_selector.getSelectedItem()
color = self.color_selector.getSelectedItem()
keyword = self.keyword_field.getText().strip().lower()
if keyword:
for row in range(self.table_model.getRowCount()):
if (self.table_model.getValueAt(row, 0) == scope and
self.table_model.getValueAt(row, 1) == color and
self.table_model.getValueAt(row, 2).lower() == keyword):
return
self.table_model.addRow([scope, color, keyword])
self.cached_keywords.append((scope, color.lower(), keyword))
def update_keyword(self, event):
row = self.table.getSelectedRow()
if row != -1:
old = (self.table_model.getValueAt(row, 0),
self.table_model.getValueAt(row, 1).lower(),
self.table_model.getValueAt(row, 2).lower())
new_scope = self.scope_selector.getSelectedItem()
new_color = self.color_selector.getSelectedItem()
new_keyword = self.keyword_field.getText().strip().lower()
self.table_model.setValueAt(new_scope, row, 0)
self.table_model.setValueAt(new_color, row, 1)
self.table_model.setValueAt(new_keyword, row, 2)
self.cached_keywords = [kw for kw in self.cached_keywords if kw != old]
self.cached_keywords.append((new_scope, new_color.lower(), new_keyword))
def delete_keyword(self, event):
row = self.table.getSelectedRow()
if row != -1:
scope = self.table_model.getValueAt(row, 0)
color = self.table_model.getValueAt(row, 1).lower()
keyword = self.table_model.getValueAt(row, 2).lower()
self.cached_keywords = [kw for kw in self.cached_keywords if kw != (scope, color, keyword)]
self.table_model.removeRow(row)
def on_discovered_tree_click(self, event):
sel_row = self.disc_tree.getClosestRowForLocation(event.getX(), event.getY())
self.disc_tree.setSelectionRow(sel_row)
node = self.disc_tree.getLastSelectedPathComponent()
if node and node.isLeaf():
url = self.get_full_path(node)
content = self.discovered_map.get(url, "")
self.info_area.setText("[+] URL: {}\n\n{}".format(url, content))
def get_full_path(self, node):
path = []
while node:
path.insert(0, str(node.getUserObject()))
node = node.getParent()
if node and node.getUserObject() == "Discovered JS URLs":
break
return "/" + "/".join(path)
def update_discovered_tree(self):
self.disc_root_node.removeAllChildren()
full_urls = sorted(self.discovered_map.keys())
for full_url in full_urls:
parts = [p for p in full_url.strip("/").split("/") if p]
current_node = self.disc_root_node
for part in parts:
match = None
for i in range(current_node.getChildCount()):
child = current_node.getChildAt(i)
if child.getUserObject() == part:
match = child
break
if not match:
match = DefaultMutableTreeNode(part)
self.disc_tree_model.insertNodeInto(match, current_node, current_node.getChildCount())
current_node = match
self.disc_tree_model.reload()
self.disc_tree.expandRow(0)
def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
if toolFlag not in [self.callbacks.TOOL_PROXY, self.callbacks.TOOL_REPEATER]:
return
try:
request_info = self.helpers.analyzeRequest(messageInfo)
url = request_info.getUrl()
host, path, full_url = url.getHost(), url.getPath(), str(url)
response = messageInfo.getResponse()
if not response:
return
response_info = self.helpers.analyzeResponse(response)
headers = response_info.getHeaders()
body = response[response_info.getBodyOffset():].tostring().decode("utf-8", "ignore")
content_type = ""
for h in headers:
if h.lower().startswith("content-type:"):
content_type = h.lower()
break
if path.endswith(".js") or "javascript" in content_type:
found_urls = re.findall(r'["\'](/[^\'"\\s]{1,200})["\']', body)
added = False
for u in found_urls:
full = host + u
if full not in self.discovered_map:
self.discovered_map[full] = "[Extracted from {}]".format(full_url)
added = True
if added:
EventQueue.invokeLater(self.update_discovered_tree)
request_str = messageInfo.getRequest()[request_info.getBodyOffset():].tostring().decode("utf-8", "ignore").lower()
response_str = body.lower()
for scope, color, keyword in self.cached_keywords:
if (scope == "Request" and keyword in request_str) or \
(scope == "Response" and keyword in response_str) or \
(scope == "Both" and (keyword in request_str or keyword in response_str)):
messageInfo.setHighlight(color)
break
except Exception as e:
print("[!] Error: {}".format(str(e)))