├── .gitignore ├── README.md ├── TODO.txt ├── pom.xml └── src ├── main ├── java │ └── burp │ │ ├── BurpExtender.java │ │ └── FormHelp.java └── resources │ ├── Burplay.properties │ ├── FormHelp.html │ └── define_session.png └── test └── java └── burp └── BurplayTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | work 3 | .DS* 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # burplay 2 | Burplay is a Burp Extension allowing for replaying any number of requests using same modifications definition. Its main purpose is to aid in searching for Privilege Escalation issues. 3 | 4 | Build: 5 | $ mvn package 6 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | 2 | - min width for right panel 3 | - handle burp connection problems (FIXED?) 4 | - add manual session definition 5 | - reset modifications 6 | - max width for session define dialog box 7 | - allow for defining a regex triggering priv escalation alert 8 | - multi-threading 9 | 10 | 11 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | UTF-8 6 | 7 | 8 | 9 | 10 | org.apache.maven.plugins 11 | maven-compiler-plugin 12 | 3.6.1 13 | 14 | 1.8 15 | 1.8 16 | 17 | 18 | 19 | exec-maven-plugin 20 | org.codehaus.mojo 21 | 1.6.0 22 | 23 | 24 | Version Calculation 25 | package 26 | 27 | exec 28 | 29 | 30 | ln 31 | 32 | -fnsv 33 | Burplay-${project.version}.jar 34 | target/Burplay.jar 35 | 36 | 37 | 38 | 39 | 40 | 41 | org.apache.maven.plugins 42 | maven-jar-plugin 43 | 3.0.2 44 | 45 | 46 | 47 | true 48 | true 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | src/main/resources 57 | true 58 | 59 | 60 | Burplay-${project.version} 61 | 62 | burp 63 | Burplay 64 | jar 65 | 0.6.2-beta 66 | Burplay 67 | 68 | 69 | 70 | junit 71 | junit 72 | 3.8.1 73 | test 74 | 75 | 76 | net.portswigger.burp.extender 77 | burp-extender-api 78 | 1.7.22 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/main/java/burp/BurpExtender.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import javax.swing.*; 4 | import javax.swing.border.TitledBorder; 5 | import javax.swing.event.ListSelectionEvent; 6 | import javax.swing.event.ListSelectionListener; 7 | import javax.swing.table.AbstractTableModel; 8 | import javax.swing.table.TableColumn; 9 | import javax.swing.table.TableModel; 10 | import java.awt.*; 11 | import java.awt.event.ActionEvent; 12 | import java.awt.event.ActionListener; 13 | import java.awt.event.MouseEvent; 14 | import java.awt.event.MouseListener; 15 | import java.io.OutputStream; 16 | import java.io.PrintStream; 17 | import java.lang.reflect.Array; 18 | import java.util.*; 19 | import java.util.List; 20 | 21 | /** 22 | * Created by mtalecki on 05/07/2017. 23 | */ 24 | 25 | 26 | 27 | public class BurpExtender implements IBurpExtender, IContextMenuFactory, ITab { 28 | 29 | private IBurpExtenderCallbacks callbacks; 30 | private IExtensionHelpers helpers; 31 | 32 | private PrintStream printStream; 33 | private PrintStream errorStream; 34 | 35 | // main panel 36 | private final JPanel panelMain = new JPanel(); 37 | 38 | private String version; 39 | 40 | // progress panel 41 | private final JPanel panelProgress = new JPanel(); 42 | private final JProgressBar barProgress = new JProgressBar(); 43 | private final JButton buttonReplay = new JButton(); 44 | private final JButton buttonReset = new JButton(); 45 | 46 | // replay table and controller 47 | private final ReplayTableController replayTableController = new ReplayTableController(); 48 | private final ReplayTable replayTable = new ReplayTable(replayTableController); 49 | 50 | // modification table and controller 51 | private final ModificationTableController modificationTableController = new ModificationTableController(); 52 | private final ModificationTable modificationTable = new ModificationTable(modificationTableController); 53 | private final ModificationConfig modificationConfig = new ModificationConfig(); 54 | 55 | // session table and controller 56 | private final SessionTableController sessionTableController = new SessionTableController(); 57 | private final SessionTable sessionTable = new SessionTable(sessionTableController); 58 | 59 | // details tabbed pane 60 | private ReplayDetailsTabbedPane tabbedReplayDetails; 61 | 62 | public void registerExtenderCallbacks (IBurpExtenderCallbacks callbacks) { 63 | 64 | // keep a reference to our callbacks object 65 | this.callbacks = callbacks; 66 | 67 | // obtain an extension helpers object 68 | this.helpers = callbacks.getHelpers(); 69 | 70 | // set our extension name 71 | this.callbacks.setExtensionName("Burplay"); 72 | 73 | // context menu 74 | callbacks.registerContextMenuFactory(this); 75 | 76 | // stdout / stderr 77 | OutputStream stdOut = callbacks.getStdout(); 78 | OutputStream stdErr = callbacks.getStderr(); 79 | printStream = new PrintStream(stdOut); 80 | errorStream = new PrintStream(stdErr); 81 | 82 | Properties properties = new Properties(); 83 | 84 | try { 85 | properties.load(this.getClass().getResourceAsStream("/Burplay.properties")); 86 | version = "v." + properties.getProperty("version"); 87 | } catch (Exception e) { 88 | version = ""; 89 | } 90 | 91 | // welcome message 92 | log(String.format("Burplay %s\nMichal Talecki ", version)); 93 | 94 | SwingUtilities.invokeLater(new Runnable() { 95 | @Override 96 | public void run() { 97 | setUI(); 98 | } 99 | }); 100 | } 101 | 102 | private void setUI() { 103 | JPanel panelLeft = new JPanel(); 104 | JPanel panelRight = new JPanel(); 105 | 106 | // mainSplitPane 107 | JSplitPane mainSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, 108 | panelLeft, panelRight); 109 | mainSplitPane.setResizeWeight(0); 110 | panelMain.setLayout(new BorderLayout()); 111 | panelMain.add(mainSplitPane, BorderLayout.CENTER); 112 | 113 | // panelLeft 114 | panelLeft.setLayout(new BorderLayout()); 115 | panelLeft.setMinimumSize(new Dimension(500,200)); 116 | 117 | // panel progress 118 | panelProgress.setLayout(new BoxLayout(panelProgress, BoxLayout.X_AXIS)); 119 | panelProgress.add(barProgress); 120 | panelProgress.add(buttonReplay); 121 | 122 | // button reset 123 | buttonReset.setAction(new ResetAction()); 124 | buttonReset.setText("Reset"); 125 | 126 | // button replay 127 | buttonReplay.setAction(new ReplayAction()); 128 | buttonReplay.setActionCommand("replay"); 129 | buttonReplay.setText("REPLAY!"); 130 | 131 | // panel top (box layout) 132 | JPanel panelTop = new JPanel(); 133 | panelTop.setLayout(new BoxLayout(panelTop, BoxLayout.X_AXIS)); 134 | 135 | 136 | JButton buttonDoc = new JButton(); 137 | buttonDoc.addActionListener(new HelpAction()); 138 | buttonDoc.setText("?"); 139 | panelTop.add(buttonDoc); 140 | panelTop.add(buttonReset); 141 | panelTop.add(panelProgress); 142 | 143 | // panelTop is PAGE START in panelLeft's BorderLayout 144 | panelLeft.add(panelTop, BorderLayout.PAGE_START); 145 | 146 | 147 | JScrollPane replayScrollPane = new JScrollPane(replayTable); 148 | panelLeft.add(replayScrollPane, BorderLayout.CENTER); 149 | 150 | JScrollPane modificationScrollPane = new JScrollPane(modificationTable); 151 | JScrollPane sessionScrollPane = new JScrollPane(sessionTable); 152 | 153 | 154 | JPanel panelConfig = new JPanel(); 155 | panelConfig.setLayout(new BoxLayout(panelConfig, BoxLayout.Y_AXIS)); 156 | 157 | 158 | // panel modifications 159 | JPanel panelModificationsAll = new JPanel(); 160 | TitledBorder titleModifications; 161 | titleModifications = BorderFactory.createTitledBorder("Modifications"); 162 | panelModificationsAll.setBorder(titleModifications); 163 | panelModificationsAll.setLayout(new BoxLayout(panelModificationsAll, BoxLayout.Y_AXIS)); 164 | 165 | JPanel panelModifications = new JPanel(); 166 | panelModifications.setLayout(new BoxLayout(panelModifications, BoxLayout.X_AXIS)); 167 | 168 | // button remove modification 169 | JButton buttonRemoveModification = new JButton(); 170 | buttonRemoveModification.setAction(new AddRemoveAction()); 171 | buttonRemoveModification.setText("Remove"); 172 | buttonRemoveModification.setActionCommand("removemod"); 173 | 174 | panelModifications.add(modificationScrollPane); 175 | panelModifications.add(buttonRemoveModification); 176 | 177 | Dimension dimMod = modificationTable.getPreferredSize(); 178 | modificationScrollPane.setPreferredSize( 179 | new Dimension(400,modificationTable.getRowHeight()*7+1)); 180 | modificationScrollPane.setMaximumSize( 181 | new Dimension(400, modificationTable.getRowHeight()*7+1)); 182 | 183 | panelModificationsAll.add(panelModifications); 184 | panelModificationsAll.add(modificationConfig); 185 | 186 | 187 | 188 | 189 | // panel sessions 190 | JPanel panelSessionsAll = new JPanel(); 191 | TitledBorder titleSessions; 192 | titleSessions = BorderFactory.createTitledBorder("Sessions"); 193 | panelSessionsAll.setBorder(titleSessions); 194 | panelSessionsAll.setLayout(new BoxLayout(panelSessionsAll, BoxLayout.Y_AXIS)); 195 | 196 | JPanel panelSessions = new JPanel(); 197 | panelSessions.setLayout(new BoxLayout(panelSessions, BoxLayout.X_AXIS)); 198 | 199 | // button remove session 200 | JPanel panelSessionButtons = new JPanel(); 201 | panelSessionButtons.setLayout(new BoxLayout(panelSessionButtons, BoxLayout.Y_AXIS)); 202 | 203 | JButton buttonRemoveSession = new JButton(); 204 | buttonRemoveSession.setAction(new AddRemoveAction()); 205 | buttonRemoveSession.setText("Remove"); 206 | buttonRemoveSession.setActionCommand("removeses"); 207 | 208 | JButton buttonApplySession = new JButton(); 209 | buttonApplySession.setAction(new AddRemoveAction()); 210 | buttonApplySession.setText("Apply"); 211 | buttonApplySession.setActionCommand("applyses"); 212 | 213 | panelSessionButtons.add(buttonApplySession); 214 | panelSessionButtons.add(buttonRemoveSession); 215 | 216 | 217 | panelSessions.add(sessionScrollPane); 218 | panelSessions.add(panelSessionButtons); 219 | 220 | Dimension dimSes = sessionTable.getPreferredSize(); 221 | sessionScrollPane.setPreferredSize( 222 | new Dimension(500,sessionTable.getRowHeight()*7+1)); 223 | sessionScrollPane.setMaximumSize( 224 | new Dimension(500, sessionTable.getRowHeight()*7+1)); 225 | 226 | panelSessions.setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); 227 | panelSessionsAll.add(panelSessions); 228 | panelSessionsAll.add(new JPanel()); // sizing hack 229 | 230 | 231 | // panel config 232 | panelConfig.add(new JSeparator(SwingConstants.HORIZONTAL)); 233 | panelConfig.add(panelModificationsAll); 234 | 235 | panelConfig.add(new JSeparator(SwingConstants.HORIZONTAL)); 236 | panelConfig.add(panelSessionsAll); 237 | 238 | //panelConfig.setBorder(BorderFactory.createEmptyBorder(30,30,30,30)); 239 | panelLeft.add(panelConfig, BorderLayout.PAGE_END); 240 | 241 | 242 | 243 | 244 | 245 | // panelRight 246 | panelRight.setLayout(new BorderLayout()); 247 | tabbedReplayDetails = new ReplayDetailsTabbedPane(); 248 | tabbedReplayDetails.addReplayTab("Original"); 249 | panelRight.add(tabbedReplayDetails); 250 | 251 | 252 | 253 | callbacks.customizeUiComponent(panelMain); 254 | // these should not be customized explicitly (should propagate from panelMain) 255 | //callbacks.customizeUiComponent(panelLeft); 256 | //callbacks.customizeUiComponent(panelRight); 257 | //callbacks.customizeUiComponent(panelRequest); 258 | //callbacks.customizeUiComponent(panelResponse); 259 | //callbacks.customizeUiComponent(replayScrollPane); 260 | 261 | callbacks.addSuiteTab(BurpExtender.this); 262 | } 263 | 264 | public class ReplayWorker extends SwingWorker { 265 | 266 | private List inputRequestResponseList; 267 | private List outputRequestResponseList; 268 | 269 | public ReplayWorker(List inp, List outp) { 270 | inputRequestResponseList = inp; 271 | outputRequestResponseList = outp; 272 | } 273 | 274 | private IHttpRequestResponse makeRequest(IHttpService s, byte[] req) { 275 | IHttpRequestResponse rr = null; 276 | try { 277 | rr = callbacks.makeHttpRequest(s, req); 278 | } catch (Exception e) { 279 | logError(e.toString() +": Error while requesting from " + s.getHost()); 280 | } 281 | return rr; 282 | } 283 | 284 | @Override 285 | protected void process(List progress) { 286 | barProgress.setValue(progress.get(progress.size() - 1)); 287 | } 288 | 289 | @Override 290 | protected Void doInBackground() throws Exception { 291 | 292 | //log("ReplayWorker thread started..."); 293 | 294 | List listMods = modificationTableController.listModifications; 295 | 296 | for (int i = 0; i < inputRequestResponseList.size(); i++) { 297 | if (this.isCancelled()) { 298 | return null; 299 | } 300 | publish(i + 1); 301 | 302 | byte[] newReq = inputRequestResponseList.get(i).getRequest(); 303 | 304 | for (int m = 0; m= 0 && selected < numRows) { 330 | replayTable.addRowSelectionInterval(selected, selected); 331 | } 332 | } 333 | 334 | } 335 | 336 | private class ReplayAction extends AbstractAction { 337 | 338 | private ReplayWorker replayWorker; 339 | 340 | 341 | @Override 342 | public void actionPerformed(ActionEvent e) { 343 | //log("replay action"); 344 | 345 | if (e.getActionCommand().equals("cancel")) { 346 | replayWorker.cancel(true); 347 | replayWorker = null; 348 | return; 349 | } 350 | 351 | // base requests 352 | ReplayDetailsPanel panelCurrentDetails = tabbedReplayDetails.getOriginalDetailsPanel(); 353 | List replayList = panelCurrentDetails.listHTTPRequestsResponses; 354 | 355 | // don't proceed with empty list 356 | if (replayList.size() == 0) { 357 | return; 358 | } 359 | 360 | // new tab 361 | // TODO give more specific name to a new tab 362 | ReplayDetailsPanel newDetailsPanel = tabbedReplayDetails.addReplayTab(null); 363 | tabbedReplayDetails.setSelectedIndex(tabbedReplayDetails.getTabCount()-1); 364 | List outputReplayList = newDetailsPanel.listHTTPRequestsResponses; 365 | 366 | // disable button so next replay can't be requested until the current one is done 367 | buttonReplay.setActionCommand("cancel"); 368 | buttonReplay.setText("Cancel"); 369 | 370 | 371 | buttonReset.setEnabled(false); 372 | barProgress.setMinimum(0); 373 | barProgress.setMaximum(replayList.size()); 374 | barProgress.setStringPainted(true); 375 | replayWorker = new ReplayWorker(replayList, outputReplayList); 376 | replayTable.lastSelectedRow = replayTable.getSelectedRow(); 377 | replayWorker.execute(); 378 | 379 | //buttonReplay.setEnabled(true); 380 | //log("Requests done, updating model..."); 381 | replayTableController.updateTableModel(); 382 | //log ("Model updated."); 383 | } 384 | } 385 | 386 | 387 | private void log(String text) { 388 | printStream.println(text); 389 | printStream.flush(); 390 | } 391 | 392 | private void logError(String text) { 393 | errorStream.println(text); 394 | errorStream.flush(); 395 | } 396 | 397 | private class ModificationConfig extends JPanel implements ActionListener { 398 | 399 | private JComboBox comboParamType; 400 | private JTextField fieldParamName; 401 | private JCheckBox chkboxRemove; 402 | private JTextField fieldParamValue; 403 | private JButton buttonAddModification; 404 | 405 | public ModificationConfig() { 406 | comboParamType = new JComboBox<>(); 407 | comboParamType.addItem("Header"); 408 | comboParamType.addItem("Cookie"); 409 | comboParamType.addItem("GET"); 410 | comboParamType.addItem("POST"); 411 | fieldParamName = new JTextField(10); 412 | fieldParamName.setToolTipText("Header/cookie/param name"); 413 | chkboxRemove = new JCheckBox(); 414 | chkboxRemove.setToolTipText("Remove this header/cookie/param"); 415 | chkboxRemove.setSelected(false); 416 | chkboxRemove.addActionListener(this); 417 | fieldParamValue = new JTextField(10); 418 | fieldParamValue.setToolTipText("Header/cookie/param value"); 419 | buttonAddModification = new JButton(); 420 | buttonAddModification.setAction(new AddRemoveAction()); 421 | buttonAddModification.setText("Add"); 422 | buttonAddModification.setActionCommand("addmod"); 423 | 424 | 425 | this.add(comboParamType); 426 | this.add(fieldParamName); 427 | this.add(chkboxRemove); 428 | this.add(fieldParamValue); 429 | this.add(buttonAddModification); 430 | 431 | } 432 | 433 | @Override 434 | public void actionPerformed(ActionEvent e) { 435 | fieldParamValue.setEnabled(!chkboxRemove.isSelected()); 436 | } 437 | } 438 | 439 | private class ModificationTableController extends AbstractTableModel { 440 | 441 | public final List listModifications = new ArrayList<>(); 442 | private final List data = new ArrayList<>(); 443 | private String[] columnNames = {"Type", "Name", "Value"}; 444 | 445 | public void addModification(ReplayModification mod) { 446 | listModifications.add(mod); 447 | updateTableModel(); 448 | } 449 | 450 | public void removeModification(int index) { 451 | listModifications.remove(index); 452 | updateTableModel(); 453 | } 454 | 455 | public void updateTableModel() { 456 | data.clear(); 457 | 458 | for (int i = 0; i < listModifications.size(); i++) { 459 | ReplayModification mod = listModifications.get(i); 460 | String[] row = new String[3]; 461 | row[0] = mod.modTypeString; 462 | row[1] = mod.modName; 463 | row[2] = mod.modValue; 464 | data.add(row); 465 | } 466 | //log("updating model..."); 467 | fireTableDataChanged(); 468 | } 469 | 470 | @Override 471 | public int getRowCount() { 472 | return data.size(); 473 | } 474 | 475 | @Override 476 | public int getColumnCount() { 477 | return 3; 478 | } 479 | 480 | public String getColumnName(int column) { 481 | return columnNames[column]; 482 | } 483 | 484 | @Override 485 | public Object getValueAt(int rowIndex, int columnIndex) { 486 | String[] row = data.get(rowIndex); 487 | if (columnIndex == 2 && row[columnIndex] == null) { 488 | return "-- REMOVE --"; 489 | } 490 | return row[columnIndex]; 491 | } 492 | } 493 | 494 | private class ModificationTable extends JTable { 495 | public ModificationTable(ModificationTableController modificationTableController) { 496 | super(modificationTableController); 497 | this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 498 | } 499 | } 500 | 501 | private class BurplaySession { 502 | String sessionName; 503 | String cookieName; 504 | String cookieValue; 505 | 506 | public BurplaySession (String sName, String cName, String cValue) { 507 | sessionName = sName; 508 | cookieName = cName; 509 | cookieValue = cValue; 510 | } 511 | } 512 | 513 | private class SessionTableController extends AbstractTableModel { 514 | 515 | public final List listSessions = new ArrayList<>(); 516 | private final List data = new ArrayList<>(); 517 | private String[] columnNames = {"Session Name", "Cookie Name", "Cookie Value"}; 518 | 519 | public void addSession(BurplaySession s) { 520 | listSessions.add(s); 521 | updateTableModel(); 522 | } 523 | 524 | public void removeSession(int index) { 525 | listSessions.remove(index); 526 | updateTableModel(); 527 | } 528 | 529 | public void updateTableModel() { 530 | data.clear(); 531 | 532 | for (int i = 0; i < listSessions.size(); i++) { 533 | BurplaySession s = listSessions.get(i); 534 | String[] row = new String[3]; 535 | row[0] = s.sessionName; 536 | row[1] = s.cookieName; 537 | row[2] = s.cookieValue; 538 | data.add(row); 539 | } 540 | //log("updating model..."); 541 | fireTableDataChanged(); 542 | } 543 | 544 | @Override 545 | public int getRowCount() { 546 | return data.size(); 547 | } 548 | 549 | @Override 550 | public int getColumnCount() { 551 | return 3; 552 | } 553 | 554 | public String getColumnName(int column) { 555 | return columnNames[column]; 556 | } 557 | 558 | @Override 559 | public Object getValueAt(int rowIndex, int columnIndex) { 560 | String[] row = data.get(rowIndex); 561 | return row[columnIndex]; 562 | } 563 | } 564 | 565 | private class SessionTable extends JTable { 566 | public SessionTable(SessionTableController sessionTableController) { 567 | super(sessionTableController); 568 | this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 569 | } 570 | } 571 | 572 | 573 | private class ReplayContextMenu extends JPopupMenu implements ActionListener { 574 | JMenuItem miRemoveRequests; 575 | ReplayContextMenu() { 576 | super(); 577 | miRemoveRequests = new JMenuItem("Remove request(s)"); 578 | miRemoveRequests.setActionCommand("removeRequests"); 579 | miRemoveRequests.addActionListener(this); 580 | this.add(miRemoveRequests); 581 | } 582 | 583 | @Override 584 | public void actionPerformed(ActionEvent e) { 585 | String actCommand = e.getActionCommand(); 586 | if (actCommand == "removeRequests") { 587 | replayTableController.removeRequests(replayTable.getSelectedRows()); 588 | } 589 | } 590 | } 591 | 592 | private class ReplayTable extends JTable implements MouseListener{ 593 | private int lastSelectedRow = 0; 594 | public ReplayTable(TableModel tableModel) { 595 | super(tableModel); 596 | this.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); 597 | this.getTableHeader().setReorderingAllowed(true); 598 | //this.getTableHeader().setResizingAllowed(false); 599 | // this.setRowSelectionAllowed(true); 600 | // this.setCellSelectionEnabled(false); 601 | // this.setColumnSelectionAllowed(false); 602 | this.selectionModel.addListSelectionListener((ReplayTableController)tableModel); 603 | TableColumn indexColumn = this.getColumnModel().getColumn(0); 604 | indexColumn.setMaxWidth(40); 605 | TableColumn methodColumn = this.getColumnModel().getColumn(1); 606 | methodColumn.setMaxWidth(60); 607 | this.addMouseListener(this); 608 | } 609 | 610 | @Override 611 | public void mouseClicked(MouseEvent e) { 612 | 613 | } 614 | 615 | @Override 616 | public void mousePressed(MouseEvent e) { 617 | if (e.isPopupTrigger()) { 618 | ReplayContextMenu cm = new ReplayContextMenu(); 619 | cm.show(e.getComponent(), e.getX(), e.getY()); 620 | } 621 | } 622 | 623 | @Override 624 | public void mouseReleased(MouseEvent e) { 625 | 626 | } 627 | 628 | @Override 629 | public void mouseEntered(MouseEvent e) { 630 | 631 | } 632 | 633 | @Override 634 | public void mouseExited(MouseEvent e) { 635 | 636 | } 637 | } 638 | 639 | // JPanel 640 | @Override 641 | public String getTabCaption() { 642 | return "Replay"; 643 | } 644 | 645 | @Override 646 | public Component getUiComponent() { 647 | return panelMain; 648 | } 649 | 650 | // IContextMenuFactory 651 | @Override 652 | public List createMenuItems(IContextMenuInvocation invocation) { 653 | 654 | byte invocationContext = invocation.getInvocationContext(); 655 | 656 | if (invocationContext == IContextMenuInvocation.CONTEXT_PROXY_HISTORY || 657 | invocationContext == IContextMenuInvocation.CONTEXT_TARGET_SITE_MAP_TABLE ) { 658 | List menu = new ArrayList<>(); 659 | Action sendToReplayAction = new SendToReplayAction("Send to Replay", invocation); 660 | JMenuItem sendToReplayMenuItem = new JMenuItem(sendToReplayAction); 661 | menu.add(sendToReplayMenuItem); 662 | return menu; 663 | } 664 | 665 | if (invocationContext == IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_REQUEST || 666 | invocationContext == IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_REQUEST || 667 | invocationContext == IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_RESPONSE || 668 | invocationContext == IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_RESPONSE) { 669 | List menu = new ArrayList<>(); 670 | Action defineSessionAction = new DefineSessionAction("Define Burplay session", invocation); 671 | JMenuItem defineSessionMenuItem = new JMenuItem(defineSessionAction); 672 | menu.add(defineSessionMenuItem); 673 | return menu; 674 | } 675 | return null; 676 | } 677 | 678 | class DefineSessionAction extends AbstractAction { 679 | 680 | IContextMenuInvocation invocation; 681 | 682 | public DefineSessionAction(String text, IContextMenuInvocation invocation) { 683 | super(text); 684 | this.invocation = invocation; 685 | } 686 | 687 | // this handles defining session 688 | @Override 689 | public void actionPerformed(ActionEvent e) { 690 | //log("defining session"); 691 | IHttpRequestResponse[] reqsResps = invocation.getSelectedMessages(); 692 | if (reqsResps.length != 1) { return; } 693 | int[] bounds = invocation.getSelectionBounds(); 694 | if (bounds.length != 2 || bounds[0] == bounds[1]) { return; } 695 | 696 | byte context = invocation.getInvocationContext(); 697 | byte[] messageContent; 698 | if (context == IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_REQUEST || 699 | context == IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_REQUEST) { 700 | messageContent = reqsResps[0].getRequest(); 701 | } else { 702 | messageContent = reqsResps[0].getResponse(); 703 | } 704 | 705 | String selected = helpers.bytesToString(messageContent).substring(bounds[0], bounds[1]); 706 | 707 | String[] cookieData = selected.split("="); 708 | if (cookieData.length != 2) { return; } 709 | 710 | String dialogMessage = String.format("Enter name for the following session cookie details:\n\n%s\n\n", selected); 711 | 712 | String sesName = JOptionPane.showInputDialog((Component)e.getSource(), dialogMessage,"Define Burplay session", JOptionPane.QUESTION_MESSAGE); 713 | if (sesName == null) { 714 | return; 715 | } 716 | 717 | BurplaySession newSession = new BurplaySession(sesName, cookieData[0], cookieData[1]); 718 | sessionTableController.addSession(newSession); 719 | } 720 | } 721 | 722 | 723 | // this is a table model but also a controller for details tabs 724 | class ReplayTableController extends AbstractTableModel implements ListSelectionListener { 725 | //private final String[] columnNames = {"Host", "Method", "URL"}; 726 | private final String[] columnNames = {"#","Method", "URL"}; 727 | //public final List replayTable = new ArrayList<>(); 728 | 729 | // list for table 730 | private final List data = new ArrayList<>(); 731 | 732 | public void addRequestsResponses(IHttpRequestResponse[] rrs) { 733 | // update tabbed details 734 | for (IHttpRequestResponse rr: rrs) { 735 | tabbedReplayDetails.addRequestResponse(rr); 736 | } 737 | // update table model 738 | updateTableModel(); 739 | } 740 | 741 | public void resetTable() { 742 | data.clear(); 743 | fireTableDataChanged(); 744 | tabbedReplayDetails.resetTabs(); 745 | } 746 | 747 | // TODO make it better 748 | public void removeRequests(int[] rows) { 749 | tabbedReplayDetails.removeRequests(rows); 750 | this.updateTableModel(); 751 | } 752 | 753 | 754 | public void updateTableModel() { 755 | data.clear(); 756 | // this is safe because we update this one first 757 | List rrs = tabbedReplayDetails.getOriginalDetailsPanel().listHTTPRequestsResponses; 758 | 759 | for (int i = 0; i < rrs.size(); i++) { 760 | IHttpRequestResponse reqres = rrs.get(i); 761 | //IHttpService httpService = reqres.getHttpService(); 762 | IRequestInfo reqInfo = helpers.analyzeRequest(reqres); 763 | String method = reqInfo.getMethod(); 764 | String url = reqInfo.getUrl().toString(); 765 | String[] row = new String[3]; 766 | row[0] = Integer.toString(i+1); 767 | row[1] = method; 768 | row[2] = url; 769 | data.add(row); 770 | } 771 | //log("updating model..."); 772 | fireTableDataChanged(); 773 | } 774 | 775 | @Override 776 | public int getRowCount() { 777 | if (data == null) { 778 | return 0; 779 | } 780 | return data.size(); 781 | } 782 | 783 | public String getColumnName(int c) { 784 | return columnNames[c]; 785 | } 786 | 787 | @Override 788 | public int getColumnCount() { 789 | return 3; 790 | } 791 | 792 | 793 | @Override 794 | public Object getValueAt(int rowIndex, int columnIndex) { 795 | return data.get(rowIndex)[columnIndex]; 796 | } 797 | 798 | public void valueChanged(ListSelectionEvent e) { 799 | ListSelectionModel lsm = (ListSelectionModel) e.getSource(); 800 | int selected = lsm.getMinSelectionIndex(); 801 | if (selected >= 0) { 802 | tabbedReplayDetails.chooseRequestNo(selected); 803 | 804 | } 805 | } 806 | 807 | } 808 | 809 | class SendToReplayAction extends AbstractAction { 810 | 811 | IContextMenuInvocation invocation; 812 | 813 | public SendToReplayAction(String text, IContextMenuInvocation invocation) { 814 | super(text); 815 | this.invocation = invocation; 816 | } 817 | 818 | // this handles adding requests to the extension 819 | @Override 820 | public void actionPerformed(ActionEvent e) { 821 | IHttpRequestResponse[] selectedReqsResps = this.invocation.getSelectedMessages(); 822 | replayTableController.addRequestsResponses(selectedReqsResps); 823 | } 824 | } 825 | 826 | class ResetAction extends AbstractAction { 827 | 828 | @Override 829 | public void actionPerformed(ActionEvent e) { 830 | int yesno = JOptionPane.showConfirmDialog(panelMain, "Remove all requests?", 831 | "Reset Confirmation", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); 832 | if (yesno == JOptionPane.NO_OPTION) { 833 | return; 834 | } 835 | replayTableController.resetTable(); 836 | } 837 | } 838 | 839 | class HelpAction extends AbstractAction { 840 | 841 | @Override 842 | public void actionPerformed(ActionEvent e) { 843 | FormHelp formHelp = new FormHelp(); 844 | formHelp.pack(); 845 | formHelp.setVisible(true); 846 | } 847 | } 848 | 849 | class AddRemoveAction extends AbstractAction { 850 | 851 | @Override 852 | public void actionPerformed(ActionEvent e) { 853 | String actionCommand = e.getActionCommand(); 854 | switch(actionCommand) { 855 | case "addmod": 856 | String t = modificationConfig.comboParamType.getSelectedItem().toString(); 857 | String n = modificationConfig.fieldParamName.getText(); 858 | String v = (modificationConfig.chkboxRemove.isSelected())?null:modificationConfig.fieldParamValue.getText(); 859 | ReplayModification modAdd = new ReplayModification(t, n, v); 860 | modificationTableController.addModification(modAdd); 861 | modificationConfig.fieldParamName.setText(""); 862 | modificationConfig.chkboxRemove.setSelected(false); 863 | modificationConfig.fieldParamValue.setEnabled(true); 864 | modificationConfig.fieldParamValue.setText(""); 865 | break; 866 | case "removemod": 867 | int rowMod = modificationTable.getSelectedRow(); 868 | if (rowMod >= 0 ) { 869 | modificationTableController.removeModification(rowMod); 870 | } 871 | break; 872 | case "removeses": 873 | int rowSes = sessionTable.getSelectedRow(); 874 | if (rowSes >= 0) { 875 | sessionTableController.removeSession(rowSes); 876 | } 877 | break; 878 | case "applyses": 879 | int rowSesApply = sessionTable.getSelectedRow(); 880 | BurplaySession ses; 881 | if (rowSesApply >= 0) { 882 | ses = sessionTableController.listSessions.get(rowSesApply); 883 | } else { 884 | return; 885 | } 886 | ReplayModification modApply = new ReplayModification("Cookie", ses.cookieName, ses.cookieValue); 887 | modificationTableController.addModification(modApply); 888 | break; 889 | } 890 | 891 | } 892 | } 893 | 894 | class ReplayDetailsTabbedPane extends JTabbedPane { 895 | 896 | public int currentTab = 1; 897 | 898 | public ReplayDetailsTabbedPane() { 899 | super(); 900 | } 901 | 902 | public ReplayDetailsPanel addReplayTab(String name) { 903 | 904 | ReplayDetailsPanel panel = new ReplayDetailsPanel(); 905 | if (name == null) { 906 | name = Integer.toString(currentTab); 907 | currentTab++; 908 | } 909 | 910 | // if (parentName != null) { 911 | // parentName = parentName.replaceAll("\\(.*\\)", ""); 912 | // name += String.format("(%s)", parentName); 913 | // } 914 | 915 | this.addTab(name, panel); 916 | return panel; 917 | } 918 | 919 | public void removeRequests(int[] rows) { 920 | int tabCount = this.getTabCount(); 921 | for (int i = 0; i0 ; i--) { 931 | this.remove(i); 932 | } 933 | this.updateUI(); 934 | this.getOriginalDetailsPanel().listHTTPRequestsResponses.clear(); 935 | this.getOriginalDetailsPanel().updateMessages(-1); 936 | currentTab = 1; 937 | } 938 | 939 | // TODO this should add request and response to the original reqres tab 940 | // TODO should also add the request to other tabs, so they can issue them and get responses 941 | public void addRequestResponse(IHttpRequestResponse reqres) { 942 | ReplayDetailsPanel panelOriginalDetails = (ReplayDetailsPanel) this.getComponentAt(0); 943 | panelOriginalDetails.addRequestResponse(reqres); 944 | } 945 | 946 | 947 | public ReplayDetailsPanel getOriginalDetailsPanel() { 948 | ReplayDetailsPanel panelDetails = (ReplayDetailsPanel) this.getComponentAt(0); 949 | return panelDetails; 950 | } 951 | 952 | public ReplayDetailsPanel getCurrentDetailsPanel() { 953 | ReplayDetailsPanel panelDetails = (ReplayDetailsPanel) this.getSelectedComponent(); 954 | return panelDetails; 955 | } 956 | 957 | 958 | public void chooseRequestNo(int n) { 959 | for (int i = 0; i listHTTPRequestsResponses = new ArrayList<>(); 998 | private IMessageEditor messageEditorRequest; 999 | private IMessageEditor messageEditorResponse; 1000 | private MessageEditorController messageEditorController = new MessageEditorController(); 1001 | 1002 | public ReplayDetailsPanel() { 1003 | super(); 1004 | messageEditorRequest = callbacks.createMessageEditor(messageEditorController, false); 1005 | messageEditorResponse = callbacks.createMessageEditor(messageEditorController, false); 1006 | JPanel panelRequest = new JPanel(); 1007 | JPanel panelResponse = new JPanel(); 1008 | 1009 | JSplitPane detailsSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, 1010 | panelRequest, panelResponse); 1011 | detailsSplitPane.setResizeWeight(0.5); 1012 | panelRequest.setLayout(new BorderLayout()); 1013 | panelResponse.setLayout(new BorderLayout()); 1014 | 1015 | panelRequest.add(messageEditorRequest.getComponent()); 1016 | panelResponse.add(messageEditorResponse.getComponent()); 1017 | 1018 | this.setLayout(new BorderLayout()); 1019 | this.add(detailsSplitPane, BorderLayout.CENTER); 1020 | } 1021 | 1022 | public void addRequestResponse(IHttpRequestResponse reqres) { 1023 | this.listHTTPRequestsResponses.add(reqres); 1024 | } 1025 | 1026 | public void removeRequest(int row) { 1027 | listHTTPRequestsResponses.remove(row); 1028 | } 1029 | 1030 | // TODO fixit 1031 | public void removeRequests(int[] rows) { 1032 | List newList = new ArrayList<>(); 1033 | for (int i=0; i headers = requestInfo.getHeaders(); 1138 | Iterator iterator = headers.iterator(); 1139 | // TODO collection.removeIf 1140 | while (iterator.hasNext()) { 1141 | if (iterator.next().startsWith(modName + ":")) { 1142 | iterator.remove(); 1143 | } 1144 | } 1145 | // modValue == null means remove 1146 | if (modValue != null) { 1147 | headers.add(String.format("%s: %s", modName, modValue)); 1148 | } 1149 | byte[] body = Arrays.copyOfRange(inputRequest, requestInfo.getBodyOffset(), inputRequest.length); 1150 | newReq = helpers.buildHttpMessage(headers, body); 1151 | } else { 1152 | // this is Cookie, GET or POST 1153 | newReq = inputRequest.clone(); 1154 | List params = requestInfo.getParameters(); 1155 | for (IParameter p: params) { 1156 | if (p.getName().equals(modName) && this.isOfType(p.getType())) { 1157 | newReq = helpers.removeParameter(newReq,p); 1158 | } 1159 | } 1160 | // modValue == null means remove 1161 | if (modValue != null) { 1162 | newParam = helpers.buildParameter(modName, modValue, paramType); 1163 | newReq = helpers.addParameter(newReq, newParam); 1164 | } 1165 | } 1166 | return newReq; 1167 | } 1168 | 1169 | } 1170 | } 1171 | 1172 | 1173 | -------------------------------------------------------------------------------- /src/main/java/burp/FormHelp.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import javax.swing.*; 4 | import java.awt.*; 5 | import java.net.URL; 6 | 7 | /** 8 | * Created by mike on 7/24/17. 9 | */ 10 | 11 | public class FormHelp extends JFrame { 12 | 13 | public FormHelp() { 14 | URL htmlFile = FormHelp.class.getResource("/FormHelp.html"); 15 | 16 | JEditorPane editorPane; 17 | try { 18 | editorPane = new JEditorPane(htmlFile); 19 | } catch (Exception e) { 20 | editorPane = new JEditorPane(); 21 | } 22 | this.setTitle("Burplay Manual"); 23 | editorPane.setEditable(false); 24 | JScrollPane editorScroll = new JScrollPane(editorPane); 25 | editorScroll.setPreferredSize(new Dimension(600,400)); 26 | this.add(editorScroll); 27 | 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/Burplay.properties: -------------------------------------------------------------------------------- 1 | version=${project.version} 2 | -------------------------------------------------------------------------------- /src/main/resources/FormHelp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Burplay Help 6 | 7 | 8 | 9 |

