├── README.md
└── burpseek.py
/README.md:
--------------------------------------------------------------------------------
1 | # burp-deepseek
2 | A quick and dirty (and a little shitty) burp extension that uses cheap deepseek api to send request and response and maybe found something interesting.
3 |
4 | THIS IS IN BETA-MODE. Nothing its going to break on your project (I think) but maybe you found so much use-cases that are not working right now
5 |
6 | # DeepSeek Burp Extension
7 |
8 | **DeepSeek Burp Extension** is a beta-stage plugin for [PortSwigger Burp Suite](https://portswigger.net/burp) that sends HTTP requests/responses to the [DeepSeek API](https://api.deepseek.com) for AI-driven security analysis. This extension helps bug hunters and security researchers identify potential vulnerabilities, suspicious endpoints, and sensitive data exposures.
9 |
10 | > **Disclaimer:** This is a beta version and may contain bugs or incomplete features. Use at your own risk, and always validate any results you get from this extension.
11 |
12 | ---
13 |
14 | ## Features
15 |
16 | - **Context Menu Integration**
17 | Right-click any request/response in Burp (Proxy, Repeater, etc.) to send it to DeepSeek.
18 |
19 | - **Asynchronous Requests**
20 | The DeepSeek API call is performed in a separate thread, so Burp remains responsive.
21 |
22 | - **Custom or Default Prompt**
23 | Define a default prompt, or enter a custom one for on-the-fly analysis.
24 |
25 | - **Creates Burp Issues**
26 | Results are stored as **Information**-level issues in Burp, allowing you to review them later.
27 |
28 | ---
29 |
30 | ## How It Works
31 |
32 | 1. **Select a request/response** in Burp Suite (e.g., in **Proxy** or **Repeater**).
33 | 2. **Right-click** and choose **_Send to DeepSeek_** or **_Send to DeepSeek (custom prompt)_**.
34 |
35 | 
36 |
37 |
38 | 3. The extension sends the HTTP data to the DeepSeek API for analysis.
39 | 4. Once the API responds, the extension creates a “DeepSeek Analysis” issue in Burp, containing the AI-generated insights.
40 |
41 |
42 | ##Configure
43 |
44 | A prompt will appear when it loads for first time
45 |
46 |
47 | After that you can just click hereto reconfigure
48 | 
49 |
50 |
51 | Those are the options
52 | 
53 |
54 |
55 | ---
56 |
57 | ## Requirements
58 |
59 | - **Burp Suite** (Community or Professional).
60 | - **Jython** 2.7+ if you are loading this as a Jython-based extension.
61 | - A valid **DeepSeek** API key.
62 |
63 | ---
64 |
65 | ## Installation
66 |
67 | 1. **Download or clone** this repository.
68 | 2. In Burp Suite, go to **Extender** > **Extensions**.
69 | 3. Add a new extension:
70 | - **Extension Type**: Python
71 | - **Location**: Select the `burp_deepseek.py` file (or similar) from this repository.
72 | 4. Go to the **DeepSeek Analyzer** tab in Burp to configure your **API Key** and default prompt. (Create here the API key https://platform.deepseek.com/api_keys)
73 | 5. (Optional) Enable **Debug Mode** if you want verbose logs in the Extender console.
74 |
75 | ---
76 |
77 | ## Usage
78 |
79 | 1. **Right-click** on any request or response.
80 | 2. Select **_Send to DeepSeek_** (uses the default prompt) or **_Send to DeepSeek (custom prompt)_**.
81 | 3. The extension will call the DeepSeek API in a separate thread.
82 | 4. When the response comes back, a new **DeepSeek Analysis** issue with severity **Information** appears in **Scanner** > **Issues**.
83 |
84 | ---
85 |
86 |
87 | ## Costs
88 |
89 | Obviously it depends how much you use :D
90 | Also, the longer the request/response you send to the api, more is going to cost, but in my benchmakrs and tests, 10 requests are 1 cent, so.
91 | 1.000 analyzed requests are going to cost about 1 €
92 |
93 |
94 | 
95 |
96 | But this is an estimation. Just set up the billing alerts, and top the api with a controlled budget
97 |
98 |
99 |
100 | ---
101 |
102 | ## Contributing
103 |
104 | We welcome any contributions or pull requests to improve functionality, fix bugs, or add new features. Feel free to open an issue or submit a PR if you have ideas or encounter any problems.
105 |
106 | ---
107 |
108 | ## Modifications
109 |
110 | You can just change the endpoints if you want to use chatGPT or another LLM. It is in standard mode.
111 | Also, you can change the default system prompt if you find other system prompot that works better.
112 |
113 | ---
114 |
115 | ## License
116 |
117 | This project is released under the [MIT License](LICENSE).
118 |
119 | ---
120 |
121 | **Enjoy bug hunting with DeepSeek!**
122 |
123 |
124 | #Issues
125 | Sometimes takes a loooong time to get the response. Don't expect real time
126 | Sometimes it breaks the parser it the input is too long.
127 | Sometimes just it doesn't work
128 |
129 |
130 |
131 | #TODO
132 | - **Debug Mode**
133 | Optionally enable debug logging to see full request/response details in Burp's extender console.
134 |
135 | - **HTML-Formatted Responses**
136 | Instruct DeepSeek to return its analysis in HTML for a cleaner readout in Burp’s **Scanner > Issues** panel.
137 |
--------------------------------------------------------------------------------
/burpseek.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from burp import (
4 | IBurpExtender,
5 | IContextMenuFactory,
6 | IContextMenuInvocation,
7 | IHttpRequestResponse,
8 | IExtensionHelpers,
9 | ITab,
10 | IScanIssue
11 | )
12 | from javax.swing import (
13 | JMenuItem,
14 | JOptionPane,
15 | JPanel,
16 | JLabel,
17 | JTextField,
18 | JButton
19 | )
20 | from java.awt import GridLayout, BorderLayout
21 | import json
22 | import urllib2
23 | import threading # Para crear el hilo en segundo plano
24 |
25 | def to_unicode(obj, encoding='utf-8'):
26 | """
27 | Convierte un string a 'unicode' en Jython/Python 2.
28 | Evita errores de codificación ASCII cuando hay caracteres especiales.
29 | """
30 | if obj is None:
31 | return u''
32 | if isinstance(obj, unicode):
33 | return obj
34 | try:
35 | return obj.decode(encoding)
36 | except:
37 | return obj.decode(encoding, 'replace')
38 |
39 | class CustomScanIssue(IScanIssue):
40 | """
41 | Clase que implementa IScanIssue para reportar hallazgos personalizados
42 | en Burp. Evita depender de createScanIssue(...) que no existe en Community.
43 | """
44 | def __init__(self, http_service, url, http_messages, issue_name, issue_detail, severity):
45 | self._http_service = http_service
46 | self._url = url
47 | self._http_messages = http_messages # lista/array de IHttpRequestResponse
48 | self._issue_name = issue_name
49 | self._issue_detail = issue_detail
50 | self._severity = severity
51 |
52 | def getUrl(self):
53 | return self._url
54 |
55 | def getIssueName(self):
56 | return self._issue_name
57 |
58 | def getIssueType(self):
59 | # Puedes devolver un número único para tu tipo de issue o usar 0
60 | # https://portswigger.net/burp/extender/api/constant-values
61 | return 0
62 |
63 | def getSeverity(self):
64 | # "High", "Medium", "Low", "Information"
65 | return self._severity
66 |
67 | def getConfidence(self):
68 | # "Certain", "Firm" o "Tentative"
69 | return "Certain"
70 |
71 | def getIssueBackground(self):
72 | return None
73 |
74 | def getRemediationBackground(self):
75 | return None
76 |
77 | def getIssueDetail(self):
78 | # Aquí va el texto principal que se mostrará en el panel de detalles del Issue
79 | return self._issue_detail
80 |
81 | def getRemediationDetail(self):
82 | return None
83 |
84 | def getHttpMessages(self):
85 | return self._http_messages
86 |
87 | def getHttpService(self):
88 | return self._http_service
89 |
90 |
91 | class BurpExtender(IBurpExtender, IContextMenuFactory, ITab):
92 |
93 | def registerExtenderCallbacks(self, callbacks):
94 | self._callbacks = callbacks
95 | self._helpers = callbacks.getHelpers()
96 | self._callbacks.setExtensionName("DeepSeek Analyzer")
97 |
98 | # Registramos el menú contextual y la pestaña
99 | self._callbacks.registerContextMenuFactory(self)
100 | self._callbacks.addSuiteTab(self)
101 |
102 | # Configuración por defecto
103 | self.api_key = ""
104 | self.default_prompt = (
105 | "Analyze this HTTP request/response focusing ONLY on potential vulnerabilities. "
106 | "Look for suspicious endpoints, possible IDOR, or any exposed secrets like API keys. "
107 | "Do NOT provide any remediation or mitigation steps."
108 | )
109 |
110 | # Muestra el diálogo de configuración al cargar
111 | self.show_config_dialog()
112 |
113 | #
114 | # Context Menu Factory
115 | #
116 | def createMenuItems(self, context_menu):
117 | menu_list = []
118 | context_id = context_menu.getInvocationContext()
119 |
120 | # Añadimos opciones al menú contextual solo si es request/response
121 | if context_id in [
122 | IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_REQUEST,
123 | IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_RESPONSE,
124 | IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_REQUEST,
125 | IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_RESPONSE
126 | ]:
127 | menu_list.append(
128 | JMenuItem(
129 | "Send to DeepSeek",
130 | actionPerformed=lambda event, ctx=context_menu:
131 | self.send_to_deepseek(ctx, self.default_prompt)
132 | )
133 | )
134 | menu_list.append(
135 | JMenuItem(
136 | "Send to DeepSeek (custom prompt)",
137 | actionPerformed=lambda event, ctx=context_menu:
138 | self.send_to_deepseek_custom_prompt(ctx)
139 | )
140 | )
141 | return menu_list if menu_list else None
142 |
143 | #
144 | # Lógica principal
145 | #
146 | def send_to_deepseek(self, context, prompt):
147 | selected_messages = context.getSelectedMessages()
148 | if not selected_messages:
149 | JOptionPane.showMessageDialog(None, "No message selected!")
150 | return
151 |
152 | message_info = selected_messages[0] # IHttpRequestResponse
153 | invocation_id = context.getInvocationContext()
154 |
155 | # Llamamos a la función que hará la petición, en un hilo aparte
156 | self.send_to_deepseek_async(message_info, invocation_id, prompt)
157 |
158 | def send_to_deepseek_custom_prompt(self, context):
159 | custom_prompt = JOptionPane.showInputDialog("Enter your custom prompt:")
160 | if custom_prompt:
161 | self.send_to_deepseek(context, custom_prompt)
162 |
163 | def send_to_deepseek_async(self, message_info, invocation_id, prompt):
164 | """
165 | Lanza la lógica de conexión a DeepSeek en un hilo aparte para no
166 | bloquear la interfaz de Burp al hacer clic derecho.
167 | """
168 | def worker():
169 | # Aquí dentro hacemos la llamada y creamos el Issue
170 | self._do_deepseek_request(message_info, invocation_id, prompt)
171 | t = threading.Thread(target=worker)
172 | t.start()
173 |
174 | def _do_deepseek_request(self, message_info, invocation_id, prompt):
175 | """
176 | Función interna que realmente realiza la llamada a DeepSeek.
177 | Al estar en un hilo separado, no congela Burp.
178 | """
179 | if not self.api_key:
180 | # Si no hay API Key, abortamos
181 | return
182 |
183 | # Decidimos si usar request o response según el contexto
184 | if invocation_id in [
185 | IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_REQUEST,
186 | IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_REQUEST
187 | ]:
188 | raw_data = message_info.getRequest()
189 | else:
190 | raw_data = message_info.getResponse()
191 |
192 | # Convertimos a unicode seguro
193 | data_text = to_unicode(self._helpers.bytesToString(raw_data))
194 | prompt = to_unicode(prompt)
195 | system_text = to_unicode(
196 | "You are a specialized cybersecurity auditor focused on bug bounty. "
197 | "Your ONLY goal is to identify potential vulnerabilities, suspicious endpoints, "
198 | "possible IDOR, or sensitive data such as exposed API keys or credentials. "
199 | "Attempt to reconstruct endpoints from partial information. "
200 | "Do NOT provide any remediation or mitigation steps."
201 | )
202 |
203 | # Construimos el payload JSON
204 | url = "https://api.deepseek.com/chat/completions"
205 | headers = {
206 | "Authorization": "Bearer {}".format(self.api_key),
207 | "Content-Type": "application/json"
208 | }
209 | payload = {
210 | "model": "deepseek-chat",
211 | "messages": [
212 | {"role": "system", "content": system_text},
213 | {"role": "user", "content": u"{}\n\n{}".format(prompt, data_text)}
214 | ],
215 | "stream": False
216 | }
217 |
218 | try:
219 | req_data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
220 | req = urllib2.Request(url, req_data, headers)
221 | response = urllib2.urlopen(req)
222 | response_json = json.loads(response.read())
223 |
224 | if "choices" in response_json and len(response_json["choices"]) > 0:
225 | analysis_text = response_json["choices"][0]["message"]["content"]
226 | else:
227 | analysis_text = "No analysis received from DeepSeek."
228 |
229 | # Reemplazamos saltos de línea por
para que Burp lo muestre en HTML multilinea
230 | analysis_text_formatted = analysis_text.replace("\n", "
")
231 |
232 | self._create_issue_in_burp(message_info, analysis_text_formatted)
233 |
234 | except Exception as e:
235 | # Podemos loguear el error en la consola de Extensiones
236 | print("[DeepSeek Extension] Error: {}".format(str(e)))
237 | # O mostrarlo en un Popup, pero habría que hacerlo en el hilo de Swing
238 | # SwingUtilities.invokeLater(lambda: JOptionPane.showMessageDialog(None, ...))
239 |
240 | def _create_issue_in_burp(self, message_info, analysis_html):
241 | """
242 | Crea el Issue con severidad 'Information', usando la clase CustomScanIssue.
243 | """
244 | request_info = self._helpers.analyzeRequest(message_info)
245 | url = request_info.getUrl()
246 | http_service = message_info.getHttpService()
247 |
248 | # Añadimos markers si queremos (aquí None, None)
249 | self._callbacks.applyMarkers(message_info, None, None)
250 |
251 | issue_name = "DeepSeek Analysis"
252 | issue_detail = (
253 | "DeepSeek Analysis
"
254 | "{}".format(analysis_html)
255 | )
256 |
257 | new_issue = CustomScanIssue(
258 | http_service=http_service,
259 | url=url,
260 | http_messages=[message_info],
261 | issue_name=issue_name,
262 | issue_detail=issue_detail,
263 | severity="Information" # Aquí en vez de "High"
264 | )
265 | self._callbacks.addScanIssue(new_issue)
266 |
267 | #
268 | # Configuración (ITab)
269 | #
270 | def show_config_dialog(self):
271 | panel = JPanel(GridLayout(0, 2))
272 | panel.add(JLabel("API Key:"))
273 | api_key_field = JTextField(self.api_key, 20)
274 | panel.add(api_key_field)
275 |
276 | panel.add(JLabel("Default Prompt:"))
277 | prompt_field = JTextField(self.default_prompt, 20)
278 | panel.add(prompt_field)
279 |
280 | result = JOptionPane.showConfirmDialog(None, panel, "Configure DeepSeek", JOptionPane.OK_CANCEL_OPTION)
281 | if result == JOptionPane.OK_OPTION:
282 | self.api_key = api_key_field.getText()
283 | self.default_prompt = prompt_field.getText()
284 | JOptionPane.showMessageDialog(None, "Configuration saved!")
285 |
286 | #
287 | # Implementación de ITab (pestaña en Burp)
288 | #
289 | def getTabCaption(self):
290 | return "DeepSeek Analyzer"
291 |
292 | def getUiComponent(self):
293 | panel = JPanel(BorderLayout())
294 | config_button = JButton("Configure DeepSeek", actionPerformed=lambda x: self.show_config_dialog())
295 | panel.add(config_button, BorderLayout.NORTH)
296 | return panel
297 |
298 | # Hook de entrada para Burp
299 | if __name__ in ["__main__", "burp"]:
300 | BurpExtender()
301 |
--------------------------------------------------------------------------------