11# -*- coding: utf-8 -*-
22# type: ignore[import]
3+ from datetime import datetime
34from burp import IBurpExtender , ITab , IHttpListener , IScanIssue
4- from java .awt import BorderLayout , GridBagLayout , GridBagConstraints , Font
5+ from java .awt import BorderLayout , GridBagLayout , GridBagConstraints , Font , Dimension
56from javax .swing import (
67 JPanel , JTextArea , JScrollPane ,
78 BorderFactory , JSplitPane , JButton , JComboBox ,
89 JTable , table , ListSelectionModel , JOptionPane , JTextField , JTabbedPane )
10+ from javax .swing import BoxLayout , JLabel
911from javax .swing .table import DefaultTableCellRenderer , TableRowSorter
1012from javax .swing .border import TitledBorder
1113from java .util import Comparator
1214import json
1315import urllib2
1416import os
15- from datetime import datetime
1617from consts import *
1718from api_adapters import get_api_adapter
1819from issues import BurpferenceIssue
20+ from threading import Thread
21+ from scanner import BurpferenceScanner
1922
2023
2124def load_ascii_art (file_path ):
@@ -51,6 +54,8 @@ def __init__(self):
5154 self .temp_log_messages = []
5255 self .request_counter = 0
5356 self .log_message ("Extension initialized and running." )
57+ self ._hosts = set ()
58+ self .scanner = None # Will initialize after callbacks
5459
5560 def registerExtenderCallbacks (self , callbacks ):
5661 self ._callbacks = callbacks
@@ -264,12 +269,35 @@ def compare(self, s1, s2):
264269 self ._panel .add (diffSplitPane , BorderLayout .NORTH )
265270
266271 self .inference_tab = self .create_inference_logger_tab ()
272+ self .scanner_tab = self .create_scanner_tab ()
273+
267274 self .tabbedPane = JTabbedPane ()
268275 self .tabbedPane .setBackground (DARK_BACKGROUND )
269276 self .tabbedPane .setForeground (DREADNODE_GREY )
270277 self .tabbedPane .addTab ("burpference" , self ._panel )
271278 self .tabbedPane .addTab ("Inference Logger" , self .inference_tab )
272279
280+ # Initialize scanner AFTER loading config
281+ self .scanner = None
282+
283+ # Now initialize scanner with current config
284+ colors = {
285+ 'DARK_BACKGROUND' : DARK_BACKGROUND ,
286+ 'LIGHTER_BACKGROUND' : LIGHTER_BACKGROUND ,
287+ 'DREADNODE_GREY' : DREADNODE_GREY ,
288+ 'DREADNODE_ORANGE' : DREADNODE_ORANGE
289+ }
290+ self .scanner = BurpferenceScanner (
291+ callbacks = self ._callbacks ,
292+ helpers = self ._helpers ,
293+ config = None ,
294+ api_adapter = None ,
295+ colors = colors
296+ )
297+
298+ self .scanner_tab = self .scanner .create_scanner_tab ()
299+ self .tabbedPane .addTab ("Scanner" , self .scanner_tab )
300+
273301 for i in range (self .tabbedPane .getTabCount ()):
274302 self .tabbedPane .setBackgroundAt (i , DREADNODE_GREY )
275303 self .tabbedPane .setForegroundAt (i , DREADNODE_ORANGE )
@@ -333,33 +361,50 @@ def loadConfiguration(self, event):
333361 try :
334362 with open (config_path , 'r' ) as config_file :
335363 self .config = json .load (config_file )
336- self .log_message ( "Loaded configuration: %s" %
337- json . dumps ( self . config , indent = 2 ))
364+ self .config [ "config_file" ] = selected_config
365+
338366 try :
339367 self .api_adapter = get_api_adapter (self .config )
368+ if self .scanner :
369+ self .scanner .config = self .config
370+ self .scanner .api_adapter = self .api_adapter
371+ self .scanner .update_config_display ()
340372 self .log_message ("API adapter initialized successfully" )
341373 except ValueError as e :
342374 self .log_message ("Error initializing API adapter: %s" % str (e ))
343375 self .api_adapter = None
376+ if self .scanner :
377+ self .scanner .api_adapter = None
344378 except Exception as e :
345379 self .log_message (
346380 "Unexpected error initializing API adapter: %s" % str (e ))
347381 self .api_adapter = None
382+ if self .scanner :
383+ self .scanner .api_adapter = None
348384 except ValueError as e :
349385 self .log_message (
350386 "Error parsing JSON in configuration file: %s" % str (e ))
351387 self .config = None
352388 self .api_adapter = None
389+ if self .scanner :
390+ self .scanner .config = None
391+ self .scanner .api_adapter = None
353392 except Exception as e :
354393 self .log_message (
355394 "Unexpected error loading configuration: %s" % str (e ))
356395 self .config = None
357396 self .api_adapter = None
397+ if self .scanner :
398+ self .scanner .config = None
399+ self .scanner .api_adapter = None
358400 else :
359401 self .log_message (
360402 "Configuration file %s not found." % selected_config )
361403 self .config = None
362404 self .api_adapter = None
405+ if self .scanner :
406+ self .scanner .config = None
407+ self .scanner .api_adapter = None
363408
364409 def create_inference_logger_tab (self ):
365410 panel = JPanel (BorderLayout ())
@@ -434,6 +479,133 @@ def create_inference_logger_tab(self):
434479
435480 return panel
436481
482+ def create_scanner_tab (self ):
483+ """Creates the burpference scanner tab with domain filtering and direct model interaction"""
484+ panel = JPanel ()
485+ panel .setLayout (BoxLayout (panel , BoxLayout .Y_AXIS ))
486+ panel .setBackground (DARK_BACKGROUND )
487+
488+ # Create top control panel
489+ top_panel = JPanel ()
490+ top_panel .setLayout (BoxLayout (top_panel , BoxLayout .X_AXIS ))
491+ top_panel .setBackground (DARK_BACKGROUND )
492+
493+ # Domain selector
494+ domain_panel = JPanel ()
495+ domain_panel .setBackground (DARK_BACKGROUND )
496+ domain_label = JLabel ("Target Domain:" )
497+ domain_label .setForeground (DREADNODE_GREY )
498+ self ._domain_selector = JComboBox (list (self ._hosts ))
499+ self ._domain_selector .setBackground (LIGHTER_BACKGROUND )
500+ self ._domain_selector .setForeground (DREADNODE_GREY )
501+ domain_panel .add (domain_label )
502+ domain_panel .add (self ._domain_selector )
503+ top_panel .add (domain_panel )
504+
505+ # Optional prompt input
506+ middle_panel = JPanel ()
507+ middle_panel .setBackground (DARK_BACKGROUND )
508+ middle_panel .setLayout (BoxLayout (middle_panel , BoxLayout .Y_AXIS ))
509+ prompt_label = JLabel ("Custom Analysis Prompt:" )
510+ prompt_label .setForeground (DREADNODE_GREY )
511+ self ._custom_prompt = JTextArea (5 , 50 )
512+ self ._custom_prompt .setLineWrap (True )
513+ self ._custom_prompt .setWrapStyleWord (True )
514+ self ._custom_prompt .setBackground (LIGHTER_BACKGROUND )
515+ self ._custom_prompt .setForeground (DREADNODE_ORANGE )
516+ prompt_scroll = JScrollPane (self ._custom_prompt )
517+
518+ # Analyze button
519+ analyze_button = JButton ("Analyze Domain" , actionPerformed = self .analyze_domain )
520+ analyze_button .setBackground (DREADNODE_ORANGE )
521+ analyze_button .setForeground (DREADNODE_GREY )
522+
523+ middle_panel .add (prompt_label )
524+ middle_panel .add (prompt_scroll )
525+ middle_panel .add (analyze_button )
526+
527+ # Results area
528+ self ._scanner_output = JTextArea (20 , 50 )
529+ self ._scanner_output .setEditable (False )
530+ self ._scanner_output .setLineWrap (True )
531+ self ._scanner_output .setWrapStyleWord (True )
532+ self ._scanner_output .setBackground (LIGHTER_BACKGROUND )
533+ self ._scanner_output .setForeground (DREADNODE_ORANGE )
534+ scanner_scroll = JScrollPane (self ._scanner_output )
535+
536+ # Add all components
537+ panel .add (top_panel )
538+ panel .add (middle_panel )
539+ panel .add (scanner_scroll )
540+
541+ return panel
542+
543+ def analyze_domain (self , event ):
544+ """Handles the domain analysis button click"""
545+ domain = self ._domain_selector .getSelectedItem ()
546+ custom_prompt = self ._custom_prompt .getText ()
547+
548+ def run_analysis ():
549+ self ._scanner_output .setText ("Analyzing domain: %s...\n " % domain )
550+ try :
551+ # Get all requests for selected domain
552+ http_pairs = self .get_domain_traffic (domain )
553+ if not http_pairs :
554+ self ._scanner_output .append ("\n No traffic found for domain." )
555+ return
556+
557+ # Use custom prompt if provided, otherwise use default
558+ prompt = custom_prompt if custom_prompt else "Analyze this domain's traffic for security issues:"
559+
560+ # Prepare and send to current model
561+ analysis_request = self .api_adapter .prepare_request (
562+ user_content = json .dumps (http_pairs , indent = 2 ),
563+ system_content = prompt
564+ )
565+
566+ # Make request and process response
567+ req = urllib2 .Request (self .config .get ("host" , "" ))
568+ for header , value in self .config .get ("headers" , {}).items ():
569+ req .add_header (header , value )
570+
571+ response = urllib2 .urlopen (req , json .dumps (analysis_request ))
572+ response_data = response .read ()
573+ analysis = self .api_adapter .process_response (response_data )
574+
575+ # Update UI
576+ self ._scanner_output .setText ("Analysis for %s:\n \n %s" % (domain , analysis ))
577+
578+ except Exception as e :
579+ self ._scanner_output .setText ("Error analyzing domain: %s" % str (e ))
580+
581+ # Run analysis in background thread
582+ Thread (target = run_analysis ).start ()
583+
584+ def get_domain_traffic (self , domain ):
585+ """Gets all traffic for a specific domain"""
586+ traffic = []
587+ for message in self ._callbacks .getProxyHistory ():
588+ if domain in message .getHttpService ().getHost ():
589+ analyzed_request = self ._helpers .analyzeRequest (message )
590+ analyzed_response = self ._helpers .analyzeResponse (message .getResponse ())
591+
592+ # Extract request/response data
593+ request_info = {
594+ "method" : analyzed_request .getMethod (),
595+ "url" : str (message .getUrl ()),
596+ "headers" : dict (header .split (': ' , 1 ) for header in analyzed_request .getHeaders ()[1 :] if ': ' in header ),
597+ "body" : message .getRequest ()[analyzed_request .getBodyOffset ():].tostring ()
598+ }
599+
600+ response_info = {
601+ "status" : analyzed_response .getStatusCode (),
602+ "headers" : dict (header .split (': ' , 1 ) for header in analyzed_response .getHeaders ()[1 :] if ': ' in header ),
603+ "body" : message .getResponse ()[analyzed_response .getBodyOffset ():].tostring ()
604+ }
605+
606+ traffic .append ({"request" : request_info , "response" : response_info })
607+ return traffic
608+
437609 def inferenceLogSelectionChanged (self , event ):
438610 selectedRow = self .inferenceLogTable .getSelectedRow ()
439611 if selectedRow != - 1 :
@@ -451,9 +623,7 @@ def inferenceLogSelectionChanged(self, event):
451623 except (ValueError , TypeError ):
452624 formatted_request = str (request )
453625
454- # For response, try to extract the message content if it's a model response
455626 try :
456- # Handle case where response is already a dict
457627 if isinstance (response , dict ):
458628 response_obj = response
459629 else :
@@ -552,7 +722,7 @@ def getTableCellRendererComponent(self, table, value, isSelected, hasFocus, row,
552722
553723 def log_message (self , message ):
554724 timestamp = datetime .now ().strftime ("%Y-%m-%d %H:%M:%S" )
555- log_entry = "[{0}] {1}\n " .format (timestamp , message ) # Python2 format strings
725+ log_entry = "[{0}] {1}\n " .format (timestamp , message )
556726
557727 if self .logArea is None :
558728 self .temp_log_messages .append (log_entry )
@@ -562,12 +732,10 @@ def log_message(self, message):
562732 self .logArea .getDocument ().getLength ())
563733
564734 try :
565- # Try to create/write to log file with explicit permissions
566735 log_dir = os .path .dirname (self .log_file_path )
567736 if not os .path .exists (log_dir ):
568- os .makedirs (log_dir , 0755 ) # Python2 octal notation
737+ os .makedirs (log_dir , 0755 )
569738
570- # Open with explicit write permissions
571739 with open (self .log_file_path , 'a+' ) as log_file :
572740 log_file .write (log_entry )
573741 except (IOError , OSError ) as e :
@@ -629,7 +797,7 @@ def create_scan_issue(self, messageInfo, processed_response):
629797 detail = str (processed_response )
630798
631799 if detail .startswith ('"' ) and detail .endswith ('"' ):
632- detail = detail [1 :- 1 ] # Remove surrounding quotes
800+ detail = detail [1 :- 1 ]
633801
634802 # Create properly formatted issue name
635803 issue_name = "burpference: %s Security Finding" % severity
@@ -651,6 +819,13 @@ def create_scan_issue(self, messageInfo, processed_response):
651819 self .log_message ("Error creating scan issue: %s" % str (e ))
652820
653821 def processHttpMessage (self , toolFlag , messageIsRequest , messageInfo ):
822+ if messageIsRequest :
823+ # Add new domains to both main extension and scanner
824+ host = messageInfo .getHttpService ().getHost ()
825+ if host not in self ._hosts :
826+ self ._hosts .add (host )
827+ if self .scanner :
828+ self .scanner .add_host (host )
654829 if not self .is_running :
655830 return
656831 if not self .api_adapter :
@@ -661,6 +836,13 @@ def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
661836 if messageIsRequest :
662837 # Store the request for later use
663838 self .current_request = messageInfo
839+ host = messageInfo .getHttpService ().getHost ()
840+ if host not in self ._hosts :
841+ self ._hosts .add (host )
842+ if hasattr (self , '_domain_selector' ):
843+ self ._domain_selector .addItem (host )
844+ if self .scanner :
845+ self .scanner .add_host (host )
664846 else :
665847 request = self .current_request
666848 response = messageInfo
@@ -799,7 +981,6 @@ def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
799981
800982 self .requestArea .append ("\n \n === Request #" + str (self .request_counter ) + " ===\n " )
801983 try :
802- # Format the request nicely
803984 formatted_request = json .dumps (http_pair , indent = 2 )
804985 formatted_request = formatted_request .replace ('\\ n' , '\n ' )
805986 formatted_request = formatted_request .replace ('\\ "' , '"' )
@@ -810,7 +991,6 @@ def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
810991
811992 self .responseArea .append ("\n \n === Response #" + str (self .request_counter ) + " ===\n " )
812993 try :
813- # Format the response nicely
814994 if isinstance (processed_response , dict ) and 'message' in processed_response and 'content' in processed_response ['message' ]:
815995 formatted_response = processed_response ['message' ]['content' ]
816996 else :
0 commit comments