├── target └── RepeaterSearch.jar ├── src └── main │ └── java │ ├── burp │ └── BurpExtender.java │ └── com │ └── staticflow │ ├── RepeaterSearch.java │ ├── ExtensionState.java │ └── Utils.java ├── README.md └── pom.xml /target/RepeaterSearch.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Static-Flow/RepeaterSearch/HEAD/target/RepeaterSearch.jar -------------------------------------------------------------------------------- /src/main/java/burp/BurpExtender.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import com.staticflow.RepeaterSearch; 4 | 5 | public class BurpExtender extends RepeaterSearch { 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RepeaterSearch 2 | 3 | This plugin adds a search bar to Repeater that allows you to search Requests and/or Responses for a string. Regex is also supported. 4 | -------------------------------------------------------------------------------- /src/main/java/com/staticflow/RepeaterSearch.java: -------------------------------------------------------------------------------- 1 | package com.staticflow; 2 | 3 | 4 | import burp.api.montoya.BurpExtension; 5 | import burp.api.montoya.MontoyaApi; 6 | import burp.api.montoya.extension.ExtensionUnloadingHandler; 7 | 8 | /** 9 | * The entry point for a Burp Suite extension that adds a custom search bar to the Repeater tab. 10 | * This extension allows users to search through the request body and/or the response body of requests 11 | * using a simple string or a regular expression (regex). 12 | * 13 | * The `RepeaterSearch` class implements the `BurpExtension` and `ExtensionUnloadingHandler` interfaces, 14 | * allowing it to handle extension initialization and unloading events. 15 | * 16 | * Upon initialization, the `initialize` method registers the extension's unloading handler, sets the 17 | * necessary callbacks, and adds the search bar to the Repeater tab. 18 | * 19 | * When the extension is unloaded, the `extensionUnloaded` method is called to perform any necessary 20 | * clean-up operations. 21 | * 22 | * @see BurpExtension 23 | * @see ExtensionUnloadingHandler 24 | */ 25 | public class RepeaterSearch implements BurpExtension, ExtensionUnloadingHandler { 26 | 27 | @Override 28 | public void initialize(MontoyaApi api) { 29 | api.extension().registerUnloadingHandler(this); 30 | ExtensionState.getInstance().setCallbacks(api); 31 | Utils.addSearchBarToRepeaterTab(); 32 | } 33 | 34 | @Override 35 | public void extensionUnloaded() { 36 | Utils.cleanUpExtension(); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /src/main/java/com/staticflow/ExtensionState.java: -------------------------------------------------------------------------------- 1 | package com.staticflow; 2 | 3 | import burp.api.montoya.MontoyaApi; 4 | import main.java.com.staticflow.BurpGuiControl; 5 | import javax.swing.*; 6 | import java.awt.*; 7 | import java.awt.event.*; 8 | 9 | /** 10 | * This Singleton class holds all custom state for the extension and provides a central means of accessing it. 11 | */ 12 | public class ExtensionState { 13 | private static final String REPEATER = "Repeater"; 14 | 15 | 16 | private final Component repeaterComponent; 17 | private final JTabbedPane repeaterTabbedPane; 18 | private MontoyaApi callbacks; 19 | 20 | private static ExtensionState state; 21 | 22 | /** 23 | * Initializes the {@code ExtensionState} Singleton. 24 | * 25 | * This constructor obtains a reference to the Burp Suite Repeater tab swing component using 26 | * {@link BurpGuiControl#getBaseBurpComponent}. 27 | *
28 | * It then attaches a hierarchy listener to the Repeater tab component to determine when the Repeater tab 29 | * is in view. 30 | *
31 | * When the Repeater tab is shown (using the {@link HierarchyEvent#SHOWING_CHANGED} flag of the hierarchy event), the 32 | * custom search bar created by this extension is set to be visible. This is because anytime a new tab is created in Repeater, Burp Suite recreates the 33 | * whole Repeater tab and JTabbedPane which for some magic swing reason sets our custom Component to be hidden. 34 | *
35 | * Finally, this constructor obtains a reference to the {@link JTabbedPane} within the Repeater tab 36 | * component using {@link BurpGuiControl#findFirstComponentOfType}. 37 | */ 38 | private ExtensionState() { 39 | this.repeaterComponent = BurpGuiControl.getBaseBurpComponent(REPEATER); 40 | this.repeaterComponent.addHierarchyListener(e -> { 41 | if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) { 42 | Container component = (Container) e.getComponent(); 43 | if (component.isShowing()) { 44 | component.getComponent(0).setVisible(true); 45 | } 46 | } 47 | }); 48 | this.repeaterTabbedPane = (JTabbedPane) BurpGuiControl.findFirstComponentOfType((Container) repeaterComponent,JTabbedPane.class); 49 | } 50 | 51 | /* 52 | GETTERS/SETTERS BELOW 53 | */ 54 | static ExtensionState getInstance() { 55 | if (state == null) { 56 | state = new ExtensionState(); 57 | } 58 | return state; 59 | } 60 | 61 | public JTabbedPane getRepeaterTabbedPane() { 62 | return repeaterTabbedPane; 63 | } 64 | 65 | public Component getRepeaterComponent() { 66 | return this.repeaterComponent; 67 | } 68 | 69 | public MontoyaApi getCallbacks() { 70 | return this.callbacks; 71 | } 72 | 73 | public void setCallbacks(MontoyaApi callbacks) { 74 | this.callbacks = callbacks; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | staticflow 8 | RepeaterSearch 9 | 2.0-SNAPSHOT 10 | 11 | 12 | jitpack.io 13 | https://jitpack.io 14 | 15 | 16 | 17 | 18 | net.portswigger.burp.extensions 19 | montoya-api 20 | 2023.3 21 | 22 | 23 | com.github.Static-Flow 24 | BurpSuiteGuiLibrary 25 | 2.1 26 | 27 | 28 | 29 | 30 | ${project.basedir}/src/main/java 31 | RepeaterSearch 32 | 33 | 34 | 35 | org.apache.maven.plugins 36 | maven-eclipse-plugin 37 | 2.9 38 | 39 | true 40 | false 41 | 42 | 43 | 44 | 45 | 46 | org.apache.maven.plugins 47 | maven-surefire-plugin 48 | 2.14.1 49 | 50 | -Djdk.attach.allowAttachSelf=true 51 | 52 | 53 | 54 | 55 | 56 | 57 | org.apache.maven.plugins 58 | maven-compiler-plugin 59 | 2.3.2 60 | 61 | 18 62 | 18 63 | 64 | 65 | 66 | 67 | 68 | org.apache.maven.plugins 69 | maven-assembly-plugin 70 | 2.4.1 71 | 72 | 73 | false 74 | 75 | jar-with-dependencies 76 | 77 | 78 | 79 | 80 | 81 | make-assembly 82 | 83 | package 84 | 85 | single 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/main/java/com/staticflow/Utils.java: -------------------------------------------------------------------------------- 1 | package com.staticflow; 2 | 3 | import main.java.com.staticflow.BurpGuiControl; 4 | 5 | import javax.swing.*; 6 | import java.awt.*; 7 | import java.awt.event.*; 8 | import java.util.List; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | /** 13 | * Utility class which contains the various functionality for this extension 14 | */ 15 | public class Utils { 16 | 17 | private static final String SEARCH = "Search"; 18 | private static final String ENTER_QUERY = "Enter query..."; 19 | private static boolean searchResponseForText = false; 20 | private static boolean searchRequestForText = true; 21 | private static boolean useRegex = false; 22 | 23 | private Utils() {} 24 | 25 | /** 26 | * Called by {@link RepeaterSearch#extensionUnloaded()} it first resets the color on any repeater tabs, removes the custom search bar from the Repeater 27 | * tab component, then repaints the Repeater tab component 28 | */ 29 | public static void cleanUpExtension() { 30 | resetRepeaterTabs(); 31 | Component repeaterComponent = ExtensionState.getInstance().getRepeaterComponent(); 32 | ((Container)repeaterComponent).remove(0); 33 | repeaterComponent.revalidate(); 34 | repeaterComponent.repaint(); 35 | } 36 | 37 | /** 38 | * This method, called during Extension initialization at {@link RepeaterSearch#initialize} injects the custom search bar into the Repeater tab component 39 | * using the following steps: 40 | *
41 | * 1. Obtain references to the Repeater tab Component and its inner JTabbedPane from {@link ExtensionState}
42 | * 2. Change the Swing Layout of the Repeater tab Component to {@link GridBagLayout}
43 | * 3. Remove the JTabbedPane from the Swing Tree of the Repeater tab Component
44 | * 4. Create the custom search bar component using {@link Utils#generateSearchBar()}
45 | * 5. Insert the custom search bar into the Repeater tab Component using the configured {@link GridBagConstraints} and set it to visible
46 | * 6. Re-insert the JTabbedPane into the Repeater tab Component using the configured {@link GridBagConstraints} and set it to visible
47 | * 7. Revalidate/Repaint the Repeater tab Component 48 | */ 49 | public static void addSearchBarToRepeaterTab() { 50 | Component repeaterComponent = ExtensionState.getInstance().getRepeaterComponent(); 51 | JTabbedPane repeaterTabbedPane = ExtensionState.getInstance().getRepeaterTabbedPane(); 52 | 53 | ((Container) repeaterComponent).setLayout(new GridBagLayout()); 54 | ((Container) repeaterComponent).remove(repeaterTabbedPane); 55 | Component customRepeaterComponent = generateSearchBar(); 56 | 57 | GridBagConstraints gbc = new GridBagConstraints(); 58 | gbc.gridx = 0; 59 | gbc.gridy = 0; 60 | gbc.weighty = 0.01; 61 | gbc.anchor = GridBagConstraints.NORTH; 62 | gbc.fill = GridBagConstraints.BOTH; 63 | ((Container) repeaterComponent).add(customRepeaterComponent,gbc); 64 | customRepeaterComponent.setVisible(true); 65 | GridBagConstraints constraints = ((GridBagLayout) ((Container) repeaterComponent).getLayout()).getConstraints(repeaterTabbedPane); 66 | constraints.gridx = 0; 67 | constraints.gridy = 1; 68 | constraints.weighty = 1; 69 | constraints.weightx = 1; 70 | constraints.anchor = GridBagConstraints.NORTH; 71 | constraints.fill = GridBagConstraints.BOTH; 72 | ((Container) repeaterComponent).add(repeaterTabbedPane,constraints); 73 | repeaterTabbedPane.setVisible(true); 74 | repeaterComponent.revalidate(); 75 | repeaterComponent.repaint(); 76 | } 77 | 78 | /** 79 | * This method creates the custom search bar used by this extension. It comprises a JTextField for the search query, a JButton for executing the search, 80 | * and 3 JCheckboxes which configure the different search modes which include:
81 | * 1. Searching the Request body
82 | * 2. Searching the Response body
83 | * 3. Interpreting the search query as a regular expression
84 | * Any combination of the 3 may be used to facilitate different search requirements. 85 | * @return The custom search bar Component 86 | */ 87 | private static Component generateSearchBar() { 88 | //HIGH LEVEL COMPONENTS,CONTAINERS, AND LAYOUT MANAGERS 89 | Container customRepeaterComponent = new JPanel(new GridBagLayout()); 90 | JPanel searchBarPanel = new JPanel(new GridBagLayout()); 91 | JPanel searchBarButtonsPanel = new JPanel(); 92 | searchBarButtonsPanel.setLayout(new BoxLayout(searchBarButtonsPanel, 93 | BoxLayout.Y_AXIS)); 94 | JButton searchButton = new JButton(SEARCH); 95 | JTextField searchBar = new JTextField(ENTER_QUERY); 96 | GridBagConstraints c = new GridBagConstraints(); 97 | 98 | //BUILD SEARCH BAR 99 | /* 100 | * This listener is purely for a nicer user experience. When clicking into the search bar, the default text is highlighted for easy replacement 101 | */ 102 | searchBar.addFocusListener(new FocusListener() { 103 | @Override 104 | public void focusGained(FocusEvent e) { 105 | if(searchBar.getText().equals(ENTER_QUERY)) { 106 | searchBar.selectAll(); 107 | } 108 | } 109 | 110 | @Override 111 | public void focusLost(FocusEvent e) { 112 | //UnNeeded 113 | } 114 | }); 115 | GridBagConstraints gbc = new GridBagConstraints(); 116 | gbc.gridx = 0; 117 | gbc.gridy = 0; 118 | gbc.fill = GridBagConstraints.BOTH; 119 | gbc.weightx = 1; 120 | gbc.weighty = 1; 121 | searchBarPanel.add(searchBar,gbc); 122 | 123 | //BUILD SEARCH SUBMIT AND FILTER COMPONENTS 124 | searchButton.addActionListener(e -> searchRepeaterTabsForString(searchBar.getText())); 125 | searchBarButtonsPanel.add(searchButton); 126 | JCheckBox searchRequest = new JCheckBox("Request"); 127 | searchRequest.setSelected(true); 128 | searchRequest.addChangeListener(e -> searchRequestForText = !searchRequestForText); 129 | searchBarButtonsPanel.add(searchRequest); 130 | JCheckBox searchResponse = new JCheckBox("Response"); 131 | searchResponse.addChangeListener(e -> searchResponseForText = !searchResponseForText); 132 | searchBarButtonsPanel.add(searchResponse); 133 | JCheckBox searchRegex = new JCheckBox("Regex"); 134 | searchRegex.addChangeListener(e -> useRegex = !useRegex); 135 | searchBarButtonsPanel.add(searchRegex); 136 | 137 | //INSERT SEARCH BAR AND BUTTON PANEL TO MAIN COMPONENT 138 | c.gridx = 0; 139 | c.gridy = 0; 140 | c.weightx = 0.90; 141 | c.weighty = 1; 142 | c.fill = GridBagConstraints.BOTH; 143 | customRepeaterComponent.add(searchBarPanel,c); 144 | c.gridx = 1; 145 | c.weightx = 0.10; 146 | customRepeaterComponent.add(searchBarButtonsPanel,c); 147 | return customRepeaterComponent; 148 | } 149 | 150 | /** 151 | * This method loops over every Repeater Tab and uses {@link BurpGuiControl#findAllComponentsOfType} to obtain references to the Request/Response JTextAreas 152 | * and depending on the users search filter options passes either one or both of them to {@link Utils#searchTextArea} to determine if they contain the users 153 | * search query. If either return True, the Repeater Tab's color is changed to indicate a match. 154 | * @param search The string to use for searching. Can either be a plain string or a Regular Expression 155 | */ 156 | private static void searchRepeaterTabsForString(String search) { 157 | JTabbedPane repeaterTabs = ExtensionState.getInstance().getRepeaterTabbedPane(); 158 | ExtensionState.getInstance().getCallbacks().logging().logToOutput("Searching for: "+search); 159 | for( int i=0; i < repeaterTabs.getTabCount(); i++) { 160 | if (repeaterTabs.getComponentAt(i) != null) { 161 | repeaterTabs.setBackgroundAt(i, new Color(0xBBBBBB)); 162 | List repeaterTabRequestResponseJTextAreas = BurpGuiControl.findAllComponentsOfType((Container) repeaterTabs.getComponentAt(i), JTextArea.class); 163 | ExtensionState.getInstance().getCallbacks().logging().logToOutput("jtextarea count: "+repeaterTabRequestResponseJTextAreas.size()); 164 | if (searchRequestForText) { 165 | JTextArea requestTextArea = (JTextArea) repeaterTabRequestResponseJTextAreas.get(0); 166 | if (searchTextArea(search, requestTextArea)) { 167 | ExtensionState.getInstance().getCallbacks().logging().logToOutput(requestTextArea.getText()); 168 | repeaterTabs.setBackgroundAt(i, new Color(0xff6633)); 169 | } 170 | } 171 | if (searchResponseForText) { 172 | for(int x = 1; x