├── .github
└── images
│ ├── 0x999_X.png
│ ├── change_in_monitored.png
│ ├── change_in_monitored2.png
│ ├── hosts.png
│ ├── jsluicepp-filters.gif
│ ├── jsluicepp-process-from-sitemap.gif
│ ├── jsluicepp.png
│ ├── logo.png
│ ├── no_results_monitored.png
│ ├── secret_notification.png
│ └── tomnomnom_X.png
├── LICENSE
├── README.md
└── jsluicepp.py
/.github/images/0x999_X.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x999-x/jsluicepp/e829f18faaef63673f2ec465c83371a4caa8bf52/.github/images/0x999_X.png
--------------------------------------------------------------------------------
/.github/images/change_in_monitored.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x999-x/jsluicepp/e829f18faaef63673f2ec465c83371a4caa8bf52/.github/images/change_in_monitored.png
--------------------------------------------------------------------------------
/.github/images/change_in_monitored2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x999-x/jsluicepp/e829f18faaef63673f2ec465c83371a4caa8bf52/.github/images/change_in_monitored2.png
--------------------------------------------------------------------------------
/.github/images/hosts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x999-x/jsluicepp/e829f18faaef63673f2ec465c83371a4caa8bf52/.github/images/hosts.png
--------------------------------------------------------------------------------
/.github/images/jsluicepp-filters.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x999-x/jsluicepp/e829f18faaef63673f2ec465c83371a4caa8bf52/.github/images/jsluicepp-filters.gif
--------------------------------------------------------------------------------
/.github/images/jsluicepp-process-from-sitemap.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x999-x/jsluicepp/e829f18faaef63673f2ec465c83371a4caa8bf52/.github/images/jsluicepp-process-from-sitemap.gif
--------------------------------------------------------------------------------
/.github/images/jsluicepp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x999-x/jsluicepp/e829f18faaef63673f2ec465c83371a4caa8bf52/.github/images/jsluicepp.png
--------------------------------------------------------------------------------
/.github/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x999-x/jsluicepp/e829f18faaef63673f2ec465c83371a4caa8bf52/.github/images/logo.png
--------------------------------------------------------------------------------
/.github/images/no_results_monitored.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x999-x/jsluicepp/e829f18faaef63673f2ec465c83371a4caa8bf52/.github/images/no_results_monitored.png
--------------------------------------------------------------------------------
/.github/images/secret_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x999-x/jsluicepp/e829f18faaef63673f2ec465c83371a4caa8bf52/.github/images/secret_notification.png
--------------------------------------------------------------------------------
/.github/images/tomnomnom_X.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x999-x/jsluicepp/e829f18faaef63673f2ec465c83371a4caa8bf52/.github/images/tomnomnom_X.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 0x999
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [](#)
4 |
5 |
6 |
7 | ## 📄 Table of Contents
8 | - [👋 Introduction](#-introduction)
9 | - [🛠️ Setup](#%EF%B8%8F-setup)
10 | - [📝 Usage](#-usage)
11 | - [📜 Features](#-features)
12 | - [Monitor URLs](#monitor-urls)
13 | - [Send to Repeater](#send-to-repeater)
14 | - [Secrets](#secrets)
15 | - [Secret Notifications](#secret-notifications)
16 | - [Copy URL](#copy-url)
17 | - [Positive/Negative Match](#positivenegative-match)
18 | - [In-scope only](#in-scope-only)
19 | - [In-line Tags](#in-line-tags)
20 | - [Hide Duplicates](#hide-duplicates)
21 | - [Show Parameterized](#show-parameterized)
22 | - [Import/Export](#importexport)
23 | - [Save Settings](#save-settings)
24 | - [🤝 Contributors](#-contributors)
25 |
26 |
27 | ## 👋 Introduction
28 |
29 | jsluice++ is a Burp Suite extension designed for passive and active scanning of JavaScript traffic using the CLI tool [jsluice](https://github.com/BishopFox/jsluice/tree/main/cmd/jsluice).
30 | The extension utilizes jsluice's capabilities to extract URLs, paths, and secrets from static JavaScript files and integrates it with Burp Suite, allowing you to easily scan javascript traffic from Burp Suite's Sitemap or Proxy while also offering a user-friendly interface for data inspection and a variety of additional useful features
31 |
32 |
33 |
34 |
35 |
36 | ## 🛠️ Setup
37 |
38 | Requirements:
39 | - jsluice CLI
40 | - Jython(2.7.3)
41 |
42 | if this isn't your first time installing a jython extension you can skip to step 3.
43 | 1. Visit [Jython's Official Website](https://www.jython.org/download) and download Jython's standalone JAR
44 | 2. In Burp Suite -> "Extensions" -> "Extensions Settings" -> under "Python environment" select the "Location of Jython standalone JAR file"
45 | 3. Download and install [jsluice's](https://github.com/BishopFox/jsluice/tree/main/cmd/jsluice) CLI go install github.com/BishopFox/jsluice/cmd/jsluice@latest
(ensure that the jsluice binary is in your $PATH
otherwise the extension won't work)
46 | 4. Download jsluicepp.py, then in Burp Suite go to "Extensions" -> "Installed" -> Click "Add" -> under "Extension type" select "Python" -> Select the jsluicepp.py file.
47 |
48 | ## 📝 Usage
49 |
50 | ##### Active scan
51 |
52 | The extension adds an item to Burp Suite's context menu which allows you to easily process responses from Burp Suite's Sitemap tab
53 |
54 | to do so simply right click any host in the sitemap tree or any item in the sitemap table and select Extensions->jsluice++->Process selected item(s)
in Burp Suite's context menu.
55 | When processing items from the site map tree the extension will get the site map of every selected item (Multiple hosts can be processed)
56 |
57 | 
58 |
59 | ##### Passive scan
60 | Default: Off
61 |
62 | When Passive scan is toggled on the extension will register an http handler and process responses from traffic flowing through Burp Suite's Proxy (it's recommended to use the in-scope only feature when enabling passive scan to reduce noise)
63 |
64 | ---
65 |
66 | The extension will process any URL with a .js file extension or a JavaScript mime type and a success status code (2xx) using jsluice's urls mode.
67 |
68 | The processed JavaScript file is temporarily saved locally in the ".jsluicepp" directory and gets removed after jsluice has finished processing it,
69 |
70 | If jsluice returns any data, the host associated with the URL will be added to the Hosts list, to view the results from jsluice simply select the host and the desired file (multiple files can be selected).
71 |
72 | Note: the same URL (GET parameters excluded) will not be processed more than once until the extension is reloaded, this doesn't apply to monitored urls.
73 |
74 | ---
75 | ## 📜 Features
76 |
77 | Monitor URLs
78 |
79 | Every file in the Files list can be monitored by the extension, to do so simply right-click the file and select the "Monitor URL" option from the popup menu (repeat this step if you wish to Stop Monitoring the URL),
80 | When a new URL is monitored it's details are saved to .jsluicepp/monitored_urls.txt
and a copy of the output from jsluice is saved locally to .jsluicepp/monitored_files/{host}_{filename_hash}
(Secrets excluded),
81 | Monitored files are colored green in the Files list,
82 | The rate at which requests are sent to the monitored URLs is determined by the selected value in the Monitor Interval selector,
83 |
84 | Monitor interval options are:
85 | - Off - Don't monitor
86 | - Once - Once when the extension loads
87 | - Hourly - Once every hour
88 | - Daily - Once every day
89 | - Weekly - Once every week
90 | - Monthly - Once every month
91 |
92 | if jsluice has returned data that is different from the locally saved copy you will be notified with the following popup dialog:
93 |
94 | 
95 |
96 | the locally saved copy of the file will get moved to .jsluicepp/monitored_files/{host}_{filename_hash}.old
and a new copy of the new file will be saved,
97 | When selecting the monitored file in the extension new/modified rows will be colored green and previous versions of modified rows/deleted rows will be colored red, example:
98 |
99 | 
100 |
101 |
102 | if a Monitored URL responds with a non-success status code(2xx) or if jsluice returns no output you will be prompted with a popup dialog asking if you wish to Stop Monitoring the URL, if "Yes" is selected all local copies of the file will be removed.
103 |
104 | 
105 |
106 | Send to Repeater
107 |
108 | Every row in the URL/Paths results table can be sent to Burp Suite's Repeater by right clicking any row and selecting the "Send to Repeater" option in the popup menu.
109 | Query Params, Body Params & Headers will be included if any exist,
110 | if the URL/Path column starts with "http://" or "https://" or "//" the host will be extracted from the URL/Path column otherwise the selected host will be used,
111 | if a content-type header with a value of application/json or application/xml or text/xml
is present in the Headers column the body of the request will be formatted accordingly.
112 |
113 | Secrets
114 | Default: On
115 |
116 | If selected a "Secrets" results table is added to the UI and the extension will use jsluice's secrets mode on the file after the urls mode has finished, if any unique secrets are found the host will be colored red with a 🤫 emoji next to it, example:
117 |
118 | 
119 |
120 | if you wish to use a custom patterns config you can do so by modifying the 'secrets_command' variable in the code.
121 |
122 | Secret Notifications
123 | Default: Off
124 |
125 | If selected and the Secrets checkbox is also selected you will be notified with a popup dialog when a new unique secret is found, the dialog is closed automatically after 15 seconds , example dialog:
126 | 
127 |
128 | Copy URL
129 |
130 | The URL of every processed file can be copied to clipboard by right-clicking the file in the Files list and selecting the "Copy URL" option from the popup menu.
131 |
132 | Positive/Negative Match
133 |
134 | The positive/negative match filter feature is designed to target the URL/Path column within the results table, When adding a positive filter, only rows that contain the positive match filter within the URL/Path column will be included in the results table. Conversely, when adding a negative filter, rows that have the negative match filter in the URL/Path column will be excluded from the results,
135 | Multiple filters can be applied.
136 | 
137 |
138 | In-scope only
139 | Default: Off
140 |
141 | If selected the extension will use Burp Suite's scope to determine whether a URL should be processed, additionally out of scope hosts will not be displayed in the hosts list.
142 |
143 | In-line Tags
144 | Default: Off
145 |
146 | When selected, in addition to URLs with a .js file extension/mime type, the extension will look for responses with HTML mime type, if such response is found the extension will attempt to extract any and all
tags from the response body, the script tags are then concatenated using new lines and saved to a file which then gets processed by jsluice.
147 |
148 | Hide Duplicates
149 | Default: On
150 |
151 | If selected duplicate rows will be hidden from the results tables(excluding the Type & File columns)
152 |
153 | Show Parameterized
154 | Default: Off
155 |
156 | If selected only rows that contain Query Params / Body Params / Headers will be displayed in the URL/Paths results table
157 |
158 |
159 | Import/Export
160 |
161 | Import/Export results or currently selected settings as JSON
162 |
163 |
164 | Save Settings
165 |
166 | Save current selected settings using Burp Suite's API (persists across restarts)
167 |
168 |
169 | ## 🤝 Contributors
170 |
171 | [TomNomNom](https://github.com/tomnomnom) for creating jsluice 💖 [](https://twitter.com/intent/follow?screen_name=tomnomnom)
[Me](https://github.com/0x999-x) [](https://twitter.com/intent/follow?screen_name=_0x999)
You?
172 |
173 |
--------------------------------------------------------------------------------
/jsluicepp.py:
--------------------------------------------------------------------------------
1 | from burp import IBurpExtender, IHttpListener, ITab, IExtensionStateListener, IContextMenuFactory
2 | from threading import Thread, Timer, Semaphore
3 | from time import time
4 | from javax.swing.table import DefaultTableCellRenderer
5 | from javax.swing.filechooser import FileNameExtensionFilter
6 | from java.awt import Toolkit
7 | from java.awt.datatransfer import StringSelection
8 | import javax.swing.event
9 | import java.awt.event
10 | from java.net import URL
11 | from javax.imageio import ImageIO
12 | from java.io import ByteArrayInputStream
13 | from base64 import b64decode
14 | from hashlib import md5
15 | from datetime import datetime, timedelta
16 | from urlparse import urlparse
17 | import javax.swing as swing
18 | import java.awt as awt
19 | import re
20 | import os
21 | import subprocess
22 | import json
23 | import distutils.spawn
24 |
25 | class PopupMenu(awt.event.ActionListener, awt.event.MouseAdapter):
26 | def __init__(self, component, callbacks=None, selected_host="",file_names_list=None, processed_file_details=None, monitored_urls=None, logo_image=None, directory=None, monitored_urls_path=None, monitored_urls_directory=None):
27 | self.callbacks = callbacks
28 | self.popup = swing.JPopupMenu()
29 | self.component = component
30 | self.selected_host = selected_host
31 | self.file_names_list = file_names_list
32 | self.processed_file_details = processed_file_details
33 | self.monitored_urls = monitored_urls
34 | self.logo_image = logo_image
35 | self.directory = directory
36 | self.monitored_urls_path = monitored_urls_path
37 | self.monitored_urls_directory = monitored_urls_directory
38 | if isinstance(self.component, swing.JTable):
39 | self.menu_item_send_to_repeater = swing.JMenuItem("Send to Repeater")
40 | self.menu_item_send_to_repeater.addActionListener(self.sendToRepeater)
41 | self.popup.add(self.menu_item_send_to_repeater)
42 | elif isinstance(self.component, swing.JList):
43 | self.menu_item_copy_url = swing.JMenuItem("Copy URL")
44 | self.menu_item_copy_url.addActionListener(self.copyURL)
45 | self.popup.add(self.menu_item_copy_url)
46 | self.menu_item_monitor_url = swing.JMenuItem("Monitor URL")
47 | self.menu_item_monitor_url.addActionListener(self.monitor_url)
48 | self.popup.add(self.menu_item_monitor_url)
49 |
50 | def mouseReleased(self, e):
51 | self.showPopup(e)
52 |
53 | def mousePressed(self, e):
54 | self.showPopup(e)
55 |
56 | def showPopup(self, event):
57 | if event.isPopupTrigger():
58 | if isinstance(self.component, swing.JTable):
59 | row = self.component.rowAtPoint(event.getPoint())
60 | self.component.getSelectionModel().setSelectionInterval(row, row)
61 | elif isinstance(self.component, swing.JList):
62 | if self.component.getSelectedIndex() == -1:
63 | return
64 | index = self.component.locationToIndex(event.getPoint())
65 | self.component.setSelectedIndex(index)
66 | url_monitored = False
67 | if self.monitored_urls is not None:
68 | for url in self.monitored_urls:
69 | if self.monitored_urls[url]['host'] == self.selected_host and self.monitored_urls[url]['file_name'] == self.file_names_list.getSelectedValue():
70 | url_monitored = True
71 | break
72 | if url_monitored:
73 | self.menu_item_monitor_url.setText("Stop Monitoring URL")
74 | else:
75 | self.menu_item_monitor_url.setText("Monitor URL")
76 | self.popup.show(self.component, event.getX(), event.getY())
77 |
78 | def monitor_url(self, event):
79 | if event.getSource() == self.menu_item_monitor_url:
80 | if self.file_names_list is None:
81 | return
82 | selected_file = self.file_names_list.getSelectedValue()
83 | selected_file_hash = md5(selected_file).hexdigest()
84 | for details in self.processed_file_details.values():
85 | if details.get('file_name') == selected_file and details.get('host') == self.selected_host:
86 | processed_file_details = details
87 | break
88 | if processed_file_details is not None:
89 | origin_url = processed_file_details['origin_url']
90 | host = processed_file_details['host']
91 | monitor_urls_file = open(self.directory + self.monitored_urls_path, "a+")
92 | monitor_urls_file.seek(0)
93 | for line in monitor_urls_file:
94 | json_line = json.loads(line)
95 | if json_line.get('origin_url') == origin_url and json_line.get('host') == host:
96 | stop_monitor_confirmation_dialog = swing.JDialog()
97 | stop_monitor_confirmation_dialog.setTitle("jsluice++ | Monitored URLs")
98 | stop_monitor_confirmation_dialog.setModal(True)
99 | stop_monitor_confirmation_dialog.setAlwaysOnTop(True)
100 | stop_monitor_confirmation_dialog.setSize(644, 244)
101 | label_font = awt.Font("Cantarell", awt.Font.BOLD, 14)
102 | logo_label = swing.JLabel(swing.ImageIcon(self.logo_image))
103 | logo_label.setVerticalAlignment(swing.JLabel.TOP)
104 | stop_monitor_confirmation_dialog.getContentPane().add(logo_label, awt.BorderLayout.NORTH)
105 | confirmation_label = swing.JLabel('Are you sure you want to stop monitoring "' + origin_url + '"?', swing.JLabel.CENTER)
106 | confirmation_label.setFont(label_font)
107 | stop_monitor_confirmation_dialog.getContentPane().add(confirmation_label, awt.BorderLayout.CENTER)
108 | stop_monitor_confirmation_dialog.setLocationRelativeTo(None)
109 | stop_monitor_confirmation_dialog.setDefaultCloseOperation(swing.WindowConstants.DISPOSE_ON_CLOSE)
110 |
111 | button_panel = swing.JPanel()
112 | button_panel.setLayout(swing.BoxLayout(button_panel, swing.BoxLayout.X_AXIS))
113 |
114 | def stop_monitoring(event):
115 | monitor_urls_file.seek(0)
116 | lines = monitor_urls_file.readlines()
117 | monitor_urls_file.seek(0)
118 | for line in lines:
119 | json_line = json.loads(line)
120 | if json_line.get('origin_url') != origin_url:
121 | monitor_urls_file.write(line)
122 | monitor_urls_file.truncate()
123 | monitor_urls_file.close()
124 | if os.path.exists(self.directory + self.monitored_urls_directory + host + "_" + selected_file_hash):
125 | os.remove(self.directory + self.monitored_urls_directory + host + "_" + selected_file_hash)
126 | if os.path.exists(self.directory + self.monitored_urls_directory + host + "_" + selected_file_hash + ".old"):
127 | os.remove(self.directory + self.monitored_urls_directory + host + "_" + selected_file_hash + ".old")
128 | del self.monitored_urls[origin_url]
129 | self.file_names_list.repaint()
130 | self.file_names_list.revalidate()
131 | print("Stopped monitoring: " + origin_url)
132 | stop_monitor_confirmation_dialog.dispose()
133 |
134 | button = swing.JButton("Yes", actionPerformed=stop_monitoring)
135 | button2 = swing.JButton("No", actionPerformed=lambda event: (stop_monitor_confirmation_dialog.dispose(), monitor_urls_file.close()))
136 |
137 | button_panel.add(button)
138 | button_panel.add(swing.Box.createHorizontalStrut(10))
139 | button_panel.add(button2)
140 |
141 | center_panel = swing.JPanel()
142 | center_panel.setLayout(swing.BoxLayout(center_panel, swing.BoxLayout.Y_AXIS))
143 | center_panel.add(swing.Box.createVerticalGlue())
144 | center_panel.add(button_panel)
145 | center_panel.add(swing.Box.createVerticalGlue())
146 |
147 | stop_monitor_confirmation_dialog.getContentPane().add(center_panel, awt.BorderLayout.SOUTH)
148 | stop_monitor_confirmation_dialog.pack()
149 | stop_monitor_confirmation_dialog.setVisible(True)
150 |
151 | return
152 | monitor_urls_file.write(json.dumps({'origin_url': origin_url, 'file_name': selected_file, 'host': host}) + "\n")
153 | monitor_urls_file.close()
154 | if not os.path.exists(self.directory + self.monitored_urls_directory):
155 | os.makedirs(self.directory + self.monitored_urls_directory)
156 | monitor_url_file = open(self.directory + self.monitored_urls_directory + host + "_" + selected_file_hash, "w+")
157 | monitor_url_file.write(json.dumps(processed_file_details['output']))
158 | monitor_url_file.close()
159 | if origin_url not in self.monitored_urls:
160 | print("Adding monitored URL: " + str(origin_url))
161 | self.monitored_urls[origin_url] = {'origin_url': origin_url, 'file_name': selected_file, 'host': host}
162 | self.file_names_list.repaint()
163 | self.file_names_list.revalidate()
164 |
165 | def copyURL(self, event):
166 | if event.getSource() == self.menu_item_copy_url:
167 | if self.file_names_list is None:
168 | return
169 | selected_file = self.file_names_list.getSelectedValue()
170 | for details in self.processed_file_details.values():
171 | if details.get('file_name') == selected_file and details.get('host') == self.selected_host:
172 | processed_file_details = details
173 | break
174 | if processed_file_details is not None:
175 | origin_url = processed_file_details['origin_url']
176 | Toolkit.getDefaultToolkit().getSystemClipboard().setContents(StringSelection(origin_url), None)
177 |
178 | def sendToRepeater(self, event):
179 | if event.getSource() == self.menu_item_send_to_repeater:
180 | row = self.component.getSelectedRow()
181 | self.scheme = ''
182 | self.host = ''
183 | self.query_params = ''
184 | self.port = None
185 | use_https = False
186 | if row != -1:
187 | url = str(self.component.getValueAt(row, 0))
188 | if url.startswith("http:") or url.startswith("https:") or url.startswith("//"):
189 | parsed_url = urlparse(url)
190 | self.scheme = parsed_url.scheme
191 | self.host = parsed_url.netloc if parsed_url.port not in [80, 443] else parsed_url.hostname
192 | if "@" in self.host:
193 | self.host = self.host.split("@")[1]
194 | self.port = parsed_url.port
195 | self.query_params = parsed_url.query
196 | use_https = True if parsed_url.scheme == "https" else False
197 | url = parsed_url.path
198 | if not url.startswith("/"):
199 | url = "/" + url
200 | if self.host == '':
201 | self.host = self.selected_host
202 | self.port = int(self.host.split(':')[1]) if ':' in self.host else None
203 | port = self.port if self.port != None else 80 if self.scheme == "http" else 443
204 | if ":" in self.host:
205 | port = int(self.host.split(":")[1])
206 | self.host = self.host.split(":")[0]
207 | use_https = True if self.port == None and self.scheme != "http" else use_https
208 | bodyParams = str(self.component.getValueAt(row, 2))
209 | method = str(self.component.getValueAt(row, 3))
210 | headers_str = str(self.component.getValueAt(row, 4))
211 | is_json = False
212 | is_xml = False
213 | try:
214 | headers_dict = json.loads(headers_str)
215 | except ValueError:
216 | headers_dict = {}
217 | if method is None or method == "":
218 | method = "GET"
219 | default_headers = [
220 | "{method} {url}{query_params} HTTP/1.1".format(method=method, url=url, query_params="?" + self.query_params if self.query_params != '' else ''),
221 | "Host: " + self.host,
222 | "User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0",
223 | "Accept: */*",
224 | "Accept-Language: en-US,en;q=0.5",
225 | "Accept-Encoding: gzip, deflate, br",
226 | "Connection: close",
227 | "Cache-Control: max-age=0",
228 | ]
229 | if headers_dict:
230 | headers = default_headers + ["{}: {}".format(k, v) for k, v in headers_dict.items()]
231 | for header in headers:
232 | if header.lower().startswith('content-type'):
233 | if 'application/json' in header:
234 | is_json = True
235 | elif any(mime_type in header for mime_type in ['application/xml', 'text/xml']):
236 | is_xml = True
237 | break
238 | else:
239 | headers = default_headers
240 | if bodyParams.startswith('[') and bodyParams.endswith(']'):
241 | bodyParams = bodyParams.strip('][').split(', ')
242 | if is_json:
243 | bodyParams = "{" + ",".join(['"{}":"{}"'.format(val.strip('"'), 'x') for val in bodyParams]) + "}"
244 | elif is_xml:
245 | bodyParams = "" + "".join(["<{}>{}{}>".format(val.strip('"'), 'x', val.strip('"')) for val in bodyParams]) + ""
246 | else:
247 | bodyParams = "&".join(["{}={}".format(val.strip('"'), 'x') for val in bodyParams])
248 | else:
249 | bodyParams = {}
250 | request = self.callbacks.getHelpers().buildHttpMessage(headers, bodyParams)
251 | self.callbacks.sendToRepeater(self.host, port, use_https, request, None)
252 |
253 | class TextFieldsListener(swing.event.DocumentListener, awt.event.MouseAdapter):
254 | def __init__(self, callback, search_text_field, negative_match_text_field, positive_match_text_field, panel):
255 | self.callback = callback
256 | self.search_text_field = search_text_field
257 | self.negative_match_text_field = negative_match_text_field
258 | self.positive_match_text_field = positive_match_text_field
259 | self.search_text_field.setEditable(False)
260 | self.negative_match_text_field.setEditable(False)
261 | self.positive_match_text_field.setEditable(False)
262 | self.panel = panel
263 |
264 | def insertUpdate(self, event):
265 | self.callback(event)
266 |
267 | def removeUpdate(self, event):
268 | self.callback(event)
269 |
270 | def changedUpdate(self, event):
271 | self.callback(event)
272 |
273 | def handleTextFieldClick(self, textField, defaultText):
274 | field_text = textField.getText()
275 | if field_text == defaultText:
276 | textField.setText("")
277 | textField.setEditable(True)
278 | textField.setCaretPosition(0)
279 | elif field_text == "":
280 | textField.setText(defaultText)
281 | textField.setEditable(False)
282 | textField.setCaretPosition(0)
283 |
284 | def mouseClicked(self, event):
285 | if event.getSource() == self.search_text_field:
286 | self.handleTextFieldClick(self.search_text_field, "Search:")
287 | elif event.getSource() == self.negative_match_text_field:
288 | self.handleTextFieldClick(self.negative_match_text_field, "Negative Match:")
289 | elif event.getSource() == self.positive_match_text_field:
290 | self.handleTextFieldClick(self.positive_match_text_field, "Positive Match:")
291 | else:
292 | def set_default_text(field, default_text):
293 | if field.getText() == "":
294 | field.setText(default_text)
295 | field.setEditable(False)
296 | self.panel.requestFocusInWindow()
297 | field.setCaretPosition(0)
298 |
299 | search_text = self.search_text_field
300 | negative_match_text = self.negative_match_text_field
301 | positive_match_text = self.positive_match_text_field
302 |
303 | set_default_text(search_text, "Search:")
304 | set_default_text(negative_match_text, "Negative Match:")
305 | set_default_text(positive_match_text, "Positive Match:")
306 |
307 | class ColorFiles(swing.DefaultListCellRenderer):
308 | def __init__(self, monitored_urls, processed_file_details, selected_host, directory, monitored_urls_directory):
309 | self.monitored_urls = monitored_urls
310 | self.selected_host = selected_host
311 | self.directory = directory
312 | self.monitored_urls_directory = monitored_urls_directory
313 | self.processed_file_details = {details['file_name']: details for details in processed_file_details.values() if details.get('host') == self.selected_host}
314 |
315 | def getListCellRendererComponent(self, list, value, index, isSelected, cellHasFocus):
316 | component = super(ColorFiles, self).getListCellRendererComponent(
317 | list, value, index, isSelected, cellHasFocus
318 | )
319 | if os.path.exists(self.directory+self.monitored_urls_directory+self.selected_host+"_"+md5(value).hexdigest()):
320 | component.setFont(component.getFont().deriveFont(awt.Font.BOLD))
321 | component.setForeground(awt.Color(110, 204, 154))
322 | else:
323 | component.setForeground(list.getForeground() if not isSelected else list.getSelectionForeground())
324 |
325 | component.setBackground(list.getSelectionBackground() if isSelected else list.getBackground())
326 | return component
327 |
328 | class ColorFilters(swing.DefaultListCellRenderer):
329 | def __init__(self, results_filters):
330 | self.results_filters = results_filters
331 | def getListCellRendererComponent(self, list, value, index, isSelected, cellHasFocus):
332 | component = super(ColorFilters, self).getListCellRendererComponent(
333 | list, value, index, isSelected, cellHasFocus
334 | )
335 | component.setForeground(awt.Color.BLACK)
336 | filter_type = self.results_filters.get(value)
337 | color_map = {'negative': awt.Color(240,128,128), 'positive': awt.Color(201,255,229)}
338 | component.setBackground(color_map.get(filter_type, list.getBackground()))
339 | if isSelected:
340 | component.setForeground(color_map.get(filter_type, list.getForeground()))
341 | component.setBackground(list.getSelectionBackground())
342 | return component
343 |
344 | class ColorHosts(swing.DefaultListCellRenderer):
345 | def __init__(self, hosts_with_secrets, secret_image_base64):
346 | self.hosts_with_secrets = hosts_with_secrets
347 | secret_image_base64 = secret_image_base64
348 | secret_image_bytes = b64decode(secret_image_base64)
349 | secret_image_stream = ByteArrayInputStream(secret_image_bytes)
350 | secret_image_resized = ImageIO.read(secret_image_stream).getScaledInstance(16, 16, awt.Image.SCALE_SMOOTH)
351 | self.icon = swing.ImageIcon(secret_image_resized)
352 | def getListCellRendererComponent(self, listbox, value, index, isSelected, cellHasFocus):
353 | icon = None
354 | if value in self.hosts_with_secrets:
355 | self.setText(str(value))
356 | if self.getIcon() is None:
357 | self.setIcon(self.icon)
358 | icon = self.icon
359 | if isSelected:
360 | self.setBackground(listbox.getSelectionBackground())
361 | self.setForeground(awt.Color(255, 102, 102))
362 | else:
363 | self.setBackground(swing.UIManager.getColor("Label.background"))
364 | self.setForeground(awt.Color(255, 102, 102))
365 | font_metrics = self.getFontMetrics(self.getFont())
366 | text_width = font_metrics.stringWidth(str(value))
367 | icon_width = icon.getIconWidth()
368 | self.setHorizontalAlignment(swing.JLabel.LEFT)
369 | self.setHorizontalTextPosition(swing.JLabel.RIGHT)
370 | label_width = self.getSize().width
371 | icon_text_gap = label_width - (text_width + icon_width)
372 | self.setIconTextGap(icon_text_gap)
373 | return self
374 | else:
375 | label = swing.JLabel(str(value))
376 | label.setBackground(swing.UIManager.getColor("Label.background"))
377 | label.setOpaque(True)
378 | icon = None
379 | if isSelected:
380 | label.setForeground(listbox.getSelectionForeground())
381 | label.setBackground(listbox.getSelectionBackground())
382 | label.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 6, 0, 0))
383 | return label
384 |
385 | class ColorResults(DefaultTableCellRenderer):
386 | def __init__(self, differences, deleted_lines):
387 | self.differences = differences
388 | self.deleted_lines = deleted_lines
389 | def getTableCellRendererComponent(self, table, value, isSelected, hasFocus, row, column):
390 | component = super(ColorResults, self).getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column)
391 | view_row = table.convertRowIndexToModel(row)
392 | row_data = tuple(table.getModel().getValueAt(view_row, col) for col in range(table.getColumnCount()))
393 | if row_data in self.differences:
394 | component.setBackground(awt.Color(201,255,229))
395 | component.setForeground(awt.Color.BLACK)
396 | elif row_data in self.deleted_lines:
397 | component.setBackground(awt.Color(255, 204, 204))
398 | component.setForeground(awt.Color.BLACK)
399 | else:
400 | if row % 2 == 0:
401 | component.setBackground(swing.UIManager.getColor("Table.background"))
402 | component.setForeground(swing.UIManager.getColor("Table.foreground"))
403 | else:
404 | component.setBackground(swing.UIManager.getColor("Table.alternateRowColor"))
405 | component.setForeground(swing.UIManager.getColor("Table.foreground"))
406 | if isSelected:
407 | component.setBackground(table.getSelectionBackground())
408 | if row_data in self.differences:
409 | component.setForeground(awt.Color(201,255,229))
410 | elif row_data in self.deleted_lines:
411 | component.setForeground(awt.Color(255, 204, 204))
412 | else:
413 | component.setForeground(table.getSelectionForeground())
414 |
415 | return component
416 |
417 | class BurpExtender(IBurpExtender, IHttpListener, ITab, IExtensionStateListener, IContextMenuFactory):
418 | def registerExtenderCallbacks(self, callbacks):
419 | self._callbacks = callbacks
420 | self._helpers = callbacks.getHelpers()
421 | callbacks.setExtensionName("jsluice++")
422 | self.monitored_urls = {}
423 | self.processed_file_details = {}
424 | self.results_filters = {}
425 | self.unique_secrets = set()
426 | self.seen_endpoints = set()
427 | self.hosts_list = set()
428 | self.hosts_with_secrets = set()
429 | self.selected_host = ""
430 | self.path_to_binary = "jsluice"
431 | self.directory = ".jsluicepp"
432 | self.monitored_urls_path = "/monitored_urls.txt"
433 | self.monitored_urls_directory = "/monitored_files/"
434 | self.cancelled = False
435 | if not os.path.exists(self.directory):
436 | os.makedirs(self.directory)
437 | self.red_border = swing.BorderFactory.createLineBorder(awt.Color(240,128,128), 1)
438 | self.green_border = swing.BorderFactory.createLineBorder(awt.Color(110, 204, 154), 1)
439 | black_border = swing.BorderFactory.createLineBorder(awt.Color.BLACK, 1)
440 | light_red = awt.Color(255, 102, 102)
441 | self.orange = awt.Color(216, 102, 51)
442 | swing.ToolTipManager.sharedInstance().setDismissDelay(30000)
443 | self.tooltips_dict = {
444 | "on_off_button": "Toggle Passive Scan On/Off - When toggled on responses from Burp Suite's Proxy will be processed.",
445 |
446 | "search_text_field": "Filter the hosts list by search term (case insensitive)",
447 |
448 | "monitor_interval_selector": "Select how often a request should be sent to Monitored URLs (if any exist)\nOff - Don't monitor\nOnce - Once when the extension loads\nHourly - Once every hour\nDaily - Once every day\nWeekly - Once every week\nMonthly - Once every month",
449 |
450 | "autoselectall_button": "Automatically select all files in the Files list when a host is selected.",
451 |
452 | "negative_match_text_field": "Filter the results table by the URL/Path column,\nOnly rows that do not contain the specified negative match filter in the URL/Path column will be displayed (case sensitive),\nMultiple filters can be applied.",
453 |
454 | "positive_match_text_field": "Filter the results table by the URL/Path column,\nOnly rows that contain the specified positive match filter in the URL/Path column will be displayed (case sensitive),\nMultiple filters can be applied.",
455 |
456 | "include_inline_tags_checkbox": "If selected the extension will also process responses with an HTML mime type.",
457 |
458 | "in_scope_checkbox": "If selected the extension will only process URLs that are in Burp Suite's Scope,\nadditionally only hosts that are in-scope will be displayed in the Hosts list.",
459 |
460 | "hide_duplicates_checkbox": "If selected duplicate rows will be hidden from the results tables (Type & File columns excluded).",
461 |
462 | "show_parameterized": "If selected only rows that contain Query Params / Body Params / Headers will be displayed in the URL/Paths results table.",
463 |
464 | "secrets_checkbox": "If selected the extension will use jsluice's secrets mode on every processed file after the urls mode has finished\nHosts with secrets will be colored red in the Hosts list.",
465 |
466 | "secret_notifications_checkbox": "If selected and the Secrets checkbox is selected a popup dialog will appear whenever a unique secret is found (the dialog will self close after 15 seconds).",
467 |
468 | "save_settings_button": "Save current selected settings using Burp Suite's API (persists across restarts)",
469 |
470 | "import_results_button": "Import results from a JSON file",
471 |
472 | "export_results_button": "Export results to a JSON file",
473 |
474 | "import_settings_button": "Import settings from a JSON file",
475 |
476 | "export_settings_button": "Export settings to a JSON file",
477 | }
478 | self.on_off_button = swing.JToggleButton("Off",actionPerformed=lambda event: self.toggle_on_off(event))
479 | self.on_off_button.setFont(awt.Font("Cantarell", awt.Font.BOLD, 13))
480 | self.on_off_button.setSelected(False)
481 | self.on_off_button.setBorder(self.red_border)
482 | self.on_off_button.setPreferredSize(awt.Dimension(74, 40))
483 |
484 | self.host_list_model = swing.DefaultListModel()
485 | self.host_list = swing.JList(self.host_list_model)
486 | self.host_list.addListSelectionListener(lambda event: self.on_host_selected(event))
487 | self.host_list_scroll_pane = swing.JScrollPane(self.host_list)
488 | self.host_list_scroll_pane.setPreferredSize(awt.Dimension(300, 240))
489 | self.host_list.setSelectionMode(swing.ListSelectionModel.SINGLE_SELECTION)
490 |
491 | self.tabbed_pane_hosts = swing.JTabbedPane()
492 | self.tabbed_pane_files = swing.JTabbedPane()
493 | self.tabbed_pane_results = swing.JTabbedPane()
494 | self.tabbed_pane_filters = swing.JTabbedPane()
495 | self.tabbed_pane_negative_positive = swing.JTabbedPane()
496 | self.layout = swing.SpringLayout()
497 | self.panel = swing.JPanel(self.layout)
498 |
499 | self.panel.add(self.on_off_button, None)
500 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, self.on_off_button, -10, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
501 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.on_off_button, 265, swing.SpringLayout.NORTH, self.panel)
502 |
503 | self.loading_label = swing.JLabel("Loading files...")
504 | self.loading_label.setFont(awt.Font("Cantarell", awt.Font.BOLD, 14))
505 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, self.loading_label, 244, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
506 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.loading_label, 184, swing.SpringLayout.NORTH, self.panel)
507 | self.panel.add(self.loading_label)
508 | self.loading_label.setVisible(False)
509 |
510 | self.panel.add(self.tabbed_pane_hosts, None)
511 | self.search_text_field = swing.JTextField()
512 | self.negative_match_text_field = swing.JTextField()
513 | self.positive_match_text_field = swing.JTextField()
514 | self.tabbed_pane_negative_positive.setPreferredSize(awt.Dimension(180, 46))
515 | self.tabbed_pane_negative_positive.addTab("Negative", self.negative_match_text_field)
516 | self.tabbed_pane_negative_positive.addTab("Positive", self.positive_match_text_field)
517 | textfield_listener = TextFieldsListener(self.on_search_changed, self.search_text_field, self.negative_match_text_field, self.positive_match_text_field, self.panel)
518 |
519 | self.search_text_field.getDocument().addDocumentListener(textfield_listener)
520 | self.search_text_field.addMouseListener(textfield_listener)
521 | self.panel.addMouseListener(textfield_listener)
522 | self.search_text_field.setText("Search:")
523 | self.search_text_field.setPreferredSize(awt.Dimension(134, 24))
524 | self.panel.add(self.search_text_field, None)
525 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, self.search_text_field, -326, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
526 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.search_text_field, 14, swing.SpringLayout.NORTH, self.panel)
527 |
528 | self.tabbed_pane_hosts.addTab("Hosts", self.host_list_scroll_pane)
529 | self.panel.add(self.tabbed_pane_files, None)
530 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, self.tabbed_pane_hosts, -244, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
531 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.tabbed_pane_hosts, 44, swing.SpringLayout.NORTH, self.panel)
532 |
533 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, self.tabbed_pane_files, 244, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
534 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.tabbed_pane_files, 44, swing.SpringLayout.NORTH, self.panel)
535 |
536 | self.panel.add(self.tabbed_pane_results, None)
537 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, self.tabbed_pane_results, 0, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
538 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.tabbed_pane_results, 324, swing.SpringLayout.NORTH, self.panel)
539 | self.tabbed_pane_results.setPreferredSize(awt.Dimension(1200, 264))
540 | self.result_table_model = swing.table.DefaultTableModel([], ["URL/Path", "Query Params", "Body Params", "Method", "Headers", "Type"])
541 | self.result_table = swing.JTable(self.result_table_model)
542 | self.result_table.setAutoCreateRowSorter(True)
543 | scroll_pane_result = swing.JScrollPane(self.result_table)
544 | self.tabbed_pane_results.addTab("Results", scroll_pane_result)
545 | self.results_table_popupmenu = PopupMenu(self.result_table, callbacks=self._callbacks)
546 | self.result_table.addMouseListener(self.results_table_popupmenu)
547 |
548 | self.file_names_model = swing.DefaultListModel()
549 | self.file_names_list = swing.JList(self.file_names_model)
550 | self.file_names_list.setCellRenderer(ColorFiles(self.monitored_urls, self.processed_file_details, self.selected_host, self.directory, self.monitored_urls_directory))
551 | self.file_names_list.setSelectionMode(swing.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
552 | self.file_names_list.addListSelectionListener(lambda event: (self.file_names_list.setValueIsAdjusting(True),self.display_result(event, self.file_names_list)))
553 |
554 | self.secrets_table_model = swing.table.DefaultTableModel([], ["kind", "data", "severity", "context"])
555 | self.secrets_table = swing.JTable(self.secrets_table_model)
556 | self.scroll_pane_secrets = swing.JScrollPane(self.secrets_table)
557 | self.tabbed_pane_results.addTab("Secrets", self.scroll_pane_secrets)
558 | self.logo_base64 = "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"
559 | logo_bytes = b64decode(self.logo_base64)
560 | logo_image_stream = ByteArrayInputStream(logo_bytes)
561 | self.logo_image = ImageIO.read(logo_image_stream)
562 | logo_label = swing.JLabel(swing.ImageIcon(self.logo_image))
563 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, logo_label, 0, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
564 | self.layout.putConstraint(swing.SpringLayout.NORTH, logo_label, 0, swing.SpringLayout.NORTH, self.panel)
565 | self.panel.add(logo_label)
566 | self.secret_image_base64data = "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"
567 |
568 | self.file_names_scroll_pane = swing.JScrollPane(self.file_names_list)
569 | self.file_names_scroll_pane.setPreferredSize(awt.Dimension(300, 240))
570 | self.file_list_popupmenu = PopupMenu(self.file_names_list, callbacks=self._callbacks, logo_image=self.logo_image, directory=self.directory, monitored_urls_path=self.monitored_urls_path, monitored_urls_directory=self.monitored_urls_directory)
571 | self.file_names_list.addMouseListener(self.file_list_popupmenu)
572 | self.tabbed_pane_files.addTab("Files", self.file_names_scroll_pane)
573 |
574 | self.results_filters_list = swing.JList()
575 | self.results_filters_list.setCellRenderer(ColorFilters(self.results_filters))
576 | self.results_filters_list.setSelectionMode(swing.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
577 | self.results_filters_list_model = swing.DefaultListModel()
578 | self.results_filters_list.setModel(self.results_filters_list_model)
579 | self.results_filters_scroll_pane = swing.JScrollPane(self.results_filters_list)
580 | self.results_filters_scroll_pane.setPreferredSize(awt.Dimension(80, 80))
581 | self.panel.add(self.tabbed_pane_filters, None)
582 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, self.tabbed_pane_filters, -436, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
583 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.tabbed_pane_filters, 190, swing.SpringLayout.NORTH, self.panel)
584 |
585 | self.remove_filter_button = swing.JButton("X", actionPerformed=self.remove_filter)
586 | self.remove_filter_button.setFont(awt.Font("Arial Black", awt.Font.BOLD, 10))
587 | self.remove_filter_button.setOpaque(True)
588 | self.remove_filter_button.setBackground(light_red)
589 | self.remove_filter_button.setForeground(awt.Color.BLACK)
590 | self.remove_filter_button.setPreferredSize(awt.Dimension(80, 12))
591 | self.remove_filter_button.setBorder(black_border)
592 |
593 | self.negative_match_text_field.getDocument().addDocumentListener(textfield_listener)
594 | self.negative_match_text_field.addMouseListener(textfield_listener)
595 | self.negative_match_text_field.setPreferredSize(awt.Dimension(124, 24))
596 | self.negative_match_text_field.setText("Negative Match:")
597 | self.positive_match_text_field.getDocument().addDocumentListener(textfield_listener)
598 | self.positive_match_text_field.addMouseListener(textfield_listener)
599 | self.positive_match_text_field.setPreferredSize(awt.Dimension(124, 24))
600 | self.positive_match_text_field.setText("Positive Match:")
601 | self.panel.add(self.tabbed_pane_negative_positive, None)
602 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, self.tabbed_pane_negative_positive, -508, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
603 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.tabbed_pane_negative_positive, 594, swing.SpringLayout.NORTH, self.panel)
604 |
605 | self.add_filter_button = swing.JButton("Add Filter", actionPerformed=self.add_filter)
606 | self.add_filter_button.setOpaque(True)
607 | self.add_filter_button.setBorder(None)
608 | self.add_filter_button.setFont(awt.Font("Cantarell", awt.Font.BOLD, 13))
609 | self.add_filter_button.setBackground(self.orange)
610 | self.add_filter_button.setForeground(awt.Color.WHITE)
611 | self.add_filter_button.setPreferredSize(awt.Dimension(94, 24))
612 | self.panel.add(self.add_filter_button, None)
613 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, self.add_filter_button, -364, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
614 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.add_filter_button, 616, swing.SpringLayout.NORTH, self.panel)
615 |
616 | self.autoselectall_button = swing.JToggleButton("Auto-Select All", actionPerformed=self.toggle_autoselectall)
617 | self.autoselectall_button.setFont(awt.Font("Cantarell", awt.Font.BOLD, 13))
618 | self.autoselectall_button.setBorder(self.green_border)
619 | self.autoselectall_button.setPreferredSize(awt.Dimension(114, 24))
620 | self.autoselectall_button.setSelected(True)
621 | self.panel.add(self.autoselectall_button, None)
622 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, self.autoselectall_button, 454, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
623 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.autoselectall_button, 280, swing.SpringLayout.NORTH, self.panel)
624 | self.copy_all_menu = swing.JPopupMenu()
625 | def handle_copy(event):
626 | parameters = ""
627 | urls_paths = ""
628 | source = event.getSource().getText()
629 | if source == "URLs/Paths":
630 | urls_paths = "\n".join(set("/" + self.result_table_model.getValueAt(i, 0) if not any(self.result_table_model.getValueAt(i, 0).startswith(prefix) for prefix in ["http://", "https://", "//", "/"]) else self.result_table_model.getValueAt(i, 0) for i in range(self.result_table_model.getRowCount())))
631 | elif source == "Query Parameters":
632 | parameters = '\n'.join([self.result_table_model.getValueAt(i, 1) for i in range(self.result_table_model.getRowCount())]).replace(", ", "\n").replace("\"", "")
633 | elif source == "Body Parameters":
634 | parameters = '\n'.join([self.result_table_model.getValueAt(i, 2) for i in range(self.result_table_model.getRowCount())]).replace(", ", "\n").replace("[", "").replace("]", "").replace("\"", "")
635 | elif source == "All Parameters":
636 | parameters = '\n'.join([self.result_table_model.getValueAt(i, 1) + "\n" + self.result_table_model.getValueAt(i, 2) for i in range(self.result_table_model.getRowCount())]).replace(", ", "\n").replace("[", "").replace("]", "").replace("\"", "")
637 | parameters = "\n".join(list(set([line for line in parameters.split("\n") if line.strip()])))
638 | if source == "All Parameters":
639 | parameters = "&".join([line + "=JSLPP" + str(i) for i, line in enumerate(parameters.split("\n"), 1)])
640 | clipboard = awt.Toolkit.getDefaultToolkit().getSystemClipboard()
641 | clipboard.setContents(awt.datatransfer.StringSelection(parameters if source != "URLs/Paths" else urls_paths), None)
642 |
643 | urls_paths_menu_item = swing.JMenuItem("URLs/Paths", actionPerformed=handle_copy)
644 | urls_paths_menu_item.setHorizontalTextPosition(swing.SwingConstants.LEFT)
645 | urls_paths_menu_item.setFont(awt.Font("Cantarell", awt.Font.BOLD, 10))
646 | urls_paths_menu_item.setBackground(awt.Color.GRAY)
647 | urls_paths_menu_item.setPreferredSize(awt.Dimension(94, 24))
648 | self.copy_all_menu.add(urls_paths_menu_item)
649 |
650 | query_parameters_menu_item = swing.JMenuItem("Query Parameters", actionPerformed=handle_copy)
651 | query_parameters_menu_item.setHorizontalTextPosition(swing.SwingConstants.LEFT)
652 | query_parameters_menu_item.setFont(awt.Font("Cantarell", awt.Font.BOLD, 10))
653 | query_parameters_menu_item.setBackground(awt.Color.GRAY)
654 | query_parameters_menu_item.setPreferredSize(awt.Dimension(94, 24))
655 | self.copy_all_menu.add(query_parameters_menu_item)
656 |
657 | body_parameters_menu_item = swing.JMenuItem("Body Parameters", actionPerformed=handle_copy)
658 | body_parameters_menu_item.setHorizontalTextPosition(swing.SwingConstants.LEFT)
659 | body_parameters_menu_item.setFont(awt.Font("Cantarell", awt.Font.BOLD, 10))
660 | body_parameters_menu_item.setBackground(awt.Color.GRAY)
661 | body_parameters_menu_item.setPreferredSize(awt.Dimension(94, 24))
662 | self.copy_all_menu.add(body_parameters_menu_item)
663 |
664 | all_parameters_menu_item = swing.JMenuItem("All Parameters", actionPerformed=handle_copy)
665 | all_parameters_menu_item.setHorizontalTextPosition(swing.SwingConstants.LEFT)
666 | all_parameters_menu_item.setFont(awt.Font("Cantarell", awt.Font.BOLD, 10))
667 | all_parameters_menu_item.setBackground(awt.Color.GRAY)
668 | all_parameters_menu_item.setPreferredSize(awt.Dimension(94, 24))
669 | self.copy_all_menu.add(all_parameters_menu_item)
670 |
671 | def show_copy_all_menu(event):
672 | has_query_params = False
673 | has_body_params = False
674 | for i in range(self.result_table_model.getRowCount()):
675 | if self.result_table_model.getValueAt(i, 1):
676 | has_query_params = True
677 | elif self.result_table_model.getValueAt(i, 2):
678 | has_body_params = True
679 | query_parameters_menu_item.setEnabled(has_query_params)
680 | body_parameters_menu_item.setEnabled(has_body_params)
681 | all_parameters_menu_item.setEnabled(has_query_params or has_body_params)
682 | if self.result_table_model.getRowCount() == 0:
683 | urls_paths_menu_item.setEnabled(False)
684 | query_parameters_menu_item.setEnabled(False)
685 | body_parameters_menu_item.setEnabled(False)
686 | all_parameters_menu_item.setEnabled(False)
687 | else:
688 | urls_paths_menu_item.setEnabled(True)
689 | self.copy_all_menu.show(self.copy_all_button, 0, 24)
690 |
691 |
692 | self.copy_all_button = swing.JButton("Copy All", actionPerformed=show_copy_all_menu)
693 | self.copy_all_button.setIcon(swing.UIManager.getIcon("Table.descendingSortIcon"))
694 | self.copy_all_button.setHorizontalTextPosition(swing.SwingConstants.LEFT)
695 | self.copy_all_button.setToolTipText("Choose options...")
696 | self.copy_all_button.setFont(awt.Font("Cantarell", awt.Font.BOLD, 13))
697 | self.copy_all_button.setPreferredSize(awt.Dimension(94, 24))
698 | self.copy_all_button.setBorder(swing.BorderFactory.createLineBorder(None, 1))
699 | self.panel.add(self.copy_all_button, None)
700 |
701 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, self.copy_all_button, 652, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
702 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.copy_all_button, 564, swing.SpringLayout.NORTH, self.panel)
703 |
704 |
705 | checkboxesPanel = swing.JPanel()
706 | checkboxesPanel.setLayout(swing.BoxLayout(checkboxesPanel, swing.BoxLayout.X_AXIS))
707 | largeRoundedBorder = swing.BorderFactory.createLineBorder(awt.Color(216, 102, 51), 2, True)
708 | checkboxesPanel.setBorder(swing.BorderFactory.createCompoundBorder(largeRoundedBorder, swing.BorderFactory.createEmptyBorder(10, 10, 10, 10)))
709 |
710 | self.include_inline_tags_checkbox = swing.JCheckBox("In-line Tags")
711 | self.include_inline_tags_checkbox.setFont(awt.Font("Cantarell", awt.Font.BOLD, 12))
712 | self.include_inline_tags_checkbox.setSelected(False)
713 |
714 | self.in_scope_checkbox = swing.JCheckBox("In-scope only")
715 | self.in_scope_checkbox.setFont(awt.Font("Cantarell", awt.Font.BOLD, 12))
716 | self.in_scope_checkbox.addActionListener(lambda event: self.filter_hosts())
717 | self.in_scope_checkbox.setSelected(False)
718 |
719 | self.hide_duplicates_checkbox = swing.JCheckBox("Hide Duplicates")
720 | self.hide_duplicates_checkbox.setFont(awt.Font("Cantarell", awt.Font.BOLD, 12))
721 | self.hide_duplicates_checkbox.addActionListener(lambda event: self.display_result(event, self.file_names_list))
722 | self.hide_duplicates_checkbox.setSelected(True)
723 |
724 | self.secret_notifications_checkbox = swing.JCheckBox("Secret Notifications")
725 | self.secret_notifications_checkbox.setFont(awt.Font("Cantarell", awt.Font.BOLD, 12))
726 | self.secret_notifications_checkbox.setSelected(False)
727 |
728 | self.secrets_checkbox = swing.JCheckBox("Secrets")
729 | self.secrets_checkbox.setFont(awt.Font("Cantarell", awt.Font.BOLD, 12))
730 | self.secrets_checkbox.setSelected(True)
731 | self.secrets_checkbox.addActionListener(self.update_secrets_checkbox)
732 |
733 | self.show_parameterized = swing.JCheckBox("Show Parameterized")
734 | self.show_parameterized.setFont(awt.Font("Cantarell", awt.Font.BOLD, 12))
735 | self.show_parameterized.setSelected(False)
736 | self.show_parameterized.addActionListener(lambda event: self.display_result(event, self.file_names_list))
737 |
738 | checkboxes = [
739 | self.include_inline_tags_checkbox,
740 | self.in_scope_checkbox,
741 | self.hide_duplicates_checkbox,
742 | self.show_parameterized,
743 | self.secret_notifications_checkbox,
744 | self.secrets_checkbox
745 | ]
746 | for checkbox in checkboxes:
747 | checkboxesPanel.add(checkbox)
748 | self.add_horizontal_strut(checkboxesPanel, 10)
749 | if checkbox == checkboxes[-1]:
750 | break
751 | self.add_vertical_line(checkboxesPanel)
752 | self.add_horizontal_strut(checkboxesPanel, 10)
753 | self.panel.add(checkboxesPanel, None)
754 |
755 | self.layout.putConstraint(swing.SpringLayout.WEST, checkboxesPanel, 114, swing.SpringLayout.WEST, self.add_filter_button)
756 | self.layout.putConstraint(swing.SpringLayout.NORTH, checkboxesPanel, 604, swing.SpringLayout.NORTH, self.panel)
757 |
758 | self.monitor_interval_selector = swing.JComboBox(["Off", "Once", "Hourly", "Daily", "Weekly", "Monthly"])
759 | self.monitor_interval_selector.setSelectedItem("Off")
760 | self.panel.add(self.monitor_interval_selector, None)
761 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, self.monitor_interval_selector, 384, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
762 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.monitor_interval_selector, 246, swing.SpringLayout.NORTH, self.panel)
763 |
764 | self.settings_dict = {
765 | "Hide Duplicates": "hide_duplicates_checkbox",
766 | "In-scope only": "in_scope_checkbox",
767 | "In-line Tags": "include_inline_tags_checkbox",
768 | "Secrets": "secrets_checkbox",
769 | "Secrets Notifications": "secret_notifications_checkbox",
770 | "On/Off": "on_off_button",
771 | "Auto-Select": "autoselectall_button",
772 | "Monitor Interval": "monitor_interval_selector",
773 | "Show Parameterized": "show_parameterized",
774 | "Path to jsluice": "path_to_binary"
775 | }
776 |
777 | self.save_settings_button = swing.JButton("Save Settings", actionPerformed=self.save_settings)
778 | self.save_settings_button.setOpaque(True)
779 | self.save_settings_button.setBorder(swing.BorderFactory.createLineBorder(None, 1))
780 | self.save_settings_button.setFont(awt.Font("Cantarell", awt.Font.BOLD, 12))
781 | self.save_settings_button.setPreferredSize(awt.Dimension(94, 34))
782 | self.panel.add(self.save_settings_button, None)
783 | self.layout.putConstraint(swing.SpringLayout.EAST, self.save_settings_button, 124, swing.SpringLayout.EAST, checkboxesPanel)
784 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.save_settings_button, 606, swing.SpringLayout.NORTH, self.panel)
785 |
786 | self.settings_saved_label = swing.JLabel("Settings saved")
787 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, self.settings_saved_label, 0, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
788 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.settings_saved_label, 34, swing.SpringLayout.NORTH, self.panel)
789 | self.settings_saved_label.setVisible(False)
790 | self.settings_saved_label.setFont(awt.Font("Cantarell", awt.Font.BOLD, 14))
791 | self.settings_saved_label.setForeground(awt.Color(110, 204, 154))
792 | self.panel.add(self.settings_saved_label, None)
793 |
794 | self.export_settings_button = swing.JButton("Export", actionPerformed=lambda x: self.export_import("export", "settings"))
795 | self.export_settings_button.setOpaque(True)
796 | self.export_settings_button.setBorder(swing.BorderFactory.createLineBorder(None, 1))
797 | self.export_settings_button.setFont(awt.Font("Cantarell", awt.Font.BOLD, 12))
798 | self.export_settings_button.setPreferredSize(awt.Dimension(64, 24))
799 | self.panel.add(self.export_settings_button, None)
800 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, self.export_settings_button, 144, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
801 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.export_settings_button, 0, swing.SpringLayout.NORTH, self.panel)
802 |
803 | self.import_settings_button = swing.JButton("Import", actionPerformed=lambda x: self.export_import("import", "settings"))
804 | self.import_settings_button.setOpaque(True)
805 | self.import_settings_button.setBorder(swing.BorderFactory.createLineBorder(None, 1))
806 | self.import_settings_button.setFont(awt.Font("Cantarell", awt.Font.BOLD, 12))
807 | self.import_settings_button.setPreferredSize(awt.Dimension(64, 24))
808 | self.panel.add(self.import_settings_button, None)
809 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, self.import_settings_button, 244, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
810 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.import_settings_button, 0, swing.SpringLayout.NORTH, self.panel)
811 |
812 | self.import_results_button = swing.JButton("Import", actionPerformed=lambda x: self.export_import("import", "results"))
813 | self.import_results_button.setOpaque(True)
814 | self.import_results_button.setBorder(swing.BorderFactory.createLineBorder(None, 1))
815 | self.import_results_button.setFont(awt.Font("Cantarell", awt.Font.BOLD, 12))
816 | self.import_results_button.setPreferredSize(awt.Dimension(64, 24))
817 | self.panel.add(self.import_results_button, None)
818 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, self.import_results_button, -144, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
819 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.import_results_button, 0, swing.SpringLayout.NORTH, self.panel)
820 |
821 | self.export_results_button = swing.JButton("Export", actionPerformed=lambda x: self.export_import("export", "results"))
822 | self.export_results_button.setOpaque(True)
823 | self.export_results_button.setBorder(swing.BorderFactory.createLineBorder(None, 1))
824 | self.export_results_button.setFont(awt.Font("Cantarell", awt.Font.BOLD, 12))
825 | self.export_results_button.setPreferredSize(awt.Dimension(64, 24))
826 | self.panel.add(self.export_results_button, None)
827 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, self.export_results_button, 0, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
828 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.export_results_button, 0, swing.SpringLayout.NORTH, self.panel)
829 |
830 | mainPanel = swing.JPanel()
831 | mainPanel.setLayout(swing.BoxLayout(mainPanel, swing.BoxLayout.Y_AXIS))
832 | mainPanel.setBorder(swing.BorderFactory.createCompoundBorder(swing.BorderFactory.createLineBorder(awt.Color(216, 102, 51), 1, True), swing.BorderFactory.createEmptyBorder(0, 0, 0, 0)))
833 | resultsButtons = [self.import_results_button, self.export_results_button]
834 | settingsButtons = [self.import_settings_button, self.export_settings_button]
835 | def add_horizontal_line(self, panel):
836 | separator = swing.JSeparator(swing.SwingConstants.HORIZONTAL)
837 | separator.setForeground(awt.Color(216, 102, 51))
838 | separator.setMaximumSize(awt.Dimension(184, 184))
839 | panel.add(separator)
840 |
841 | def createButtonPanel(title, buttons):
842 | panel = swing.JPanel()
843 | panel.setLayout(awt.BorderLayout())
844 | titlePanel = swing.JPanel()
845 | titlePanel.setLayout(awt.FlowLayout(awt.FlowLayout.CENTER, 0, 0))
846 | titleLabel = swing.JLabel(title)
847 | titleLabel.setFont(awt.Font("Cantarell", awt.Font.BOLD, 12))
848 | titlePanel.add(titleLabel)
849 | panel.add(titlePanel, awt.BorderLayout.PAGE_START)
850 | buttonsPanel = swing.JPanel()
851 | buttonsPanel.setLayout(awt.FlowLayout(awt.FlowLayout.CENTER, 0, 0))
852 | for i, button in enumerate(buttons):
853 | self.add_horizontal_strut(buttonsPanel, 10)
854 | self.add_vertical_strut(buttonsPanel, 20)
855 | buttonsPanel.add(button)
856 | self.add_horizontal_strut(buttonsPanel, 10)
857 | panel.add(buttonsPanel, awt.BorderLayout.CENTER)
858 | return panel
859 |
860 | self.add_vertical_strut(mainPanel, 10)
861 | mainPanel.add(createButtonPanel("Results", resultsButtons))
862 | self.add_vertical_strut(mainPanel, 10)
863 | add_horizontal_line(self, mainPanel)
864 | self.add_vertical_strut(mainPanel, 10)
865 | mainPanel.add(createButtonPanel("Settings", settingsButtons))
866 | self.add_vertical_strut(mainPanel, 10)
867 | add_horizontal_line(self, mainPanel)
868 | self.add_vertical_strut(mainPanel, 10)
869 | mainPanel.add(createButtonPanel("Passive Scan", [self.on_off_button]))
870 | self.add_vertical_strut(mainPanel, 10)
871 | add_horizontal_line(self, mainPanel)
872 | self.add_vertical_strut(mainPanel, 10)
873 | mainPanel.add(createButtonPanel("Monitor Interval", [self.monitor_interval_selector]))
874 | self.add_vertical_strut(mainPanel, 10)
875 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, mainPanel, 0, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
876 | self.layout.putConstraint(swing.SpringLayout.NORTH, mainPanel, 52, swing.SpringLayout.NORTH, self.panel)
877 | self.panel.add(mainPanel, None)
878 |
879 | for key, value in self.tooltips_dict.items():
880 | getattr(self, key).setToolTipText(value)
881 |
882 | callbacks.customizeUiComponent(self.panel)
883 | callbacks.addSuiteTab(self)
884 | callbacks.registerExtensionStateListener(self)
885 | callbacks.registerContextMenuFactory(self)
886 | self.load_settings()
887 | print("Extension loaded")
888 |
889 | self.binary_path_label = swing.JLabel("jsluice binary not found in $PATH, Please select it.")
890 | self.binary_path = swing.JTextField(self.path_to_binary, 20)
891 | self.binary_path.setToolTipText("Path to the jsluice binary")
892 | self.select_binary_button = swing.JButton("Select file", actionPerformed=self.handle_binary_not_found)
893 | self.select_binary_button.setToolTipText("Select the jsluice binary")
894 | self.not_found_panel = swing.JPanel()
895 | self.not_found_panel.setLayout(swing.BoxLayout(self.not_found_panel, swing.BoxLayout.X_AXIS))
896 | self.binary_path.setPreferredSize(self.binary_path.getPreferredSize())
897 | self.binary_path.setMaximumSize(awt.Dimension(114, 24))
898 | self.binary_path.setEditable(False)
899 | self.binary_path.setEnabled(False)
900 | self.not_found_panel.add(self.binary_path)
901 | self.not_found_panel.add(self.select_binary_button)
902 | self.layout.putConstraint(swing.SpringLayout.WEST, self.not_found_panel, 304, swing.SpringLayout.WEST, self.tabbed_pane_files)
903 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.not_found_panel, 252, swing.SpringLayout.NORTH, self.panel)
904 | if not distutils.spawn.find_executable(self.path_to_binary):
905 | print("[WARNING] jsluice binary not found in $PATH: \"" + os.environ['PATH'] + "\", Please select it using the file selector in the jsluice++ extension tab otherwise the extension will not work")
906 | self.binary_path_label.setFont(awt.Font("Cantarell", awt.Font.BOLD, 12))
907 | self.binary_path_label.setBorder(self.red_border)
908 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, self.binary_path_label, 0, swing.SpringLayout.HORIZONTAL_CENTER, self.not_found_panel)
909 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.binary_path_label, -24, swing.SpringLayout.NORTH, self.not_found_panel)
910 | self.panel.add(self.binary_path_label)
911 | self.panel.add(self.not_found_panel, None)
912 | elif self.path_to_binary != "jsluice":
913 | self.panel.add(self.not_found_panel, None)
914 |
915 | self.threads = []
916 | if os.path.exists(self.directory + self.monitored_urls_path):
917 | self.monitored_urls = self.get_monitored_urls()
918 | if len(self.monitored_urls) > 0:
919 | if self.monitor_interval_selector.getSelectedItem() != "Off":
920 | self.schedule_event(0, self.schedule_monitor)
921 |
922 | def handle_binary_not_found(self, event):
923 | file_chooser = swing.JFileChooser()
924 | file_chooser.setDialogTitle("Select jsluice binary")
925 | file_chooser.setFileSelectionMode(swing.JFileChooser.FILES_ONLY)
926 | file_chooser.setFileHidingEnabled(False)
927 | if file_chooser.showOpenDialog(self.panel) == swing.JFileChooser.APPROVE_OPTION:
928 | file = file_chooser.getSelectedFile()
929 | self.path_to_binary = file.getAbsolutePath()
930 | self.save_settings(None)
931 | self.binary_path.setText(self.path_to_binary)
932 | self.panel.remove(self.binary_path_label)
933 |
934 | def export_import(self, option, type):
935 | file_chooser = swing.JFileChooser()
936 | file_chooser.setFileFilter(FileNameExtensionFilter("JSON", ["json"]))
937 | file_chooser.setFileSelectionMode(swing.JFileChooser.FILES_ONLY)
938 | file_chooser.setFileHidingEnabled(False)
939 | if option == "export":
940 | file_chooser.setDialogTitle("Export Results" if type == "results" else "Export Settings")
941 | file_chooser.setSelectedFile(java.io.File("results.json" if type == "results" else "settings.json"))
942 | if file_chooser.showSaveDialog(self.panel) == swing.JFileChooser.APPROVE_OPTION:
943 | file = file_chooser.getSelectedFile()
944 | if file.exists():
945 | dialog = swing.JOptionPane.showConfirmDialog(self.panel, str(file.getAbsolutePath()) + " already exists. Overwrite?", "jsluice++", swing.JOptionPane.YES_NO_OPTION)
946 | if dialog != swing.JOptionPane.YES_OPTION:
947 | return
948 | if type == "results":
949 | self.export_results(file)
950 | elif type == "settings":
951 | self.export_settings(file)
952 | elif option == "import":
953 | file_chooser.setDialogTitle("Import Results" if type == "results" else "Import Settings")
954 | if file_chooser.showOpenDialog(self.panel) == swing.JFileChooser.APPROVE_OPTION:
955 | file = file_chooser.getSelectedFile()
956 | if type == "results":
957 | self.import_results(file)
958 | elif type == "settings":
959 | self.import_settings(file)
960 |
961 | def export_results(self, file):
962 | results = {
963 | "seen_endpoints": list(self.seen_endpoints),
964 | "processed_file_details": self.processed_file_details
965 | }
966 | with open(file.getAbsolutePath(), "w") as f:
967 | f.write(json.dumps(results, indent=4))
968 | print("Results saved to " + file.getAbsolutePath())
969 |
970 | def import_results(self, file):
971 | with open(file.getAbsolutePath(), "r") as f:
972 | results = json.loads(f.read())
973 | try:
974 | self.seen_endpoints.update(results["seen_endpoints"])
975 | self.processed_file_details.update(results["processed_file_details"])
976 | for value in self.processed_file_details.values():
977 | if value["host"] not in self.hosts_list:
978 | self.hosts_list.add(value["host"])
979 | self.host_list_model.addElement(value["host"])
980 | if value["secrets"]:
981 | if value["host"] not in self.hosts_with_secrets:
982 | self.hosts_with_secrets.add(value["host"])
983 | self.add_image_to_host(self.hosts_with_secrets, self.secret_image_base64data)
984 | self.tabbed_pane_hosts.setTitleAt(0, str(self.host_list_model.getSize()) + " Hosts" if self.host_list_model.getSize() > 0 else "Hosts")
985 | print(str(len(self.processed_file_details.keys())) + " items imported from: " + file.getAbsolutePath())
986 | except KeyError:
987 | print("Invalid results file: " + file.getAbsolutePath())
988 | return
989 |
990 | def export_settings(self, file):
991 | settings = {}
992 | for display_name, key in self.settings_dict.items():
993 | component = getattr(self, key)
994 | if isinstance(component, swing.JToggleButton):
995 | settings[display_name] = component.isSelected()
996 | elif isinstance(component, swing.JComboBox):
997 | settings[display_name] = component.getSelectedItem()
998 | elif display_name == "Path to jsluice":
999 | settings[display_name] = component
1000 |
1001 | with open(file.getAbsolutePath(), "w") as f:
1002 | f.write(json.dumps(settings, indent=4))
1003 | print("Settings saved to " + file.getAbsolutePath())
1004 |
1005 | def import_settings(self, file):
1006 | with open(file.getAbsolutePath(), "r") as f:
1007 | settings = json.loads(f.read())
1008 | for key, value in settings.items():
1009 | if key in self.settings_dict:
1010 | component = getattr(self, self.settings_dict[key])
1011 | if key == "On/Off" or key == "Auto-Select":
1012 | if value == False:
1013 | component.setBorder(self.red_border)
1014 | component.setSelected(False)
1015 | if key == "On/Off":
1016 | self.on_off_button.setText("Off")
1017 | if self._callbacks.getHttpListeners() != []:
1018 | self._callbacks.removeHttpListener(self)
1019 | elif value == True:
1020 | component.setBorder(self.green_border)
1021 | component.setSelected(True)
1022 | if key == "On/Off":
1023 | self.on_off_button.setText("On")
1024 | if self._callbacks.getHttpListeners() == []:
1025 | self._callbacks.registerHttpListener(self)
1026 | elif key == "Monitor Interval":
1027 | component.setSelectedItem(value)
1028 | elif key == "Path to jsluice":
1029 | self.path_to_binary = value
1030 | self.binary_path.setText(self.path_to_binary)
1031 | else:
1032 | component.setSelected(value == True)
1033 | if key == "Secrets":
1034 | self.update_secrets_checkbox(self)
1035 | elif key == "In-scope only" and value == True:
1036 | self.filter_hosts()
1037 | print("Settings imported from: " + file.getAbsolutePath())
1038 |
1039 | def extensionUnloaded(self):
1040 | self.cancelled = True
1041 | if self.threads != []:
1042 | print("Cancelling threads")
1043 | for thread in self.threads:
1044 | try:
1045 | thread.cancel()
1046 | except:
1047 | continue
1048 | print("Extension unloaded")
1049 |
1050 | def toggle_on_off(self, event):
1051 | http_listeners = self._callbacks.getHttpListeners()
1052 | if self.on_off_button.isSelected():
1053 | self.on_off_button.setText("On")
1054 | self.on_off_button.setBorder(self.green_border)
1055 | if http_listeners == []:
1056 | self._callbacks.registerHttpListener(self)
1057 | else:
1058 | self.on_off_button.setText("Off")
1059 | self.on_off_button.setBorder(self.red_border)
1060 | if http_listeners != []:
1061 | for listener in http_listeners:
1062 | self._callbacks.removeHttpListener(listener)
1063 |
1064 | def get_monitored_urls(self):
1065 | if os.path.exists(self.directory+self.monitored_urls_path):
1066 | with open(self.directory+self.monitored_urls_path, "r") as f:
1067 | for line in f:
1068 | try:
1069 | json_object = json.loads(line)
1070 | self.monitored_urls[json_object["origin_url"]] = json_object
1071 | if json_object["origin_url"] in self.seen_endpoints:
1072 | self.seen_endpoints.remove(json_object["origin_url"])
1073 | except:
1074 | print("Error parsing line: " + line)
1075 | continue
1076 | f.close()
1077 | return self.monitored_urls
1078 |
1079 | def save_settings(self, event):
1080 | for setting, var in self.settings_dict.items():
1081 | checkbox = getattr(self, var)
1082 | if setting == "Monitor Interval":
1083 | value = str(checkbox.getSelectedItem())
1084 | print("Cancelling threads")
1085 | for thread in self.threads:
1086 | try:
1087 | thread.cancel()
1088 | except:
1089 | continue
1090 | if (value != "Off" and value != "Once") and len(self.monitored_urls) > 0:
1091 | self.schedule_monitor()
1092 | elif setting == "Path to jsluice":
1093 | value = self.path_to_binary
1094 | else:
1095 | value = str(checkbox.isSelected())
1096 | self._callbacks.saveExtensionSetting(setting, value)
1097 | print("Settings saved")
1098 | if self.settings_saved_label.isVisible() == False:
1099 | self.settings_saved_label.setVisible(True)
1100 | self.panel.revalidate()
1101 | self.panel.repaint()
1102 | timer = swing.Timer(3000, lambda event: self.settings_saved_label.setVisible(False))
1103 | timer.setRepeats(False)
1104 | timer.start()
1105 | self.load_settings()
1106 |
1107 | def load_settings(self):
1108 | for setting, var in self.settings_dict.items():
1109 | checkbox = getattr(self, var)
1110 | value = self._callbacks.loadExtensionSetting(setting)
1111 | if value is None:
1112 | continue
1113 | if (setting == "On/Off" or setting == "Auto-Select"):
1114 | if value == "False":
1115 | checkbox.setBorder(self.red_border)
1116 | checkbox.setSelected(False)
1117 | if setting == "On/Off":
1118 | self.on_off_button.setText("Off")
1119 | elif value == "True":
1120 | checkbox.setBorder(self.green_border)
1121 | checkbox.setSelected(True)
1122 | if setting == "On/Off":
1123 | self.on_off_button.setText("On")
1124 | if self._callbacks.getHttpListeners() == []:
1125 | self._callbacks.registerHttpListener(self)
1126 | elif setting == "Monitor Interval":
1127 | checkbox.setSelectedItem(value)
1128 | elif setting == "Path to jsluice":
1129 | self.path_to_binary = value
1130 | else:
1131 | checkbox.setSelected(value == "True")
1132 | if setting == "Secrets":
1133 | self.update_secrets_checkbox(self)
1134 |
1135 | def add_image_to_host(self, hosts_with_secrets, secret_image_base64):
1136 | self.host_list.setCellRenderer(ColorHosts(hosts_with_secrets, secret_image_base64))
1137 |
1138 | def send_http_request(self, url, host, port, is_https, request_info):
1139 | http_service = self._helpers.buildHttpService(host, port, is_https)
1140 | request = self._callbacks.makeHttpRequest(http_service, request_info)
1141 | if request.getResponse() is None:
1142 | print("Got no response from monitored url: " + url)
1143 | return
1144 |
1145 | response_info = self._helpers.analyzeResponse(request.getResponse())
1146 | status_code = response_info.getStatusCode()
1147 | if (not (200 <= status_code <= 299)) and status_code != 0:
1148 | self.show_dialogs(host=host, url=url, reason_label="Returned status code: " + str(status_code))
1149 | self.process_with_jsluice(request)
1150 | return
1151 |
1152 | def schedule_monitor(self):
1153 | monitor_interval_value = self.monitor_interval_selector.getSelectedItem()
1154 | interval_mapping = {
1155 | "Hourly": 3600,
1156 | "Daily": 86400,
1157 | "Weekly": 604800,
1158 | "Monthly": 2592000,
1159 | }
1160 |
1161 | if monitor_interval_value == "Once":
1162 | self.handle_monitored_urls()
1163 | elif monitor_interval_value in interval_mapping:
1164 | last_monitored_date_str = self._callbacks.loadExtensionSetting("Last Monitored Date")
1165 | if last_monitored_date_str:
1166 | try:
1167 | last_monitored_date = datetime.strptime(last_monitored_date_str, "%Y-%m-%d %H:%M:%S.%f")
1168 | except ValueError:
1169 | last_monitored_date = datetime.strptime(last_monitored_date_str, "%Y-%m-%d %H:%M:%S")
1170 | else:
1171 | last_monitored_date = None
1172 |
1173 | last_monitored_date = last_monitored_date if last_monitored_date_str else datetime.now() - timedelta(seconds=interval_mapping[monitor_interval_value])
1174 | current_date = datetime.now()
1175 | difference = current_date - last_monitored_date
1176 | difference_in_seconds = difference.total_seconds()
1177 | interval_duration = interval_mapping[monitor_interval_value]
1178 | if difference_in_seconds >= interval_duration:
1179 | self.schedule_event(0, self.handle_monitored_urls)
1180 | self.schedule_event(interval_duration, self.schedule_monitor)
1181 | else:
1182 | remaining_seconds = interval_duration - difference_in_seconds
1183 | print("["+str(current_date)+"] Sending " + str(len(self.monitored_urls)) + " requests to monitored URLs in " + str(remaining_seconds) + " seconds.")
1184 | self.schedule_event(remaining_seconds, self.handle_monitored_urls)
1185 | self.schedule_event(remaining_seconds + interval_duration, self.schedule_monitor)
1186 | else:
1187 | print("Stopped monitoring URLs, monitor interval value: " + monitor_interval_value)
1188 | return
1189 |
1190 | def handle_monitored_urls(self):
1191 | if os.path.exists(self.directory+self.monitored_urls_path) and os.path.getsize(self.directory+self.monitored_urls_path) > 0:
1192 | self._callbacks.saveExtensionSetting("Last Monitored Date", str(datetime.now()))
1193 | self.monitored_urls = self.get_monitored_urls()
1194 | for url in self.monitored_urls:
1195 | try:
1196 | request_info = self._helpers.buildHttpRequest(URL(url))
1197 | host = URL(url).getHost()
1198 | port = URL(url).getPort()
1199 | is_https = URL(url).getProtocol() == "https"
1200 | Thread(target=self.send_http_request, args=(url, host, port, is_https, request_info)).start()
1201 | except Exception as e:
1202 | print("Error sending request to url: " + url)
1203 | print(e)
1204 | continue
1205 | print("["+str(datetime.now())+"] Sent " + str(len(self.monitored_urls)) + " requests to monitored URLs.")
1206 | else:
1207 | return
1208 |
1209 | def schedule_event(self, interval, action, args=()):
1210 | event = Timer(interval, action, args=(args))
1211 | event.start()
1212 | self.threads.append(event)
1213 |
1214 |
1215 | def add_horizontal_strut(self, panel, width):
1216 | strut = swing.Box.createHorizontalStrut(width)
1217 | panel.add(strut)
1218 |
1219 | def add_vertical_strut(self, panel, height):
1220 | strut = swing.Box.createVerticalStrut(height)
1221 | panel.add(strut)
1222 |
1223 | def add_vertical_line(self, panel):
1224 | separator = swing.JSeparator(swing.SwingConstants.VERTICAL)
1225 | separator.setForeground(awt.Color(216, 102, 51))
1226 | separator.setMaximumSize(awt.Dimension(1, 1000))
1227 | panel.add(separator)
1228 |
1229 | def update_secrets_checkbox(self, event):
1230 | isSecretsSelected = self.secrets_checkbox.isSelected()
1231 | tabIndex = self.tabbed_pane_results.indexOfComponent(self.scroll_pane_secrets)
1232 | if isSecretsSelected:
1233 | self.display_result(event, self.file_names_list)
1234 | if tabIndex == -1:
1235 | self.tabbed_pane_results.addTab("Secrets", self.scroll_pane_secrets)
1236 | self.tabbed_pane_results.setTitleAt(self.tabbed_pane_results.indexOfComponent(self.scroll_pane_secrets), "Secrets ({} rows)".format(self.secrets_table_model.getRowCount()))
1237 | else:
1238 | if tabIndex != -1:
1239 | self.tabbed_pane_results.removeTabAt(tabIndex)
1240 |
1241 | def add_filter(self, event):
1242 | def handle_match_text_field(match_text_field, match_type):
1243 | match = match_text_field.getText()
1244 | if match == match_type + ":" or match == "":
1245 | return
1246 |
1247 | if match not in self.results_filters:
1248 | self.results_filters[match] = match_type
1249 |
1250 | if self.tabbed_pane_filters.indexOfComponent(self.results_filters_scroll_pane) == -1:
1251 | self.tabbed_pane_filters.addTab("Filters", self.results_filters_scroll_pane)
1252 | self.panel.add(self.remove_filter_button, None)
1253 | self.layout.putConstraint(swing.SpringLayout.HORIZONTAL_CENTER, self.remove_filter_button, -436, swing.SpringLayout.HORIZONTAL_CENTER, self.panel)
1254 | self.layout.putConstraint(swing.SpringLayout.NORTH, self.remove_filter_button, 294, swing.SpringLayout.NORTH, self.panel)
1255 |
1256 | self.results_filters_list_model.addElement(match)
1257 | match_text_field.setText(match_type.title() + " Match:")
1258 | match_text_field.setCaretPosition(0)
1259 | match_text_field.setFocusable(False)
1260 | match_text_field.setFocusable(True)
1261 | self.results_filters_list.setSelectedIndex(len(self.results_filters) - 1)
1262 | swing.SwingUtilities.invokeLater(lambda: self.display_result(event, self.file_names_list))
1263 | match_text_field.setEditable(False)
1264 |
1265 | negative_match = self.negative_match_text_field.getText()
1266 | positive_match = self.positive_match_text_field.getText()
1267 |
1268 | if negative_match != "Negative Match:" and negative_match != "" and negative_match not in self.results_filters:
1269 | handle_match_text_field(self.negative_match_text_field, 'negative')
1270 | elif positive_match != "Positive Match:" and positive_match != "" and positive_match not in self.results_filters:
1271 | handle_match_text_field(self.positive_match_text_field, 'positive')
1272 |
1273 |
1274 |
1275 | def remove_filter(self, event):
1276 | selected_filters = self.results_filters_list.getSelectedValuesList()
1277 | for selected_filter in selected_filters:
1278 | self.results_filters.pop(selected_filter)
1279 | self.results_filters_list_model.removeElement(selected_filter)
1280 | if self.results_filters_list_model.isEmpty():
1281 | self.tabbed_pane_filters.removeTabAt(self.tabbed_pane_filters.indexOfComponent(self.results_filters_scroll_pane))
1282 | self.panel.remove(self.remove_filter_button)
1283 | self.panel.repaint()
1284 | self.panel.revalidate()
1285 | else:
1286 | self.results_filters_list.setSelectedIndex(len(self.results_filters) - 1)
1287 | swing.SwingUtilities.invokeLater(lambda: self.display_result(event, self.file_names_list))
1288 |
1289 | def on_search_changed(self, event):
1290 | search_text = self.search_text_field.getText().lower()
1291 | if search_text != "search:":
1292 | current_selection = self.host_list.getSelectedValue()
1293 | filtered_hosts = [host for host in list(self.hosts_list) if search_text in host.lower()]
1294 | if self.in_scope_checkbox.isSelected():
1295 | filtered_hosts = [host for host in filtered_hosts if self._callbacks.isInScope(URL(str("http://" + host))) or self._callbacks.isInScope(URL(str("https://" + host)))]
1296 | self.host_list_model.clear()
1297 | for host in filtered_hosts:
1298 | self.host_list_model.addElement(host)
1299 | index = 0
1300 | if current_selection in filtered_hosts:
1301 | index = filtered_hosts.index(current_selection)
1302 | else:
1303 | self.file_names_model.clear()
1304 | self.tabbed_pane_results.setTitleAt(0, "Results")
1305 | self.tabbed_pane_files.setTitleAt(0, "Files")
1306 | self.result_table_model.setRowCount(0)
1307 | if self.secrets_checkbox.isSelected():
1308 | self.tabbed_pane_results.setTitleAt(1, "Secrets")
1309 | self.secrets_table_model.setRowCount(0)
1310 | self.tabbed_pane_hosts.setTitleAt(0, str(self.host_list_model.getSize()) + " Hosts" if self.host_list_model.getSize() > 0 else "Hosts")
1311 | self.host_list.setSelectedIndex(index)
1312 |
1313 | def show_dialogs(self, url=None, host=None, reason_label=None):
1314 | if url and host == None and reason_label == None:
1315 | change_in_monitored_dialog = swing.JDialog(None, "jsluice++ | Monitored URLs", False)
1316 | change_in_monitored_dialog.setSize(644, 244)
1317 | logo_icon = swing.ImageIcon(self.logo_image)
1318 | change_in_monitored_dialog.setIconImage(self.logo_image)
1319 | label_font = awt.Font("Cantarell", awt.Font.BOLD, 14)
1320 | change_in_monitored_label = swing.JLabel("The following monitored URL has changed:", swing.JLabel.CENTER)
1321 | url_label = swing.JLabel(url, swing.JLabel.CENTER)
1322 | url_label.setForeground(self.orange)
1323 | change_in_monitored_label.setVerticalTextPosition(swing.JLabel.BOTTOM)
1324 | change_in_monitored_label.setHorizontalTextPosition(swing.JLabel.CENTER)
1325 | change_in_monitored_label.setIcon(logo_icon)
1326 | change_in_monitored_label.setFont(label_font)
1327 | url_label.setFont(awt.Font("Cantarell", awt.Font.BOLD, 18))
1328 | change_in_monitored_dialog.getContentPane().add(change_in_monitored_label, awt.BorderLayout.NORTH)
1329 | change_in_monitored_dialog.getContentPane().add(url_label, awt.BorderLayout.CENTER)
1330 | change_in_monitored_dialog.setLocationRelativeTo(None)
1331 | change_in_monitored_dialog.setDefaultCloseOperation(swing.WindowConstants.DISPOSE_ON_CLOSE)
1332 | button = swing.JButton("OK", actionPerformed=lambda x: change_in_monitored_dialog.dispose())
1333 | change_in_monitored_dialog.getContentPane().add(button, awt.BorderLayout.SOUTH)
1334 | change_in_monitored_dialog.setVisible(True)
1335 | elif host and url == None and reason_label == None:
1336 | secret_image_bytes = b64decode(self.secret_image_base64data)
1337 | secret_image_stream = ByteArrayInputStream(secret_image_bytes)
1338 | secret_image = ImageIO.read(secret_image_stream)
1339 | self.secret_icon = swing.ImageIcon(secret_image)
1340 | secrets_found_dialog = swing.JDialog(None, "jsluice++ | Secrets found", False)
1341 | secrets_found_dialog.setSize(340, 140)
1342 | secrets_found_dialog.setIconImage(secret_image)
1343 | label_font = awt.Font("Cantarell", awt.Font.BOLD, 14)
1344 | label = swing.JLabel("New secret(s) found in " + host, swing.JLabel.CENTER)
1345 | label.setFont(label_font)
1346 | label.setIcon(self.secret_icon)
1347 | label.setVerticalTextPosition(swing.JLabel.BOTTOM)
1348 | label.setHorizontalTextPosition(swing.JLabel.CENTER)
1349 | secrets_found_dialog.getContentPane().add(label)
1350 | secrets_found_dialog.setLocationRelativeTo(None)
1351 | secrets_found_dialog.setDefaultCloseOperation(swing.WindowConstants.DISPOSE_ON_CLOSE)
1352 | button = swing.JButton("OK", actionPerformed=lambda x: secrets_found_dialog.dispose())
1353 | secrets_found_dialog.getContentPane().add(button, awt.BorderLayout.SOUTH)
1354 | secrets_found_dialog.setVisible(True)
1355 | swing.Timer(15000, lambda x: secrets_found_dialog.dispose()).start()
1356 | elif host and url and reason_label:
1357 | logo_icon = swing.ImageIcon(self.logo_image)
1358 | bad_status_code_dialog = swing.JDialog(None, "jsluice++ | Monitored URLs", False)
1359 | bad_status_code_dialog.setSize(400, 200)
1360 | bad_status_code_dialog.setIconImage(self.logo_image)
1361 | status_label_font = awt.Font("Cantarell", awt.Font.BOLD, 14)
1362 | status_label = swing.JLabel("The following URL: " + str(url), swing.JLabel.CENTER)
1363 | status_label2 = swing.JLabel(reason_label, swing.JLabel.CENTER)
1364 | status_label3 = swing.JLabel("Would you like to stop monitoring it?", swing.JLabel.CENTER)
1365 | status_label.setIcon(logo_icon)
1366 | status_label.setFont(status_label_font)
1367 | status_label2.setFont(status_label_font)
1368 | status_label3.setFont(status_label_font)
1369 | labels_panel = swing.JPanel()
1370 | labels_panel.setLayout(swing.BoxLayout(labels_panel, swing.BoxLayout.Y_AXIS))
1371 | status_label.setVerticalTextPosition(swing.JLabel.BOTTOM)
1372 | status_label.setHorizontalTextPosition(swing.JLabel.CENTER)
1373 | status_label.setAlignmentX(0.5)
1374 | status_label2.setAlignmentX(0.5)
1375 | status_label3.setAlignmentX(0.5)
1376 | labels_panel.add(status_label)
1377 | labels_panel.add(status_label2)
1378 | labels_panel.add(status_label3)
1379 | bad_status_code_dialog.setLocationRelativeTo(None)
1380 | bad_status_code_dialog.setDefaultCloseOperation(swing.WindowConstants.DISPOSE_ON_CLOSE)
1381 | bad_status_code_dialog.add(labels_panel)
1382 | button_panel = swing.JPanel()
1383 | button_panel.setLayout(swing.BoxLayout(button_panel, swing.BoxLayout.X_AXIS))
1384 | def stop_monitoring(event):
1385 | file_name = ""
1386 | monitor_urls_file = open(self.directory + self.monitored_urls_path, "a+")
1387 | monitor_urls_file.seek(0)
1388 | lines = monitor_urls_file.readlines()
1389 | monitor_urls_file.seek(0)
1390 |
1391 | for line in lines:
1392 | json_line = json.loads(line)
1393 | if json_line.get('origin_url') == url:
1394 | file_name = json_line.get('file_name')
1395 | else:
1396 | monitor_urls_file.write(line)
1397 | file_name_hash = md5(file_name.encode('utf-8')).hexdigest()
1398 | monitor_urls_file.truncate()
1399 | monitor_urls_file.close()
1400 | if os.path.exists(self.directory + self.monitored_urls_directory + host + "_" + file_name_hash + ".old"):
1401 | os.remove(self.directory + self.monitored_urls_directory + host + "_" + file_name_hash + ".old")
1402 | if os.path.exists(self.directory + self.monitored_urls_directory + host + "_" + file_name_hash):
1403 | os.remove(self.directory + self.monitored_urls_directory + host + "_" + file_name_hash)
1404 | del self.monitored_urls[url]
1405 | self.file_names_list.repaint()
1406 | self.file_names_list.revalidate()
1407 | bad_status_code_dialog.dispose()
1408 | button = swing.JButton("Yes", actionPerformed=stop_monitoring)
1409 | button2 = swing.JButton("No", actionPerformed=lambda event: bad_status_code_dialog.dispose())
1410 | button_panel.add(button)
1411 | button_panel.add(swing.Box.createHorizontalStrut(10))
1412 | button_panel.add(button2)
1413 | center_panel = swing.JPanel()
1414 | center_panel.setLayout(swing.BoxLayout(center_panel, swing.BoxLayout.Y_AXIS))
1415 | center_panel.add(swing.Box.createVerticalGlue())
1416 | center_panel.add(button_panel)
1417 | center_panel.add(swing.Box.createVerticalGlue())
1418 | bad_status_code_dialog.getContentPane().add(center_panel, awt.BorderLayout.SOUTH)
1419 | bad_status_code_dialog.pack()
1420 | bad_status_code_dialog.setVisible(True)
1421 |
1422 | def process_with_jsluice(self, messageInfo):
1423 | response = messageInfo.getResponse()
1424 | status_code = self._helpers.analyzeResponse(response).getStatusCode()
1425 | url = self._helpers.analyzeRequest(messageInfo).getUrl()
1426 | url_str = str(url)
1427 | parsed_url = urlparse(url_str)
1428 | port = parsed_url.port
1429 | host = parsed_url.netloc if port not in [80, 443] else parsed_url.hostname
1430 | file_extension = os.path.splitext(parsed_url.path)[1]
1431 | url_split = parsed_url.scheme + "://" + parsed_url.netloc + parsed_url.path
1432 | is_text_html = False
1433 | is_javascript = False
1434 | javascript_mime_types = ["application/javascript", "text/javascript", "application/ecmascript", "text/ecmascript", "application/x-javascript", "application/x-ecmascript"]
1435 | if self.in_scope_checkbox.isSelected():
1436 | if not self._callbacks.isInScope(url):
1437 | print("URL is not in scope: " + url_str)
1438 | return
1439 | if url_split not in self.seen_endpoints and file_extension != ".js":
1440 | headers = self._helpers.analyzeResponse(response).getHeaders()
1441 | for header in headers:
1442 | if header.lower().startswith("content-type:"):
1443 | if self.include_inline_tags_checkbox.isSelected() and header.lower().startswith("content-type: text/html"):
1444 | is_text_html = True
1445 | elif any(mime_type in header.lower() for mime_type in javascript_mime_types):
1446 | is_javascript = True
1447 | break
1448 | if ((file_extension == ".js" or is_javascript) and url_split not in self.seen_endpoints and (200 <= status_code <= 299)) or (self.include_inline_tags_checkbox.isSelected() and (200 <= status_code <= 299) and is_text_html and url_split not in self.seen_endpoints):
1449 | analyzed_response = self._helpers.analyzeResponse(response)
1450 | response_body = response[analyzed_response.getBodyOffset():].tostring()
1451 | file_name = parsed_url.path
1452 | if self.include_inline_tags_checkbox.isSelected() and is_text_html:
1453 | script_tags = re.findall(r'(.*?)', response_body, re.DOTALL)
1454 | response_body = "\n".join(script_tags)
1455 | file_name_hash = md5(file_name.encode()).hexdigest()
1456 | file_path = self.directory + "/" + host + "_" + file_name_hash
1457 | monitor_file_path = self.directory + self.monitored_urls_directory + host + "_" + file_name_hash
1458 | urls_command = self.path_to_binary + " urls " + file_path
1459 | with open(file_path, "w") as f:
1460 | f.write(response_body)
1461 | process_urls = subprocess.Popen(urls_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1462 | urls_stdout, stderr = process_urls.communicate()
1463 | urls_stdout_list = (json.loads(line) for line in urls_stdout.splitlines())
1464 | sorted_stdout = []
1465 | for line in urls_stdout_list:
1466 | if "queryParams" in line:
1467 | line["queryParams"] = sorted(line["queryParams"])
1468 | if "bodyParams" in line:
1469 | line["bodyParams"] = sorted(line["bodyParams"])
1470 | sorted_stdout.append(json.dumps(line))
1471 | urls_stdout = "\n".join(sorted_stdout)
1472 | if os.path.exists(monitor_file_path):
1473 | monitored_file = ""
1474 | with open(monitor_file_path, "r") as f:
1475 | monitored_file = json.load(f) if os.stat(monitor_file_path).st_size > 0 else ""
1476 | if urls_stdout.decode("utf-8") != monitored_file.decode("utf-8"):
1477 | self.show_dialogs(url=url_str)
1478 | if os.path.exists(monitor_file_path + ".old"):
1479 | os.remove(monitor_file_path + ".old")
1480 | os.rename(monitor_file_path, monitor_file_path + ".old")
1481 | with open(monitor_file_path, "w") as f:
1482 | f.write(json.dumps(urls_stdout))
1483 | if self.secrets_checkbox.isSelected():
1484 | secrets_command = self.path_to_binary + " secrets " + file_path
1485 | process_secrets = subprocess.Popen(secrets_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1486 | secrets_stdout, stderr2 = process_secrets.communicate()
1487 |
1488 | if (not self.secrets_checkbox.isSelected() and urls_stdout == '') or (self.secrets_checkbox.isSelected() and urls_stdout == '' and secrets_stdout == ''):
1489 | try:
1490 | os.remove(file_path)
1491 | except OSError:
1492 | pass
1493 | if url_str in self.monitored_urls:
1494 | self.show_dialogs(host=host, url=url_str, reason_label="Returned no endpoints or secrets")
1495 | self.seen_endpoints.add(url_split)
1496 | return
1497 |
1498 | if host not in self.hosts_list:
1499 | if self.in_scope_checkbox.isSelected():
1500 | if self._callbacks.isInScope(url):
1501 | self.hosts_list.add(host)
1502 | self.host_list_model.addElement(host)
1503 | else:
1504 | self.hosts_list.add(host)
1505 | self.host_list_model.addElement(host)
1506 | self.tabbed_pane_hosts.setTitleAt(0, str(self.host_list_model.size()) + " Hosts")
1507 |
1508 | if self.secrets_checkbox.isSelected():
1509 | processed_file_details = {"host": host, "file_name": file_name, "output": urls_stdout.encode('utf-8'), "secrets": secrets_stdout.encode('utf-8'), "origin_url": url_str}
1510 | lines = secrets_stdout.strip().split("\n")
1511 | found_secret = False
1512 | for line in lines:
1513 | if line == '':
1514 | continue
1515 | data = json.loads(line, encoding='utf-8')
1516 | match = data["data"].get("match")
1517 | key = data["data"].get("key")
1518 | if match and str(match.encode('utf-8')) not in self.unique_secrets:
1519 | self.unique_secrets.add(match)
1520 | found_secret = True
1521 | elif key and str(key.encode('utf-8')) not in self.unique_secrets:
1522 | self.unique_secrets.add(key)
1523 | found_secret = True
1524 | else:
1525 | pass
1526 | if found_secret:
1527 | if host not in self.hosts_with_secrets:
1528 | self.hosts_with_secrets.add(host)
1529 | self.add_image_to_host(self.hosts_with_secrets, self.secret_image_base64data)
1530 | if self.secret_notifications_checkbox.isSelected():
1531 | self.show_dialogs(host=host)
1532 | else:
1533 | processed_file_details = {"host": host, "file_name": file_name, "output": urls_stdout.encode('utf-8'), "origin_url": url_str}
1534 | self.processed_file_details[url_split] = processed_file_details
1535 | try:
1536 | os.remove(file_path)
1537 | except OSError:
1538 | pass
1539 | self.seen_endpoints.add(url_split)
1540 | if host == self.selected_host:
1541 | if file_name not in self.file_names_model.toArray():
1542 | self.file_names_model.addElement(file_name)
1543 | self.file_names_list.setCellRenderer(ColorFiles(self.monitored_urls, self.processed_file_details, self.selected_host, self.directory, self.monitored_urls_directory))
1544 |
1545 | def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
1546 | if not messageIsRequest and ((self.on_off_button.isSelected() and toolFlag == self._callbacks.TOOL_PROXY)):
1547 | self.process_with_jsluice(messageInfo)
1548 |
1549 | def createMenuItems(self, invocation):
1550 | menuList = []
1551 | if invocation.getToolFlag() == self._callbacks.TOOL_TARGET:
1552 | if invocation.getInvocationContext() in [invocation.CONTEXT_TARGET_SITE_MAP_TREE, invocation.CONTEXT_TARGET_SITE_MAP_TABLE]:
1553 | menuItem = swing.JMenuItem("Process selected item(s)", actionPerformed=lambda x: self.schedule_event(0, self.action, args=[invocation]))
1554 | menuList.append(menuItem)
1555 | return menuList
1556 |
1557 | def show_dialog2(self, site_map, invocation):
1558 | self.dialog = swing.JDialog()
1559 | self.dialog.setTitle("jsluice++")
1560 | self.dialog.setDefaultCloseOperation(swing.JDialog.DISPOSE_ON_CLOSE)
1561 | self.dialog.setSize(300, 124)
1562 | self.dialog.setLocationRelativeTo(None)
1563 | self.dialog.setLayout(swing.BoxLayout(self.dialog.getContentPane(), swing.BoxLayout.Y_AXIS))
1564 | self.dialog.setModal(True)
1565 | self.dialog.setAlwaysOnTop(True)
1566 | self.dialog.setResizable(False)
1567 |
1568 | self.label1 = swing.JLabel("Processing " + str(len(site_map)) + " items...")
1569 | self.dialog.add(self.label1)
1570 |
1571 | self.progress_bar = swing.JProgressBar(0, len(site_map))
1572 | self.progress_bar.setStringPainted(True)
1573 | self.progress_bar.setString("0/" + str(len(site_map)))
1574 | self.dialog.add(self.progress_bar)
1575 |
1576 | self.buttons_panel = swing.JPanel()
1577 | self.buttons_panel.setLayout(swing.BoxLayout(self.buttons_panel, swing.BoxLayout.X_AXIS))
1578 | self.buttons_panel.add(swing.Box.createHorizontalGlue())
1579 |
1580 | self.threads_panel = swing.JPanel()
1581 | self.threads_panel.setLayout(swing.BoxLayout(self.threads_panel, swing.BoxLayout.X_AXIS))
1582 | self.threads_panel.add(swing.Box.createHorizontalGlue())
1583 |
1584 | self.threads_label = swing.JLabel("Threads:")
1585 | spinner_model = swing.SpinnerNumberModel(1, 1, 20, 1)
1586 | self.threads_spinner = swing.JSpinner(spinner_model)
1587 | self.threads_spinner.preferredSize = java.awt.Dimension(64, 24)
1588 | self.threads_spinner.setMaximumSize(java.awt.Dimension(64, 24))
1589 |
1590 | self.threads_panel.add(self.threads_label)
1591 | self.threads_panel.add(self.threads_spinner)
1592 | self.threads_panel.add(swing.Box.createHorizontalGlue())
1593 | self.dialog.add(self.threads_panel)
1594 |
1595 | self.start_button = swing.JButton("Start", actionPerformed=lambda x: self.schedule_event(0, self.process_site_map, args=(site_map, invocation)))
1596 | self.start_button.setForeground(awt.Color.BLACK)
1597 | self.start_button.setBackground(awt.Color(110, 204, 154))
1598 | self.buttons_panel.add(self.start_button)
1599 | self.buttons_panel.add(swing.Box.createHorizontalGlue())
1600 | self.cancel_button = swing.JButton("Cancel", actionPerformed=lambda x: (self.cancel_progress(site_map, invocation), self.dialog.dispose()))
1601 | self.buttons_panel.add(self.cancel_button)
1602 | self.buttons_panel.add(swing.Box.createHorizontalGlue())
1603 | self.dialog.add(self.buttons_panel)
1604 | self.dialog.setVisible(True)
1605 |
1606 | def cancel_progress(self, site_map, invocation):
1607 | self.cancelled = True
1608 | self.start_button.setText("Start")
1609 | self.start_button.setBackground(awt.Color(110, 204, 154))
1610 | self.start_button.actionPerformed = lambda x: self.schedule_event(0, self.process_site_map, args=(site_map, invocation))
1611 | self.threads_spinner.setEnabled(True)
1612 |
1613 | def process_site_map(self, site_map, invocation):
1614 | self.cancelled = False
1615 | self.threads_spinner.setEnabled(False)
1616 | self.start_button.setText("Stop")
1617 | self.start_button.setBackground(awt.Color(240,128,128))
1618 | self.start_button.actionPerformed = lambda x: self.cancel_progress(site_map, invocation)
1619 | if len(site_map) < self.threads_spinner.getValue():
1620 | self.threads_spinner.setValue(len(site_map))
1621 | start_time = time()
1622 | semaphore = Semaphore(int(self.threads_spinner.getValue()))
1623 | def process_item(item):
1624 | self.process_with_jsluice(item)
1625 | self.update_progress(index, len(site_map), invocation, start_time)
1626 | semaphore.release()
1627 | for index, item in enumerate(site_map, start=1):
1628 | semaphore.acquire()
1629 | if self.cancelled:
1630 | semaphore.release()
1631 | break
1632 | Thread(target=process_item, args=(item,)).start()
1633 | return
1634 |
1635 | def update_progress(self, current, total, invocation, start_time):
1636 | if current == total:
1637 | self.dialog.remove(self.threads_spinner)
1638 | self.threads_panel.remove(self.threads_spinner)
1639 | self.threads_panel.remove(self.threads_label)
1640 | self.buttons_panel.remove(self.start_button)
1641 | end_time = time()
1642 | if invocation.getInvocationContext() in [invocation.CONTEXT_TARGET_SITE_MAP_TREE, invocation.CONTEXT_TARGET_SITE_MAP_TABLE]:
1643 | self.label1.setText(str(total) + " items processed in " + str(round(end_time - start_time, 2)) + " seconds")
1644 | self.cancel_button.setText("Close")
1645 | self.dialog.setSize(self.dialog.getPreferredSize())
1646 | self.dialog.revalidate()
1647 | self.dialog.repaint()
1648 | percent = int((float(current) / float(total)) * 100)
1649 | self.progress_bar.setValue(current)
1650 | self.progress_bar.setString(str(current) + "/" + str(total) + " (" + str(percent) + "%)")
1651 | self.dialog.revalidate()
1652 | self.dialog.repaint()
1653 |
1654 | def action(self, invocation):
1655 | self.cancelled = False
1656 | selected_messages = invocation.getSelectedMessages()
1657 | if invocation.getInvocationContext() in [invocation.CONTEXT_TARGET_SITE_MAP_TREE]:
1658 | if len(selected_messages) == 1:
1659 | site_map = [item for item in self._callbacks.getSiteMap(str(selected_messages[0].getUrl())) if item.getResponse() != None]
1660 | else:
1661 | selected_messages = [msg.getUrl() for msg in selected_messages if urlparse(unicode(msg.getUrl())).path == "/"]
1662 | site_map = [item for item in [self._callbacks.getSiteMap(str(selected_messages[i])) for i in range(len(selected_messages))] for item in item if item.getResponse() != None]
1663 | elif invocation.getInvocationContext() in [invocation.CONTEXT_TARGET_SITE_MAP_TABLE]:
1664 | site_map = [item for item in selected_messages if item.getResponse() != None]
1665 | swing.SwingUtilities.invokeLater(lambda: self.show_dialog2(site_map, invocation))
1666 |
1667 | def on_host_selected(self, event):
1668 | if self.host_list.getSelectedValue() == None:
1669 | return
1670 | self.file_names_model.clear()
1671 | self.loading_label.setVisible(True)
1672 | self.tabbed_pane_files.setTitleAt(0, "Loading...")
1673 | swing.SwingUtilities.invokeLater(lambda: self.handle_selected_host(event))
1674 | return
1675 |
1676 | def handle_selected_host(self, event):
1677 | if isinstance(event, swing.event.ListSelectionEvent) and event.getValueIsAdjusting():
1678 | return
1679 | selected_host = self.host_list.getSelectedValue()
1680 | if selected_host:
1681 | self.file_names_model.clear()
1682 | file_names = self.get_processed_file_names(selected_host)
1683 |
1684 | def update_file_names():
1685 | self.loading_label.setVisible(False)
1686 | for file_name in file_names:
1687 | if file_name not in self.file_names_model.toArray():
1688 | self.file_names_model.addElement(file_name)
1689 | if file_names and not self.autoselectall_button.isSelected():
1690 | self.file_names_list.setSelectedIndex(0)
1691 | elif file_names and self.autoselectall_button.isSelected():
1692 | self.file_names_list.setSelectionInterval(0, self.file_names_model.size() - 1)
1693 | self.file_list_popupmenu.selected_host = selected_host
1694 | self.file_list_popupmenu.file_names_list = self.file_names_list
1695 | self.file_list_popupmenu.processed_file_details = self.processed_file_details
1696 | self.file_list_popupmenu.monitored_urls = self.monitored_urls
1697 | self.file_names_list.setCellRenderer(ColorFiles(self.monitored_urls, self.processed_file_details, self.selected_host, self.directory, self.monitored_urls_directory))
1698 | self.file_names_list.revalidate()
1699 | self.file_names_list.repaint()
1700 | self.tabbed_pane_files.setTitleAt(0, str(len(file_names)) + " File" + ("s" if len(file_names) > 1 else ""))
1701 | self.file_names_list.setValueIsAdjusting(False)
1702 | swing.SwingUtilities.invokeLater(lambda: update_file_names())
1703 |
1704 | def toggle_autoselectall(self, event):
1705 | if self.autoselectall_button.isSelected():
1706 | self.autoselectall_button.setBorder(self.green_border)
1707 | else:
1708 | self.autoselectall_button.setBorder(self.red_border)
1709 | if self.host_list.getSelectedValue():
1710 | self.loading_label.setVisible(True)
1711 | swing.SwingUtilities.invokeLater(lambda: self.handle_selected_host(event))
1712 |
1713 | def process_lines(self, lines, selected_files_length, selected_file_name):
1714 | processed_rows = []
1715 | for line in lines:
1716 | if line == '':
1717 | continue
1718 | try:
1719 | data = json.loads(line)
1720 | queryParams = '' if data["queryParams"] == [] else json.dumps(data["queryParams"])
1721 | bodyParams = '' if data["bodyParams"] == [] else json.dumps(data["bodyParams"])
1722 | method = '' if data["method"] == '' else json.dumps(data["method"])
1723 | headers = json.dumps(data["headers"]) if data.get("headers") is not None else ''
1724 | row_data = (
1725 | json.dumps(data["url"]).replace('"', ''),
1726 | queryParams[1:-1],
1727 | bodyParams,
1728 | method[1:-1],
1729 | headers,
1730 | json.dumps(data["type"]).replace('"', ''),
1731 | )
1732 | if selected_files_length > 1:
1733 | row_data = row_data + (selected_file_name,)
1734 | processed_rows.append(row_data)
1735 | except ValueError:
1736 | print("Error parsing the line: " + line)
1737 | return processed_rows
1738 |
1739 | def display_result(self, event, file_names_list):
1740 | if isinstance(event, swing.event.ListSelectionEvent) and "invalid" in str(event) and event.getValueIsAdjusting() and len(self.file_names_model.toArray()) != 0:
1741 | return
1742 | deleted_lines = set()
1743 | differences = set()
1744 | self.selected_host = self.host_list.getSelectedValue()
1745 | self.results_table_popupmenu.selected_host = self.selected_host
1746 | selected_files = file_names_list.getSelectedValuesList()
1747 | selected_files_length = len(selected_files)
1748 | if self.selected_host:
1749 | self.result_table_model.setRowCount(0)
1750 | self.tabbed_pane_results.setTitleAt(0, str(self.selected_host) + " - Loading...")
1751 | if self.secrets_checkbox.isSelected():
1752 | if self.tabbed_pane_results.indexOfComponent(self.scroll_pane_secrets) == -1:
1753 | self.tabbed_pane_results.addTab("Secrets", self.scroll_pane_secrets)
1754 | self.tabbed_pane_results.setTitleAt(1, "Secrets - Loading...")
1755 | rows = []
1756 | if selected_files_length > 0:
1757 | rows = self.get_results_rows(selected_files, selected_files_length, rows, differences, deleted_lines)
1758 | for row in rows:
1759 | self.result_table_model.addRow(row)
1760 | for column in range(self.result_table_model.getColumnCount()):
1761 | self.result_table.getColumnModel().getColumn(column).setCellRenderer(ColorResults(differences, deleted_lines))
1762 | self.tabbed_pane_results.setTitleAt(0, self.get_result_tab_name())
1763 | if self.secrets_checkbox.isSelected():
1764 | rows = self.get_secrets_rows(selected_files_length, selected_files)
1765 | for row in rows:
1766 | self.secrets_table_model.addRow(row)
1767 | self.tabbed_pane_results.setTitleAt(1, "Secrets ({} rows)".format(self.secrets_table_model.getRowCount()))
1768 | self.tabbed_pane_results.revalidate()
1769 | self.tabbed_pane_results.repaint()
1770 |
1771 | def get_results_rows(self, selected_files, selected_files_length, rows, differences, deleted_lines):
1772 | for selected_file_name in selected_files:
1773 | selected_file_details = None
1774 | for file in self.processed_file_details.values():
1775 | if file["host"] == self.selected_host and file["file_name"] == selected_file_name:
1776 | selected_file_details = file
1777 | break
1778 | if selected_file_details and selected_file_details["output"]:
1779 | monitor_filter_name_hash = md5(selected_file_details["file_name"].encode('utf-8')).hexdigest()
1780 | monitor_file_name = selected_file_details["host"] + "_" + monitor_filter_name_hash
1781 | monitor_file_path = self.directory + self.monitored_urls_directory + monitor_file_name + ".old" if os.path.exists(self.directory + self.monitored_urls_directory + monitor_file_name + ".old") else self.directory + self.monitored_urls_directory + monitor_file_name
1782 | lines = selected_file_details["output"].strip().split("\n")
1783 | rows.extend(self.process_lines(lines=lines, selected_files_length=selected_files_length, selected_file_name=selected_file_name))
1784 | if os.path.exists(monitor_file_path):
1785 | with open(monitor_file_path, "r") as f:
1786 | monitor_file_content = json.loads(f.read())
1787 | try:
1788 | monitor_file_lines = set(monitor_file_content.strip().split("\n"))
1789 | except ValueError:
1790 | print("Error parsing the monitor file. Please ensure it contains valid JSON.")
1791 | monitor_file_lines = set()
1792 | selected_file_lines = set(selected_file_details["output"].strip().split("\n"))
1793 | new_or_changed_lines = selected_file_lines - monitor_file_lines
1794 | removed_lines = monitor_file_lines - selected_file_lines
1795 | new_or_changed_rows = self.process_lines(lines=new_or_changed_lines, selected_files_length=selected_files_length, selected_file_name=selected_file_name)
1796 | removed_rows = self.process_lines(lines=removed_lines, selected_files_length=selected_files_length, selected_file_name=selected_file_name)
1797 | differences.update(new_or_changed_rows)
1798 | deleted_lines.update(removed_rows)
1799 | if len(removed_rows) > 0:
1800 | rows.extend(removed_rows)
1801 | self.result_table_model.setColumnIdentifiers(["URL/Path", "Query Params", "Body Params", "Method", "Headers", "Type"] + (["File"] if selected_files_length > 1 else []))
1802 | if self.hide_duplicates_checkbox.isSelected():
1803 | type_column_index = self.result_table_model.findColumn("Type")
1804 | seen_rows = set()
1805 | rows = [row for row in rows if tuple(row[:type_column_index]) not in seen_rows and not seen_rows.add(tuple(row[:type_column_index]))]
1806 | if len(self.results_filters) > 0:
1807 | filtered_rows = []
1808 | for row in rows:
1809 | if all((self.results_filters.get(f) == 'negative' and f not in row[0]) or
1810 | (self.results_filters.get(f) == 'positive' and f in row[0]) for f in self.results_filters):
1811 | filtered_rows.append(row)
1812 | rows = filtered_rows
1813 | if self.show_parameterized.isSelected():
1814 | rows = [row for row in rows if row[1] != '' or row[2] != '' or row[4] != '']
1815 | return rows
1816 |
1817 | def get_secrets_rows(self, selected_files_length, selected_files):
1818 | rows = []
1819 | if self.selected_host and selected_files_length > 0:
1820 | self.secrets_table_model.setRowCount(0)
1821 | for selected_file_name in selected_files:
1822 | selected_file_details = None
1823 | for key, value in self.processed_file_details.items():
1824 | if value["host"] == self.selected_host and value["file_name"] == selected_file_name:
1825 | selected_file_details = value
1826 | break
1827 | if selected_file_details and "secrets" in selected_file_details and selected_file_details["secrets"]:
1828 | self.secrets_table_model.setColumnIdentifiers(["kind", "data", "severity", "context"] + (["File"] if selected_files_length > 1 else []))
1829 | lines = selected_file_details["secrets"].strip().split("\n")
1830 | for line in lines:
1831 | if line == "":
1832 | continue
1833 | data = json.loads(line, encoding='utf-8')
1834 | data_string = ", ".join(["{}:{}".format(key.encode('utf-8'), value.encode('utf-8')) for key, value in data["data"].items()])
1835 | context_string = ""
1836 | if data.get("context") is not None:
1837 | context_string = ", ".join([u"{}:{}".format(key.encode('utf-8'), value.encode('utf-8')) for key, value in data["context"].items()])
1838 | row_data = [
1839 | data["kind"],
1840 | data_string,
1841 | data["severity"],
1842 | context_string,
1843 | selected_file_name
1844 | ]
1845 | rows.append(row_data)
1846 | if self.hide_duplicates_checkbox.isSelected():
1847 | if selected_files_length > 1:
1848 | file_column_index = self.secrets_table_model.findColumn("File")
1849 | seen_rows = set()
1850 | rows = [row for row in rows if tuple(row[:file_column_index] + row[file_column_index+1:]) not in seen_rows and not seen_rows.add(tuple(row[:file_column_index] + row[file_column_index+1:]))]
1851 | else:
1852 | rows = [list(x) for x in set(tuple(x) for x in rows)]
1853 | return rows
1854 |
1855 | def filter_hosts(self):
1856 | selected_hosts = list(self.hosts_list)
1857 | current_selection = self.host_list.getSelectedValue()
1858 | in_scope_hosts = []
1859 | if self.in_scope_checkbox.isSelected():
1860 | in_scope_hosts = [host for host in selected_hosts if self._callbacks.isInScope(URL(str("http://" + host))) or self._callbacks.isInScope(URL(str("https://" + host)))]
1861 | current_selection = current_selection if current_selection in in_scope_hosts else None
1862 | self.host_list_model.clear()
1863 | for host in in_scope_hosts:
1864 | self.host_list_model.addElement(host)
1865 | else:
1866 | current_selection = current_selection if current_selection in selected_hosts else None
1867 | self.host_list_model.clear()
1868 | for host in selected_hosts:
1869 | self.host_list_model.addElement(host)
1870 | self.tabbed_pane_hosts.setTitleAt(0, str(self.host_list_model.size()) + " Hosts" if self.host_list_model.size() > 0 else "Hosts")
1871 | self.host_list_scroll_pane.setViewportView(self.host_list)
1872 | if current_selection:
1873 | index = in_scope_hosts.index(current_selection) if self.in_scope_checkbox.isSelected() else selected_hosts.index(current_selection)
1874 | self.host_list.setSelectedIndex(index)
1875 | elif len(in_scope_hosts) > 0:
1876 | self.host_list.setSelectedIndex(0)
1877 | else:
1878 | self.file_names_model.clear()
1879 | self.file_names_scroll_pane.setViewportView(self.file_names_list)
1880 | self.result_table_model.setRowCount(0)
1881 | self.tabbed_pane_results.setTitleAt(0, "Results")
1882 | self.tabbed_pane_files.setTitleAt(0, "Files")
1883 | if self.secrets_checkbox.isSelected():
1884 | self.secrets_table_model.setRowCount(0)
1885 | self.tabbed_pane_results.setTitleAt(1, "Secrets")
1886 | self.host_list.clearSelection()
1887 |
1888 | def get_processed_file_names(self, selected_host):
1889 | return [value["file_name"] for value in self.processed_file_details.values() if value["host"] == selected_host]
1890 |
1891 | def get_result_tab_name(self):
1892 | selected_host = self.host_list.getSelectedValue()
1893 | selected_files = self.file_names_list.getSelectedValuesList()
1894 | num_rows = self.result_table_model.getRowCount()
1895 | if len(selected_files) == 1:
1896 | return "{} - {} ({} rows)".format(selected_host, selected_files[0], num_rows)
1897 | else:
1898 | return "{} - {} Files ({} rows)".format(selected_host, len(selected_files), num_rows)
1899 |
1900 | def getTabCaption(self):
1901 | return "jsluice++"
1902 |
1903 | def getUiComponent(self):
1904 | return self.panel
1905 |
1906 |
1907 |
1908 |
1909 |
1910 |
1911 |
1912 |
1913 |
1914 |
--------------------------------------------------------------------------------