# -*- 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 TitledBorder
from java.awt import (BorderLayout, Dimension, Insets, Color, EventQueue,
Font, Desktop)
from java.awt.event import ItemEvent, ItemListener, MouseAdapter
import java.net.URI as URI
import re
class TreeClickListener(MouseAdapter):
def __init__(self, outer):
self.outer = outer
def mouseClicked(self, event):
self.outer.on_discovered_tree_click(event)
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 Auto JS Mapper loaded.")
def getTabCaption(self):
return "7illight"
def getUiComponent(self):
return self.tabbed_panel
def init_gui(self):
self.tabbed_panel = JTabbedPane()
# 좌측 트리 - Auto JS Mapper
self.disc_root_node = DefaultMutableTreeNode("Auto JS Mapper")
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"))
# 키워드 입력 UI
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(10)
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.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.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 출력 영역 (텍스트 URL만 출력)
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("Auto JS Mapper", self.split_discovered)
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 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)
self.info_area.setText("[+] Discovered from JS:\n\nURL: {}\n".format(url))
def get_full_path(self, node):
path = []
while node:
path.insert(0, str(node.getUserObject()))
node = node.getParent()
if node and node.getUserObject() == "Auto JS Mapper":
break
return "/".join(path).replace(":/", "://")
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.replace("://", "/").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 = url.getHost(), url.getPath()
protocol = url.getProtocol()
port = url.getPort()
if port == -1 or (protocol == "http" and port == 80) or (protocol == "https" and port == 443):
base_url = "{}://{}".format(protocol, host)
else:
base_url = "{}://{}:{}".format(protocol, host, port)
full_url = 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:
absolute_url = base_url + u
if absolute_url not in self.discovered_map:
self.discovered_map[absolute_url] = "[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)))