# -*- 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,
GridBagLayout, GridBagConstraints, Font)
from java.awt.event import ItemEvent, ItemListener
import re
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.domain_headers = {}
self.known_hosts = set()
self.path_param_map = {}
self.cached_keywords = []
self.discovered_paths = set()
self.init_gui()
callbacks.addSuiteTab(self)
print("[+] 7illight extension with JS URL discovery loaded.")
def init_gui(self):
self.tabbed_panel = JTabbedPane()
# 좌측 트리 1 (Main)
self.root_node = DefaultMutableTreeNode("Website Structure")
self.tree_model = DefaultTreeModel(self.root_node)
self.path_tree = JTree(self.tree_model)
self.path_tree.setRootVisible(True)
tree_scroll = JScrollPane(self.path_tree)
tree_scroll.setPreferredSize(Dimension(350, 500))
tree_scroll.setBorder(TitledBorder("URL Path & Parameters"))
# 좌측 트리 2 (Discovered URLs)
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)
disc_scroll = JScrollPane(self.disc_tree)
disc_scroll.setPreferredSize(Dimension(350, 500))
disc_scroll.setBorder(TitledBorder("Discovered from JavaScript"))
# 우측: 키워드 및 Info
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.add_button = JButton("Add", actionPerformed=self.add_keyword)
self.delete_button = JButton("Delete", actionPerformed=self.delete_keyword)
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)
input_panel = JPanel(GridBagLayout())
gbc = GridBagConstraints()
gbc.insets = Insets(5, 5, 5, 5)
gbc.fill = GridBagConstraints.HORIZONTAL
components = [
(JLabel("Scope"), self.scope_selector),
(JLabel("Color"), self.color_selector),
(JLabel(""), self.color_preview),
(JLabel("Keyword"), self.keyword_field),
(self.add_button, self.delete_button)
]
for i, (label, field) in enumerate(components):
gbc.gridx = 0
gbc.gridy = i
input_panel.add(label, gbc)
gbc.gridx = 1
input_panel.add(field, gbc)
self.table_model = DefaultTableModel(["Scope", "Color", "Keyword"], 0)
self.table = JTable(self.table_model)
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_panel, BorderLayout.NORTH)
keyword_panel.add(keyword_scroll, BorderLayout.CENTER)
self.domain_selector = JComboBox()
self.domain_selector.addItemListener(HostSelectionListener(self))
self.refresh_button = JButton("Refresh", actionPerformed=self.refresh_info)
self.clear_button = JButton("Clear", actionPerformed=self.clear_info_area)
domain_row = JPanel()
domain_row.setLayout(BoxLayout(domain_row, BoxLayout.X_AXIS))
domain_row.setBorder(EmptyBorder(5, 5, 5, 5))
domain_row.add(JLabel("Host"))
domain_row.add(self.domain_selector)
domain_row.add(self.refresh_button)
domain_row.add(self.clear_button)
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 (Response Headers)"))
info_panel.add(domain_row)
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_main = JSplitPane(JSplitPane.HORIZONTAL_SPLIT, tree_scroll, right_panel)
self.split_main.setResizeWeight(0.35)
self.split_discovered = JSplitPane(JSplitPane.HORIZONTAL_SPLIT, disc_scroll, right_panel)
self.split_discovered.setResizeWeight(0.35)
self.tabbed_panel.addTab("Main", self.split_main)
self.tabbed_panel.addTab("Discovered URLs", self.split_discovered)
def getTabCaption(self):
return "7illight"
def getUiComponent(self):
return self.tabbed_panel
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):
selected_row = self.table.getSelectedRow()
if selected_row != -1:
scope = self.table_model.getValueAt(selected_row, 0)
color = self.table_model.getValueAt(selected_row, 1).lower()
keyword = self.table_model.getValueAt(selected_row, 2).lower()
self.cached_keywords = [kw for kw in self.cached_keywords if kw != (scope, color, keyword)]
self.table_model.removeRow(selected_row)
def clear_info_area(self, event):
self.info_area.setText("")
def refresh_info(self, event):
host = self.domain_selector.getSelectedItem()
if host and host in self.domain_headers:
self.info_area.setText("".join(self.domain_headers[host]))
self.info_area.setCaretPosition(self.info_area.getDocument().getLength())
def update_tree(self):
self.root_node.removeAllChildren()
for path, params in sorted(self.path_param_map.items()):
parts = [p for p in path.strip("/").split("/") if p]
current_node = self.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.tree_model.insertNodeInto(match, current_node, current_node.getChildCount())
current_node = match
for param, ptype in sorted(params):
label = {
0: "GET", 1: "POST", 2: "COOKIE",
3: "XML", 4: "JSON", 5: "MULTIPART"
}.get(ptype, "OTHER")
leaf = DefaultMutableTreeNode("{} [{}]".format(param, label))
self.tree_model.insertNodeInto(leaf, current_node, current_node.getChildCount())
self.tree_model.reload()
self.path_tree.expandRow(0)
def update_discovered_tree(self):
self.disc_root_node.removeAllChildren()
sorted_paths = sorted(self.discovered_paths)
for path in sorted_paths:
parts = [p for p in path.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)
# 파라미터 수집 및 트리 업데이트
params = request_info.getParameters()
param_set = self.path_param_map.get(path, set())
updated = False
for p in params:
entry = (p.getName(), p.getType())
if entry not in param_set:
param_set.add(entry)
updated = True
if updated:
self.path_param_map[path] = param_set
EventQueue.invokeLater(self.update_tree)
# 응답 처리
response = messageInfo.getResponse()
if response:
response_info = self.helpers.analyzeResponse(response)
headers = response_info.getHeaders()
body = response[response_info.getBodyOffset():].tostring().decode("utf-8", "ignore")
# Info 영역
summary = []
for h in headers:
h_lower = h.lower()
if h_lower.startswith("server") or \
h_lower.startswith("set-cookie") or \
h_lower.startswith("authorization") or \
h_lower.startswith("cookie"):
summary.append(h)
if summary:
new_info = "[+] Info from response: {}\n{}\n\n".format(full_url, "\n".join(summary))
self.domain_headers.setdefault(host, []).append(new_info)
if host not in self.known_hosts:
self.known_hosts.add(host)
EventQueue.invokeLater(lambda: self.domain_selector.addItem(host))
if self.domain_selector.getSelectedItem() == host:
EventQueue.invokeLater(lambda: self.refresh_info(None))
# JavaScript 내 URL 경로 추출
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:
if u not in self.discovered_paths:
self.discovered_paths.add(u)
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)))