├── BappDescription.html ├── BappManifest.bmf ├── CPH_Config.py ├── CPH_Help.py ├── CustomParamHandler.py ├── LICENSE ├── README.md ├── tinyweb.py └── utils └── CPH_Merger.py /BappDescription.html: -------------------------------------------------------------------------------- 1 |

2 | CPH provides a simple way to modify any part of an HTTP message, allowing manipulation with surgical precision even when using macros. Typically, macros only support HTTP parameters (name=value), but with this extension anything can be targeted. 3 |

4 |

Features include:

5 | 6 | 20 | 21 |

Source and bug reporting: github.com/elespike/burp-cph.

22 |

Wiki: github.com/elespike/burp-cph/wiki.

23 | 24 |

Requires Jython version 2.7.0

25 | -------------------------------------------------------------------------------- /BappManifest.bmf: -------------------------------------------------------------------------------- 1 | Uuid: a0c0cd68ab7c4928b3bf0a9ad48ec8c7 2 | ExtensionType: 2 3 | Name: Custom Parameter Handler 4 | RepoName: custom-parameter-handler 5 | ScreenVersion: 3.0 6 | SerialVersion: 3 7 | MinPlatformVersion: 0 8 | ProOnly: False 9 | Author: @elespike, @eth0up 10 | ShortDescription: Provides a simple way to automatically modify any part of an HTTP message. 11 | EntryPoint: CustomParamHandler_merged.py 12 | BuildCommand: python utils/CPH_Merger.py 13 | -------------------------------------------------------------------------------- /CPH_Config.py: -------------------------------------------------------------------------------- 1 | from logging import ( 2 | DEBUG , 3 | ERROR , 4 | INFO , 5 | WARNING , 6 | getLevelName, 7 | ) 8 | from SocketServer import ThreadingMixIn, TCPServer 9 | from collections import OrderedDict as odict, namedtuple 10 | from difflib import unified_diff 11 | from hashlib import sha256 12 | from itertools import product 13 | from json import dump, dumps, load, loads 14 | from re import escape as re_escape 15 | from socket import error as socket_error 16 | from thread import start_new_thread 17 | from threading import Thread 18 | from tinyweb import TinyHandler 19 | from webbrowser import open_new_tab as browser_open 20 | 21 | from burp import ITab 22 | from CPH_Help import CPH_Help 23 | 24 | from java.awt import ( 25 | CardLayout , 26 | Color , 27 | FlowLayout , 28 | Font , 29 | GridBagConstraints, 30 | GridBagLayout , 31 | Insets , 32 | ) 33 | from java.awt.event import ( 34 | ActionListener, 35 | KeyListener , 36 | MouseAdapter , 37 | ) 38 | from javax.swing import ( 39 | AbstractAction , 40 | BorderFactory , 41 | JButton , 42 | JCheckBox , 43 | JComboBox , 44 | JFileChooser , 45 | JFrame , 46 | JLabel , 47 | JOptionPane , 48 | JPanel , 49 | JScrollPane , 50 | JSeparator , 51 | JSpinner , 52 | JSplitPane , 53 | JTabbedPane , 54 | JTable , 55 | JTextArea , 56 | JTextField , 57 | KeyStroke , 58 | SpinnerNumberModel, 59 | ) 60 | from javax.swing.event import ChangeListener, ListSelectionListener 61 | from javax.swing.filechooser import FileNameExtensionFilter 62 | from javax.swing.table import AbstractTableModel 63 | from javax.swing.undo import UndoManager 64 | 65 | 66 | class MainTab(ITab, ChangeListener): 67 | mainpane = JTabbedPane() 68 | 69 | # These are set during __init__ 70 | _cph = None 71 | logger = None 72 | 73 | def __init__(self, cph): 74 | MainTab.mainpane.addChangeListener(self) 75 | MainTab._cph = cph 76 | MainTab.logger = cph.logger 77 | self.options_tab = OptionsTab() 78 | MainTab.mainpane.add('Options', self.options_tab) 79 | self._add_sign = unichr(0x002b) # addition sign 80 | MainTab.mainpane.add(self._add_sign, JPanel()) 81 | 82 | class Action(AbstractAction): 83 | def __init__(self, action): 84 | self.action = action 85 | def actionPerformed(self, e): 86 | if self.action: 87 | self.action() 88 | 89 | # Ctrl+N only on key released 90 | MainTab.mainpane.getInputMap(1).put(KeyStroke.getKeyStroke(78, 2, True), 'add_config_tab') 91 | MainTab.mainpane.getActionMap().put('add_config_tab', Action(lambda: ConfigTab())) 92 | 93 | # Ctrl+Shift+N only on key released 94 | MainTab.mainpane.getInputMap(1).put(KeyStroke.getKeyStroke(78, 3, True), 'clone_tab') 95 | MainTab.mainpane.getActionMap().put( 96 | 'clone_tab', 97 | Action( 98 | lambda: MainTab.mainpane.getSelectedComponent().clone_tab() 99 | if MainTab.mainpane.getSelectedIndex() > 0 100 | else None 101 | ) 102 | ) 103 | 104 | # Ctrl+W only on key released 105 | MainTab.mainpane.getInputMap(1).put(KeyStroke.getKeyStroke(87, 2, True), 'close_tab') 106 | MainTab.mainpane.getActionMap().put('close_tab', Action(MainTab.close_tab)) 107 | 108 | # Ctrl+E only on key released 109 | MainTab.mainpane.getInputMap(1).put(KeyStroke.getKeyStroke(69, 2, True), 'toggle_tab') 110 | MainTab.mainpane.getActionMap().put( 111 | 'toggle_tab', 112 | Action( 113 | lambda: MainTab.mainpane.getSelectedComponent().tabtitle_pane.enable_chkbox.setSelected( 114 | not MainTab.mainpane.getSelectedComponent().tabtitle_pane.enable_chkbox.isSelected() 115 | ) 116 | ) 117 | ) 118 | 119 | # Ctrl+, 120 | MainTab.mainpane.getInputMap(1).put(KeyStroke.getKeyStroke(44, 2), 'select_previous_tab') 121 | MainTab.mainpane.getActionMap().put( 122 | 'select_previous_tab', 123 | Action( 124 | lambda: MainTab.mainpane.setSelectedIndex(MainTab.mainpane.getSelectedIndex() - 1) 125 | if MainTab.mainpane.getSelectedIndex() > 0 126 | else MainTab.mainpane.setSelectedIndex(0) 127 | ) 128 | ) 129 | 130 | # Ctrl+. 131 | MainTab.mainpane.getInputMap(1).put(KeyStroke.getKeyStroke(46, 2), 'select_next_tab') 132 | MainTab.mainpane.getActionMap().put( 133 | 'select_next_tab', 134 | Action(lambda: MainTab.mainpane.setSelectedIndex(MainTab.mainpane.getSelectedIndex() + 1)) 135 | ) 136 | 137 | # Ctrl+Shift+, 138 | MainTab.mainpane.getInputMap(1).put(KeyStroke.getKeyStroke(44, 3), 'move_tab_back') 139 | MainTab.mainpane.getActionMap().put( 140 | 'move_tab_back', 141 | Action( 142 | lambda: MainTab.mainpane.getSelectedComponent().move_tab_back( 143 | MainTab.mainpane.getSelectedComponent() 144 | ) 145 | ) 146 | ) 147 | 148 | # Ctrl+Shift+. 149 | MainTab.mainpane.getInputMap(1).put(KeyStroke.getKeyStroke(46, 3), 'move_tab_fwd') 150 | MainTab.mainpane.getActionMap().put( 151 | 'move_tab_fwd', 152 | Action( 153 | lambda: MainTab.mainpane.getSelectedComponent().move_tab_fwd( 154 | MainTab.mainpane.getSelectedComponent() 155 | ) 156 | ) 157 | ) 158 | 159 | @staticmethod 160 | def getTabCaption(): 161 | return 'CPH Config' 162 | 163 | def getUiComponent(self): 164 | return MainTab.mainpane 165 | 166 | def add_config_tab(self, messages): 167 | for message in messages: 168 | ConfigTab(message) 169 | 170 | @staticmethod 171 | def get_options_tab(): 172 | return MainTab.mainpane.getComponentAt(0) 173 | 174 | @staticmethod 175 | def get_config_tabs(): 176 | components = MainTab.mainpane.getComponents() 177 | for i in range(len(components)): 178 | for tab in components: 179 | if isinstance(tab, ConfigTab) and i == MainTab.mainpane.indexOfComponent(tab): 180 | yield tab 181 | 182 | @staticmethod 183 | def get_config_tab_names(): 184 | for tab in MainTab.get_config_tabs(): 185 | yield tab.namepane_txtfield.getText() 186 | 187 | @staticmethod 188 | def get_config_tab_cache(tab_name): 189 | for tab in MainTab.get_config_tabs(): 190 | if tab.namepane_txtfield.getText() == tab_name: 191 | return tab.cached_request, tab.cached_response 192 | 193 | @staticmethod 194 | def check_configtab_names(): 195 | x = 0 196 | configtab_names = {} 197 | for name in MainTab.get_config_tab_names(): 198 | configtab_names[x] = name 199 | x += 1 200 | indices_to_rename = {} 201 | for tab_index_1, tab_name_1 in configtab_names.items(): 202 | for tab_index_2, tab_name_2 in configtab_names.items(): 203 | if tab_name_2 not in indices_to_rename: 204 | indices_to_rename[tab_name_2] = [] 205 | if tab_name_1 == tab_name_2 and tab_index_1 != tab_index_2: 206 | indices_to_rename[tab_name_2].append(tab_index_2 + 1) # +1 because the first tab is the Options tab 207 | for k, v in indices_to_rename.items(): 208 | indices_to_rename[k] = set(sorted(v)) 209 | for tab_name, indices in indices_to_rename.items(): 210 | x = 1 211 | for i in indices: 212 | MainTab.set_tab_name(MainTab.mainpane.getComponentAt(i), tab_name + ' (%s)' % x) 213 | x += 1 214 | 215 | @staticmethod 216 | def set_tab_name(tab, tab_name): 217 | tab.namepane_txtfield.tab_label.setText(tab_name) 218 | tab.namepane_txtfield.setText(tab_name) 219 | emv_tab_index = MainTab.mainpane.indexOfComponent(tab) - 1 220 | MainTab.get_options_tab().emv_tab_pane.setTitleAt(emv_tab_index, tab_name) 221 | 222 | @staticmethod 223 | def close_tab(tab_index=None): 224 | if tab_index is None: 225 | tab_index = MainTab.mainpane.getSelectedIndex() 226 | true_index = tab_index - 1 # because of the Options tab 227 | tab_count = MainTab.mainpane.getTabCount() 228 | if tab_index == 0 or tab_count == 2: 229 | return 230 | if tab_count == 3 or tab_index == tab_count - 2: 231 | MainTab.mainpane.setSelectedIndex(tab_count - 3) 232 | MainTab.mainpane.remove(tab_index) 233 | MainTab.get_options_tab().emv_tab_pane.remove(true_index) 234 | 235 | # If the closed tab was selected in subsequent tabs' combo_cached, remove selection. 236 | for i, subsequent_tab in enumerate(MainTab.get_config_tabs()): 237 | if i < true_index: 238 | continue 239 | if subsequent_tab.param_handl_combo_cached.getSelectedIndex() == true_index: 240 | subsequent_tab.param_handl_combo_cached.setSelectedItem(None) 241 | if subsequent_tab.param_handl_combo_extract.getSelectedItem() == ConfigTab.PARAM_HANDL_COMBO_EXTRACT_CACHED: 242 | MainTab.logger.warning( 243 | 'Selected cache no longer available for tab "{}"!'.format(subsequent_tab.namepane_txtfield.getText()) 244 | ) 245 | subsequent_tab.param_handl_combo_cached.removeItemAt(true_index) 246 | 247 | def stateChanged(self, e): 248 | if e.getSource() == MainTab.mainpane: 249 | index = MainTab.mainpane.getSelectedIndex() 250 | if hasattr(self, '_add_sign') and MainTab.mainpane.getTitleAt(index) == self._add_sign: 251 | MainTab.mainpane.setSelectedIndex(0) 252 | ConfigTab() 253 | 254 | 255 | class SubTab(JScrollPane, ActionListener): 256 | BTN_HELP = '?' 257 | DOCS_URL = 'https://github.com/elespike/burp-cph/wiki' 258 | INSETS = Insets(2, 4, 2, 4) 259 | # Expression pane component indices 260 | CHECKBOX_INDEX = 0 261 | TXT_FIELD_INDEX = 1 262 | # Socket pane component index tuples 263 | HTTPS_INDEX = 0 264 | HOST_INDEX = 1 265 | PORT_INDEX = 3 266 | 267 | CONFIG_MECHANISM = namedtuple('CONFIG_MECHANISM' , 'name, getter, setter') 268 | 269 | def __init__(self): 270 | self._main_tab_pane = JPanel(GridBagLayout()) 271 | self.setViewportView(self._main_tab_pane) 272 | self.getVerticalScrollBar().setUnitIncrement(16) 273 | 274 | @staticmethod 275 | def create_blank_space(): 276 | return JLabel(' ') 277 | 278 | @staticmethod 279 | def create_empty_button(button): 280 | button.setOpaque(False) 281 | button.setFocusable(False) 282 | button.setContentAreaFilled(False) 283 | button.setBorderPainted(False) 284 | 285 | @staticmethod 286 | def set_title_font(component): 287 | font = Font(Font.SANS_SERIF, Font.BOLD, 14) 288 | component.setFont(font) 289 | return component 290 | 291 | def initialize_constraints(self): 292 | constraints = GridBagConstraints() 293 | constraints.weightx = 1 294 | constraints.insets = self.INSETS 295 | constraints.fill = GridBagConstraints.HORIZONTAL 296 | constraints.anchor = GridBagConstraints.NORTHWEST 297 | constraints.gridx = 0 298 | constraints.gridy = 0 299 | return constraints 300 | 301 | @staticmethod 302 | def show_card(cardpanel, label): 303 | cl = cardpanel.getLayout() 304 | cl.show(cardpanel, label) 305 | 306 | 307 | class HelpButton(JButton): 308 | # From CPH_Help.py: 309 | # HelpPopup = namedtuple('HelpPopup', 'title, message, url') 310 | def __init__(self, help_popup=None): 311 | super(JButton, self).__init__() 312 | self.title, self.message = '', '' 313 | self.url = SubTab.DOCS_URL 314 | 315 | if help_popup is not None: 316 | self.title = help_popup.title 317 | self.message = JLabel(help_popup.message) 318 | self.message.setFont(Font(Font.MONOSPACED, Font.PLAIN, 14)) 319 | self.url = help_popup.url 320 | 321 | self.setText(SubTab.BTN_HELP) 322 | self.setFont(Font(Font.SANS_SERIF, Font.BOLD, 14)) 323 | 324 | def show_help(self): 325 | result = JOptionPane.showOptionDialog( 326 | self, 327 | self.message, 328 | self.title, 329 | JOptionPane.YES_NO_OPTION, 330 | JOptionPane.QUESTION_MESSAGE, 331 | None, 332 | ['Learn more', 'Close'], 333 | 'Close' 334 | ) 335 | if result == 0: 336 | browser_open(self.url) 337 | 338 | 339 | class ThreadedHTTPServer(ThreadingMixIn, TCPServer): 340 | pass 341 | 342 | 343 | class OptionsTab(SubTab, ChangeListener): 344 | VERBOSITY = 'Verbosity level:' 345 | BTN_QUICKSAVE = 'Quicksave' 346 | BTN_QUICKLOAD = 'Quickload' 347 | BTN_EXPORTCONFIG = 'Export Config' 348 | BTN_IMPORTCONFIG = 'Import Config' 349 | BTN_DOCS = 'Visit Wiki' 350 | BTN_EMV = 'Show EMV' 351 | DEMO_INACTIVE = 'Run the built-in httpd for interactive demos' 352 | DEMO_ACTIVE = 'Running built-in httpd on {}:{}' 353 | CHKBOX_PANE = 'Tool scope settings' 354 | QUICKSTART_PANE = 'Quickstart guide' 355 | 356 | FILEFILTER = FileNameExtensionFilter('JSON', ['json']) 357 | 358 | s = sha256() 359 | s.update('quick') 360 | CONFIGNAME_QUICK = s.hexdigest() 361 | s = sha256() 362 | s.update('options') 363 | CONFIGNAME_OPTIONS = s.hexdigest() 364 | del s 365 | 366 | def __init__(self): 367 | SubTab.__init__(self) 368 | 369 | btn_docs = JButton(self.BTN_DOCS) 370 | btn_docs.addActionListener(self) 371 | 372 | btn_emv = JButton(self.BTN_EMV) 373 | btn_emv.addActionListener(self) 374 | 375 | btn_quicksave = JButton(self.BTN_QUICKSAVE) 376 | btn_quicksave.addActionListener(self) 377 | 378 | btn_quickload = JButton(self.BTN_QUICKLOAD) 379 | btn_quickload.addActionListener(self) 380 | 381 | btn_exportconfig = JButton(self.BTN_EXPORTCONFIG) 382 | btn_exportconfig.addActionListener(self) 383 | 384 | btn_importconfig = JButton(self.BTN_IMPORTCONFIG) 385 | btn_importconfig.addActionListener(self) 386 | 387 | err, warn, info, dbg = 1, 2, 3, 4 388 | self.verbosity_translator = { 389 | err : ERROR , 390 | warn: WARNING, 391 | info: INFO , 392 | dbg : DEBUG , 393 | } 394 | 395 | # Just initializing 396 | self.httpd = None 397 | self.httpd_thread = None 398 | 399 | self.verbosity_level_lbl = JLabel(getLevelName(INFO)) 400 | self.verbosity_spinner = JSpinner(SpinnerNumberModel(info, err, dbg, 1)) 401 | self.verbosity_spinner.addChangeListener(self) 402 | 403 | verbosity_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 404 | verbosity_pane.add(JLabel(self.VERBOSITY)) 405 | verbosity_pane.add(self.verbosity_spinner) 406 | verbosity_pane.add(self.verbosity_level_lbl) 407 | 408 | self.chkbox_demo = JCheckBox(self.DEMO_INACTIVE, False) 409 | self.chkbox_demo.addActionListener(self) 410 | demo_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 411 | demo_pane.add(self.chkbox_demo) 412 | 413 | self.emv = JFrame('Effective Modification Viewer') 414 | self.emv_tab_pane = JTabbedPane() 415 | self.emv.add(self.emv_tab_pane) 416 | 417 | btn_pane = JPanel(GridBagLayout()) 418 | constraints = self.initialize_constraints() 419 | constraints.gridwidth = 2 420 | btn_pane.add(verbosity_pane, constraints) 421 | constraints.gridwidth = 1 422 | constraints.gridy = 1 423 | btn_pane.add(btn_quicksave, constraints) 424 | constraints.gridx = 1 425 | btn_pane.add(btn_exportconfig, constraints) 426 | constraints.gridy = 2 427 | constraints.gridx = 0 428 | btn_pane.add(btn_quickload, constraints) 429 | constraints.gridx = 1 430 | btn_pane.add(btn_importconfig, constraints) 431 | constraints.gridy = 3 432 | constraints.gridx = 0 433 | btn_pane.add(btn_docs, constraints) 434 | constraints.gridx = 1 435 | btn_pane.add(btn_emv, constraints) 436 | constraints.gridy = 4 437 | constraints.gridx = 0 438 | constraints.gridwidth = 2 439 | btn_pane.add(demo_pane, constraints) 440 | 441 | self.chkbox_proxy = JCheckBox('Proxy' , True ) 442 | self.chkbox_target = JCheckBox('Target' , False) 443 | self.chkbox_spider = JCheckBox('Spider' , False) 444 | self.chkbox_repeater = JCheckBox('Repeater' , True ) 445 | self.chkbox_sequencer = JCheckBox('Sequencer', False) 446 | self.chkbox_intruder = JCheckBox('Intruder' , False) 447 | self.chkbox_scanner = JCheckBox('Scanner' , False) 448 | self.chkbox_extender = JCheckBox('Extender' , False) 449 | 450 | chkbox_pane = JPanel(GridBagLayout()) 451 | chkbox_pane.setBorder(BorderFactory.createTitledBorder(self.CHKBOX_PANE)) 452 | chkbox_pane.getBorder().setTitleFont(Font(Font.SANS_SERIF, Font.ITALIC, 16)) 453 | constraints = self.initialize_constraints() 454 | chkbox_pane.add(self.chkbox_proxy, constraints) 455 | constraints.gridy = 1 456 | chkbox_pane.add(self.chkbox_target, constraints) 457 | constraints.gridy = 2 458 | chkbox_pane.add(self.chkbox_spider, constraints) 459 | constraints.gridy = 3 460 | chkbox_pane.add(self.chkbox_repeater, constraints) 461 | constraints.gridx = 1 462 | constraints.gridy = 0 463 | chkbox_pane.add(self.chkbox_sequencer, constraints) 464 | constraints.gridy = 1 465 | chkbox_pane.add(self.chkbox_intruder, constraints) 466 | constraints.gridy = 2 467 | chkbox_pane.add(self.chkbox_scanner, constraints) 468 | constraints.gridy = 3 469 | chkbox_pane.add(self.chkbox_extender, constraints) 470 | 471 | quickstart_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 472 | quickstart_pane.setBorder(BorderFactory.createTitledBorder(self.QUICKSTART_PANE)) 473 | quickstart_pane.getBorder().setTitleFont(Font(Font.SANS_SERIF, Font.ITALIC, 16)) 474 | quickstart_text_lbl = JLabel(CPH_Help.quickstart) 475 | quickstart_text_lbl.setFont(Font(Font.MONOSPACED, Font.PLAIN, 14)) 476 | quickstart_pane.add(quickstart_text_lbl) 477 | 478 | constraints = self.initialize_constraints() 479 | constraints.gridwidth = 3 480 | self._main_tab_pane.add(SubTab.create_blank_space(), constraints) 481 | constraints.gridwidth = 1 482 | constraints.gridy = 1 483 | self._main_tab_pane.add(btn_pane, constraints) 484 | constraints.gridx = 1 485 | self._main_tab_pane.add(SubTab.create_blank_space(), constraints) 486 | constraints.gridx = 2 487 | self._main_tab_pane.add(chkbox_pane, constraints) 488 | constraints.gridx = 3 489 | self._main_tab_pane.add(SubTab.create_blank_space(), constraints) 490 | constraints.gridx = 0 491 | constraints.gridy = 2 492 | constraints.gridwidth = 3 493 | self._main_tab_pane.add(SubTab.create_blank_space(), constraints) 494 | constraints.gridy = 3 495 | constraints.weighty = 1 496 | self._main_tab_pane.add(quickstart_pane, constraints) 497 | 498 | self.config_mechanisms = [ 499 | SubTab.CONFIG_MECHANISM( 500 | 'verbosity', 501 | self.verbosity_spinner.getValue, 502 | lambda cv: self.verbosity_spinner.setValue(cv) 503 | ), 504 | SubTab.CONFIG_MECHANISM( 505 | 'chkbox_proxy', 506 | self.chkbox_proxy.isSelected, 507 | lambda cv: self.chkbox_proxy.setSelected(cv) 508 | ), 509 | SubTab.CONFIG_MECHANISM( 510 | 'chkbox_target', 511 | self.chkbox_target.isSelected, 512 | lambda cv: self.chkbox_target.setSelected(cv) 513 | ), 514 | SubTab.CONFIG_MECHANISM( 515 | 'chkbox_spider', 516 | self.chkbox_spider.isSelected, 517 | lambda cv: self.chkbox_spider.setSelected(cv) 518 | ), 519 | SubTab.CONFIG_MECHANISM( 520 | 'chkbox_repeater', 521 | self.chkbox_repeater.isSelected, 522 | lambda cv: self.chkbox_repeater.setSelected(cv) 523 | ), 524 | SubTab.CONFIG_MECHANISM( 525 | 'chkbox_sequencer', 526 | self.chkbox_sequencer.isSelected, 527 | lambda cv: self.chkbox_sequencer.setSelected(cv) 528 | ), 529 | SubTab.CONFIG_MECHANISM( 530 | 'chkbox_intruder', 531 | self.chkbox_intruder.isSelected, 532 | lambda cv: self.chkbox_intruder.setSelected(cv) 533 | ), 534 | SubTab.CONFIG_MECHANISM( 535 | 'chkbox_scanner', 536 | self.chkbox_scanner.isSelected, 537 | lambda cv: self.chkbox_scanner.setSelected(cv) 538 | ), 539 | SubTab.CONFIG_MECHANISM( 540 | 'chkbox_extender', 541 | self.chkbox_extender.isSelected, 542 | lambda cv: self.chkbox_extender.setSelected(cv) 543 | ), 544 | ] 545 | 546 | def stateChanged(self, e): 547 | if e.getSource() == self.verbosity_spinner: 548 | level = self.verbosity_translator[self.verbosity_spinner.getValue()] 549 | MainTab.logger.setLevel(level) 550 | self.verbosity_level_lbl.setText(getLevelName(level)) 551 | 552 | def set_tab_values(self, config, tab, tab_name=''): 553 | warn = False 554 | for cm in tab.config_mechanisms: 555 | if cm.name in config: 556 | cm.setter(config[cm.name]) 557 | else: 558 | warn = True 559 | continue 560 | if warn: 561 | MainTab.logger.warning( 562 | 'Your configuration is corrupt or was generated by an old version of CPH. Expect the unexpected.' 563 | ) 564 | 565 | if isinstance(tab, OptionsTab): 566 | return 567 | 568 | MainTab.set_tab_name(tab, tab_name) 569 | # A couple hacks to avoid implementing an ItemListener just for this, 570 | # because ActionListener doesn't get triggered on setSelected() -_- 571 | # Reference: https://stackoverflow.com/questions/9882845 572 | try: 573 | tab.param_handl_forwarder_socket_pane.setVisible(config['enable_forwarder']) 574 | tab.param_handl_dynamic_pane .setVisible(config['dynamic_checkbox']) 575 | except KeyError: 576 | return 577 | 578 | def actionPerformed(self, e): 579 | c = e.getActionCommand() 580 | if c == self.BTN_QUICKLOAD or c == self.BTN_IMPORTCONFIG: 581 | replace_config_tabs = False 582 | result = 0 583 | tabcount = 0 584 | for tab in MainTab.get_config_tabs(): 585 | tabcount += 1 586 | break 587 | if tabcount > 0: 588 | result = JOptionPane.showOptionDialog( 589 | self, 590 | 'Would you like to Purge or Keep all existing tabs?', 591 | 'Existing Tabs Detected!', 592 | JOptionPane.YES_NO_CANCEL_OPTION, 593 | JOptionPane.QUESTION_MESSAGE, 594 | None, 595 | ['Purge', 'Keep', 'Cancel'], 596 | 'Cancel' 597 | ) 598 | # If purge... 599 | if result == 0: 600 | replace_config_tabs = True 601 | MainTab.logger.info('Replacing configuration...') 602 | # If not cancel or close dialog... 603 | # note: result can still be 0 here; do not use 'elif' 604 | if result != 2 and result != -1: 605 | if result != 0: 606 | MainTab.logger.info('Merging configuration...') 607 | 608 | if c == self.BTN_QUICKLOAD: 609 | try: 610 | config = loads( 611 | MainTab._cph.callbacks.loadExtensionSetting(OptionsTab.CONFIGNAME_QUICK), 612 | object_pairs_hook=odict 613 | ) 614 | self.load_config(config, replace_config_tabs) 615 | MainTab.logger.info('Configuration quickloaded.') 616 | except StandardError: 617 | MainTab.logger.exception('Error during quickload.') 618 | 619 | if c == self.BTN_IMPORTCONFIG: 620 | fc = JFileChooser() 621 | fc.setFileFilter(OptionsTab.FILEFILTER) 622 | result = fc.showOpenDialog(self) 623 | if result == JFileChooser.APPROVE_OPTION: 624 | fpath = fc.getSelectedFile().getPath() 625 | try: 626 | with open(fpath, 'r') as f: 627 | config = load(f, object_pairs_hook=odict) 628 | self.load_config(config, replace_config_tabs) 629 | MainTab.logger.info('Configuration imported from "{}".'.format(fpath)) 630 | except StandardError: 631 | MainTab.logger.exception('Error importing config from "{}".'.format(fpath)) 632 | if result == JFileChooser.CANCEL_OPTION: 633 | MainTab.logger.info('User canceled configuration import from file.') 634 | else: 635 | MainTab.logger.info('User canceled quickload/import.') 636 | 637 | if c == self.BTN_QUICKSAVE: 638 | try: 639 | full_config = self.prepare_to_save_all() 640 | MainTab._cph.callbacks.saveExtensionSetting(OptionsTab.CONFIGNAME_QUICK, dumps(full_config)) 641 | MainTab.logger.info('Configuration quicksaved.') 642 | except StandardError: 643 | MainTab.logger.exception('Error during quicksave.') 644 | 645 | if c == self.BTN_DOCS: 646 | browser_open(self.DOCS_URL) 647 | 648 | if c == self.BTN_EMV: 649 | if not self.emv.isVisible(): 650 | self.emv.pack() 651 | self.emv.setSize(800, 600) 652 | self.emv.show() 653 | # Un-minimize 654 | self.emv.setState(JFrame.NORMAL) 655 | self.emv.toFront() 656 | for emv_tab in self.emv_tab_pane.getComponents(): 657 | emv_tab.viewer.setDividerLocation(0.5) 658 | 659 | if c == self.BTN_EXPORTCONFIG: 660 | tabcount = 0 661 | for tab in MainTab.get_config_tabs(): 662 | tabcount += 1 663 | break 664 | if tabcount > 0: 665 | fc = JFileChooser() 666 | fc.setFileFilter(OptionsTab.FILEFILTER) 667 | result = fc.showSaveDialog(self) 668 | if result == JFileChooser.APPROVE_OPTION: 669 | fpath = fc.getSelectedFile().getPath() 670 | if not fpath.endswith('.json'): 671 | fpath += '.json' 672 | full_config = self.prepare_to_save_all() 673 | try: 674 | with open(fpath, 'w') as f: 675 | dump(full_config, f, indent=4, separators=(',', ': ')) 676 | MainTab.logger.info('Configuration exported to "{}".'.format(fpath)) 677 | except IOError: 678 | MainTab.logger.exception('Error exporting config to "{}".'.format(fpath)) 679 | if result == JFileChooser.CANCEL_OPTION: 680 | MainTab.logger.info('User canceled configuration export to file.') 681 | 682 | if c == self.DEMO_INACTIVE: 683 | # Start threaded HTTP server for interactive demos. 684 | try: 685 | self.httpd = ThreadedHTTPServer(('localhost', 9001), TinyHandler) 686 | except socket_error: 687 | # Port zero means any unused port. 688 | self.httpd = ThreadedHTTPServer(('localhost', 0), TinyHandler) 689 | self.httpd_thread = Thread(target=self.httpd.serve_forever) 690 | # Daemonize so that it terminates cleanly without needing to join(). 691 | self.httpd_thread.daemon = True 692 | self.httpd_thread.start() 693 | self.chkbox_demo.setText(self.DEMO_ACTIVE.format(*self.httpd.server_address)) 694 | 695 | if self.httpd is not None and c == self.DEMO_ACTIVE.format(*self.httpd.server_address): 696 | self.httpd.shutdown() 697 | self.httpd.server_close() 698 | self.httpd = None 699 | self.chkbox_demo.setText(self.DEMO_INACTIVE) 700 | 701 | 702 | def load_config(self, config, replace_config_tabs=False): 703 | loaded_tab_names = config.keys() 704 | 705 | if OptionsTab.CONFIGNAME_OPTIONS in loaded_tab_names: 706 | self.set_tab_values(config[OptionsTab.CONFIGNAME_OPTIONS], MainTab.get_options_tab()) 707 | loaded_tab_names.remove(OptionsTab.CONFIGNAME_OPTIONS) 708 | 709 | tabs_left_to_load = list(loaded_tab_names) 710 | tabs_to_remove = {} 711 | 712 | # Modify existing and mark for purge where applicable 713 | for tab_name, tab in product(loaded_tab_names, MainTab.get_config_tabs()): 714 | if tab_name == tab.namepane_txtfield.getText(): 715 | self.set_tab_values(config[tab_name], tab, tab_name) 716 | if tab_name in tabs_left_to_load: 717 | tabs_left_to_load.remove(tab_name) 718 | tabs_to_remove[tab] = False 719 | if tab not in tabs_to_remove: 720 | tabs_to_remove[tab] = True 721 | 722 | # Import and purge if applicable 723 | for tab, tab_marked in tabs_to_remove.items(): 724 | if tab_marked and replace_config_tabs: 725 | MainTab.get_options_tab().emv_tab_pane.remove(tab.emv_tab) 726 | MainTab.mainpane.remove(tab) 727 | for tab_name in tabs_left_to_load: 728 | self.set_tab_values(config[tab_name], ConfigTab(), tab_name) 729 | 730 | # No need to proceed if there's only 1 tab. 731 | # This is also the case when cloning a tab. 732 | if len(loaded_tab_names) <= 1: 733 | return 734 | 735 | # Restore tab order 736 | for tab in MainTab.get_config_tabs(): 737 | tab_name = tab.namepane_txtfield.getText() 738 | # Adding one because the Options tab is always the first tab. 739 | if tab_name in loaded_tab_names: 740 | ConfigTab.move_tab(tab, loaded_tab_names.index(tab_name) + 1) 741 | else: 742 | ConfigTab.move_tab(tab, len(loaded_tab_names) + 1) 743 | 744 | def prepare_to_save_all(self): 745 | MainTab.check_configtab_names() 746 | full_config = odict() 747 | for tab in MainTab.get_config_tabs(): 748 | full_config[tab.namepane_txtfield.getText()] = self.prepare_to_save_tab(tab) 749 | full_config[OptionsTab.CONFIGNAME_OPTIONS] = self.prepare_to_save_tab(MainTab.get_options_tab()) 750 | return full_config 751 | 752 | def prepare_to_save_tab(self, tab): 753 | config = {} 754 | for cm in tab.config_mechanisms: 755 | config[cm.name] = cm.getter() 756 | return config 757 | 758 | 759 | class EMVTab(JSplitPane, ListSelectionListener): 760 | MAX_ITEMS = 32 761 | def __init__(self): 762 | self.updating = False 763 | self.selected_index = -1 764 | 765 | self.table = JTable(self.EMVTableModel()) 766 | self.table_model = self.table.getModel() 767 | sm = self.table.getSelectionModel() 768 | sm.setSelectionMode(0) # Single selection 769 | sm.addListSelectionListener(self) 770 | 771 | table_pane = JScrollPane() 772 | table_pane.setViewportView(self.table) 773 | table_pane.getVerticalScrollBar().setUnitIncrement(16) 774 | 775 | self.diff_field = MainTab._cph.callbacks.createMessageEditor(None, False) 776 | self.original_field = MainTab._cph.callbacks.createMessageEditor(None, False) 777 | self.modified_field = MainTab._cph.callbacks.createMessageEditor(None, False) 778 | 779 | self.viewer = JSplitPane() 780 | self.viewer.setLeftComponent(self.original_field.getComponent()) 781 | self.viewer.setRightComponent(self.modified_field.getComponent()) 782 | 783 | self.diffpane = JSplitPane(JSplitPane.VERTICAL_SPLIT) 784 | # Top pane gets populated in value_changed(), below. 785 | self.diffpane.setTopComponent(JPanel()) 786 | self.diffpane.setBottomComponent(self.viewer) 787 | self.diffpane.setDividerLocation(100) 788 | 789 | viewer_pane = JPanel(GridBagLayout()) 790 | constraints = GridBagConstraints() 791 | constraints.weightx = 1 792 | constraints.weighty = 1 793 | constraints.fill = GridBagConstraints.BOTH 794 | constraints.anchor = GridBagConstraints.NORTH 795 | constraints.gridx = 0 796 | constraints.gridy = 0 797 | viewer_pane.add(self.diffpane, constraints) 798 | 799 | self.setOrientation(JSplitPane.VERTICAL_SPLIT) 800 | self.setTopComponent(table_pane) 801 | self.setBottomComponent(viewer_pane) 802 | self.setDividerLocation(100) 803 | 804 | def add_table_row(self, time, is_request, original_msg, modified_msg): 805 | if len(self.table_model.rows) == 0: 806 | self.viewer.setDividerLocation(0.5) 807 | 808 | message_type = 'Response' 809 | if is_request: 810 | message_type = 'Request' 811 | self.table_model.rows.insert( 812 | 0, 813 | [str(time)[:-3], message_type, len(modified_msg) - len(original_msg)] 814 | ) 815 | self.table_model.messages.insert( 816 | 0, 817 | self.table_model.MessagePair(original_msg, modified_msg) 818 | ) 819 | 820 | if len(self.table_model.rows) > self.MAX_ITEMS: 821 | self.table_model.rows.pop(-1) 822 | if len(self.table_model.messages) > self.MAX_ITEMS: 823 | self.table_model.messages.pop(-1) 824 | 825 | self.table_model.fireTableDataChanged() 826 | self.table.setRowSelectionInterval(0, 0) 827 | 828 | def valueChanged(self, e): 829 | # Jenky lock mechanism to prevent crash with many quickly-repeated triggers. 830 | if self.updating: 831 | return 832 | self.updating = True 833 | 834 | index = self.table.getSelectedRow() 835 | if self.selected_index == index: 836 | self.updating = False 837 | return 838 | self.selected_index = index 839 | original_msg = self.table_model.messages[index].original_msg 840 | modified_msg = self.table_model.messages[index].modified_msg 841 | 842 | diff = unified_diff(original_msg.splitlines(1), modified_msg.splitlines(1)) 843 | text = '' 844 | for line in diff: 845 | if '---' in line or '+++' in line: 846 | continue 847 | text += line 848 | if not text.endswith('\n'): 849 | text += '\n' 850 | 851 | dl = self.diffpane.getDividerLocation() 852 | is_request = self.table_model.rows[index][1] == 'Request' 853 | self.diff_field .setMessage(text , is_request) 854 | self.original_field.setMessage(original_msg, is_request) 855 | self.modified_field.setMessage(modified_msg, is_request) 856 | 857 | self.diffpane.setTopComponent(self.diff_field.getComponent().getComponentAt(0)) 858 | self.diffpane.setDividerLocation(dl) 859 | self.updating = False 860 | 861 | 862 | class EMVTableModel(AbstractTableModel): 863 | def __init__(self): 864 | super(EMVTab.EMVTableModel, self).__init__() 865 | self.MessagePair = namedtuple('MessagePair', 'original_msg, modified_msg') 866 | self.rows = [] 867 | self.messages = [] 868 | 869 | def getRowCount(self): 870 | return len(self.rows) 871 | 872 | def getColumnCount(self): 873 | return 3 874 | 875 | def getColumnName(self, columnIndex): 876 | if columnIndex == 0: 877 | return 'Time' 878 | if columnIndex == 1: 879 | return 'Type' 880 | if columnIndex == 2: 881 | return 'Length Difference' 882 | 883 | def getValueAt(self, rowIndex, columnIndex): 884 | return self.rows[rowIndex][columnIndex] 885 | 886 | def setValueAt(self, aValue, rowIndex, columnIndex): 887 | return 888 | 889 | def isCellEditable(self, rowIndex, columnIndex): 890 | return False 891 | 892 | 893 | class ConfigTabTitle(JPanel): 894 | def __init__(self): 895 | self.setBorder(BorderFactory.createEmptyBorder(-4, -5, -5, -5)) 896 | self.setOpaque(False) 897 | self.enable_chkbox = JCheckBox('', True) 898 | self.label = JLabel(ConfigTab.TAB_NEW_NAME) 899 | self.label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 4)) 900 | self.add(self.enable_chkbox) 901 | self.add(self.label) 902 | self.add(self.CloseButton()) 903 | 904 | class CloseButton(JButton, ActionListener): 905 | def __init__(self): 906 | self.setText(unichr(0x00d7)) # multiplication sign 907 | self.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 2)) 908 | SubTab.create_empty_button(self) 909 | self.addMouseListener(self.CloseButtonMouseListener()) 910 | self.addActionListener(self) 911 | 912 | def actionPerformed(self, e): 913 | MainTab.close_tab(MainTab.mainpane.indexOfTabComponent(self.getParent())) 914 | 915 | class CloseButtonMouseListener(MouseAdapter): 916 | def mouseEntered(self, e): 917 | button = e.getComponent() 918 | button.setForeground(Color.red) 919 | 920 | def mouseExited(self, e): 921 | button = e.getComponent() 922 | button.setForeground(Color.black) 923 | 924 | def mouseReleased(self, e): 925 | pass 926 | 927 | def mousePressed(self, e): 928 | pass 929 | 930 | 931 | class ConfigTabNameField(JTextField, KeyListener): 932 | def __init__(self, tab_label): 933 | self.setColumns(32) 934 | self.setText(ConfigTab.TAB_NEW_NAME) 935 | self.setFont(ConfigTab.FIELD_FONT) 936 | self.addKeyListener(self) 937 | self.tab_label = tab_label 938 | 939 | self.addKeyListener(UndoableKeyListener(self)) 940 | 941 | def keyReleased(self, e): 942 | self_index = MainTab.mainpane.getSelectedIndex() 943 | true_index = self_index - 1 # because of the Options tab 944 | self.tab_label.setText(self.getText()) 945 | MainTab.get_options_tab().emv_tab_pane.setTitleAt(true_index, self.getText()) 946 | for i, subsequent_tab in enumerate(MainTab.get_config_tabs()): 947 | if i <= true_index: 948 | continue 949 | subsequent_tab.param_handl_combo_cached.removeItemAt(true_index) 950 | subsequent_tab.param_handl_combo_cached.insertItemAt(self.getText(), true_index) 951 | 952 | def keyPressed(self, e): 953 | # Doing self._tab_label.setText() here is sub-optimal. Leave it above. 954 | pass 955 | 956 | def keyTyped(self, e): 957 | pass 958 | 959 | 960 | class UndoableKeyListener(KeyListener): 961 | REDO = 89 962 | UNDO = 90 963 | CTRL = 2 964 | def __init__(self, target): 965 | self.undomgr = UndoManager() 966 | target.getDocument().addUndoableEditListener(self.undomgr) 967 | 968 | def keyReleased(self, e): 969 | pass 970 | 971 | def keyPressed(self, e): 972 | if e.getModifiers() == self.CTRL: 973 | if e.getKeyCode() == self.UNDO and self.undomgr.canUndo(): 974 | self.undomgr.undo() 975 | if e.getKeyCode() == self.REDO and self.undomgr.canRedo(): 976 | self.undomgr.redo() 977 | 978 | def keyTyped(self, e): 979 | pass 980 | 981 | 982 | class ConfigTab(SubTab): 983 | TXT_FIELD_SIZE = 64 984 | FIELD_FONT = Font(Font.MONOSPACED, Font.PLAIN, 13) 985 | REGEX = 'RegEx' 986 | TAB_NEW_NAME = 'Unconfigured' 987 | 988 | BTN_BACK = '<' 989 | BTN_FWD = '>' 990 | BTN_CLONETAB = 'Clone' 991 | TAB_NAME = 'Friendly name:' 992 | 993 | # Scope pane 994 | MSG_MOD_GROUP = 'Scoping' 995 | MSG_MOD_SCOPE_BURP = ' Provided their URLs are within Burp Suite\'s scope,' 996 | MSG_MOD_TYPES_TO_MODIFY = 'this tab will work' 997 | 998 | MSG_MOD_COMBO_SCOPE_ALL = 'on all' 999 | MSG_MOD_COMBO_SCOPE_SOME = 'only on' 1000 | MSG_MOD_COMBO_SCOPE_CHOICES = [ 1001 | MSG_MOD_COMBO_SCOPE_ALL, 1002 | MSG_MOD_COMBO_SCOPE_SOME, 1003 | ] 1004 | MSG_MOD_COMBO_TYPE_REQ = 'requests' 1005 | MSG_MOD_COMBO_TYPE_RESP = 'responses' 1006 | MSG_MOD_COMBO_TYPE_BOTH = 'requests and responses' 1007 | MSG_MOD_COMBO_TYPE_CHOICES = [ 1008 | MSG_MOD_COMBO_TYPE_REQ , 1009 | MSG_MOD_COMBO_TYPE_RESP, 1010 | MSG_MOD_COMBO_TYPE_BOTH, 1011 | ] 1012 | MSG_MOD_SCOPE_SOME = ' containing this expression:' 1013 | 1014 | # Handling pane 1015 | PARAM_HANDL_GROUP = 'Parameter handling' 1016 | PARAM_HANDL_AUTO_ENCODE = 'Automatically URL-encode the first line of the request, if modified' 1017 | PARAM_HANDL_ENABLE_FORWARDER = 'Change the destination of the request' 1018 | PARAM_HANDL_MATCH_EXP = ' 1) Find matches to this expression:' 1019 | PARAM_HANDL_TARGET = '2) Target' 1020 | 1021 | PARAM_HANDL_COMBO_INDICES_FIRST = 'the first' 1022 | PARAM_HANDL_COMBO_INDICES_EACH = 'each' 1023 | PARAM_HANDL_COMBO_INDICES_SUBSET = 'a subset' 1024 | PARAM_HANDL_COMBO_INDICES_CHOICES = [ 1025 | PARAM_HANDL_COMBO_INDICES_FIRST , 1026 | PARAM_HANDL_COMBO_INDICES_EACH , 1027 | PARAM_HANDL_COMBO_INDICES_SUBSET, 1028 | ] 1029 | PARAM_HANDL_MATCH_RANGE = 'of the matches' 1030 | PARAM_HANDL_MATCH_SUBSET = 'Which subset?' 1031 | PARAM_HANDL_ACTION = ' 3) Replace each target with this expression:' 1032 | 1033 | PARAM_HANDL_DYNAMIC_CHECKBOX = 'The value I need is dynamic' 1034 | PARAM_HANDL_DYNAMIC_DESCRIPTION = '4) In the expression above, refer to the named RegEx groups you\'ll define below in order to insert:' 1035 | 1036 | PARAM_HANDL_COMBO_EXTRACT_SINGLE = 'values returned by issuing a single request' 1037 | PARAM_HANDL_COMBO_EXTRACT_MACRO = 'values returned by issuing a sequence of requests' 1038 | PARAM_HANDL_COMBO_EXTRACT_CACHED = 'values in the cached response of a previous CPH tab' 1039 | PARAM_HANDL_COMBO_EXTRACT_CHOICES = [ 1040 | PARAM_HANDL_COMBO_EXTRACT_SINGLE, 1041 | PARAM_HANDL_COMBO_EXTRACT_MACRO , 1042 | PARAM_HANDL_COMBO_EXTRACT_CACHED, 1043 | ] 1044 | PARAM_HANDL_BTN_ISSUE = 'Issue' 1045 | PARAM_HANDL_EXTRACT_STATIC = 'When "RegEx" is enabled here, backslash escape sequences and group references will be processed.' 1046 | PARAM_HANDL_EXTRACT_SINGLE = 'the request in the left pane, then extract the value from its response with this expression:' 1047 | PARAM_HANDL_EXTRACT_MACRO = 'When invoked from a Session Handling Rule, CPH will extract the value from the final macro response with this expression:' 1048 | PARAM_HANDL_EXTRACT_CACHED_PRE = 'Extract the value from' 1049 | PARAM_HANDL_EXTRACT_CACHED_POST = '\'s cached response with this expression:' 1050 | 1051 | EXPRESSION_CONFIG = namedtuple('EXPRESSION_CONFIG', 'is_regex, expression') 1052 | SOCKET_CONFIG = namedtuple('SOCKET_CONFIG' , 'https, host, port') 1053 | 1054 | def __init__(self, message=None): 1055 | SubTab.__init__(self) 1056 | 1057 | index = MainTab.mainpane.getTabCount() - 1 1058 | MainTab.mainpane.add(self, index) 1059 | self.tabtitle_pane = ConfigTabTitle() 1060 | MainTab.mainpane.setTabComponentAt(index, self.tabtitle_pane) 1061 | MainTab.mainpane.setSelectedIndex(index) 1062 | 1063 | btn_back = SubTab.set_title_font(JButton(self.BTN_BACK)) 1064 | btn_fwd = SubTab.set_title_font(JButton(self.BTN_FWD)) 1065 | btn_back.addActionListener(self) 1066 | btn_fwd .addActionListener(self) 1067 | 1068 | btn_clonetab = JButton(self.BTN_CLONETAB) 1069 | btn_clonetab.addActionListener(self) 1070 | 1071 | controlpane = JPanel(FlowLayout(FlowLayout.LEADING)) 1072 | controlpane.add(btn_back) 1073 | controlpane.add(btn_fwd) 1074 | controlpane.add(SubTab.create_blank_space()) 1075 | controlpane.add(btn_clonetab) 1076 | 1077 | namepane = JPanel(FlowLayout(FlowLayout.LEADING)) 1078 | namepane.add(SubTab.set_title_font(JLabel(self.TAB_NAME))) 1079 | self.namepane_txtfield = ConfigTabNameField(self.tabtitle_pane.label) 1080 | namepane.add(self.namepane_txtfield) 1081 | 1082 | msg_mod_layout_pane = JPanel(GridBagLayout()) 1083 | msg_mod_layout_pane.setBorder(BorderFactory.createTitledBorder(self.MSG_MOD_GROUP)) 1084 | msg_mod_layout_pane.getBorder().setTitleFont(Font(Font.SANS_SERIF, Font.ITALIC, 16)) 1085 | 1086 | param_handl_layout_pane = JPanel(GridBagLayout()) 1087 | param_handl_layout_pane.setBorder(BorderFactory.createTitledBorder(self.PARAM_HANDL_GROUP)) 1088 | param_handl_layout_pane.getBorder().setTitleFont(Font(Font.SANS_SERIF, Font.ITALIC, 16)) 1089 | 1090 | self.msg_mod_combo_scope = JComboBox(self.MSG_MOD_COMBO_SCOPE_CHOICES) 1091 | self.msg_mod_combo_type = JComboBox(self.MSG_MOD_COMBO_TYPE_CHOICES) 1092 | self.msg_mod_combo_scope.addActionListener(self) 1093 | self.msg_mod_combo_type .addActionListener(self) 1094 | 1095 | self.msg_mod_exp_pane_scope = self.create_expression_pane() 1096 | self.msg_mod_exp_pane_scope_lbl = JLabel(self.MSG_MOD_SCOPE_SOME) 1097 | self.msg_mod_exp_pane_scope .setVisible(False) 1098 | self.msg_mod_exp_pane_scope_lbl.setVisible(False) 1099 | 1100 | self.param_handl_auto_encode_chkbox = JCheckBox(self.PARAM_HANDL_AUTO_ENCODE , False) 1101 | self.param_handl_enable_forwarder_chkbox = JCheckBox(self.PARAM_HANDL_ENABLE_FORWARDER, False) 1102 | self.param_handl_enable_forwarder_chkbox.addActionListener(self) 1103 | 1104 | self.param_handl_forwarder_socket_pane = self.create_socket_pane() 1105 | self.param_handl_forwarder_socket_pane.setVisible(False) 1106 | 1107 | self.param_handl_exp_pane_target = self.create_expression_pane() 1108 | self.param_handl_combo_indices = JComboBox(self.PARAM_HANDL_COMBO_INDICES_CHOICES) 1109 | self.param_handl_combo_indices.addActionListener(self) 1110 | 1111 | self.param_handl_txtfield_match_indices = JTextField(12) 1112 | self.param_handl_txtfield_match_indices.addKeyListener( 1113 | UndoableKeyListener(self.param_handl_txtfield_match_indices) 1114 | ) 1115 | self.param_handl_txtfield_match_indices.setText('0') 1116 | self.param_handl_txtfield_match_indices.setEnabled(False) 1117 | 1118 | self.param_handl_button_indices_help = self.HelpButton(CPH_Help.indices) 1119 | self.param_handl_button_indices_help.addActionListener(self) 1120 | 1121 | self.param_handl_subset_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 1122 | 1123 | self.param_handl_dynamic_chkbox = JCheckBox(self.PARAM_HANDL_DYNAMIC_CHECKBOX, False) 1124 | self.param_handl_dynamic_chkbox.addActionListener(self) 1125 | 1126 | self.param_handl_dynamic_pane = JPanel(GridBagLayout()) 1127 | self.param_handl_dynamic_pane.setVisible(False) 1128 | 1129 | self.param_handl_exp_pane_extract_static = self.create_expression_pane(label=self.PARAM_HANDL_EXTRACT_STATIC, multiline=False) 1130 | self.param_handl_exp_pane_extract_single = self.create_expression_pane(enabled=False) 1131 | self.param_handl_exp_pane_extract_macro = self.create_expression_pane(label=self.PARAM_HANDL_EXTRACT_MACRO, enabled=False) 1132 | self.param_handl_exp_pane_extract_cached = self.create_expression_pane(enabled=False) 1133 | 1134 | self.param_handl_issuer_socket_pane = self.create_socket_pane() 1135 | 1136 | self.request , self.response = self.initialize_req_resp() 1137 | self.cached_request, self.cached_response = self.initialize_req_resp() 1138 | 1139 | if message: # init argument, defaults to None, set when using 'Send to CPH' 1140 | self.request = message.getRequest() 1141 | resp = message.getResponse() 1142 | if resp: 1143 | self.response = resp 1144 | httpsvc = message.getHttpService() 1145 | self.get_socket_pane_component(self.param_handl_issuer_socket_pane, self.HOST_INDEX).setText(httpsvc.getHost()) 1146 | self.get_socket_pane_component(self.param_handl_issuer_socket_pane, self.PORT_INDEX).setValue(httpsvc.getPort()) 1147 | self.get_socket_pane_component(self.param_handl_issuer_socket_pane, self.HTTPS_INDEX).setSelected(httpsvc.getProtocol() == 'https') 1148 | 1149 | self.param_handl_request_editor = MainTab._cph.callbacks.createMessageEditor(None, True) 1150 | self.param_handl_response_editor = MainTab._cph.callbacks.createMessageEditor(None, False) 1151 | self.param_handl_request_editor .setMessage(self.request , True) 1152 | self.param_handl_response_editor.setMessage(self.response, False) 1153 | 1154 | self.param_handl_cached_req_viewer = MainTab._cph.callbacks.createMessageEditor(None, False) 1155 | self.param_handl_cached_resp_viewer = MainTab._cph.callbacks.createMessageEditor(None, False) 1156 | self.param_handl_cached_req_viewer .setMessage(self.cached_request , True) 1157 | self.param_handl_cached_resp_viewer.setMessage(self.cached_response, False) 1158 | 1159 | self.param_handl_cardpanel_static_or_extract = JPanel(FlexibleCardLayout()) 1160 | 1161 | self.param_handl_combo_extract = JComboBox(self.PARAM_HANDL_COMBO_EXTRACT_CHOICES) 1162 | self.param_handl_combo_extract.addActionListener(self) 1163 | 1164 | self.param_handl_button_named_groups_help = self.HelpButton(CPH_Help.named_groups) 1165 | self.param_handl_button_named_groups_help.addActionListener(self) 1166 | 1167 | # These ones don't need ActionListeners; see actionPerformed(). 1168 | self.param_handl_button_extract_single_help = self.HelpButton(CPH_Help.extract_single) 1169 | self.param_handl_button_extract_macro_help = self.HelpButton(CPH_Help.extract_macro ) 1170 | self.param_handl_button_extract_cached_help = self.HelpButton(CPH_Help.extract_cached) 1171 | 1172 | self.param_handl_combo_cached = JComboBox() 1173 | self.param_handl_combo_cached.addActionListener(self) 1174 | 1175 | self.build_msg_mod_pane(msg_mod_layout_pane) 1176 | self.build_param_handl_pane(param_handl_layout_pane) 1177 | 1178 | if self.request: 1179 | self.param_handl_combo_extract.setSelectedItem(self.PARAM_HANDL_COMBO_EXTRACT_SINGLE) 1180 | # Using doClick() since it's initially unchecked, which means it'll get checked *and* the ActionListener will trigger. 1181 | self.param_handl_dynamic_chkbox.doClick() 1182 | 1183 | for previous_tab in MainTab.get_config_tabs(): 1184 | if previous_tab == self: 1185 | break 1186 | self.param_handl_combo_cached.addItem(previous_tab.namepane_txtfield.getText()) 1187 | 1188 | constraints = self.initialize_constraints() 1189 | constraints.weighty = 0.05 1190 | self._main_tab_pane.add(controlpane, constraints) 1191 | constraints.gridy = 1 1192 | self._main_tab_pane.add(namepane, constraints) 1193 | constraints.gridy = 2 1194 | self._main_tab_pane.add(msg_mod_layout_pane, constraints) 1195 | constraints.gridy = 3 1196 | constraints.weighty = 1 1197 | self._main_tab_pane.add(param_handl_layout_pane, constraints) 1198 | 1199 | self.emv_tab = EMVTab() 1200 | MainTab.get_options_tab().emv_tab_pane.add(self.namepane_txtfield.getText(), self.emv_tab) 1201 | 1202 | self.config_mechanisms = [ 1203 | SubTab.CONFIG_MECHANISM( 1204 | 'enabled', 1205 | self.tabtitle_pane.enable_chkbox.isSelected, 1206 | lambda cv: self.tabtitle_pane.enable_chkbox.setSelected(cv) 1207 | ), 1208 | SubTab.CONFIG_MECHANISM( 1209 | 'modify_scope_choice_index', 1210 | self.msg_mod_combo_scope.getSelectedIndex, 1211 | lambda cv: self.msg_mod_combo_scope.setSelectedIndex(cv) 1212 | ), 1213 | SubTab.CONFIG_MECHANISM( 1214 | 'modify_type_choice_index', 1215 | self.msg_mod_combo_type.getSelectedIndex, 1216 | lambda cv: self.msg_mod_combo_type.setSelectedIndex(cv) 1217 | ), 1218 | SubTab.CONFIG_MECHANISM( 1219 | 'modify_expression', 1220 | lambda : self.get_exp_pane_config(self.msg_mod_exp_pane_scope ), 1221 | lambda cv: self.set_exp_pane_config(self.msg_mod_exp_pane_scope, cv) 1222 | ), 1223 | SubTab.CONFIG_MECHANISM( 1224 | 'auto_encode', 1225 | self.param_handl_auto_encode_chkbox.isSelected, 1226 | lambda cv: self.param_handl_auto_encode_chkbox.setSelected(cv) 1227 | ), 1228 | SubTab.CONFIG_MECHANISM( 1229 | 'enable_forwarder', 1230 | self.param_handl_enable_forwarder_chkbox.isSelected, 1231 | lambda cv: self.param_handl_enable_forwarder_chkbox.setSelected(cv) 1232 | ), 1233 | SubTab.CONFIG_MECHANISM( 1234 | 'forwarder', 1235 | lambda : self.get_socket_pane_config(self.param_handl_forwarder_socket_pane ), 1236 | lambda cv: self.set_socket_pane_config(self.param_handl_forwarder_socket_pane, cv) 1237 | ), 1238 | SubTab.CONFIG_MECHANISM( 1239 | 'match_expression', 1240 | lambda : self.get_exp_pane_config(self.param_handl_exp_pane_target ), 1241 | lambda cv: self.set_exp_pane_config(self.param_handl_exp_pane_target, cv) 1242 | ), 1243 | SubTab.CONFIG_MECHANISM( 1244 | 'indices_choice_index', 1245 | self.param_handl_combo_indices.getSelectedIndex, 1246 | lambda cv: self.param_handl_combo_indices.setSelectedIndex(cv) 1247 | ), 1248 | SubTab.CONFIG_MECHANISM( 1249 | 'extract_choice_index', 1250 | self.param_handl_combo_extract.getSelectedIndex, 1251 | lambda cv: self.param_handl_combo_extract.setSelectedIndex(cv) 1252 | ), 1253 | SubTab.CONFIG_MECHANISM( 1254 | 'match_indices', 1255 | self.param_handl_txtfield_match_indices.getText, 1256 | lambda cv: self.param_handl_txtfield_match_indices.setText(cv) 1257 | ), 1258 | SubTab.CONFIG_MECHANISM( 1259 | 'static_expression', 1260 | lambda : self.get_exp_pane_config(self.param_handl_exp_pane_extract_static ), 1261 | lambda cv: self.set_exp_pane_config(self.param_handl_exp_pane_extract_static, cv) 1262 | ), 1263 | SubTab.CONFIG_MECHANISM( 1264 | 'dynamic_checkbox', 1265 | self.param_handl_dynamic_chkbox.isSelected, 1266 | lambda cv: self.param_handl_dynamic_chkbox.setSelected(cv) 1267 | ), 1268 | SubTab.CONFIG_MECHANISM( 1269 | 'single_expression', 1270 | lambda : self.get_exp_pane_config(self.param_handl_exp_pane_extract_single ), 1271 | lambda cv: self.set_exp_pane_config(self.param_handl_exp_pane_extract_single, cv) 1272 | ), 1273 | SubTab.CONFIG_MECHANISM( 1274 | 'issuer', 1275 | lambda : self.get_socket_pane_config(self.param_handl_issuer_socket_pane ), 1276 | lambda cv: self.set_socket_pane_config(self.param_handl_issuer_socket_pane, cv) 1277 | ), 1278 | SubTab.CONFIG_MECHANISM( 1279 | 'single_request', 1280 | lambda : MainTab._cph.helpers.bytesToString(self.param_handl_request_editor.getMessage()), 1281 | lambda cv: self.param_handl_request_editor.setMessage(MainTab._cph.helpers.stringToBytes(cv), True) 1282 | ), 1283 | SubTab.CONFIG_MECHANISM( 1284 | 'single_response', 1285 | lambda : MainTab._cph.helpers.bytesToString(self.param_handl_response_editor.getMessage()), 1286 | lambda cv: self.param_handl_response_editor.setMessage(MainTab._cph.helpers.stringToBytes(cv), False) 1287 | ), 1288 | SubTab.CONFIG_MECHANISM( 1289 | 'macro_expression', 1290 | lambda : self.get_exp_pane_config(self.param_handl_exp_pane_extract_macro ), 1291 | lambda cv: self.set_exp_pane_config(self.param_handl_exp_pane_extract_macro, cv) 1292 | ), 1293 | SubTab.CONFIG_MECHANISM( 1294 | 'cached_expression', 1295 | lambda : self.get_exp_pane_config(self.param_handl_exp_pane_extract_cached ), 1296 | lambda cv: self.set_exp_pane_config(self.param_handl_exp_pane_extract_cached, cv) 1297 | ), 1298 | SubTab.CONFIG_MECHANISM( 1299 | 'cached_selection', 1300 | self.param_handl_combo_cached.getSelectedItem, 1301 | lambda cv: self.param_handl_combo_cached.setSelectedItem(cv) 1302 | ), 1303 | ] 1304 | 1305 | def initialize_req_resp(self): 1306 | return [], MainTab._cph.helpers.stringToBytes(''.join([' \r\n' for i in range(6)])) 1307 | 1308 | def create_expression_pane(self, label=None, multiline=True, checked=True, enabled=True): 1309 | field = JTextArea() 1310 | if not multiline: 1311 | field = JTextField() 1312 | field.setColumns(self.TXT_FIELD_SIZE) 1313 | field.setFont(self.FIELD_FONT) 1314 | field.addKeyListener(UndoableKeyListener(field)) 1315 | 1316 | box = JCheckBox(self.REGEX, checked) 1317 | if not enabled: 1318 | box.setEnabled(False) 1319 | 1320 | child_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 1321 | child_pane.add(box) 1322 | child_pane.add(field) 1323 | 1324 | parent_pane = JPanel(GridBagLayout()) 1325 | constraints = self.initialize_constraints() 1326 | if label: 1327 | parent_pane.add(JLabel(label), constraints) 1328 | constraints.gridy += 1 1329 | parent_pane.add(child_pane, constraints) 1330 | 1331 | return parent_pane 1332 | 1333 | def create_socket_pane(self): 1334 | host_field = JTextField() 1335 | host_field.setColumns(self.TXT_FIELD_SIZE) 1336 | host_field.setText('host') 1337 | host_field.setFont(self.FIELD_FONT) 1338 | host_field.addKeyListener(UndoableKeyListener(host_field)) 1339 | 1340 | port_spinner = JSpinner(SpinnerNumberModel(80, 1, 65535, 1)) 1341 | port_spinner.setFont(self.FIELD_FONT) 1342 | port_spinner.setEditor(JSpinner.NumberEditor(port_spinner, '#')) 1343 | 1344 | socket_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 1345 | https_box = JCheckBox('HTTPS') 1346 | https_box.setSelected(False) 1347 | socket_pane.add(https_box ) 1348 | socket_pane.add(host_field ) 1349 | socket_pane.add(JLabel(':') ) 1350 | socket_pane.add(port_spinner) 1351 | 1352 | return socket_pane 1353 | 1354 | def get_exp_pane_component(self, pane, component_index): 1355 | """ 1356 | component_index values: 1357 | 0: regex checkbox 1358 | 1: expression field 1359 | See create_expression_pane() for further details 1360 | """ 1361 | comp_count = pane.getComponentCount() 1362 | if comp_count == 1: 1363 | # then there's no label and child_pane is the only component 1364 | child_pane = pane.getComponent(0) 1365 | elif comp_count == 2: 1366 | # then there is a label and child_pane is the second component 1367 | child_pane = pane.getComponent(1) 1368 | return child_pane.getComponent(component_index) 1369 | 1370 | def get_exp_pane_expression(self, pane): 1371 | expression = self.get_exp_pane_component(pane, ConfigTab.TXT_FIELD_INDEX).getText() 1372 | # If the RegEx checkbox is unchecked, run re.escape() 1373 | # in order to treat it like a literal string. 1374 | if not self.get_exp_pane_component(pane, ConfigTab.CHECKBOX_INDEX).isSelected(): 1375 | expression = re_escape(expression) 1376 | return expression 1377 | 1378 | def get_exp_pane_config(self, pane): 1379 | config = self.EXPRESSION_CONFIG( 1380 | self.get_exp_pane_component(pane, ConfigTab.CHECKBOX_INDEX).isSelected(), 1381 | self.get_exp_pane_component(pane, ConfigTab.TXT_FIELD_INDEX).getText() 1382 | ) 1383 | return config 1384 | 1385 | def set_exp_pane_config(self, pane, config): 1386 | config = self.EXPRESSION_CONFIG(*config) 1387 | self.get_exp_pane_component(pane, ConfigTab.CHECKBOX_INDEX ).setSelected(config.is_regex ) 1388 | self.get_exp_pane_component(pane, ConfigTab.TXT_FIELD_INDEX).setText (config.expression) 1389 | 1390 | def get_socket_pane_component(self, pane, component_index): 1391 | """ 1392 | indices_tuple values: 1393 | 0: https checkbox 1394 | 1: host field 1395 | 3: port spinner (2 is the ':' JLabel) 1396 | See create_socket_pane() for further details 1397 | """ 1398 | return pane.getComponent(component_index) 1399 | 1400 | def get_socket_pane_config(self, pane): 1401 | config = self.SOCKET_CONFIG( 1402 | self.get_socket_pane_component(pane, ConfigTab.HTTPS_INDEX).isSelected(), 1403 | self.get_socket_pane_component(pane, ConfigTab.HOST_INDEX ).getText (), 1404 | self.get_socket_pane_component(pane, ConfigTab.PORT_INDEX ).getValue () 1405 | ) 1406 | return config 1407 | 1408 | def set_socket_pane_config(self, pane, config): 1409 | config = self.SOCKET_CONFIG(*config) 1410 | self.get_socket_pane_component(pane, ConfigTab.HTTPS_INDEX).setSelected(config.https) 1411 | self.get_socket_pane_component(pane, ConfigTab.HOST_INDEX ).setText (config.host ) 1412 | self.get_socket_pane_component(pane, ConfigTab.PORT_INDEX ).setValue (config.port ) 1413 | 1414 | def build_msg_mod_pane(self, msg_mod_pane): 1415 | msg_mod_req_or_resp_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 1416 | msg_mod_req_or_resp_pane.add(JLabel(self.MSG_MOD_TYPES_TO_MODIFY)) 1417 | msg_mod_req_or_resp_pane.add(self.msg_mod_combo_scope) 1418 | msg_mod_req_or_resp_pane.add(self.msg_mod_combo_type) 1419 | msg_mod_req_or_resp_pane.add(self.msg_mod_exp_pane_scope_lbl) 1420 | 1421 | constraints = self.initialize_constraints() 1422 | msg_mod_pane.add(SubTab.set_title_font(JLabel(self.MSG_MOD_SCOPE_BURP)), constraints) 1423 | constraints.gridy = 1 1424 | msg_mod_pane.add(msg_mod_req_or_resp_pane, constraints) 1425 | constraints.gridy = 2 1426 | msg_mod_pane.add(self.msg_mod_exp_pane_scope, constraints) 1427 | 1428 | def build_param_handl_pane(self, param_derivation_pane): 1429 | target_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 1430 | target_pane.add(SubTab.set_title_font(JLabel(self.PARAM_HANDL_TARGET))) 1431 | target_pane.add(self.param_handl_combo_indices) 1432 | target_pane.add(SubTab.set_title_font(JLabel(self.PARAM_HANDL_MATCH_RANGE))) 1433 | 1434 | self.param_handl_subset_pane.add(JLabel(self.PARAM_HANDL_MATCH_SUBSET)) 1435 | self.param_handl_subset_pane.add(self.param_handl_txtfield_match_indices) 1436 | self.param_handl_subset_pane.add(self.param_handl_button_indices_help) 1437 | self.param_handl_subset_pane.setVisible(False) 1438 | 1439 | derive_param_single_card = JPanel(GridBagLayout()) 1440 | constraints = self.initialize_constraints() 1441 | derive_param_single_card.add(self.param_handl_issuer_socket_pane, constraints) 1442 | constraints.gridy = 1 1443 | issue_request_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 1444 | issue_request_button = JButton(self.PARAM_HANDL_BTN_ISSUE) 1445 | issue_request_button.addActionListener(self) 1446 | issue_request_pane.add(issue_request_button) 1447 | issue_request_pane.add(JLabel(self.PARAM_HANDL_EXTRACT_SINGLE)) 1448 | derive_param_single_card.add(issue_request_pane, constraints) 1449 | constraints.gridy = 2 1450 | derive_param_single_card.add(self.param_handl_exp_pane_extract_single, constraints) 1451 | constraints.gridy = 3 1452 | constraints.gridwidth = 2 1453 | splitpane = JSplitPane() 1454 | splitpane.setLeftComponent (self.param_handl_request_editor .getComponent()) 1455 | splitpane.setRightComponent(self.param_handl_response_editor.getComponent()) 1456 | derive_param_single_card.add(splitpane, constraints) 1457 | splitpane.setDividerLocation(0.5) 1458 | 1459 | derive_param_macro_card = JPanel(GridBagLayout()) 1460 | constraints = self.initialize_constraints() 1461 | derive_param_macro_card.add(self.param_handl_exp_pane_extract_macro, constraints) 1462 | 1463 | cached_param_card = JPanel(GridBagLayout()) 1464 | constraints = self.initialize_constraints() 1465 | tab_choice_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 1466 | tab_choice_pane.add(JLabel(self.PARAM_HANDL_EXTRACT_CACHED_PRE)) 1467 | tab_choice_pane.add(self.param_handl_combo_cached) 1468 | tab_choice_pane.add(JLabel(self.PARAM_HANDL_EXTRACT_CACHED_POST)) 1469 | cached_param_card.add(tab_choice_pane, constraints) 1470 | constraints.gridy = 1 1471 | cached_param_card.add(self.param_handl_exp_pane_extract_cached, constraints) 1472 | constraints.gridy = 2 1473 | constraints.gridwidth = 2 1474 | splitpane = JSplitPane() 1475 | splitpane.setLeftComponent (self.param_handl_cached_req_viewer .getComponent()) 1476 | splitpane.setRightComponent(self.param_handl_cached_resp_viewer.getComponent()) 1477 | cached_param_card.add(splitpane, constraints) 1478 | splitpane.setDividerLocation(0.5) 1479 | 1480 | self.param_handl_cardpanel_static_or_extract.add(derive_param_single_card, self.PARAM_HANDL_COMBO_EXTRACT_SINGLE) 1481 | self.param_handl_cardpanel_static_or_extract.add(derive_param_macro_card , self.PARAM_HANDL_COMBO_EXTRACT_MACRO ) 1482 | self.param_handl_cardpanel_static_or_extract.add(cached_param_card , self.PARAM_HANDL_COMBO_EXTRACT_CACHED) 1483 | 1484 | # Making a FlowLayout panel here so the combo box doesn't stretch. 1485 | combo_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 1486 | combo_pane.add(self.param_handl_combo_extract) 1487 | placeholder_btn = self.HelpButton() 1488 | placeholder_btn.addActionListener(self) 1489 | combo_pane.add(placeholder_btn) 1490 | combo_pane.add(SubTab.create_blank_space()) 1491 | constraints = self.initialize_constraints() 1492 | dyn_desc_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 1493 | dyn_desc_pane.add(SubTab.set_title_font(JLabel(self.PARAM_HANDL_DYNAMIC_DESCRIPTION))) 1494 | dyn_desc_pane.add(self.param_handl_button_named_groups_help) 1495 | self.param_handl_dynamic_pane.add(dyn_desc_pane, constraints) 1496 | constraints.gridy = 1 1497 | self.param_handl_dynamic_pane.add(combo_pane, constraints) 1498 | constraints.gridy = 2 1499 | constraints.gridwidth = GridBagConstraints.REMAINDER - 1 1500 | self.param_handl_dynamic_pane.add(self.param_handl_cardpanel_static_or_extract, constraints) 1501 | 1502 | constraints = self.initialize_constraints() 1503 | param_derivation_pane.add(self.param_handl_auto_encode_chkbox, constraints) 1504 | constraints.gridy = 1 1505 | param_derivation_pane.add(self.param_handl_enable_forwarder_chkbox, constraints) 1506 | constraints.gridy = 2 1507 | param_derivation_pane.add(self.param_handl_forwarder_socket_pane, constraints) 1508 | constraints.gridy = 3 1509 | param_derivation_pane.add(SubTab.set_title_font(JLabel(self.PARAM_HANDL_MATCH_EXP)), constraints) 1510 | constraints.gridy = 4 1511 | param_derivation_pane.add(self.param_handl_exp_pane_target, constraints) 1512 | constraints.gridy = 5 1513 | param_derivation_pane.add(target_pane, constraints) 1514 | constraints.gridy = 6 1515 | param_derivation_pane.add(self.param_handl_subset_pane, constraints) 1516 | constraints.gridy = 7 1517 | param_derivation_pane.add(SubTab.set_title_font(JLabel(self.PARAM_HANDL_ACTION)), constraints) 1518 | constraints.gridy = 8 1519 | param_derivation_pane.add(self.param_handl_exp_pane_extract_static, constraints) 1520 | constraints.gridy = 9 1521 | param_derivation_pane.add(self.param_handl_dynamic_chkbox, constraints) 1522 | constraints.gridy = 10 1523 | param_derivation_pane.add(self.param_handl_dynamic_pane, constraints) 1524 | 1525 | @staticmethod 1526 | def restore_combo_cached_selection(tab, selected_item): 1527 | tab.param_handl_combo_cached.setSelectedItem(selected_item) 1528 | # If the item has been removed, remove selection. 1529 | if tab.param_handl_combo_cached.getSelectedItem() != selected_item: 1530 | tab.param_handl_combo_cached.setSelectedItem(None) 1531 | if tab.param_handl_combo_extract.getSelectedItem() == ConfigTab.PARAM_HANDL_COMBO_EXTRACT_CACHED: 1532 | MainTab.logger.warning( 1533 | 'Selected cache no longer available for tab "{}"!'.format(tab.namepane_txtfield.getText()) 1534 | ) 1535 | 1536 | @staticmethod 1537 | def move_tab(tab, desired_index): 1538 | # The Options tab is index 0, hence subtracting 1 in a number of lines below. 1539 | if desired_index <= 0 or desired_index >= MainTab.mainpane.getTabCount() - 1: 1540 | return 1541 | 1542 | MainTab.mainpane.setSelectedIndex(0) 1543 | emv_sel_tab = MainTab.get_options_tab().emv_tab_pane.getSelectedComponent() 1544 | current_index = MainTab.mainpane.indexOfComponent(tab) 1545 | combo_cached_item = tab.param_handl_combo_cached.getSelectedItem() 1546 | 1547 | if current_index > desired_index: 1548 | MainTab.mainpane.add(tab, desired_index) 1549 | MainTab.get_options_tab().emv_tab_pane.add(tab.emv_tab, desired_index - 1) 1550 | # Rearrange combo_cached appropriately. 1551 | for i, other_tab in enumerate(MainTab.get_config_tabs()): 1552 | if i < desired_index - 1: 1553 | continue 1554 | selected_item = other_tab.param_handl_combo_cached.getSelectedItem() 1555 | if i > desired_index - 1 and i <= current_index - 1: 1556 | tab.param_handl_combo_cached.removeItemAt(tab.param_handl_combo_cached.getItemCount() - 1) 1557 | other_tab.param_handl_combo_cached.insertItemAt(tab.namepane_txtfield.getText(), desired_index - 1) 1558 | if i > current_index - 1: 1559 | other_tab.param_handl_combo_cached.removeItemAt(current_index - 1) 1560 | other_tab.param_handl_combo_cached.insertItemAt(tab.namepane_txtfield.getText(), desired_index - 1) 1561 | ConfigTab.restore_combo_cached_selection(other_tab, selected_item) 1562 | 1563 | else: 1564 | # I've no idea why +1 is needed here. =) 1565 | MainTab.mainpane.add(tab, desired_index + 1) 1566 | MainTab.get_options_tab().emv_tab_pane.add(tab.emv_tab, desired_index) 1567 | # Rearrange combo_cached appropriately. 1568 | for i, other_tab in enumerate(MainTab.get_config_tabs()): 1569 | if i < current_index - 1: 1570 | continue 1571 | selected_item = other_tab.param_handl_combo_cached.getSelectedItem() 1572 | if i < desired_index - 1: 1573 | tab.param_handl_combo_cached.insertItemAt(other_tab.namepane_txtfield.getText(), i) 1574 | other_tab.param_handl_combo_cached.removeItemAt(current_index - 1) 1575 | if i > desired_index - 1: 1576 | other_tab.param_handl_combo_cached.removeItemAt(current_index - 1) 1577 | other_tab.param_handl_combo_cached.insertItemAt(tab.namepane_txtfield.getText(), desired_index - 1) 1578 | ConfigTab.restore_combo_cached_selection(other_tab, selected_item) 1579 | 1580 | MainTab.mainpane.setTabComponentAt(desired_index, tab.tabtitle_pane) 1581 | MainTab.mainpane.setSelectedIndex (desired_index) 1582 | MainTab.get_options_tab().emv_tab_pane.setTitleAt( 1583 | desired_index - 1, 1584 | tab.namepane_txtfield.getText() 1585 | ) 1586 | MainTab.get_options_tab().emv_tab_pane.setSelectedComponent(emv_sel_tab) 1587 | ConfigTab.restore_combo_cached_selection(tab, combo_cached_item) 1588 | 1589 | @staticmethod 1590 | def move_tab_back(tab): 1591 | desired_index = MainTab.mainpane.getSelectedIndex() - 1 1592 | ConfigTab.move_tab(tab, desired_index) 1593 | 1594 | @staticmethod 1595 | def move_tab_fwd(tab): 1596 | desired_index = MainTab.mainpane.getSelectedIndex() + 1 1597 | ConfigTab.move_tab(tab, desired_index) 1598 | 1599 | def clone_tab(self): 1600 | desired_index = MainTab.mainpane.getSelectedIndex() + 1 1601 | 1602 | newtab = ConfigTab() 1603 | MainTab.set_tab_name(newtab, self.namepane_txtfield.getText()) 1604 | config = MainTab.get_options_tab().prepare_to_save_tab(self) 1605 | MainTab.get_options_tab().load_config({self.namepane_txtfield.getText(): config}) 1606 | 1607 | ConfigTab.move_tab(newtab, desired_index) 1608 | 1609 | # def disable_cache_viewers(self): 1610 | # self.cached_request, self.cached_response = self.initialize_req_resp() 1611 | # self.param_handl_cached_req_viewer .setMessage(self.cached_request , False) 1612 | # self.param_handl_cached_resp_viewer.setMessage(self.cached_response, False) 1613 | 1614 | # @staticmethod 1615 | # def disable_all_cache_viewers(): 1616 | # for tab in MainTab.mainpane.getComponents(): 1617 | # if isinstance(tab, ConfigTab): 1618 | # tab.disable_cache_viewers() 1619 | 1620 | def actionPerformed(self, e): 1621 | c = e.getActionCommand() 1622 | 1623 | if c == self.BTN_HELP: 1624 | source = e.getSource() 1625 | if hasattr(source, 'title') and source.title: 1626 | source.show_help() 1627 | else: 1628 | # The dynamic help button (placeholder_btn) has no title, 1629 | # so use the selected combobox item to show the appropriate help message. 1630 | extract_combo_selection = self.param_handl_combo_extract.getSelectedItem() 1631 | if extract_combo_selection == self.PARAM_HANDL_COMBO_EXTRACT_SINGLE: 1632 | self.param_handl_button_extract_single_help.show_help() 1633 | if extract_combo_selection == self.PARAM_HANDL_COMBO_EXTRACT_MACRO: 1634 | self.param_handl_button_extract_macro_help.show_help() 1635 | if extract_combo_selection == self.PARAM_HANDL_COMBO_EXTRACT_CACHED: 1636 | self.param_handl_button_extract_cached_help.show_help() 1637 | 1638 | if c == 'comboBoxChanged': 1639 | c = e.getSource().getSelectedItem() 1640 | 1641 | if c == self.MSG_MOD_COMBO_TYPE_RESP: 1642 | self.param_handl_auto_encode_chkbox .setVisible(False) 1643 | self.param_handl_enable_forwarder_chkbox.setVisible(False) 1644 | self.param_handl_forwarder_socket_pane .setVisible(False) 1645 | elif c == self.MSG_MOD_COMBO_TYPE_REQ or c == self.MSG_MOD_COMBO_TYPE_BOTH: 1646 | self.param_handl_auto_encode_chkbox .setVisible(True) 1647 | self.param_handl_enable_forwarder_chkbox.setVisible(True) 1648 | self.param_handl_forwarder_socket_pane .setVisible(self.param_handl_enable_forwarder_chkbox.isSelected()) 1649 | 1650 | if c == self.MSG_MOD_COMBO_SCOPE_ALL: 1651 | self.msg_mod_exp_pane_scope_lbl.setVisible(False) 1652 | self.msg_mod_exp_pane_scope.setVisible(False) 1653 | if c == self.MSG_MOD_COMBO_SCOPE_SOME: 1654 | self.msg_mod_exp_pane_scope_lbl.setVisible(True) 1655 | self.msg_mod_exp_pane_scope.setVisible(True) 1656 | 1657 | if c == self.PARAM_HANDL_ENABLE_FORWARDER: 1658 | self.param_handl_forwarder_socket_pane.setVisible(self.param_handl_enable_forwarder_chkbox.isSelected()) 1659 | 1660 | if c == self.PARAM_HANDL_COMBO_INDICES_FIRST: 1661 | self.param_handl_txtfield_match_indices.setEnabled(False) 1662 | self.param_handl_txtfield_match_indices.setText('0') 1663 | self.param_handl_subset_pane.setVisible(False) 1664 | if c == self.PARAM_HANDL_COMBO_INDICES_EACH: 1665 | self.param_handl_txtfield_match_indices.setEnabled(False) 1666 | self.param_handl_txtfield_match_indices.setText('0:-1,-1') 1667 | self.param_handl_subset_pane.setVisible(False) 1668 | if c == self.PARAM_HANDL_COMBO_INDICES_SUBSET: 1669 | self.param_handl_txtfield_match_indices.setEnabled(True) 1670 | self.param_handl_subset_pane.setVisible(True) 1671 | 1672 | if c == self.PARAM_HANDL_DYNAMIC_CHECKBOX: 1673 | is_selected = self.param_handl_dynamic_chkbox.isSelected() 1674 | self.param_handl_dynamic_pane.setVisible(is_selected) 1675 | 1676 | if c in self.PARAM_HANDL_COMBO_EXTRACT_CHOICES: 1677 | SubTab.show_card(self.param_handl_cardpanel_static_or_extract, c) 1678 | 1679 | # Set the cached request/response viewers to the selected tab's cache 1680 | if e.getSource() == self.param_handl_combo_cached: 1681 | if c is None: 1682 | req, resp = self.initialize_req_resp() 1683 | if c in MainTab.get_config_tab_names(): 1684 | req, resp = MainTab.get_config_tab_cache(c) 1685 | self.param_handl_cached_req_viewer .setMessage(req , True) 1686 | self.param_handl_cached_resp_viewer.setMessage(resp, False) 1687 | 1688 | if c == self.PARAM_HANDL_BTN_ISSUE: 1689 | start_new_thread(MainTab._cph.issue_request, (self,)) 1690 | 1691 | if c == self.BTN_BACK: 1692 | ConfigTab.move_tab_back(self) 1693 | if c == self.BTN_FWD: 1694 | ConfigTab.move_tab_fwd(self) 1695 | if c == self.BTN_CLONETAB: 1696 | self.clone_tab() 1697 | 1698 | 1699 | class FlexibleCardLayout(CardLayout): 1700 | def __init__(self): 1701 | super(FlexibleCardLayout, self).__init__() 1702 | 1703 | def preferredLayoutSize(self, parent): 1704 | current = FlexibleCardLayout.find_current_component(parent) 1705 | if current: 1706 | insets = parent.getInsets() 1707 | pref = current.getPreferredSize() 1708 | pref.width += insets.left + insets.right 1709 | pref.height += insets.top + insets.bottom 1710 | return pref 1711 | return super.preferredLayoutSize(parent) 1712 | 1713 | @staticmethod 1714 | def find_current_component(parent): 1715 | for comp in parent.getComponents(): 1716 | if comp.isVisible(): 1717 | return comp 1718 | return None 1719 | 1720 | -------------------------------------------------------------------------------- /CPH_Help.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | 4 | class CPH_Help: 5 | quickstart = """ 6 | The quicksave and quickload functionality (see buttons above) persist through
7 | reloading not only the extension, but Burp Suite entirely. All values of each existing
8 | configuration tab will be saved, along with the order of all tabs.
9 |
10 | Use the Export/Import Config buttons to save/load your current configuration to/from a JSON file.
11 |
12 |

