├── 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