카테고리 없음

ㅇㅇ

1231. 2025. 3. 27. 13:05
# -*- 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)))