Adding configuration tabs

13 |   - Click '+' to add an empty tab; or
14 |   - Select one or many requests from anywhere in Burp, right-click, and choose 'Send to CPH'.
15 |     This will create as many tabs as the number of selected requests, and populate each tab
16 |     with each selected request to be issued for parameter extraction from its response.
17 |
18 |

Enabling/Disabling configuration tabs

19 |     Simply click the checkbox next to the tab's name.
20 |     New tabs are enabled by default but require a valid configuration in order to have any effect.
21 |
22 |

Tab order

23 |     Leftmost tabs will be processed first; therefore, tab order may be important,
24 |     especially when extracting values from cached responses.
25 |     Please visit the Wiki to learn more about utilizing cached responses.
26 | """ 27 | 28 | HelpPopup = namedtuple('HelpPopup', 'title, message, url') 29 | 30 | indices = HelpPopup( 31 | 'Targeting a subset of matches', 32 | """ 33 | To target a specific subset of matches,
34 | enter comma-separated indices and/or slices, such as:
35 | 0,3,5,7 - targets the 1st, 4th, 6th and 8th matches
36 | 0:7     - targets the first 7 matches but not the 8th match
37 | 0:7,9   - targets the first 7 matches and the 10th match
38 | -1,-2   - targets the last and penultimate matches
39 | 0:-1    - targets all but the last match 40 | """, 41 | 'https://github.com/elespike/burp-cph/wiki/04.-Targeting-matches' 42 | ) 43 | 44 | named_groups = HelpPopup( 45 | 'Inserting a dynamic value using named groups', 46 | """ 47 | In the expression field shown in step 4,
48 | define named groups for values you wish to extract
49 | from the appropriate response.
50 |
51 | For example, (?P<mygroup>[Ss]ome.[Rr]eg[Ee]x)
52 |
53 | Then, in the expression field shown in step 3,
54 | ensure that the RegEx box is selected,
55 | and use named group references to access your extracted values.
56 |
57 | In line with the above example, \\g<mygroup> 58 | """, 59 | 'https://github.com/elespike/burp-cph/wiki/05.-Issuing-a-separate-request-to-use-a-dynamic-value-from-its-response' 60 | ) 61 | 62 | extract_single = HelpPopup( 63 | 'Extracting a value after issuing a request', 64 | """ 65 | To replace your target match(es) with a value
66 | or append a value to your target match(es) when
67 | that value depends on another request to be issued,
68 | set up the request on the left pane and craft a RegEx
69 | to extract the desired value from its response.
70 |
71 | The Issue button may be used to test the request,
72 | helping ensure a proper response. 73 | """, 74 | 'https://github.com/elespike/burp-cph/wiki/05.-Issuing-a-separate-request-to-use-a-dynamic-value-from-its-response' 75 | ) 76 | 77 | extract_macro = HelpPopup( 78 | 'Extracting a value after issuing sequential requests', 79 | """ 80 | To replace your target match(es) with a value
81 | or append a value to your target match(es) when
82 | that value depends on sequential requests to be issued,
83 | set up a Burp Suite Macro and invoke the CPH handler
84 | from the Macro's associated Session Handling Rule.
85 |
86 | Finally, craft a RegEx to extract the desired value
87 | from the final Macro response. 88 | """, 89 | 'https://github.com/elespike/burp-cph/wiki/07.-Extracting-replace-value-from-final-macro-response' 90 | ) 91 | 92 | extract_cached = HelpPopup( 93 | 'Extracting a value from a previous tab', 94 | """ 95 | To replace your target match(es) with a value
96 | or append a value to your target match(es) when
97 | that value has been cached by a previous CPH tab,
98 | simply select the desired tab from the dynamic drop-down.
99 | NOTE: If the desired tab is not in the drop-down, ensure
100 | that the tab has seen its request at least once.

