카테고리 없음

ㅅ-.- 凸

1231. 2025. 3. 27. 09:24
# -*- 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, DefaultMutableTreeNode,
                         DefaultTreeModel, JTabbedPane)
from javax.swing.table import DefaultTableModel
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(f"{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 기반 처리 시작
            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)))