├── README.md ├── pom.xml └── src ├── burp └── BurpExtender.java └── requestcleaner ├── BurpCleanerPanel.java ├── BurpCleanerPanel.jfd ├── CleanerPanel.java ├── ExtensionState.java ├── HttpRequestResponse.java ├── HttpService.java ├── ManualRequestSenderContextMenu.java ├── MessageEditorController.java ├── Parameter.java └── TabButton.java /README.md: -------------------------------------------------------------------------------- 1 | # BurpRequestCleaner 2 | This extension redacts potentially sensitive header and parameter values from requests using Shannon Entropy analysis. 3 | 4 | While on Twitter I saw Ben's [tweet](https://twitter.com/pry0cc/status/1343629699487039495) wanting Burp to have a "demo" mode that replaced the domain and potentially secret values so that it could be easily shareable. This is my attempt at solving that. It targets the Host header directly by replacing all but the root FQDN, resulting in transformations like so: 5 | - "foo.bar.baz.root.com" ---> "redacted.root.com" 6 | - "foo.root.com" ---> "redacted.root.com" 7 | 8 | For all other headers the extension check them against a list of known sensitive headers and if they match it redact them. Currently it checks for just 2: 9 | - Authorization 10 | - Proxy-Authorization 11 | 12 | This is by far the weakest part of the extension and something I'll give more thought to. Shannon Entropy fails us with headers because lots of headers have very high entropy but are obviously safe. 13 | 14 | For example, the `User-Agent` header shouldn't really ever be redacted but based on Shannon Entropy its a strong candidate for redaction. 15 | 16 | The best I can currently think of is letting the user decide which headers they want to ignore along with some likely safe suggestions. 17 | 18 | For parameters we use Shannon Entropy to determine if values in Cookies, JSON/XML fields, URL query string, body, or multi-part fields are likely sensitive content and should be redacted. 19 | 20 | Some features to add down the road: 21 | - Cleaning Responses 22 | - User cofiguration for headers to clean 23 | - User configuration for redaction string used to replace values 24 | - Other features I haven't thought of yet 25 | - Community Suggestions 26 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | staticflow 8 | BurpRequestCleaner 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | net.portswigger.burp.extender 14 | burp-extender-api 15 | 2.1 16 | 17 | 18 | 19 | 20 | ${project.basedir}/src 21 | BurpSuiteRequestCleaner 22 | 23 | 24 | 25 | org.apache.maven.plugins 26 | maven-eclipse-plugin 27 | 2.9 28 | 29 | true 30 | false 31 | 32 | 33 | 34 | 35 | 36 | org.apache.maven.plugins 37 | maven-compiler-plugin 38 | 2.3.2 39 | 40 | 1.8 41 | 1.8 42 | 43 | 44 | 45 | 46 | 47 | org.apache.maven.plugins 48 | maven-assembly-plugin 49 | 2.4.1 50 | 51 | 52 | false 53 | 54 | jar-with-dependencies 55 | 56 | 57 | 58 | 59 | 60 | make-assembly 61 | 62 | package 63 | 64 | single 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/burp/BurpExtender.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | 4 | import requestcleaner.BurpCleanerPanel; 5 | import requestcleaner.ExtensionState; 6 | import requestcleaner.ManualRequestSenderContextMenu; 7 | 8 | import java.awt.*; 9 | 10 | /* 11 | This extension lets you send requests to a cleaning operation which uses 12 | Shannon Entropy to detect potentially sensitive headers or parameter values 13 | and redacts them. Made because of this tweet https://twitter.com/pry0cc/status/1343629699487039495 14 | */ 15 | public class BurpExtender implements IBurpExtender, ITab { 16 | private IBurpExtenderCallbacks callbacks; 17 | 18 | public void registerExtenderCallbacks(IBurpExtenderCallbacks iBurpExtenderCallbacks) { 19 | callbacks = iBurpExtenderCallbacks; 20 | ExtensionState.setCallbacks(iBurpExtenderCallbacks); 21 | callbacks.setExtensionName("Burp Suite Request Cleaner"); 22 | iBurpExtenderCallbacks.registerContextMenuFactory(new ManualRequestSenderContextMenu()); 23 | iBurpExtenderCallbacks.addSuiteTab(this); 24 | 25 | 26 | } 27 | 28 | public String getTabCaption() { 29 | return "Request Cleaner"; 30 | } 31 | 32 | public Component getUiComponent() { 33 | BurpCleanerPanel panel = ExtensionState.getInstance().getCleanerPanel(); 34 | callbacks.customizeUiComponent(panel); 35 | return panel; 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /src/requestcleaner/BurpCleanerPanel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Created by JFormDesigner on Mon Dec 28 22:28:33 CST 2020 3 | */ 4 | 5 | package requestcleaner; 6 | 7 | import java.awt.*; 8 | import java.awt.event.*; 9 | import javax.swing.*; 10 | 11 | /* 12 | This is the GUI for the extension. Lots of this is auto-generated 13 | */ 14 | public class BurpCleanerPanel extends JPanel { 15 | public BurpCleanerPanel() { 16 | initComponents(); 17 | } 18 | 19 | //This is the only custom bit, we create out tab and update the state 20 | public void addTab(JPanel cleanerTab) { 21 | String tabTitle = String.valueOf(ExtensionState.getInstance().getNumberOfCleanedRequests()); 22 | this.cleanerTabPane.addTab(tabTitle,cleanerTab); 23 | this.cleanerTabPane.setTabComponentAt(ExtensionState.getInstance().getNumberOfCleanedRequests(),new TabButton(this.cleanerTabPane)); 24 | ExtensionState.getInstance().setNumberOfCleanedRequests(ExtensionState.getInstance().getNumberOfCleanedRequests()+1); 25 | } 26 | 27 | private void parameterSetLevelActionPerformed(ActionEvent e) { 28 | ExtensionState.getInstance().setParameterEntropyLevel(Integer.parseInt(parameterLevelText.getText())); 29 | } 30 | 31 | private void initComponents() { 32 | // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents 33 | // Generated using JFormDesigner Evaluation license - unknown 34 | cleanerTabPane = new JTabbedPane(); 35 | panel1 = new JPanel(); 36 | label2 = new JLabel(); 37 | parameterLevelText = new JTextField(); 38 | parameterSetLevel = new JButton(); 39 | 40 | //======== this ======== 41 | setBorder(new javax.swing.border.CompoundBorder(new javax.swing.border.TitledBorder(new javax.swing.border.EmptyBorder( 42 | 0,0,0,0), "JF\u006frm\u0044es\u0069gn\u0065r \u0045va\u006cua\u0074io\u006e",javax.swing.border.TitledBorder.CENTER,javax.swing.border.TitledBorder 43 | .BOTTOM,new java.awt.Font("D\u0069al\u006fg",java.awt.Font.BOLD,12),java.awt.Color. 44 | red), getBorder())); addPropertyChangeListener(new java.beans.PropertyChangeListener(){@Override public void propertyChange(java. 45 | beans.PropertyChangeEvent e){if("\u0062or\u0064er".equals(e.getPropertyName()))throw new RuntimeException();}}); 46 | setLayout(new BorderLayout()); 47 | add(cleanerTabPane, BorderLayout.CENTER); 48 | 49 | //======== panel1 ======== 50 | { 51 | panel1.setLayout(new GridLayout(1, 3)); 52 | 53 | //---- label2 ---- 54 | label2.setText("Parameter Entropy Level"); 55 | panel1.add(label2); 56 | panel1.add(parameterLevelText); 57 | 58 | //---- parameterSetLevel ---- 59 | parameterSetLevel.setText("Set"); 60 | parameterSetLevel.addActionListener(e -> parameterSetLevelActionPerformed(e)); 61 | panel1.add(parameterSetLevel); 62 | } 63 | add(panel1, BorderLayout.NORTH); 64 | // JFormDesigner - End of component initialization //GEN-END:initComponents 65 | } 66 | 67 | // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables 68 | // Generated using JFormDesigner Evaluation license - unknown 69 | private JTabbedPane cleanerTabPane; 70 | private JPanel panel1; 71 | private JLabel label2; 72 | private JTextField parameterLevelText; 73 | private JButton parameterSetLevel; 74 | // JFormDesigner - End of variables declaration //GEN-END:variables 75 | } 76 | -------------------------------------------------------------------------------- /src/requestcleaner/BurpCleanerPanel.jfd: -------------------------------------------------------------------------------- 1 | JFDML JFormDesigner: "7.0.3.0.337" Java: "11.0.8" encoding: "UTF-8" 2 | 3 | new FormModel { 4 | contentType: "form/swing" 5 | root: new FormRoot { 6 | add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.BorderLayout ) ) { 7 | name: "this" 8 | add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) { 9 | name: "cleanerTabPane" 10 | }, new FormLayoutConstraints( class java.lang.String ) { 11 | "value": "Center" 12 | } ) 13 | add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.GridLayout ) { 14 | "columns": 3 15 | } ) { 16 | name: "panel1" 17 | add( new FormComponent( "javax.swing.JLabel" ) { 18 | name: "label2" 19 | "text": "Parameter Entropy Level" 20 | } ) 21 | add( new FormComponent( "javax.swing.JTextField" ) { 22 | name: "parameterLevelText" 23 | } ) 24 | add( new FormComponent( "javax.swing.JButton" ) { 25 | name: "parameterSetLevel" 26 | "text": "Set" 27 | addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "parameterSetLevelActionPerformed", true ) ) 28 | } ) 29 | }, new FormLayoutConstraints( class java.lang.String ) { 30 | "value": "North" 31 | } ) 32 | }, new FormLayoutConstraints( null ) { 33 | "location": new java.awt.Point( 0, 0 ) 34 | "size": new java.awt.Dimension( 400, 300 ) 35 | } ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/requestcleaner/CleanerPanel.java: -------------------------------------------------------------------------------- 1 | package requestcleaner; 2 | 3 | import burp.IMessageEditor; 4 | import burp.IParameter; 5 | import burp.IRequestInfo; 6 | 7 | import javax.swing.*; 8 | import java.awt.*; 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | 12 | /* 13 | This panel is the inner view of each cleaning tab. It shows the original request 14 | alongside the cleaned one 15 | */ 16 | public class CleanerPanel extends JPanel { 17 | 18 | /* 19 | This may and should change. Finding a definitive list of headers to clean 20 | is tough so we should probably find a better way. Shannon Entropy doesn't 21 | work well for headers because examples like this: 22 | 23 | Accept-Datetime: Thu, 31 May 2007 20:35:00 GMT 24 | 25 | Have high Shannon Entropy but obviously shouldn't be redacted. 26 | */ 27 | String[] CLEAN_HEADERS = new String[]{"Authorization","Proxy" + 28 | "-Authorization"}; 29 | 30 | public CleanerPanel(HttpRequestResponse requestResponseToClean) { 31 | this.setLayout(new BorderLayout()); 32 | JTabbedPane reqRespTabbedPane = new JTabbedPane(); 33 | IMessageEditor requestMessageToDisplay = 34 | ExtensionState.getCallbacks().createMessageEditor( 35 | new MessageEditorController( 36 | requestResponseToClean.getHttpService(), 37 | requestResponseToClean.getRequest(), 38 | requestResponseToClean.getResponse()), 39 | false); 40 | requestMessageToDisplay.setMessage(requestResponseToClean.getRequest(), 41 | true); 42 | 43 | cleanRequest(requestResponseToClean); 44 | 45 | 46 | ExtensionState.getCallbacks().printOutput(new String(requestResponseToClean.getCleanedRequest())); 47 | IMessageEditor requestMessageToDisplayCleaned = 48 | ExtensionState.getCallbacks().createMessageEditor( 49 | new MessageEditorController( 50 | requestResponseToClean.getHttpService(), 51 | requestResponseToClean.getCleanedRequest(), 52 | requestResponseToClean.getResponse()), 53 | true); 54 | requestMessageToDisplayCleaned.setMessage(requestResponseToClean.getCleanedRequest(), 55 | true); 56 | 57 | reqRespTabbedPane.addTab("Original", 58 | requestMessageToDisplay.getComponent()); 59 | reqRespTabbedPane.addTab("Cleaned", 60 | requestMessageToDisplayCleaned.getComponent()); 61 | this.add(reqRespTabbedPane); 62 | } 63 | 64 | //Cleans the Host header by removing all but the root FDQN 65 | // foo.bar.root.com ---> redacted.root.com 66 | // foo.root.com ----> redacted.root.com 67 | private String cleanHostHeader(String header) { 68 | String[] hostPieces = header.split(":")[1].split("\\."); 69 | if(hostPieces.length > 2) { 70 | return "Host: "+("redacted."+hostPieces[hostPieces.length-2]+"."+hostPieces[hostPieces.length-1]); 71 | } else { 72 | return "Host: "+("redacted."+hostPieces[hostPieces.length-1]); 73 | } 74 | } 75 | 76 | 77 | //Checks if a header should be redacted 78 | private boolean shouldCleanHeader(String header) { 79 | for(String skipHeader : CLEAN_HEADERS) { 80 | if(header.contains(skipHeader)){ 81 | return true; 82 | } 83 | } 84 | return false; 85 | } 86 | 87 | //cleans headers that meet the configured Shannon Entropy level 88 | private void cleanHeaders(HttpRequestResponse requestResponseToClean) { 89 | IRequestInfo rinfo = 90 | ExtensionState.getCallbacks().getHelpers().analyzeRequest(requestResponseToClean.getRequest()); 91 | ArrayList rInfoHeaders = (ArrayList) rinfo.getHeaders(); 92 | ExtensionState.getCallbacks().printOutput("Cleaning headers"); 93 | 94 | for(int i = 0;i 1 && shouldCleanHeader(headerPieces[0])) { 104 | String headerValue = headerPieces[1]; 105 | ExtensionState.getCallbacks().printOutput(headerValue); 106 | rInfoHeaders.set(i, rInfoHeaders.get(i).split(":")[0] + 107 | ": Redacted"); 108 | ExtensionState.getCallbacks().printOutput(rInfoHeaders.get(i)); 109 | } 110 | 111 | } 112 | } 113 | requestResponseToClean.setCleanedRequest(ExtensionState.getCallbacks().getHelpers().buildHttpMessage(rInfoHeaders, 114 | Arrays.copyOfRange(requestResponseToClean.getRequest(), rinfo.getBodyOffset(), requestResponseToClean.getRequest().length))); 115 | } 116 | 117 | //cleans parameters that meet the configured Shannon Entropy level 118 | private void cleanParameters(HttpRequestResponse requestResponseToClean) { 119 | IRequestInfo rinfo = 120 | ExtensionState.getCallbacks().getHelpers().analyzeRequest(requestResponseToClean.getRequest()); 121 | ArrayList rParams = (ArrayList) rinfo.getParameters(); 122 | 123 | for (IParameter rParam : rParams) { 124 | ExtensionState.getCallbacks().printOutput(rParam.getValue()); 125 | double entropy = 126 | ExtensionState.getShannonEntropy(rParam.getValue()); 127 | ExtensionState.getCallbacks().printOutput("Entropy: " + entropy); 128 | if (entropy > ExtensionState.getInstance().getParameterEntropyLevel()) { 129 | Parameter parameter = new Parameter(rParam); 130 | parameter.setValue("redacted"); 131 | requestResponseToClean.setCleanedRequest(ExtensionState.getCallbacks().getHelpers().updateParameter(requestResponseToClean.getCleanedRequest(), parameter)); 132 | } 133 | } 134 | } 135 | 136 | //main entry to cleaning a request 137 | private void cleanRequest(HttpRequestResponse requestResponseToClean) { 138 | ExtensionState.getCallbacks().printOutput("Cleaning request"); 139 | requestResponseToClean.setCleanedRequest(requestResponseToClean.getRequest()); 140 | cleanHeaders(requestResponseToClean); 141 | cleanParameters(requestResponseToClean); 142 | ExtensionState.getCallbacks().printOutput(new String(requestResponseToClean.getCleanedRequest())); 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/requestcleaner/ExtensionState.java: -------------------------------------------------------------------------------- 1 | package requestcleaner; 2 | 3 | import burp.IBurpExtenderCallbacks; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | 9 | /* 10 | This stores our state for the extension as a Singleton 11 | */ 12 | public class ExtensionState { 13 | 14 | //State object 15 | private static ExtensionState instance = null; 16 | //Burp callbacks 17 | private IBurpExtenderCallbacks callbacks; 18 | //Number of current cleaned requests 19 | private int numberOfCleanedRequests; 20 | //User defined Shannon Entropy level for headers 21 | private int headerEntropyLevel; 22 | //User defined Shannon Entropy level for parameters 23 | private int parameterEntropyLevel; 24 | //Reference to the UI panel 25 | private final BurpCleanerPanel teamPanel; 26 | 27 | 28 | private ExtensionState() { 29 | headerEntropyLevel = 3; 30 | parameterEntropyLevel = 3; 31 | numberOfCleanedRequests = 0; 32 | teamPanel = new BurpCleanerPanel(); 33 | } 34 | 35 | public static void setCallbacks(IBurpExtenderCallbacks callbacks) { 36 | getInstance().callbacks = callbacks; 37 | } 38 | 39 | public static IBurpExtenderCallbacks getCallbacks() { 40 | return getInstance().callbacks; 41 | } 42 | 43 | public BurpCleanerPanel getCleanerPanel() { 44 | return getInstance().teamPanel; 45 | } 46 | 47 | private static double log2(double a) { 48 | return Math.log(a) / Math.log(2); 49 | } 50 | //Shannon Entropy taken from here: https://rosettacode.org/wiki/Entropy#Java 51 | public static double getShannonEntropy(String s) { 52 | int n = 0; 53 | Map occ = new HashMap<>(); 54 | 55 | for (int c_ = 0; c_ < s.length(); ++c_) { 56 | char cx = s.charAt(c_); 57 | if (occ.containsKey(cx)) { 58 | occ.put(cx, occ.get(cx) + 1); 59 | } else { 60 | occ.put(cx, 1); 61 | } 62 | ++n; 63 | } 64 | 65 | double e = 0.0; 66 | for (Map.Entry entry : occ.entrySet()) { 67 | double p = (double) entry.getValue() / n; 68 | e += p * log2(p); 69 | } 70 | return -e; 71 | } 72 | 73 | public static ExtensionState getInstance() { 74 | if(instance==null) { 75 | instance = new ExtensionState(); 76 | } 77 | return instance; 78 | } 79 | 80 | public int getNumberOfCleanedRequests() { 81 | return numberOfCleanedRequests; 82 | } 83 | 84 | public void setNumberOfCleanedRequests(int numberOfCleanedRequests) { 85 | this.numberOfCleanedRequests = numberOfCleanedRequests; 86 | } 87 | 88 | public int getHeaderEntropyLevel() { 89 | return headerEntropyLevel; 90 | } 91 | 92 | public void setHeaderEntropyLevel(int entropyLevel) { 93 | this.headerEntropyLevel = entropyLevel; 94 | } 95 | 96 | public int getParameterEntropyLevel() { 97 | return parameterEntropyLevel; 98 | } 99 | 100 | public void setParameterEntropyLevel(int entropyLevel) { 101 | this.parameterEntropyLevel = entropyLevel; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/requestcleaner/HttpRequestResponse.java: -------------------------------------------------------------------------------- 1 | package requestcleaner; 2 | 3 | import burp.IHttpRequestResponse; 4 | import burp.IHttpService; 5 | 6 | import java.util.Arrays; 7 | 8 | public class HttpRequestResponse implements IHttpRequestResponse 9 | { 10 | 11 | private byte[] request; 12 | private byte[] cleanedRequest; 13 | private byte[] response; 14 | private String comment; 15 | private String highlight; 16 | private HttpService httpService; 17 | 18 | public HttpRequestResponse() { 19 | } 20 | 21 | public HttpRequestResponse(IHttpRequestResponse copy) { 22 | this.request = copy.getRequest(); 23 | this.response = copy.getResponse(); 24 | this.comment = copy.getComment(); 25 | this.highlight = copy.getHighlight(); 26 | this.httpService = new HttpService(copy.getHttpService()); 27 | } 28 | 29 | @Override 30 | public byte[] getRequest() { 31 | if(request == null) { 32 | return new byte[]{}; 33 | } 34 | return request; 35 | } 36 | 37 | @Override 38 | public void setRequest(byte[] message) { 39 | request = message; 40 | } 41 | 42 | @Override 43 | public byte[] getResponse() { 44 | if(response == null) { 45 | return new byte[]{}; 46 | } 47 | return response; 48 | } 49 | 50 | @Override 51 | public void setResponse(byte[] message) { 52 | response = message; 53 | } 54 | 55 | @Override 56 | public String getComment() { 57 | return comment; 58 | } 59 | 60 | @Override 61 | public void setComment(String comment) { 62 | this.comment = comment; 63 | } 64 | 65 | @Override 66 | public String getHighlight() { 67 | return highlight; 68 | } 69 | 70 | @Override 71 | public void setHighlight(String color) { 72 | this.highlight = color; 73 | } 74 | 75 | @Override 76 | public IHttpService getHttpService() { 77 | return httpService; 78 | } 79 | 80 | @Override 81 | public void setHttpService(IHttpService httpService) { 82 | this.httpService = new HttpService(httpService); 83 | } 84 | 85 | @Override 86 | public String toString() { 87 | return "HttpRequestResponse{" + 88 | "request=" + Arrays.toString(request) + 89 | ", response=" + Arrays.toString(response) + 90 | ", comment='" + comment + '\'' + 91 | ", highlight='" + highlight + '\'' + 92 | ", httpService=" + httpService + 93 | '}'; 94 | } 95 | 96 | 97 | @Override 98 | public boolean equals(Object o) { 99 | if (this == o) return true; 100 | if (o == null || getClass() != o.getClass()) return false; 101 | HttpRequestResponse that = (HttpRequestResponse) o; 102 | return Arrays.equals(request, that.request) && 103 | Arrays.equals(response, that.response); 104 | } 105 | 106 | @Override 107 | public int hashCode() { 108 | int result = Arrays.hashCode(request); 109 | result = 31 * result + Arrays.hashCode(response); 110 | return result; 111 | } 112 | 113 | public byte[] getCleanedRequest() { 114 | return cleanedRequest; 115 | } 116 | 117 | public void setCleanedRequest(byte[] cleanedRequest) { 118 | this.cleanedRequest = cleanedRequest; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/requestcleaner/HttpService.java: -------------------------------------------------------------------------------- 1 | package requestcleaner; 2 | 3 | import burp.IHttpService; 4 | 5 | public class HttpService implements IHttpService 6 | { 7 | private final String host; 8 | private final int port; 9 | private final String protocol; 10 | 11 | HttpService(IHttpService copy) { 12 | this.host = copy.getHost(); 13 | this.port = copy.getPort(); 14 | this.protocol = copy.getProtocol(); 15 | } 16 | 17 | @Override 18 | public String getHost() { 19 | if(host == null){ 20 | return ""; 21 | } 22 | return host; 23 | } 24 | 25 | @Override 26 | public int getPort() { 27 | return port; 28 | } 29 | 30 | @Override 31 | public String getProtocol() { 32 | if(protocol == null){ 33 | return ""; 34 | } 35 | return protocol; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/requestcleaner/ManualRequestSenderContextMenu.java: -------------------------------------------------------------------------------- 1 | package requestcleaner; 2 | 3 | import burp.IContextMenuFactory; 4 | import burp.IContextMenuInvocation; 5 | import burp.IHttpRequestResponse; 6 | 7 | import javax.swing.*; 8 | import java.util.ArrayList; 9 | import java.util.Collection; 10 | import java.util.List; 11 | import java.util.Objects; 12 | 13 | /* 14 | This handles our context menu for sending requests to the cleaner 15 | */ 16 | public class ManualRequestSenderContextMenu implements IContextMenuFactory { 17 | 18 | 19 | @Override 20 | public List createMenuItems(IContextMenuInvocation invocation) { 21 | ArrayList menues = new ArrayList<>(); 22 | if (Objects.equals(IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_REQUEST , invocation.getInvocationContext())|| 23 | Objects.equals(IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_REQUEST, invocation.getInvocationContext()) || 24 | Objects.equals(IContextMenuInvocation.CONTEXT_TARGET_SITE_MAP_TREE, invocation.getInvocationContext()) || 25 | Objects.equals(IContextMenuInvocation.CONTEXT_TARGET_SITE_MAP_TABLE, invocation.getInvocationContext())) { 26 | menues.addAll(createCleaningMenu(invocation)); 27 | } 28 | return menues; 29 | } 30 | 31 | private Collection createCleaningMenu(IContextMenuInvocation invocation) { 32 | JMenuItem sendToCleaner = new JMenuItem("Send to cleaner"); 33 | sendToCleaner.addActionListener(e -> 34 | sendRequestToCleaner(invocation)); 35 | 36 | ArrayList menuList = new ArrayList<>(); 37 | menuList.add(sendToCleaner); 38 | return menuList; 39 | } 40 | 41 | 42 | private void sendRequestToCleaner(IContextMenuInvocation invocation) { 43 | HttpRequestResponse httpRequestResponse = 44 | new HttpRequestResponse(); 45 | for (IHttpRequestResponse message : invocation.getSelectedMessages()) { 46 | new SwingWorker() { 47 | @Override 48 | public Boolean doInBackground() { 49 | httpRequestResponse.setRequest(message.getRequest()); 50 | httpRequestResponse.setHttpService(message.getHttpService()); 51 | ExtensionState.getInstance().getCleanerPanel().addTab(new CleanerPanel(httpRequestResponse)); 52 | return Boolean.TRUE; 53 | } 54 | 55 | @Override 56 | public void done() { 57 | //we don't need to do any cleanup so this is empty 58 | } 59 | }.execute(); 60 | } 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/requestcleaner/MessageEditorController.java: -------------------------------------------------------------------------------- 1 | package requestcleaner; 2 | 3 | import burp.IHttpService; 4 | import burp.IMessageEditorController; 5 | 6 | public class MessageEditorController implements IMessageEditorController { 7 | 8 | private IHttpService httpService; 9 | private byte[] request; 10 | private byte[] response; 11 | 12 | MessageEditorController(IHttpService httpService, byte[] request, byte[] response) { 13 | this.httpService = httpService; 14 | this.request = request; 15 | this.response = response; 16 | } 17 | 18 | 19 | @Override 20 | public IHttpService getHttpService() { 21 | return this.httpService; 22 | } 23 | 24 | @Override 25 | public byte[] getRequest() { 26 | return this.request; 27 | } 28 | 29 | @Override 30 | public byte[] getResponse() { 31 | return this.response; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/requestcleaner/Parameter.java: -------------------------------------------------------------------------------- 1 | package requestcleaner; 2 | 3 | import burp.IParameter; 4 | 5 | public class Parameter implements IParameter { 6 | 7 | private byte type; 8 | private String name; 9 | private String value; 10 | private int nameStart; 11 | private int nameEnd; 12 | private int valueStart; 13 | private int valueEnd; 14 | 15 | public Parameter(IParameter parameter) { 16 | this.type = parameter.getType(); 17 | this.name = parameter.getName(); 18 | this.value = parameter.getValue(); 19 | this.nameStart = parameter.getNameStart(); 20 | this.nameEnd = parameter.getNameEnd(); 21 | this.valueStart = parameter.getValueStart(); 22 | this.valueEnd = parameter.getValueEnd(); 23 | } 24 | 25 | @Override 26 | public byte getType() { 27 | return this.type; 28 | } 29 | 30 | @Override 31 | public String getName() { 32 | return this.name; 33 | } 34 | 35 | @Override 36 | public String getValue() { 37 | return this.value; 38 | } 39 | 40 | public void setValue(String value) { 41 | this.value = value; 42 | } 43 | 44 | @Override 45 | public int getNameStart() { 46 | return this.nameStart; 47 | } 48 | 49 | @Override 50 | public int getNameEnd() { 51 | return this.nameEnd; 52 | } 53 | 54 | @Override 55 | public int getValueStart() { 56 | return this.valueStart; 57 | } 58 | 59 | @Override 60 | public int getValueEnd() { 61 | return this.valueEnd; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/requestcleaner/TabButton.java: -------------------------------------------------------------------------------- 1 | package requestcleaner; 2 | 3 | import javax.swing.*; 4 | import javax.swing.plaf.basic.BasicButtonUI; 5 | import java.awt.*; 6 | import java.awt.event.*; 7 | 8 | /* 9 | This class lets us have those fancy x's on tab buttons. Taken from here 10 | https://docs.oracle.com/javase/tutorial/uiswing/components/tabbedpane.html#appearanceapi 11 | */ 12 | public class TabButton extends JPanel { 13 | private final JTabbedPane pane; 14 | 15 | public TabButton(final JTabbedPane pane) { 16 | //unset default FlowLayout' gaps 17 | super(new FlowLayout(FlowLayout.LEFT, 0, 0)); 18 | if (pane == null) { 19 | throw new NullPointerException("TabbedPane is null"); 20 | } 21 | this.pane = pane; 22 | setOpaque(false); 23 | 24 | //make JLabel read titles from JTabbedPane 25 | JLabel label = new JLabel() { 26 | public String getText() { 27 | int i = pane.indexOfTabComponent(TabButton.this); 28 | if (i != -1) { 29 | return pane.getTitleAt(i); 30 | } 31 | return null; 32 | } 33 | }; 34 | 35 | add(label); 36 | //add more space between the label and the button 37 | label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5)); 38 | //tab button 39 | JButton button = new Button(); 40 | add(button); 41 | //add more space to the top of the component 42 | setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 0)); 43 | } 44 | 45 | private class Button extends JButton implements ActionListener { 46 | public Button() { 47 | int size = 17; 48 | setPreferredSize(new Dimension(size, size)); 49 | setToolTipText("close this tab"); 50 | //Make the button looks the same for all Laf's 51 | setUI(new BasicButtonUI()); 52 | //Make it transparent 53 | setContentAreaFilled(false); 54 | //No need to be focusable 55 | setFocusable(false); 56 | setBorder(BorderFactory.createEtchedBorder()); 57 | setBorderPainted(false); 58 | //Making nice rollover effect 59 | //we use the same listener for all buttons 60 | addMouseListener(buttonMouseListener); 61 | setRolloverEnabled(true); 62 | //Close the proper tab by clicking the button 63 | addActionListener(this); 64 | } 65 | 66 | public void actionPerformed(ActionEvent e) { 67 | int i = pane.indexOfTabComponent(TabButton.this); 68 | if (i != -1) { 69 | pane.remove(i); 70 | } 71 | ExtensionState.getInstance().setNumberOfCleanedRequests(ExtensionState.getInstance().getNumberOfCleanedRequests()-1); 72 | } 73 | 74 | //we don't want to update UI for this button 75 | public void updateUI() { 76 | } 77 | 78 | //paint the cross 79 | protected void paintComponent(Graphics g) { 80 | super.paintComponent(g); 81 | Graphics2D g2 = (Graphics2D) g.create(); 82 | //shift the image for pressed buttons 83 | if (getModel().isPressed()) { 84 | g2.translate(1, 1); 85 | } 86 | g2.setStroke(new BasicStroke(2)); 87 | g2.setColor(Color.BLACK); 88 | if (getModel().isRollover()) { 89 | g2.setColor(Color.MAGENTA); 90 | } 91 | int delta = 6; 92 | g2.drawLine(delta, delta, getWidth() - delta - 1, getHeight() - delta - 1); 93 | g2.drawLine(getWidth() - delta - 1, delta, delta, getHeight() - delta - 1); 94 | g2.dispose(); 95 | } 96 | } 97 | 98 | private final static MouseListener buttonMouseListener = new MouseAdapter() { 99 | public void mouseEntered(MouseEvent e) { 100 | Component component = e.getComponent(); 101 | if (component instanceof AbstractButton) { 102 | AbstractButton button = (AbstractButton) component; 103 | button.setBorderPainted(true); 104 | } 105 | } 106 | 107 | public void mouseExited(MouseEvent e) { 108 | Component component = e.getComponent(); 109 | if (component instanceof AbstractButton) { 110 | AbstractButton button = (AbstractButton) component; 111 | button.setBorderPainted(false); 112 | } 113 | } 114 | }; 115 | } --------------------------------------------------------------------------------