카테고리 없음

ㅊㄹ

1231. 2025. 3. 27. 10:17
# -*- 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)
from java.awt.event import ItemEvent, ItemListener, MouseAdapter
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 JS URL discovery loaded.")

    def init_gui(self):
        self.tabbed_panel = JTabbedPane()

        # 좌측 트리
        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"))

        # 키워드 입력 영역 (가로 정렬)
        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 패널
        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 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.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)))