├── 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 | ![image](https://github.com/user-attachments/assets/4844b45f-003a-43ce-a65e-e4c0d47071b8) 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 | ![image](https://github.com/user-attachments/assets/af70e918-b636-4449-be81-c460bd118752) 49 | 50 | 51 | Those are the options 52 | ![image](https://github.com/user-attachments/assets/6e54c6b8-59d8-426f-94e0-4e6b7901fef7) 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 | ![image](https://github.com/user-attachments/assets/bf03ceb6-6048-4c8f-bf5f-6b2ae475d679) 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 | --------------------------------------------------------------------------------