101 |
102 | Then, craft a RegEx to extract the desired value
103 | from the selected tab's cached response.
104 | Note that disabled tabs will still cache HTTP messages
105 | and therefore can be used as a mechanism for value extraction. 106 | """, 107 | 'https://github.com/elespike/burp-cph/wiki/08.-Utilizing-cached-responses' 108 | ) 109 | 110 | def __init__(self): 111 | pass 112 | 113 | -------------------------------------------------------------------------------- /CustomParamHandler.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from datetime import datetime as dt 4 | from sys import stdout 5 | from urllib import quote 6 | 7 | from logging import ( 8 | Formatter , 9 | INFO , 10 | StreamHandler, 11 | getLogger , 12 | ) 13 | 14 | from CPH_Config import MainTab 15 | 16 | from burp import IBurpExtender 17 | from burp import IContextMenuFactory 18 | from burp import IExtensionStateListener 19 | from burp import IHttpListener 20 | from burp import ISessionHandlingAction 21 | 22 | from javax.swing import JMenuItem 23 | 24 | 25 | class BurpExtender(IBurpExtender, IContextMenuFactory, IExtensionStateListener, IHttpListener, ISessionHandlingAction): 26 | def __init__(self): 27 | self.messages_to_send = [] 28 | self.final_macro_resp = '' 29 | 30 | self.logger = getLogger(__name__) 31 | self.initialize_logger() 32 | 33 | self.maintab = MainTab(self) 34 | 35 | def initialize_logger(self): 36 | fmt = '\n%(asctime)s:%(msecs)03d [%(levelname)s] %(message)s' 37 | datefmt = '%H:%M:%S' 38 | formatter = Formatter(fmt=fmt, datefmt=datefmt) 39 | 40 | handler = StreamHandler(stream=stdout) 41 | handler.setFormatter(formatter) 42 | 43 | self.logger.addHandler(handler) 44 | self.logger.setLevel(INFO) 45 | 46 | def registerExtenderCallbacks(self, callbacks): 47 | self.callbacks = callbacks 48 | self.helpers = callbacks.getHelpers() 49 | callbacks.setExtensionName('Custom Parameter Handler') 50 | callbacks.registerContextMenuFactory(self) 51 | callbacks.registerExtensionStateListener(self) 52 | callbacks.registerHttpListener(self) 53 | callbacks.registerSessionHandlingAction(self) 54 | callbacks.addSuiteTab(self.maintab) 55 | 56 | def getActionName(self): 57 | return 'CPH: extract replace value from the final macro response' 58 | 59 | def performAction(self, currentRequest, macroItems): 60 | if not macroItems: 61 | self.logger.error('No macro found, or macro is empty!') 62 | return 63 | self.final_macro_resp = self.helpers.bytesToString(macroItems[-1].getResponse()) 64 | 65 | def createMenuItems(self, invocation): 66 | context = invocation.getInvocationContext() 67 | if context == invocation.CONTEXT_MESSAGE_EDITOR_REQUEST \ 68 | or context == invocation.CONTEXT_MESSAGE_VIEWER_REQUEST \ 69 | or context == invocation.CONTEXT_PROXY_HISTORY \ 70 | or context == invocation.CONTEXT_TARGET_SITE_MAP_TABLE \ 71 | or context == invocation.CONTEXT_SEARCH_RESULTS: 72 | self.messages_to_send = invocation.getSelectedMessages() 73 | if len(self.messages_to_send): 74 | return [JMenuItem('Send to CPH', actionPerformed=self.send_to_cph)] 75 | else: 76 | return None 77 | 78 | def send_to_cph(self, e): 79 | self.maintab.add_config_tab(self.messages_to_send) 80 | 81 | def extensionUnloaded(self): 82 | if self.maintab.options_tab.httpd is not None: 83 | self.maintab.options_tab.httpd.shutdown() 84 | self.maintab.options_tab.httpd.server_close() 85 | try: 86 | while self.maintab.options_tab.emv_tab_pane.getTabCount(): 87 | self.maintab.options_tab.emv_tab_pane.remove( 88 | self.maintab.options_tab.emv_tab_pane.getTabCount() - 1 89 | ) 90 | self.maintab.options_tab.emv.dispose() 91 | except AttributeError: 92 | self.logger.warning( 93 | 'Effective Modification Viewer not found! You may be using an outdated version of CPH!' 94 | ) 95 | 96 | while self.maintab.mainpane.getTabCount(): 97 | # For some reason, the last tab isn't removed until the next loop, 98 | # hence the try/except block with just a continue. Thx, Java. 99 | try: 100 | self.maintab.mainpane.remove( 101 | self.maintab.mainpane.getTabCount() - 1 102 | ) 103 | except: 104 | continue 105 | 106 | def issue_request(self, tab): 107 | tab.request = tab.param_handl_request_editor.getMessage() 108 | 109 | issuer_config = tab.get_socket_pane_config(tab.param_handl_issuer_socket_pane) 110 | host = issuer_config.host 111 | port = issuer_config.port 112 | https = issuer_config.https 113 | 114 | tab.request = self.update_content_length(tab.request, True) 115 | tab.param_handl_request_editor.setMessage(tab.request, True) 116 | 117 | try: 118 | httpsvc = self.helpers.buildHttpService(host, port, https) 119 | response_bytes = self.callbacks.makeHttpRequest(httpsvc, tab.request).getResponse() 120 | self.logger.debug('Issued configured request from tab "{}" to host "{}:{}"'.format( 121 | tab.namepane_txtfield.getText(), 122 | httpsvc.getHost(), 123 | httpsvc.getPort() 124 | )) 125 | if response_bytes: 126 | tab.param_handl_response_editor.setMessage(response_bytes, False) 127 | tab.response = response_bytes 128 | self.logger.debug('Got response!') 129 | # Generic except because misc. Java exceptions might occur. 130 | except: 131 | self.logger.exception('Error issuing configured request from tab "{}" to host "{}:{}"'.format( 132 | tab.namepane_txtfield.getText(), 133 | host, 134 | port 135 | )) 136 | tab.response = self.helpers.stringToBytes('Error! See extension output for details.') 137 | tab.param_handl_response_editor.setMessage(tab.response, False) 138 | 139 | def update_content_length(self, message_bytes, is_request): 140 | if is_request: 141 | message_info = self.helpers.analyzeRequest(message_bytes) 142 | else: 143 | message_info = self.helpers.analyzeResponse(message_bytes) 144 | 145 | content_length = len(message_bytes) - message_info.getBodyOffset() 146 | msg_as_string = self.helpers.bytesToString(message_bytes) 147 | msg_as_string = re.sub( 148 | 'Content-Length: \d+\r\n', 149 | 'Content-Length: {}\r\n'.format(content_length), 150 | msg_as_string, 151 | 1 152 | ) 153 | return self.helpers.stringToBytes(msg_as_string) 154 | 155 | def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo): 156 | dbg_skip_tool = 'Skipping message received from {} on account of global tool scope options.' 157 | if toolFlag == self.callbacks.TOOL_PROXY: 158 | if not self.maintab.options_tab.chkbox_proxy.isSelected(): 159 | self.logger.debug(dbg_skip_tool.format('Proxy')) 160 | return 161 | elif toolFlag == self.callbacks.TOOL_TARGET: 162 | if not self.maintab.options_tab.chkbox_target.isSelected(): 163 | self.logger.debug(dbg_skip_tool.format('Target')) 164 | return 165 | elif toolFlag == self.callbacks.TOOL_SPIDER: 166 | if not self.maintab.options_tab.chkbox_spider.isSelected(): 167 | self.logger.debug(dbg_skip_tool.format('Spider')) 168 | return 169 | elif toolFlag == self.callbacks.TOOL_REPEATER: 170 | if not self.maintab.options_tab.chkbox_repeater.isSelected(): 171 | self.logger.debug(dbg_skip_tool.format('Repeater')) 172 | return 173 | elif toolFlag == self.callbacks.TOOL_SEQUENCER: 174 | if not self.maintab.options_tab.chkbox_sequencer.isSelected(): 175 | self.logger.debug(dbg_skip_tool.format('Sequencer')) 176 | return 177 | elif toolFlag == self.callbacks.TOOL_INTRUDER: 178 | if not self.maintab.options_tab.chkbox_intruder.isSelected(): 179 | self.logger.debug(dbg_skip_tool.format('Intruder')) 180 | return 181 | elif toolFlag == self.callbacks.TOOL_SCANNER: 182 | if not self.maintab.options_tab.chkbox_scanner.isSelected(): 183 | self.logger.debug(dbg_skip_tool.format('Scanner')) 184 | return 185 | elif toolFlag == self.callbacks.TOOL_EXTENDER: 186 | if not self.maintab.options_tab.chkbox_extender.isSelected(): 187 | self.logger.debug(dbg_skip_tool.format('Extender')) 188 | return 189 | else: 190 | self.logger.debug('Skipping message received from unsupported Burp tool.') 191 | return 192 | 193 | requestinfo = self.helpers.analyzeRequest(messageInfo) 194 | requesturl = requestinfo.getUrl() 195 | 196 | if not self.callbacks.isInScope(requesturl): 197 | return 198 | 199 | # Leave these out of the 'if' statement; the 'else' needs req_as_string. 200 | request_bytes = messageInfo.getRequest() 201 | req_as_string = self.helpers.bytesToString(request_bytes) 202 | if messageIsRequest: 203 | original_req = req_as_string 204 | for tab in self.maintab.get_config_tabs(): 205 | if request_bytes == tab.request: 206 | continue 207 | 208 | if tab.tabtitle_pane.enable_chkbox.isSelected() \ 209 | and self.is_in_cph_scope(req_as_string, messageIsRequest, tab): 210 | 211 | self.logger.info('Sending request to tab "{}" for modification'.format( 212 | tab.namepane_txtfield.getText() 213 | )) 214 | 215 | req_as_string = self.modify_message(tab, req_as_string) 216 | if req_as_string != original_req: 217 | if tab.param_handl_auto_encode_chkbox.isSelected(): 218 | # URL-encode the first line of the request, since it was modified 219 | first_req_line_old = req_as_string.split('\r\n')[0] 220 | self.logger.debug('first_req_line_old:\n{}'.format(first_req_line_old)) 221 | first_req_line_old = first_req_line_old.split(' ') 222 | first_req_line_new = '{} {} {}'.format( 223 | first_req_line_old[0], 224 | ''.join([quote(char, safe='/%+=?&') for char in '%20'.join(first_req_line_old[1:-1])]), 225 | first_req_line_old[-1] 226 | ) 227 | self.logger.debug('first_req_line_new:\n{}'.format(first_req_line_new)) 228 | req_as_string = req_as_string.replace( 229 | ' '.join(first_req_line_old), 230 | first_req_line_new 231 | ) 232 | self.logger.debug('Resulting first line of request:\n{}'.format( 233 | req_as_string.split('\r\n')[0] 234 | )) 235 | 236 | request_bytes = self.helpers.stringToBytes(req_as_string) 237 | 238 | forwarder_config = tab.get_socket_pane_config(tab.param_handl_forwarder_socket_pane) 239 | host = forwarder_config.host 240 | port = forwarder_config.port 241 | https = forwarder_config.https 242 | 243 | # Need to update content-length. 244 | request_bytes = self.update_content_length(request_bytes, messageIsRequest) 245 | req_as_string = self.helpers.bytesToString(request_bytes) 246 | 247 | if req_as_string != original_req: 248 | tab.emv_tab.add_table_row(dt.now().time(), True, original_req, req_as_string) 249 | 250 | if tab.param_handl_enable_forwarder_chkbox.isSelected(): 251 | try: 252 | messageInfo.setHttpService(self.helpers.buildHttpService(host, int(port), https)) 253 | httpsvc = messageInfo.getHttpService() 254 | self.logger.info('Tab "{}" is re-routing its request to "{}:{}"'.format( 255 | tab.namepane_txtfield.getText(), 256 | httpsvc.getHost(), 257 | httpsvc.getPort() 258 | )) 259 | # Generic except because misc. Java exceptions might occur. 260 | except: 261 | self.logger.exception('Error re-routing request:') 262 | 263 | messageInfo.setRequest(request_bytes) 264 | 265 | if not messageIsRequest: 266 | response_bytes = messageInfo.getResponse() 267 | resp_as_string = self.helpers.bytesToString(response_bytes) 268 | original_resp = resp_as_string 269 | 270 | for tab in self.maintab.get_config_tabs(): 271 | if tab.tabtitle_pane.enable_chkbox.isSelected() \ 272 | and self.is_in_cph_scope(resp_as_string, messageIsRequest, tab): 273 | 274 | self.logger.info('Sending response to tab "{}" for modification'.format( 275 | tab.namepane_txtfield.getText() 276 | )) 277 | 278 | resp_as_string = self.modify_message(tab, resp_as_string) 279 | response_bytes = self.helpers.stringToBytes(resp_as_string) 280 | response_bytes = self.update_content_length(response_bytes, messageIsRequest) 281 | resp_as_string = self.helpers.bytesToString(response_bytes) 282 | 283 | if resp_as_string != original_resp: 284 | tab.emv_tab.add_table_row(dt.now().time(), False, original_resp, resp_as_string) 285 | 286 | messageInfo.setResponse(response_bytes) 287 | 288 | for working_tab in self.maintab.get_config_tabs(): 289 | selected_item = working_tab.param_handl_combo_cached.getSelectedItem() 290 | if self.is_in_cph_scope(req_as_string , True , working_tab)\ 291 | or self.is_in_cph_scope(resp_as_string, False, working_tab): 292 | working_tab.cached_request = request_bytes 293 | working_tab.cached_response = response_bytes 294 | self.logger.debug('Messages cached for tab {}!'.format( 295 | working_tab.namepane_txtfield.getText() 296 | )) 297 | # If this tab is set to extract a value from one of the previous tabs, 298 | # update its cached message panes with that tab's cached messages. 299 | for previous_tab in self.maintab.get_config_tabs(): 300 | if previous_tab == working_tab: 301 | break 302 | item = previous_tab.namepane_txtfield.getText() 303 | if item == selected_item: 304 | working_tab.param_handl_cached_req_viewer .setMessage(previous_tab.cached_request , True) 305 | working_tab.param_handl_cached_resp_viewer.setMessage(previous_tab.cached_response, False) 306 | 307 | def is_in_cph_scope(self, msg_as_string, is_request, tab): 308 | rms_scope_all = tab.msg_mod_combo_scope.getSelectedItem() == tab.MSG_MOD_COMBO_SCOPE_ALL 309 | rms_scope_some = tab.msg_mod_combo_scope.getSelectedItem() == tab.MSG_MOD_COMBO_SCOPE_SOME 310 | 311 | rms_type_requests = tab.msg_mod_combo_type.getSelectedItem() == tab.MSG_MOD_COMBO_TYPE_REQ 312 | rms_type_responses = tab.msg_mod_combo_type.getSelectedItem() == tab.MSG_MOD_COMBO_TYPE_RESP 313 | rms_type_both = tab.msg_mod_combo_type.getSelectedItem() == tab.MSG_MOD_COMBO_TYPE_BOTH 314 | 315 | rms_scope_exp = tab.get_exp_pane_expression(tab.msg_mod_exp_pane_scope) 316 | 317 | if is_request and (rms_type_requests or rms_type_both): 318 | pass 319 | elif not is_request and (rms_type_responses or rms_type_both): 320 | pass 321 | else: 322 | self.logger.debug('Preliminary scope check negative!') 323 | return False 324 | 325 | if rms_scope_all: 326 | return True 327 | elif rms_scope_some and rms_scope_exp: 328 | regexp = re.compile(rms_scope_exp) 329 | if regexp.search(msg_as_string): 330 | return True 331 | else: 332 | self.logger.warning('Scope restriction is active but no expression was specified. Skipping tab "{}".'.format( 333 | tab.namepane_txtfield.getText() 334 | )) 335 | return False 336 | 337 | def modify_message(self, tab, msg_as_string): 338 | ph_matchnum_txt = tab.param_handl_txtfield_match_indices.getText() 339 | 340 | ph_target_exp = tab.get_exp_pane_expression(tab.param_handl_exp_pane_target ) 341 | ph_extract_static_exp = tab.get_exp_pane_expression(tab.param_handl_exp_pane_extract_static) 342 | ph_extract_single_exp = tab.get_exp_pane_expression(tab.param_handl_exp_pane_extract_single) 343 | ph_extract_macro_exp = tab.get_exp_pane_expression(tab.param_handl_exp_pane_extract_macro ) 344 | ph_extract_cached_exp = tab.get_exp_pane_expression(tab.param_handl_exp_pane_extract_cached) 345 | 346 | if not ph_target_exp: 347 | self.logger.warning( 348 | 'No match expression specified! Skipping tab "{}".'.format( 349 | tab.namepane_txtfield.getText() 350 | ) 351 | ) 352 | return msg_as_string 353 | 354 | exc_invalid_regex = 'Skipping tab "{}" due to error in expression {{}}: {{}}'.format( 355 | tab.namepane_txtfield.getText() 356 | ) 357 | 358 | try: 359 | match_exp = re.compile(ph_target_exp) 360 | except re.error as e: 361 | self.logger.error(exc_invalid_regex.format(ph_target_exp, e)) 362 | return msg_as_string 363 | 364 | # The following code does not remove support for groups, 365 | # as the original expression will be used for actual replacements. 366 | # We simply need an expression without capturing groups to feed into re.findall(), 367 | # which enables the logic for granular control over which match indices to target. 368 | 369 | # Removing named groups to normalize capturing groups. 370 | findall_exp = re.sub('\?P<.+?>', '', ph_target_exp) 371 | # Removing capturing groups to search for full matches only. 372 | findall_exp = re.sub(r'(?', findall_exp) 373 | findall_exp = re.compile(findall_exp) 374 | self.logger.debug('findall_exp: {}'.format(findall_exp.pattern)) 375 | 376 | all_matches = re.findall(findall_exp, msg_as_string) 377 | self.logger.debug('all_matches: {}'.format(all_matches)) 378 | 379 | match_count = len(all_matches) 380 | if not match_count: 381 | self.logger.warning( 382 | 'Skipping tab "{}" because this expression found no matches: {}'.format( 383 | tab.namepane_txtfield.getText(), 384 | ph_target_exp 385 | ) 386 | ) 387 | return msg_as_string 388 | 389 | matches = list() 390 | dyn_values = '' 391 | replace_exp = ph_extract_static_exp 392 | 393 | if tab.param_handl_dynamic_chkbox.isSelected(): 394 | find_exp, target_txt = '', '' 395 | selected_item = tab.param_handl_combo_extract.getSelectedItem() 396 | 397 | if selected_item == tab.PARAM_HANDL_COMBO_EXTRACT_CACHED: 398 | find_exp, target_txt = ph_extract_cached_exp, tab.param_handl_cached_resp_viewer.getMessage() 399 | target_txt = self.helpers.bytesToString(target_txt) 400 | 401 | elif selected_item == tab.PARAM_HANDL_COMBO_EXTRACT_SINGLE: 402 | self.issue_request(tab) 403 | find_exp, target_txt = ph_extract_single_exp, self.helpers.bytesToString(tab.response) 404 | 405 | elif selected_item == tab.PARAM_HANDL_COMBO_EXTRACT_MACRO: 406 | find_exp, target_txt = ph_extract_macro_exp, self.final_macro_resp 407 | 408 | if not find_exp: 409 | self.logger.warning( 410 | 'No dynamic value extraction expression specified! Skipping tab "{}".'.format( 411 | tab.namepane_txtfield.getText() 412 | ) 413 | ) 414 | return msg_as_string 415 | 416 | try: 417 | # Making a list to enable multiple iterations. 418 | matches = list(re.finditer(find_exp, target_txt)) 419 | except re.error as e: 420 | self.logger.error(exc_invalid_regex.format(ph_extract_macro_exp, e)) 421 | return msg_as_string 422 | 423 | if not matches: 424 | self.logger.warning('Skipping tab "{}" because this expression found no matches: {}'.format( 425 | tab.namepane_txtfield.getText(), 426 | find_exp 427 | )) 428 | return msg_as_string 429 | 430 | groups = {} 431 | groups_keys = groups.viewkeys() 432 | for match in matches: 433 | gd = match.groupdict() 434 | # The given expression should have unique group matches. 435 | for k in gd.keys(): 436 | if k in groups_keys: 437 | self.logger.warning('Skipping tab "{}" because this expression found ambiguous matches: {}'.format( 438 | tab.namepane_txtfield.getText(), 439 | find_exp 440 | )) 441 | return msg_as_string 442 | groups.update(gd) 443 | 444 | # Remove '$' not preceded by '\' 445 | exp = re.sub(r'(?{})'.format(group_name, re.escape(group_match)) 452 | for group_name, group_match in groups.items() 453 | ]) 454 | dyn_values = ''.join(groups.values()) 455 | 456 | # No need for another try/except around this re.compile(), 457 | # as ph_target_exp was already checked when compiling match_exp earlier. 458 | match_exp = re.compile(exp + groups_exp) 459 | self.logger.debug('match_exp adjusted to:\n{}'.format(match_exp.pattern)) 460 | 461 | subsets = ph_matchnum_txt.replace(' ', '').split(',') 462 | match_indices = [] 463 | for subset in subsets: 464 | try: 465 | if ':' in subset: 466 | sliceindex = subset.index(':') 467 | start = int(subset[:sliceindex ]) 468 | end = int(subset[ sliceindex+1:]) 469 | if start < 0: 470 | start = match_count + start 471 | if end < 0: 472 | end = match_count + end 473 | for match_index in range(start, end): 474 | match_indices.append(match_index) 475 | else: 476 | match_index = int(subset) 477 | if match_index < 0: 478 | match_index = match_count + match_index 479 | match_indices.append(match_index) 480 | except ValueError as e: 481 | self.logger.error( 482 | 'Ignoring invalid match index or slice on tab "{}" due to {}'.format( 483 | tab.namepane_txtfield.getText(), 484 | e 485 | ) 486 | ) 487 | continue 488 | 489 | match_indices = set(sorted([m for m in match_indices if m < match_count])) 490 | self.logger.debug('match_indices: {}'.format(match_indices)) 491 | 492 | # Using findall_exp to avoid including capture groups in the result. 493 | message_parts = re.split(findall_exp, msg_as_string) 494 | self.logger.debug('message_parts: {}'.format(message_parts)) 495 | 496 | # The above strategy to use re.split() in order to enable the usage of match_indices 497 | # ends up breaking non-capturing groups. At this point, however, we can safely remove 498 | # all non-capturing groups and everything will be peachy. 499 | ncg_exp = re.compile('\(\?[^P].+?\)') 500 | if re.search(ncg_exp, match_exp.pattern) is not None: 501 | match_exp = re.compile(ncg_exp.sub('', match_exp.pattern)) 502 | if flags is not None: 503 | match_exp = re.compile(flags.group(0) + match_exp.pattern) 504 | self.logger.debug('match_exp adjusted to:\n{}'.format(match_exp.pattern)) 505 | 506 | modified_message = '' 507 | remaining_indices = list(match_indices) 508 | for part_index, message_part in enumerate(message_parts): 509 | if remaining_indices and part_index == remaining_indices[0]: 510 | try: 511 | final_value = match_exp.sub(replace_exp, all_matches[part_index] + dyn_values) 512 | except (re.error, IndexError) as e: 513 | self.logger.error(exc_invalid_regex.format(match_exp.pattern + ' or expression ' + replace_exp, e)) 514 | return msg_as_string 515 | self.logger.debug('Found:\n{}\nreplaced using:\n{}\nin string:\n{}'.format( 516 | match_exp.pattern, 517 | replace_exp, 518 | all_matches[part_index] + dyn_values 519 | )) 520 | final_value = message_part + final_value 521 | modified_message += final_value 522 | remaining_indices.pop(0) 523 | elif part_index < match_count: 524 | modified_message += message_part + all_matches[part_index] 525 | else: 526 | modified_message += message_part 527 | 528 | return modified_message 529 | 530 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 elespike 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Custom Parameter Handler extension for Burp Suite: a power tool for modifying HTTP messages with surgical precision, even when using macros. 2 | 3 | #### The quicksave and quickload functionality persists through reloading not only the extension, but Burp Suite entirely. All values of each existing configuration tab will be saved, along with the order of all tabs. 4 | 5 | #### Use the Export/Import Config buttons to save/load your current configuration to/from a JSON file. 6 | 7 | ### Manual installation 8 | 9 | 1. Download the [latest release](https://github.com/elespike/burp-cph/releases) file; e.g., `CustomParameterHandler_3.0.py` 10 | 2. Under _Extender_ > _Options_ > _Python Environment_, point Burp to the location of your copy of Jython's standalone .jar file. 11 | a. If you don't already have Jython's standalone .jar file, [download it here](http://www.jython.org/downloads.html). 12 | 3. Finally, add `CustomParamHandler_3.0.py` in _Extender_ > _Extensions_. 13 | 14 | ### Installation from the BApp store 15 | 16 | 1. Under _Extender_ > _Options_ > _Python Environment_, point Burp to the location of your copy of Jython's standalone .jar file. 17 | a. If you don't already have Jython's standalone .jar file, [download it here](http://www.jython.org/downloads.html). 18 | 2. Find and select _Custom Parameter Handler_ within the _Extender_ > _BApp Store_ tab, then click the _Install_ button. 19 | 20 | ### Adding configuration tabs 21 | 22 | - Click '+' to add an empty tab; or 23 | - Select one or many requests from anywhere in Burp, right-click, and choose _Send to CPH_. 24 | This will create as many tabs as the number of selected requests, and populate each tab with each selected request to be issued for parameter extraction. 25 | 26 | ### Enabling/Disabling configuration tabs 27 | 28 | Simply click the checkbox next to the tab's name. 29 | New tabs are enabled by default but require a valid configuration in order to have any effect. 30 | 31 | ### Reordering configuration tabs 32 | 33 | - Click the '<' and '>' buttons to swap the currently selected tab with its preceding or succeeding neighbor. 34 | 35 | Leftmost tabs will be processed first; therefore, tab order may be important, especially when extracting values from cached responses. 36 | 37 | Visit the Wiki to learn more about [utilizing cached responses](https://github.com/elespike/burp-cph/wiki/08.-Utilizing-cached-responses). 38 | 39 | ### Tab configuration at a glance 40 | 41 | #### _Scoping_ 42 | Depending on the selected option, this tab will take action on either: 43 | - Requests only, Responses only, or both Requests and Responses; then either 44 | - All requests coming through Burp which are also in Burp's scope; or 45 | - Requests coming through Burp, in Burp's scope, and also matching the given expression. 46 | 47 | #### _Parameter handling_ 48 | The supplied expression will be used to find the value that will be replaced with the replacement value. 49 | 50 | RegEx features may be used to fine-tune modifications. Visit the Wiki to learn more. 51 | 52 | When targeting a subset of matches, enter comma-separated, zero-based indices or slices (following Python's slice syntax). 53 | E.g.: `1,3,6:9` would act on the 2nd, 4th, 7th, 8th and 9th matches. 54 | 55 | If not using a static value, the supplied expression will be used to find the desired replacement value. 56 | 57 | ### Please [visit the Wiki](https://github.com/elespike/burp-cph/wiki) for explanations on the remaining options. 58 | -------------------------------------------------------------------------------- /tinyweb.py: -------------------------------------------------------------------------------- 1 | from SimpleHTTPServer import SimpleHTTPRequestHandler 2 | from json import loads 3 | from random import randint 4 | from re import search as re_search 5 | from urllib import unquote 6 | 7 | 8 | class TinyHandler(SimpleHTTPRequestHandler, object): 9 | the_number = randint(1, 99999) 10 | def __init__(self, *args, **kwargs): 11 | self.protocol_version = 'HTTP/1.1' 12 | super(TinyHandler, self).__init__(*args, **kwargs) 13 | 14 | @staticmethod 15 | def normalize(number): 16 | try: 17 | number = int(number) 18 | except ValueError: 19 | return randint(1, 99999) 20 | if number == 0: 21 | return 1 22 | if number < 0: 23 | number = abs(number) 24 | while number > 99999: 25 | number = number / 10 26 | return number 27 | 28 | def do_GET(self): 29 | headers = {} 30 | response_body = 'https://github.com/elespike/burp-cph/wiki/00.-Interactive-demos' 31 | 32 | if self.path == '/': 33 | headers['Content-Type'] = 'text/html' 34 | response_body = '

Welcome!

Please visit the Wiki for instructions.' 35 | 36 | if self.path.startswith('/number'): 37 | response_body = str(TinyHandler.the_number) 38 | 39 | if self.path.startswith('/indices'): 40 | response_body = '[0][ ]1st [1][ ]2nd [2][ ]3rd\n\n[3][ ]4th [4][ ]5th [5][ ]6th\n\n[6][ ]7th [7][ ]8th [8][ ]9th' 41 | 42 | # E.g., /1/12345 43 | s = re_search('^/[123]/?.*?(\d{1,5})$', self.path) 44 | if s is not None: 45 | number = TinyHandler.normalize(s.group(1)) 46 | if number == TinyHandler.the_number: 47 | response_body = '{} was correct!'.format(number) 48 | else: 49 | response_body = 'Try again!' 50 | TinyHandler.the_number = randint(1, 99999) 51 | response_body += '\nNew number: {}'.format(TinyHandler.the_number) 52 | 53 | if self.path.startswith('/echo/'): 54 | response_body = self.path.replace('/echo/', '') 55 | response_body = unquote(response_body) 56 | 57 | if self.path.startswith('/check'): 58 | number = 0 59 | s = re_search('number=(\d{1,5})', self.headers.get('cookie', '')) 60 | if s is not None and s.groups(): 61 | number = TinyHandler.normalize(s.group(1)) 62 | if not number: 63 | # Search again in the path/querystring. 64 | s = re_search('\d{1,5}', self.path) 65 | if s is not None: 66 | number = TinyHandler.normalize(s.group(0)) 67 | if number == TinyHandler.the_number: 68 | response_body = '{} was correct!'.format(number) 69 | else: 70 | response_body = 'Try again!' 71 | 72 | self.respond(response_body, headers) 73 | 74 | def do_POST(self): 75 | headers = {} 76 | response_body = 'Try again!' 77 | 78 | content_length = int(self.headers.get('content-length', 0)) 79 | body = self.rfile.read(size=content_length) 80 | 81 | if self.path.startswith('/cookie'): 82 | number = 0 83 | # Accept both JSON and url-encoded form data. 84 | try: 85 | number = TinyHandler.normalize(loads(body)['number']) 86 | except: 87 | s = re_search('number=(\d{1,5})', body) 88 | if s is not None and s.groups(): 89 | number = TinyHandler.normalize(s.group(1)) 90 | if number == TinyHandler.the_number: 91 | headers['Set-Cookie'] = 'number={}'.format(TinyHandler.the_number) 92 | response_body = '"number" cookie set to {}!'.format(TinyHandler.the_number) 93 | 94 | if self.path.startswith('/number'): 95 | s = re_search('number=(\d{1,5})', self.headers.get('cookie', '')) 96 | number_cookie = 0 97 | if s is not None and s.groups(): 98 | number_cookie = int(s.group(1)) 99 | if number_cookie == TinyHandler.the_number: 100 | number = randint(1, 99999) 101 | # Accept both JSON and url-encoded form data. 102 | try: 103 | number = TinyHandler.normalize(loads(body)['number']) 104 | except: 105 | s = re_search('number=(\d{1,5})', body) 106 | if s is not None and s.groups(): 107 | number = TinyHandler.normalize(s.group(1)) 108 | TinyHandler.the_number = number 109 | response_body = 'Number set to {}!'.format(TinyHandler.the_number) 110 | 111 | self.respond(response_body, headers) 112 | 113 | def respond(self, response_body, headers=dict()): 114 | self.send_response(200, 'OK') 115 | self.send_header('Content-Length', len(response_body)) 116 | for h, v in headers.items(): 117 | self.send_header(h, v) 118 | self.end_headers() 119 | self.wfile.write(response_body) 120 | 121 | -------------------------------------------------------------------------------- /utils/CPH_Merger.py: -------------------------------------------------------------------------------- 1 | from os import path, chdir, SEEK_CUR 2 | from sys import argv 3 | 4 | 5 | chdir(path.dirname(path.abspath(argv[0]))) 6 | cph_help = open('../CPH_Help.py' , 'r') 7 | cph_config = open('../CPH_Config.py' , 'r') 8 | cph_main = open('../CustomParamHandler.py', 'r') 9 | tinyweb = open('../tinyweb.py' , 'r') 10 | 11 | 12 | def write_imports(opened_file): 13 | line = opened_file.readline() 14 | while line: 15 | if line.startswith('class'): 16 | opened_file.seek(len(line) * -1, SEEK_CUR) 17 | merged_file.write('\n') 18 | break 19 | if line.strip().startswith('from CPH_Config')\ 20 | or line.strip().startswith('from CPH_Help' )\ 21 | or line.strip().startswith('from tinyweb' )\ 22 | or line.strip().startswith('#' )\ 23 | or not line.strip(): 24 | line = opened_file.readline() 25 | continue 26 | merged_file.write(line) 27 | line = opened_file.readline() 28 | 29 | with open('../CustomParamHandler_merged.py', 'w') as merged_file: 30 | write_imports(cph_help) 31 | write_imports(tinyweb) 32 | write_imports(cph_config) 33 | write_imports(cph_main) 34 | 35 | for line in cph_help.readlines(): 36 | merged_file.write(line) 37 | for line in tinyweb.readlines(): 38 | merged_file.write(line) 39 | for line in cph_config.readlines(): 40 | merged_file.write(line) 41 | for line in cph_main.readlines(): 42 | merged_file.write(line) 43 | 44 | 45 | cph_help .close() 46 | cph_config.close() 47 | cph_main .close() 48 | tinyweb .close() 49 | 50 | --------------------------------------------------------------------------------