Burplay

10 |

Burplay is a Burp Extension allowing for replaying any number of requests using same modifications definition. 11 | Its main purpose is to aid in searching for Privilege Escalation issues. 12 |

13 |
14 |

Request List

15 |

16 | Add requests to Burplay from Proxy -> HTTP History or Target -> Site map ->Contents table by choosing "Send to Replay" from the context menu. 17 |

18 |
19 |

Modifications

20 |

21 | Define any number of modifications by adding them to the modifications table. 22 | They will be applied to the base requests ("Original" tab).
23 | Four types of modifications are available: 24 |

    25 |
  • Header
  • 26 |
  • Cookie
  • 27 |
  • GET
  • 28 |
  • POST
  • 29 |
30 | For each modification, if no Header/Cookie/parameter exists in the base request, it will be added, otherwise modified. 31 |

32 |
33 |

Sessions

34 |

You can define a Burplay session in any Request/Response Editor within Burp by selecting text 35 | (typically "SESSIONID=sessionid") and choosing "Define Burplay session" from the context menu.


36 | 37 | 38 | 39 | 40 |

The session will be added to the Sessions table and can be applied as a modification with "Apply" button.
41 | Only cookie-based session identifiers are currently supported. 42 |

43 |
44 |

Details

45 |

Details tabs on the right hand side show the original set of requests (base requests) and each replay round 46 | in numbered tabs. You can compare responses manually or use Burp Comparer. 47 |

48 |
49 |
50 |
51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /src/main/resources/define_session.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpiderLabs/burplay/2fdcea6b39ed470e4211ec157b55f483535e9e56/src/main/resources/define_session.png -------------------------------------------------------------------------------- /src/test/java/burp/BurplayTest.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class BurplayTest extends TestCase 11 | { 12 | /** 13 | * Create the test case 14 | * 15 | * @param testName name of the test case 16 | */ 17 | public BurplayTest( String testName ) 18 | { 19 | super( testName ); 20 | } 21 | 22 | /** 23 | * @return the suite of tests being tested 24 | */ 25 | public static Test suite() 26 | { 27 | return new TestSuite( BurplayTest.class ); 28 | } 29 | 30 | /** 31 | * Rigourous Test :-) 32 | */ 33 | public void testApp() 34 | { 35 | assertTrue( true ); 36 | } 37 | } 38 | --------------------------------------------------------------------------------