├── .gitignore ├── BappDescription.html ├── BappManifest.bmf ├── LICENSE ├── ParamMinorIcon.png ├── ParamMinorIcon.svg ├── README.md ├── albinowaxUtils-all.jar ├── build.gradle ├── param-miner.iml ├── resources ├── assetnote-params ├── boring_headers ├── functions ├── headers ├── params └── words ├── settings.gradle └── src └── burp ├── BurpExtender.java ├── FatGet.java ├── GrabScan.java ├── HeaderMutationGuesser.java ├── HeaderMutationScan.java ├── HeaderPoison.java ├── Keysmith.java ├── NormalisedParamScan.java ├── NormalisedPathScan.java ├── OfferParamGuess.java ├── ParamAttack.java ├── ParamGrabber.java ├── ParamGuesser.java ├── ParamHolder.java ├── PartialParam.java ├── PortDOS.java ├── Probe.java ├── RailsUtmScan.java ├── RandomComparator.java ├── TriggerParamGuesser.java ├── UnkeyedParamScan.java ├── ValueGuesser.java └── WordProvider.java /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | .idea/ 3 | build/ 4 | -------------------------------------------------------------------------------- /BappDescription.html: -------------------------------------------------------------------------------- 1 |

This extension identifies hidden, unlinked parameters. It's particularly useful for finding web cache poisoning vulnerabilities.

2 | 3 |

It combines advanced diffing logic from Backslash Powered Scanner with a binary search technique to guess up to 65,536 param names per request. Param names come from a carefully curated built in wordlist, and it also harvests additional words from all in-scope traffic.

4 | 5 |

To use it, right click on a request in Burp and click "Guess (cookies|headers|params)". If you're using Burp Suite Pro, identified parameters will be reported as scanner issues. If not, you can find them listed under Extender->Extensions->Param Miner->Output

6 | 7 |

You can also launch guessing attacks on multiple selected requests at the same time - this will use a thread pool so you can safely use it on thousands of requests if you want. Alternatively, you can enable auto-mining of all in scope traffic. Please note that this tool is designed to be highly scalable but may require tuning to avoid performance issues.

8 | 9 |

For further information, please refer to the whitepaper at 10 | https://portswigger.net/blog/practical-web-cache-poisoning

11 | 12 | -------------------------------------------------------------------------------- /BappManifest.bmf: -------------------------------------------------------------------------------- 1 | Uuid: 17d2949a985c4b7ca092728dba871943 2 | ExtensionType: 1 3 | Name: Param Miner 4 | RepoName: param-miner 5 | ScreenVersion: 1.31 6 | SerialVersion: 15 7 | MinPlatformVersion: 0 8 | ProOnly: False 9 | Author: James 'albinowax' Kettle, PortSwigger Web Security 10 | ShortDescription: This extension identifies hidden, unlinked parameters. It's particularly useful for finding web cache poisoning vulnerabilities. 11 | EntryPoint: build/libs/param-miner-all.jar 12 | BuildCommand: gradle fatJar 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 PortSwigger Web Security 2 | Copyright 2016 James Kettle 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | -------------------------------------------------------------------------------- /ParamMinorIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intruder-io/param-miner/b7ebe91a0e44a1b5e6f45fb42a4ca94f0ae2b81e/ParamMinorIcon.png -------------------------------------------------------------------------------- /ParamMinorIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | ParamMinorIcon 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # param-miner 2 | 3 | This extension identifies hidden, unlinked parameters. It's particularly useful for finding web cache poisoning vulnerabilities. 4 | 5 | It combines advanced diffing logic from Backslash Powered Scanner with a binary search technique to guess up to 65,000 param names per request. 6 | Param names come from a carefully curated built in wordlist, and it also harvests additional words from all in-scope traffic. 7 | 8 | To use it, right click on a request in Burp and click "Guess (cookies|headers|params)". 9 | If you're using Burp Suite Pro, identified parameters will be reported as scanner issues. If not, you can find them listed under Extender->Extensions->Param Miner->Output 10 | 11 | You can also launch guessing attacks on multiple selected requests at the same time - this will use a thread pool so you can safely use it on thousands of requests if you want. 12 | Alternatively, you can enable auto-mining of all in scope traffic. Please note that this tool is designed to be highly scalable but may require tuning to avoid performance issues. 13 | 14 | For further information, please refer to the whitepapers: 15 | 16 | 2020: https://portswigger.net/research/web-cache-entanglement 17 | 18 | 2018: https://portswigger.net/research/practical-web-cache-poisoning 19 | 20 | The code can be found at https://github.com/portswigger/param-miner 21 | 22 | If you'd like to rate limit your attack, use the Distribute Damage extension. 23 | 24 | Contributions and feature requests are welcome. 25 | 26 | **Web Cache Entanglement update** 27 | 28 | Here's a video of the new features being used to find a fat GET cache poisoning vulnerability in a demo site using Rack::Cache 29 | 30 | [![Param Miner demo video](https://img.youtube.com/vi/TQ42N8fqxw4/0.jpg)](https://www.youtube.com/watch?v=TQ42N8fqxw4) 31 | 32 | Another video targeting a real site is coming soon - I'm just waiting on the target to patch. 33 | 34 | # Changelog 35 | **1.21 2020-09-02** 36 | - Non-default settings are now highlighted, and can be reset to default 37 | - Various bugfixes 38 | 39 | **1.20 2020-08-05** 40 | - Major update for Web Cache Entanglement 41 | 42 | **1.07 2018-12-06** 43 | - Fix config window size for small screens (thanks @misoxxx) 44 | 45 | **1.06 2018-10-10** 46 | - Support custom wordlists 47 | - Support fuzz-based detection 48 | - Numerous bug fixes and quality of life tweaks 49 | 50 | **1.03 2018-08-09** 51 | - First public release 52 | 53 | # Installation 54 | This extension requires Burp Suite 1.7.10 or later. To install it, simply use the BApps tab in Burp. 55 | 56 | # Build 57 | Requires Java >1.8, Gradle. 58 | 59 | Navigate to param-miner directory and execute `gradle build fatjar`. 60 | 61 | Load the resulting jar: `build/libs/param-miner-all.jar` 62 | -------------------------------------------------------------------------------- /albinowaxUtils-all.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intruder-io/param-miner/b7ebe91a0e44a1b5e6f45fb42a4ca94f0ae2b81e/albinowaxUtils-all.jar -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | repositories { 4 | mavenCentral() 5 | } 6 | 7 | dependencies { 8 | compile 'net.portswigger.burp.extender:burp-extender-api:1.7.13' 9 | compile 'org.apache.commons:commons-lang3:3.5' 10 | compile group: 'org.apache.commons', name: 'commons-collections4', version: '4.1' 11 | compile 'com.google.code.gson:gson:2.8.1' 12 | compile 'com.fasterxml.jackson.core:jackson-databind:2.9.0.pr4' 13 | compile 'org.jsoup:jsoup:1.8.1' 14 | compile files('albinowaxUtils-all.jar') 15 | } 16 | 17 | sourceSets { 18 | main { 19 | java { 20 | srcDir 'src' 21 | } 22 | resources { 23 | srcDir 'resources' 24 | } 25 | } 26 | } 27 | 28 | task fatJar(type: Jar) { 29 | 30 | baseName = project.name + '-all' 31 | from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } 32 | with jar 33 | } 34 | 35 | compileJava { 36 | targetCompatibility '1.8' 37 | sourceCompatibility '1.8' 38 | } -------------------------------------------------------------------------------- /param-miner.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /resources/boring_headers: -------------------------------------------------------------------------------- 1 | host 2 | cookie 3 | user-agent 4 | content-encoding 5 | cf-connecting-ip 6 | content-range 7 | content-encoding 8 | x_alto_ajax_key 9 | referer 10 | upgrade 11 | expect 12 | vary 13 | connection 14 | accept-encoding 15 | accept-language 16 | accept 17 | transfer-encoding 18 | content-type 19 | content-length 20 | range 21 | if-unmodified-since 22 | if-modified-since 23 | if-match 24 | if 25 | proxy 26 | trailer 27 | waf-stuff-below 28 | negotiate 29 | javascript 30 | action 31 | authorization 32 | label 33 | start 34 | date 35 | keep-alive 36 | x-scanner 37 | zreferrer 38 | zvia 39 | zaccess-control-request-method 40 | zaccess-control-request-headers 41 | zorigin 42 | zx-request-id 43 | zx-timer 44 | zmax-forwards -------------------------------------------------------------------------------- /resources/functions: -------------------------------------------------------------------------------- 1 | zend_version 2 | func_num_args 3 | func_get_arg 4 | func_get_args 5 | strlen 6 | strcmp 7 | strncmp 8 | strcasecmp 9 | strncasecmp 10 | each 11 | error_reporting 12 | define 13 | defined 14 | get_class 15 | get_called_class 16 | get_parent_class 17 | method_exists 18 | property_exists 19 | class_exists 20 | interface_exists 21 | trait_exists 22 | function_exists 23 | class_alias 24 | get_included_files 25 | get_required_files 26 | is_subclass_of 27 | is_a 28 | get_class_vars 29 | get_object_vars 30 | get_class_methods 31 | trigger_error 32 | user_error 33 | set_error_handler 34 | restore_error_handler 35 | set_exception_handler 36 | restore_exception_handler 37 | get_declared_classes 38 | get_declared_traits 39 | get_declared_interfaces 40 | get_defined_functions 41 | get_defined_vars 42 | create_function 43 | get_resource_type 44 | get_loaded_extensions 45 | extension_loaded 46 | get_extension_funcs 47 | get_defined_constants 48 | debug_backtrace 49 | debug_print_backtrace 50 | gc_collect_cycles 51 | gc_enabled 52 | gc_enable 53 | gc_disable 54 | strtotime 55 | date 56 | idate 57 | gmdate 58 | mktime 59 | gmmktime 60 | checkdate 61 | strftime 62 | gmstrftime 63 | time 64 | localtime 65 | getdate 66 | date_create 67 | date_create_immutable 68 | date_create_from_format 69 | date_create_immutable_from_format 70 | date_parse 71 | date_parse_from_format 72 | date_get_last_errors 73 | date_format 74 | date_modify 75 | date_add 76 | date_sub 77 | date_timezone_get 78 | date_timezone_set 79 | date_offset_get 80 | date_diff 81 | date_time_set 82 | date_date_set 83 | date_isodate_set 84 | date_timestamp_set 85 | date_timestamp_get 86 | timezone_open 87 | timezone_name_get 88 | timezone_name_from_abbr 89 | timezone_offset_get 90 | timezone_transitions_get 91 | timezone_location_get 92 | timezone_identifiers_list 93 | timezone_abbreviations_list 94 | timezone_version_get 95 | date_interval_create_from_date_string 96 | date_interval_format 97 | date_default_timezone_set 98 | date_default_timezone_get 99 | date_sunrise 100 | date_sunset 101 | date_sun_info 102 | ereg 103 | ereg_replace 104 | eregi 105 | eregi_replace 106 | split 107 | spliti 108 | sql_regcase 109 | libxml_set_streams_context 110 | libxml_use_internal_errors 111 | libxml_get_last_error 112 | libxml_clear_errors 113 | libxml_get_errors 114 | libxml_disable_entity_loader 115 | libxml_set_external_entity_loader 116 | openssl_pkey_free 117 | openssl_pkey_new 118 | openssl_pkey_export 119 | openssl_pkey_export_to_file 120 | openssl_pkey_get_private 121 | openssl_pkey_get_public 122 | openssl_pkey_get_details 123 | openssl_free_key 124 | openssl_get_privatekey 125 | openssl_get_publickey 126 | openssl_x509_read 127 | openssl_x509_free 128 | openssl_x509_parse 129 | openssl_x509_checkpurpose 130 | openssl_x509_check_private_key 131 | openssl_x509_export 132 | openssl_x509_export_to_file 133 | openssl_pkcs12_export 134 | openssl_pkcs12_export_to_file 135 | openssl_pkcs12_read 136 | openssl_csr_new 137 | openssl_csr_export 138 | openssl_csr_export_to_file 139 | openssl_csr_sign 140 | openssl_csr_get_subject 141 | openssl_csr_get_public_key 142 | openssl_digest 143 | openssl_encrypt 144 | openssl_decrypt 145 | openssl_cipher_iv_length 146 | openssl_sign 147 | openssl_verify 148 | openssl_seal 149 | openssl_open 150 | openssl_pkcs7_verify 151 | openssl_pkcs7_decrypt 152 | openssl_pkcs7_sign 153 | openssl_pkcs7_encrypt 154 | openssl_private_encrypt 155 | openssl_private_decrypt 156 | openssl_public_encrypt 157 | openssl_public_decrypt 158 | openssl_get_md_methods 159 | openssl_get_cipher_methods 160 | openssl_dh_compute_key 161 | openssl_random_pseudo_bytes 162 | openssl_error_string 163 | preg_match 164 | preg_match_all 165 | preg_replace 166 | preg_replace_callback 167 | preg_filter 168 | preg_split 169 | preg_quote 170 | preg_grep 171 | preg_last_error 172 | readgzfile 173 | gzrewind 174 | gzclose 175 | gzeof 176 | gzgetc 177 | gzgets 178 | gzgetss 179 | gzread 180 | gzopen 181 | gzpassthru 182 | gzseek 183 | gztell 184 | gzwrite 185 | gzputs 186 | gzfile 187 | gzcompress 188 | gzuncompress 189 | gzdeflate 190 | gzinflate 191 | gzencode 192 | gzdecode 193 | zlib_encode 194 | zlib_decode 195 | zlib_get_coding_type 196 | ob_gzhandler 197 | bcadd 198 | bcsub 199 | bcmul 200 | bcdiv 201 | bcmod 202 | bcpow 203 | bcsqrt 204 | bcscale 205 | bccomp 206 | bcpowmod 207 | bzopen 208 | bzread 209 | bzwrite 210 | bzflush 211 | bzclose 212 | bzerrno 213 | bzerrstr 214 | bzerror 215 | bzcompress 216 | bzdecompress 217 | jdtogregorian 218 | gregoriantojd 219 | jdtojulian 220 | juliantojd 221 | jdtojewish 222 | jewishtojd 223 | jdtofrench 224 | frenchtojd 225 | jddayofweek 226 | jdmonthname 227 | easter_date 228 | easter_days 229 | unixtojd 230 | jdtounix 231 | cal_to_jd 232 | cal_from_jd 233 | cal_days_in_month 234 | cal_info 235 | ctype_alnum 236 | ctype_alpha 237 | ctype_cntrl 238 | ctype_digit 239 | ctype_lower 240 | ctype_graph 241 | ctype_print 242 | ctype_punct 243 | ctype_space 244 | ctype_upper 245 | ctype_xdigit 246 | curl_init 247 | curl_copy_handle 248 | curl_version 249 | curl_setopt 250 | curl_setopt_array 251 | curl_exec 252 | curl_getinfo 253 | curl_error 254 | curl_errno 255 | curl_close 256 | curl_strerror 257 | curl_multi_strerror 258 | curl_reset 259 | curl_escape 260 | curl_unescape 261 | curl_pause 262 | curl_multi_init 263 | curl_multi_add_handle 264 | curl_multi_remove_handle 265 | curl_multi_select 266 | curl_multi_exec 267 | curl_multi_getcontent 268 | curl_multi_info_read 269 | curl_multi_close 270 | curl_multi_setopt 271 | curl_share_init 272 | curl_share_close 273 | curl_share_setopt 274 | curl_file_create 275 | dba_open 276 | dba_popen 277 | dba_close 278 | dba_delete 279 | dba_exists 280 | dba_fetch 281 | dba_insert 282 | dba_replace 283 | dba_firstkey 284 | dba_nextkey 285 | dba_optimize 286 | dba_sync 287 | dba_handlers 288 | dba_list 289 | dba_key_split 290 | dom_import_simplexml 291 | exif_read_data 292 | read_exif_data 293 | exif_tagname 294 | exif_thumbnail 295 | exif_imagetype 296 | finfo_open 297 | finfo_close 298 | finfo_set_flags 299 | finfo_file 300 | finfo_buffer 301 | mime_content_type 302 | filter_input 303 | filter_var 304 | filter_input_array 305 | filter_var_array 306 | filter_list 307 | filter_has_var 308 | filter_id 309 | ftp_connect 310 | ftp_ssl_connect 311 | ftp_login 312 | ftp_pwd 313 | ftp_cdup 314 | ftp_chdir 315 | ftp_exec 316 | ftp_raw 317 | ftp_mkdir 318 | ftp_rmdir 319 | ftp_chmod 320 | ftp_alloc 321 | ftp_nlist 322 | ftp_rawlist 323 | ftp_systype 324 | ftp_pasv 325 | ftp_get 326 | ftp_fget 327 | ftp_put 328 | ftp_fput 329 | ftp_size 330 | ftp_mdtm 331 | ftp_rename 332 | ftp_delete 333 | ftp_site 334 | ftp_close 335 | ftp_set_option 336 | ftp_get_option 337 | ftp_nb_fget 338 | ftp_nb_get 339 | ftp_nb_continue 340 | ftp_nb_put 341 | ftp_nb_fput 342 | ftp_quit 343 | gd_info 344 | imagearc 345 | imageellipse 346 | imagechar 347 | imagecharup 348 | imagecolorat 349 | imagecolorallocate 350 | imagepalettecopy 351 | imagecreatefromstring 352 | imagecolorclosest 353 | imagecolorclosesthwb 354 | imagecolordeallocate 355 | imagecolorresolve 356 | imagecolorexact 357 | imagecolorset 358 | imagecolortransparent 359 | imagecolorstotal 360 | imagecolorsforindex 361 | imagecopy 362 | imagecopymerge 363 | imagecopymergegray 364 | imagecopyresized 365 | imagecreate 366 | imagecreatetruecolor 367 | imageistruecolor 368 | imagetruecolortopalette 369 | imagepalettetotruecolor 370 | imagesetthickness 371 | imagefilledarc 372 | imagefilledellipse 373 | imagealphablending 374 | imagesavealpha 375 | imagecolorallocatealpha 376 | imagecolorresolvealpha 377 | imagecolorclosestalpha 378 | imagecolorexactalpha 379 | imagecopyresampled 380 | imagerotate 381 | imageflip 382 | imageantialias 383 | imagecrop 384 | imagecropauto 385 | imagescale 386 | imageaffine 387 | imageaffinematrixconcat 388 | imageaffinematrixget 389 | imagesetinterpolation 390 | imagesettile 391 | imagesetbrush 392 | imagesetstyle 393 | imagecreatefrompng 394 | imagecreatefromgif 395 | imagecreatefromjpeg 396 | imagecreatefromwbmp 397 | imagecreatefromxbm 398 | imagecreatefromgd 399 | imagecreatefromgd2 400 | imagecreatefromgd2part 401 | imagepng 402 | imagegif 403 | imagejpeg 404 | imagewbmp 405 | imagegd 406 | imagegd2 407 | imagedestroy 408 | imagegammacorrect 409 | imagefill 410 | imagefilledpolygon 411 | imagefilledrectangle 412 | imagefilltoborder 413 | imagefontwidth 414 | imagefontheight 415 | imageinterlace 416 | imageline 417 | imageloadfont 418 | imagepolygon 419 | imagerectangle 420 | imagesetpixel 421 | imagestring 422 | imagestringup 423 | imagesx 424 | imagesy 425 | imagedashedline 426 | imagetypes 427 | jpeg2wbmp 428 | png2wbmp 429 | image2wbmp 430 | imagelayereffect 431 | imagexbm 432 | imagecolormatch 433 | imagefilter 434 | imageconvolution 435 | hash 436 | hash_file 437 | hash_hmac 438 | hash_hmac_file 439 | hash_init 440 | hash_update 441 | hash_update_stream 442 | hash_update_file 443 | hash_final 444 | hash_copy 445 | hash_algos 446 | hash_pbkdf2 447 | iconv 448 | iconv_get_encoding 449 | iconv_set_encoding 450 | iconv_strlen 451 | iconv_substr 452 | iconv_strpos 453 | iconv_strrpos 454 | iconv_mime_encode 455 | iconv_mime_decode 456 | iconv_mime_decode_headers 457 | json_encode 458 | json_decode 459 | json_last_error 460 | json_last_error_msg 461 | ldap_connect 462 | ldap_close 463 | ldap_bind 464 | ldap_sasl_bind 465 | ldap_unbind 466 | ldap_read 467 | ldap_list 468 | ldap_search 469 | ldap_free_result 470 | ldap_count_entries 471 | ldap_first_entry 472 | ldap_next_entry 473 | ldap_get_entries 474 | ldap_first_attribute 475 | ldap_next_attribute 476 | ldap_get_attributes 477 | ldap_get_values 478 | ldap_get_values_len 479 | ldap_get_dn 480 | ldap_explode_dn 481 | ldap_dn2ufn 482 | ldap_add 483 | ldap_delete 484 | ldap_modify_batch 485 | ldap_modify 486 | ldap_mod_add 487 | ldap_mod_replace 488 | ldap_mod_del 489 | ldap_errno 490 | ldap_err2str 491 | ldap_error 492 | ldap_compare 493 | ldap_sort 494 | ldap_rename 495 | ldap_get_option 496 | ldap_set_option 497 | ldap_first_reference 498 | ldap_next_reference 499 | ldap_parse_reference 500 | ldap_parse_result 501 | ldap_start_tls 502 | ldap_set_rebind_proc 503 | ldap_control_paged_result 504 | ldap_control_paged_result_response 505 | mb_convert_case 506 | mb_strtoupper 507 | mb_strtolower 508 | mb_language 509 | mb_internal_encoding 510 | mb_http_input 511 | mb_http_output 512 | mb_detect_order 513 | mb_substitute_character 514 | mb_parse_str 515 | mb_output_handler 516 | mb_preferred_mime_name 517 | mb_strlen 518 | mb_strpos 519 | mb_strrpos 520 | mb_stripos 521 | mb_strripos 522 | mb_strstr 523 | mb_strrchr 524 | mb_stristr 525 | mb_strrichr 526 | mb_substr_count 527 | mb_substr 528 | mb_strcut 529 | mb_strwidth 530 | mb_strimwidth 531 | mb_convert_encoding 532 | mb_detect_encoding 533 | mb_list_encodings 534 | mb_encoding_aliases 535 | mb_convert_kana 536 | mb_encode_mimeheader 537 | mb_decode_mimeheader 538 | mb_convert_variables 539 | mb_encode_numericentity 540 | mb_decode_numericentity 541 | mb_send_mail 542 | mb_get_info 543 | mb_check_encoding 544 | mb_regex_encoding 545 | mb_regex_set_options 546 | mb_ereg 547 | mb_eregi 548 | mb_ereg_replace 549 | mb_eregi_replace 550 | mb_ereg_replace_callback 551 | mb_split 552 | mb_ereg_match 553 | mb_ereg_search 554 | mb_ereg_search_pos 555 | mb_ereg_search_regs 556 | mb_ereg_search_init 557 | mb_ereg_search_getregs 558 | mb_ereg_search_getpos 559 | mb_ereg_search_setpos 560 | mbregex_encoding 561 | mbereg 562 | mberegi 563 | mbereg_replace 564 | mberegi_replace 565 | mbsplit 566 | mbereg_match 567 | mbereg_search 568 | mbereg_search_pos 569 | mbereg_search_regs 570 | mbereg_search_init 571 | mbereg_search_getregs 572 | mbereg_search_getpos 573 | mbereg_search_setpos 574 | mysql_connect 575 | mysql_pconnect 576 | mysql_close 577 | mysql_select_db 578 | mysql_query 579 | mysql_unbuffered_query 580 | mysql_db_query 581 | mysql_list_dbs 582 | mysql_list_tables 583 | mysql_list_fields 584 | mysql_list_processes 585 | mysql_error 586 | mysql_errno 587 | mysql_affected_rows 588 | mysql_insert_id 589 | mysql_result 590 | mysql_num_rows 591 | mysql_num_fields 592 | mysql_fetch_row 593 | mysql_fetch_array 594 | mysql_fetch_assoc 595 | mysql_fetch_object 596 | mysql_data_seek 597 | mysql_fetch_lengths 598 | mysql_fetch_field 599 | mysql_field_seek 600 | mysql_free_result 601 | mysql_field_name 602 | mysql_field_table 603 | mysql_field_len 604 | mysql_field_type 605 | mysql_field_flags 606 | mysql_escape_string 607 | mysql_real_escape_string 608 | mysql_stat 609 | mysql_thread_id 610 | mysql_client_encoding 611 | mysql_ping 612 | mysql_get_client_info 613 | mysql_get_host_info 614 | mysql_get_proto_info 615 | mysql_get_server_info 616 | mysql_info 617 | mysql_set_charset 618 | mysql 619 | mysql_fieldname 620 | mysql_fieldtable 621 | mysql_fieldlen 622 | mysql_fieldtype 623 | mysql_fieldflags 624 | mysql_selectdb 625 | mysql_freeresult 626 | mysql_numfields 627 | mysql_numrows 628 | mysql_listdbs 629 | mysql_listtables 630 | mysql_listfields 631 | mysql_db_name 632 | mysql_dbname 633 | mysql_tablename 634 | mysql_table_name 635 | mysqli_affected_rows 636 | mysqli_autocommit 637 | mysqli_begin_transaction 638 | mysqli_change_user 639 | mysqli_character_set_name 640 | mysqli_close 641 | mysqli_commit 642 | mysqli_connect 643 | mysqli_connect_errno 644 | mysqli_connect_error 645 | mysqli_data_seek 646 | mysqli_dump_debug_info 647 | mysqli_debug 648 | mysqli_errno 649 | mysqli_error 650 | mysqli_error_list 651 | mysqli_stmt_execute 652 | mysqli_execute 653 | mysqli_fetch_field 654 | mysqli_fetch_fields 655 | mysqli_fetch_field_direct 656 | mysqli_fetch_lengths 657 | mysqli_fetch_all 658 | mysqli_fetch_array 659 | mysqli_fetch_assoc 660 | mysqli_fetch_object 661 | mysqli_fetch_row 662 | mysqli_field_count 663 | mysqli_field_seek 664 | mysqli_field_tell 665 | mysqli_free_result 666 | mysqli_get_connection_stats 667 | mysqli_get_client_stats 668 | mysqli_get_charset 669 | mysqli_get_client_info 670 | mysqli_get_client_version 671 | mysqli_get_host_info 672 | mysqli_get_proto_info 673 | mysqli_get_server_info 674 | mysqli_get_server_version 675 | mysqli_get_warnings 676 | mysqli_init 677 | mysqli_info 678 | mysqli_insert_id 679 | mysqli_kill 680 | mysqli_more_results 681 | mysqli_multi_query 682 | mysqli_next_result 683 | mysqli_num_fields 684 | mysqli_num_rows 685 | mysqli_options 686 | mysqli_ping 687 | mysqli_poll 688 | mysqli_prepare 689 | mysqli_report 690 | mysqli_query 691 | mysqli_real_connect 692 | mysqli_real_escape_string 693 | mysqli_real_query 694 | mysqli_reap_async_query 695 | mysqli_release_savepoint 696 | mysqli_rollback 697 | mysqli_savepoint 698 | mysqli_select_db 699 | mysqli_set_charset 700 | mysqli_stmt_affected_rows 701 | mysqli_stmt_attr_get 702 | mysqli_stmt_attr_set 703 | mysqli_stmt_bind_param 704 | mysqli_stmt_bind_result 705 | mysqli_stmt_close 706 | mysqli_stmt_data_seek 707 | mysqli_stmt_errno 708 | mysqli_stmt_error 709 | mysqli_stmt_error_list 710 | mysqli_stmt_fetch 711 | mysqli_stmt_field_count 712 | mysqli_stmt_free_result 713 | mysqli_stmt_get_result 714 | mysqli_stmt_get_warnings 715 | mysqli_stmt_init 716 | mysqli_stmt_insert_id 717 | mysqli_stmt_more_results 718 | mysqli_stmt_next_result 719 | mysqli_stmt_num_rows 720 | mysqli_stmt_param_count 721 | mysqli_stmt_prepare 722 | mysqli_stmt_reset 723 | mysqli_stmt_result_metadata 724 | mysqli_stmt_send_long_data 725 | mysqli_stmt_store_result 726 | mysqli_stmt_sqlstate 727 | mysqli_sqlstate 728 | mysqli_ssl_set 729 | mysqli_stat 730 | mysqli_store_result 731 | mysqli_thread_id 732 | mysqli_thread_safe 733 | mysqli_use_result 734 | mysqli_warning_count 735 | mysqli_refresh 736 | mysqli_escape_string 737 | mysqli_set_opt 738 | spl_classes 739 | spl_autoload 740 | spl_autoload_extensions 741 | spl_autoload_register 742 | spl_autoload_unregister 743 | spl_autoload_functions 744 | spl_autoload_call 745 | class_parents 746 | class_implements 747 | class_uses 748 | spl_object_hash 749 | iterator_to_array 750 | iterator_count 751 | iterator_apply 752 | pdo_drivers 753 | posix_kill 754 | posix_getpid 755 | posix_getppid 756 | posix_getuid 757 | posix_setuid 758 | posix_geteuid 759 | posix_seteuid 760 | posix_getgid 761 | posix_setgid 762 | posix_getegid 763 | posix_setegid 764 | posix_getgroups 765 | posix_getlogin 766 | posix_getpgrp 767 | posix_setsid 768 | posix_setpgid 769 | posix_getpgid 770 | posix_getsid 771 | posix_uname 772 | posix_times 773 | posix_ctermid 774 | posix_ttyname 775 | posix_isatty 776 | posix_getcwd 777 | posix_mkfifo 778 | posix_mknod 779 | posix_access 780 | posix_getgrnam 781 | posix_getgrgid 782 | posix_getpwnam 783 | posix_getpwuid 784 | posix_getrlimit 785 | posix_get_last_error 786 | posix_errno 787 | posix_strerror 788 | posix_initgroups 789 | readline 790 | readline_info 791 | readline_add_history 792 | readline_clear_history 793 | readline_read_history 794 | readline_write_history 795 | readline_completion_function 796 | readline_callback_handler_install 797 | readline_callback_read_char 798 | readline_callback_handler_remove 799 | readline_redisplay 800 | readline_on_new_line 801 | session_name 802 | session_module_name 803 | session_save_path 804 | session_id 805 | session_regenerate_id 806 | session_decode 807 | session_encode 808 | session_start 809 | session_destroy 810 | session_unset 811 | session_set_save_handler 812 | session_cache_limiter 813 | session_cache_expire 814 | session_set_cookie_params 815 | session_get_cookie_params 816 | session_write_close 817 | session_status 818 | session_register_shutdown 819 | session_commit 820 | shmop_open 821 | shmop_read 822 | shmop_close 823 | shmop_size 824 | shmop_write 825 | shmop_delete 826 | simplexml_load_file 827 | simplexml_load_string 828 | simplexml_import_dom 829 | snmpget 830 | snmpgetnext 831 | snmpwalk 832 | snmprealwalk 833 | snmpwalkoid 834 | snmpset 835 | snmp_get_quick_print 836 | snmp_set_quick_print 837 | snmp_set_enum_print 838 | snmp_set_oid_output_format 839 | snmp_set_oid_numeric_print 840 | snmp2_get 841 | snmp2_getnext 842 | snmp2_walk 843 | snmp2_real_walk 844 | snmp2_set 845 | snmp3_get 846 | snmp3_getnext 847 | snmp3_walk 848 | snmp3_real_walk 849 | snmp3_set 850 | snmp_set_valueretrieval 851 | snmp_get_valueretrieval 852 | snmp_read_mib 853 | use_soap_error_handler 854 | is_soap_fault 855 | socket_select 856 | socket_create 857 | socket_create_listen 858 | socket_create_pair 859 | socket_accept 860 | socket_set_nonblock 861 | socket_set_block 862 | socket_listen 863 | socket_close 864 | socket_write 865 | socket_read 866 | socket_getsockname 867 | socket_getpeername 868 | socket_connect 869 | socket_strerror 870 | socket_bind 871 | socket_recv 872 | socket_send 873 | socket_recvfrom 874 | socket_sendto 875 | socket_get_option 876 | socket_set_option 877 | socket_shutdown 878 | socket_last_error 879 | socket_clear_error 880 | socket_import_stream 881 | socket_sendmsg 882 | socket_recvmsg 883 | socket_cmsg_space 884 | socket_getopt 885 | socket_setopt 886 | constant 887 | bin2hex 888 | hex2bin 889 | sleep 890 | usleep 891 | time_nanosleep 892 | time_sleep_until 893 | strptime 894 | flush 895 | wordwrap 896 | htmlspecialchars 897 | htmlentities 898 | html_entity_decode 899 | htmlspecialchars_decode 900 | get_html_translation_table 901 | sha1 902 | sha1_file 903 | md5 904 | md5_file 905 | crc32 906 | iptcparse 907 | iptcembed 908 | getimagesize 909 | getimagesizefromstring 910 | image_type_to_mime_type 911 | image_type_to_extension 912 | phpinfo 913 | phpversion 914 | phpcredits 915 | php_sapi_name 916 | php_uname 917 | php_ini_scanned_files 918 | php_ini_loaded_file 919 | strnatcmp 920 | strnatcasecmp 921 | substr_count 922 | strspn 923 | strcspn 924 | strtok 925 | strtoupper 926 | strtolower 927 | strpos 928 | stripos 929 | strrpos 930 | strripos 931 | strrev 932 | hebrev 933 | hebrevc 934 | nl2br 935 | basename 936 | dirname 937 | pathinfo 938 | stripslashes 939 | stripcslashes 940 | strstr 941 | stristr 942 | strrchr 943 | str_shuffle 944 | str_word_count 945 | str_split 946 | strpbrk 947 | substr_compare 948 | strcoll 949 | money_format 950 | substr 951 | substr_replace 952 | quotemeta 953 | ucfirst 954 | lcfirst 955 | ucwords 956 | strtr 957 | addslashes 958 | addcslashes 959 | rtrim 960 | str_replace 961 | str_ireplace 962 | str_repeat 963 | count_chars 964 | chunk_split 965 | trim 966 | ltrim 967 | strip_tags 968 | similar_text 969 | explode 970 | implode 971 | join 972 | setlocale 973 | localeconv 974 | nl_langinfo 975 | soundex 976 | levenshtein 977 | chr 978 | ord 979 | parse_str 980 | str_getcsv 981 | str_pad 982 | chop 983 | strchr 984 | sprintf 985 | printf 986 | vprintf 987 | vsprintf 988 | fprintf 989 | vfprintf 990 | sscanf 991 | fscanf 992 | parse_url 993 | urlencode 994 | urldecode 995 | rawurlencode 996 | rawurldecode 997 | http_build_query 998 | readlink 999 | linkinfo 1000 | symlink 1001 | link 1002 | unlink 1003 | exec 1004 | system 1005 | escapeshellcmd 1006 | escapeshellarg 1007 | passthru 1008 | shell_exec 1009 | proc_open 1010 | proc_close 1011 | proc_terminate 1012 | proc_get_status 1013 | proc_nice 1014 | rand 1015 | srand 1016 | getrandmax 1017 | mt_rand 1018 | mt_srand 1019 | mt_getrandmax 1020 | getservbyname 1021 | getservbyport 1022 | getprotobyname 1023 | getprotobynumber 1024 | getmyuid 1025 | getmygid 1026 | getmypid 1027 | getmyinode 1028 | getlastmod 1029 | base64_decode 1030 | base64_encode 1031 | password_hash 1032 | password_get_info 1033 | password_needs_rehash 1034 | password_verify 1035 | convert_uuencode 1036 | convert_uudecode 1037 | abs 1038 | ceil 1039 | floor 1040 | round 1041 | sin 1042 | cos 1043 | tan 1044 | asin 1045 | acos 1046 | atan 1047 | atanh 1048 | atan2 1049 | sinh 1050 | cosh 1051 | tanh 1052 | asinh 1053 | acosh 1054 | expm1 1055 | log1p 1056 | pi 1057 | is_finite 1058 | is_nan 1059 | is_infinite 1060 | pow 1061 | exp 1062 | log 1063 | log10 1064 | sqrt 1065 | hypot 1066 | deg2rad 1067 | rad2deg 1068 | bindec 1069 | hexdec 1070 | octdec 1071 | decbin 1072 | decoct 1073 | dechex 1074 | base_convert 1075 | number_format 1076 | fmod 1077 | inet_ntop 1078 | inet_pton 1079 | ip2long 1080 | long2ip 1081 | getenv 1082 | putenv 1083 | getopt 1084 | sys_getloadavg 1085 | microtime 1086 | gettimeofday 1087 | getrusage 1088 | uniqid 1089 | quoted_printable_decode 1090 | quoted_printable_encode 1091 | convert_cyr_string 1092 | get_current_user 1093 | set_time_limit 1094 | header_register_callback 1095 | get_cfg_var 1096 | magic_quotes_runtime 1097 | set_magic_quotes_runtime 1098 | get_magic_quotes_gpc 1099 | get_magic_quotes_runtime 1100 | error_log 1101 | error_get_last 1102 | call_user_func 1103 | call_user_func_array 1104 | call_user_method 1105 | call_user_method_array 1106 | forward_static_call 1107 | forward_static_call_array 1108 | serialize 1109 | unserialize 1110 | var_dump 1111 | var_export 1112 | debug_zval_dump 1113 | print_r 1114 | memory_get_usage 1115 | memory_get_peak_usage 1116 | register_shutdown_function 1117 | register_tick_function 1118 | unregister_tick_function 1119 | highlight_file 1120 | show_source 1121 | highlight_string 1122 | php_strip_whitespace 1123 | ini_get 1124 | ini_get_all 1125 | ini_set 1126 | ini_alter 1127 | ini_restore 1128 | get_include_path 1129 | set_include_path 1130 | restore_include_path 1131 | setcookie 1132 | setrawcookie 1133 | header 1134 | header_remove 1135 | headers_sent 1136 | headers_list 1137 | http_response_code 1138 | connection_aborted 1139 | connection_status 1140 | ignore_user_abort 1141 | parse_ini_file 1142 | parse_ini_string 1143 | is_uploaded_file 1144 | move_uploaded_file 1145 | gethostbyaddr 1146 | gethostbyname 1147 | gethostbynamel 1148 | gethostname 1149 | dns_check_record 1150 | checkdnsrr 1151 | dns_get_mx 1152 | getmxrr 1153 | dns_get_record 1154 | intval 1155 | floatval 1156 | doubleval 1157 | strval 1158 | boolval 1159 | gettype 1160 | settype 1161 | is_null 1162 | is_resource 1163 | is_bool 1164 | is_long 1165 | is_float 1166 | is_int 1167 | is_integer 1168 | is_double 1169 | is_real 1170 | is_numeric 1171 | is_string 1172 | is_array 1173 | is_object 1174 | is_scalar 1175 | is_callable 1176 | pclose 1177 | popen 1178 | readfile 1179 | rewind 1180 | rmdir 1181 | umask 1182 | fclose 1183 | feof 1184 | fgetc 1185 | fgets 1186 | fgetss 1187 | fread 1188 | fopen 1189 | fpassthru 1190 | ftruncate 1191 | fstat 1192 | fseek 1193 | ftell 1194 | fflush 1195 | fwrite 1196 | fputs 1197 | mkdir 1198 | rename 1199 | copy 1200 | tempnam 1201 | tmpfile 1202 | file 1203 | file_get_contents 1204 | file_put_contents 1205 | stream_select 1206 | stream_context_create 1207 | stream_context_set_params 1208 | stream_context_get_params 1209 | stream_context_set_option 1210 | stream_context_get_options 1211 | stream_context_get_default 1212 | stream_context_set_default 1213 | stream_filter_prepend 1214 | stream_filter_append 1215 | stream_filter_remove 1216 | stream_socket_client 1217 | stream_socket_server 1218 | stream_socket_accept 1219 | stream_socket_get_name 1220 | stream_socket_recvfrom 1221 | stream_socket_sendto 1222 | stream_socket_enable_crypto 1223 | stream_socket_shutdown 1224 | stream_socket_pair 1225 | stream_copy_to_stream 1226 | stream_get_contents 1227 | stream_supports_lock 1228 | fgetcsv 1229 | fputcsv 1230 | flock 1231 | get_meta_tags 1232 | stream_set_read_buffer 1233 | stream_set_write_buffer 1234 | set_file_buffer 1235 | stream_set_chunk_size 1236 | set_socket_blocking 1237 | stream_set_blocking 1238 | socket_set_blocking 1239 | stream_get_meta_data 1240 | stream_get_line 1241 | stream_wrapper_register 1242 | stream_register_wrapper 1243 | stream_wrapper_unregister 1244 | stream_wrapper_restore 1245 | stream_get_wrappers 1246 | stream_get_transports 1247 | stream_resolve_include_path 1248 | stream_is_local 1249 | get_headers 1250 | stream_set_timeout 1251 | socket_set_timeout 1252 | socket_get_status 1253 | realpath 1254 | fnmatch 1255 | fsockopen 1256 | pfsockopen 1257 | pack 1258 | unpack 1259 | get_browser 1260 | crypt 1261 | opendir 1262 | closedir 1263 | chdir 1264 | getcwd 1265 | rewinddir 1266 | readdir 1267 | dir 1268 | scandir 1269 | glob 1270 | fileatime 1271 | filectime 1272 | filegroup 1273 | fileinode 1274 | filemtime 1275 | fileowner 1276 | fileperms 1277 | filesize 1278 | filetype 1279 | file_exists 1280 | is_writable 1281 | is_writeable 1282 | is_readable 1283 | is_executable 1284 | is_file 1285 | is_dir 1286 | is_link 1287 | stat 1288 | lstat 1289 | chown 1290 | chgrp 1291 | lchown 1292 | lchgrp 1293 | chmod 1294 | touch 1295 | clearstatcache 1296 | disk_total_space 1297 | disk_free_space 1298 | diskfreespace 1299 | realpath_cache_size 1300 | realpath_cache_get 1301 | mail 1302 | ezmlm_hash 1303 | openlog 1304 | syslog 1305 | closelog 1306 | lcg_value 1307 | metaphone 1308 | ob_start 1309 | ob_flush 1310 | ob_clean 1311 | ob_end_flush 1312 | ob_end_clean 1313 | ob_get_flush 1314 | ob_get_clean 1315 | ob_get_length 1316 | ob_get_level 1317 | ob_get_status 1318 | ob_get_contents 1319 | ob_implicit_flush 1320 | ob_list_handlers 1321 | ksort 1322 | krsort 1323 | natsort 1324 | natcasesort 1325 | asort 1326 | arsort 1327 | sort 1328 | rsort 1329 | usort 1330 | uasort 1331 | uksort 1332 | shuffle 1333 | array_walk 1334 | array_walk_recursive 1335 | count 1336 | end 1337 | prev 1338 | next 1339 | reset 1340 | current 1341 | key 1342 | min 1343 | max 1344 | in_array 1345 | array_search 1346 | extract 1347 | compact 1348 | array_fill 1349 | array_fill_keys 1350 | range 1351 | array_multisort 1352 | array_push 1353 | array_pop 1354 | array_shift 1355 | array_unshift 1356 | array_splice 1357 | array_slice 1358 | array_merge 1359 | array_merge_recursive 1360 | array_replace 1361 | array_replace_recursive 1362 | array_keys 1363 | array_values 1364 | array_count_values 1365 | array_column 1366 | array_reverse 1367 | array_reduce 1368 | array_pad 1369 | array_flip 1370 | array_change_key_case 1371 | array_rand 1372 | array_unique 1373 | array_intersect 1374 | array_intersect_key 1375 | array_intersect_ukey 1376 | array_uintersect 1377 | array_intersect_assoc 1378 | array_uintersect_assoc 1379 | array_intersect_uassoc 1380 | array_uintersect_uassoc 1381 | array_diff 1382 | array_diff_key 1383 | array_diff_ukey 1384 | array_udiff 1385 | array_diff_assoc 1386 | array_udiff_assoc 1387 | array_diff_uassoc 1388 | array_udiff_uassoc 1389 | array_sum 1390 | array_product 1391 | array_filter 1392 | array_map 1393 | array_chunk 1394 | array_combine 1395 | array_key_exists 1396 | pos 1397 | sizeof 1398 | key_exists 1399 | assert 1400 | assert_options 1401 | version_compare 1402 | ftok 1403 | str_rot13 1404 | stream_get_filters 1405 | stream_filter_register 1406 | stream_bucket_make_writeable 1407 | stream_bucket_prepend 1408 | stream_bucket_append 1409 | stream_bucket_new 1410 | output_add_rewrite_var 1411 | output_reset_rewrite_vars 1412 | sys_get_temp_dir 1413 | msg_get_queue 1414 | msg_send 1415 | msg_receive 1416 | msg_remove_queue 1417 | msg_stat_queue 1418 | msg_set_queue 1419 | msg_queue_exists 1420 | sem_get 1421 | sem_acquire 1422 | sem_release 1423 | sem_remove 1424 | shm_attach 1425 | shm_remove 1426 | shm_detach 1427 | shm_put_var 1428 | shm_has_var 1429 | shm_get_var 1430 | shm_remove_var 1431 | tidy_getopt 1432 | tidy_parse_string 1433 | tidy_parse_file 1434 | tidy_get_output 1435 | tidy_get_error_buffer 1436 | tidy_clean_repair 1437 | tidy_repair_string 1438 | tidy_repair_file 1439 | tidy_diagnose 1440 | tidy_get_release 1441 | tidy_get_config 1442 | tidy_get_status 1443 | tidy_get_html_ver 1444 | tidy_is_xhtml 1445 | tidy_is_xml 1446 | tidy_error_count 1447 | tidy_warning_count 1448 | tidy_access_count 1449 | tidy_config_count 1450 | tidy_get_opt_doc 1451 | tidy_get_root 1452 | tidy_get_head 1453 | tidy_get_html 1454 | tidy_get_body 1455 | token_get_all 1456 | token_name 1457 | wddx_serialize_value 1458 | wddx_serialize_vars 1459 | wddx_packet_start 1460 | wddx_packet_end 1461 | wddx_add_vars 1462 | wddx_deserialize 1463 | xml_parser_create 1464 | xml_parser_create_ns 1465 | xml_set_object 1466 | xml_set_element_handler 1467 | xml_set_character_data_handler 1468 | xml_set_processing_instruction_handler 1469 | xml_set_default_handler 1470 | xml_set_unparsed_entity_decl_handler 1471 | xml_set_notation_decl_handler 1472 | xml_set_external_entity_ref_handler 1473 | xml_set_start_namespace_decl_handler 1474 | xml_set_end_namespace_decl_handler 1475 | xml_parse 1476 | xml_parse_into_struct 1477 | xml_get_error_code 1478 | xml_error_string 1479 | xml_get_current_line_number 1480 | xml_get_current_column_number 1481 | xml_get_current_byte_index 1482 | xml_parser_free 1483 | xml_parser_set_option 1484 | xml_parser_get_option 1485 | utf8_encode 1486 | utf8_decode 1487 | xmlrpc_encode 1488 | xmlrpc_decode 1489 | xmlrpc_decode_request 1490 | xmlrpc_encode_request 1491 | xmlrpc_get_type 1492 | xmlrpc_set_type 1493 | xmlrpc_is_fault 1494 | xmlrpc_server_create 1495 | xmlrpc_server_destroy 1496 | xmlrpc_server_register_method 1497 | xmlrpc_server_call_method 1498 | xmlrpc_parse_method_descriptions 1499 | xmlrpc_server_add_introspection_data 1500 | xmlrpc_server_register_introspection_callback 1501 | xmlwriter_open_uri 1502 | xmlwriter_open_memory 1503 | xmlwriter_set_indent 1504 | xmlwriter_set_indent_string 1505 | xmlwriter_start_comment 1506 | xmlwriter_end_comment 1507 | xmlwriter_start_attribute 1508 | xmlwriter_end_attribute 1509 | xmlwriter_write_attribute 1510 | xmlwriter_start_attribute_ns 1511 | xmlwriter_write_attribute_ns 1512 | xmlwriter_start_element 1513 | xmlwriter_end_element 1514 | xmlwriter_full_end_element 1515 | xmlwriter_start_element_ns 1516 | xmlwriter_write_element 1517 | xmlwriter_write_element_ns 1518 | xmlwriter_start_pi 1519 | xmlwriter_end_pi 1520 | xmlwriter_write_pi 1521 | xmlwriter_start_cdata 1522 | xmlwriter_end_cdata 1523 | xmlwriter_write_cdata 1524 | xmlwriter_text 1525 | xmlwriter_write_raw 1526 | xmlwriter_start_document 1527 | xmlwriter_end_document 1528 | xmlwriter_write_comment 1529 | xmlwriter_start_dtd 1530 | xmlwriter_end_dtd 1531 | xmlwriter_write_dtd 1532 | xmlwriter_start_dtd_element 1533 | xmlwriter_end_dtd_element 1534 | xmlwriter_write_dtd_element 1535 | xmlwriter_start_dtd_attlist 1536 | xmlwriter_end_dtd_attlist 1537 | xmlwriter_write_dtd_attlist 1538 | xmlwriter_start_dtd_entity 1539 | xmlwriter_end_dtd_entity 1540 | xmlwriter_write_dtd_entity 1541 | xmlwriter_output_memory 1542 | xmlwriter_flush 1543 | zip_open 1544 | zip_close 1545 | zip_read 1546 | zip_entry_open 1547 | zip_entry_close 1548 | zip_entry_read 1549 | zip_entry_filesize 1550 | zip_entry_name 1551 | zip_entry_compressedsize 1552 | zip_entry_compressionmethod 1553 | dl 1554 | cli_set_process_title 1555 | cli_get_process_title -------------------------------------------------------------------------------- /resources/headers: -------------------------------------------------------------------------------- 1 | accept 2 | accept-charset 3 | accept-encoding 4 | accept-language 5 | accept-ranges 6 | access-control-allow-credentials 7 | access-control-allow-headers 8 | access-control-allow-methods 9 | access-control-allow-origin 10 | access-control-expose-headers 11 | access-control-max-age 12 | access-control-request-headers 13 | access-control-request-method 14 | age 15 | allow 16 | authorization 17 | cache-control 18 | connection 19 | contact 20 | content-disposition 21 | content-encoding 22 | content-language 23 | content-length 24 | content-location 25 | content-range 26 | content-security-policy 27 | content-security-policy-report-only 28 | content-type 29 | cookie 30 | cookie2 31 | dnt 32 | date 33 | destination 34 | etag 35 | expect 36 | expires 37 | forwarded 38 | from 39 | host~%h:%s 40 | if-match 41 | if-modified-since 42 | if-none-match 43 | if-range 44 | if-unmodified-since 45 | keep-alive 46 | large-allocation 47 | last-modified 48 | location 49 | origin~https://%s.%h 50 | pragma 51 | profile 52 | proxy-authenticate 53 | proxy-authorization 54 | public-key-pins 55 | public-key-pins-report-only 56 | range 57 | referer~http://%s.%h/ 58 | referrer-policy 59 | report-to 60 | retry-after 61 | server 62 | set-cookie 63 | set-cookie2 64 | sourcemap 65 | strict-transport-security 66 | te 67 | timing-allow-origin 68 | tk 69 | trailer 70 | transfer-encoding 71 | upgrade-insecure-requests 72 | user-agent 73 | vary 74 | via 75 | www-authenticate 76 | warning 77 | x-content-type-options 78 | x-dns-prefetch-control 79 | x-forwarded-for 80 | x-forwarded-host~%s.%h 81 | x-forwarded-proto 82 | x-forwarded-port 83 | front-end-https 84 | x-forwarded-protocol 85 | x-forwarded-ssl 86 | x-url-scheme 87 | x-cluster-client-ip 88 | x-forwarded-server~%s.%h 89 | proxy-host 90 | x-wap-profile 91 | x-original-url 92 | x-rewrite-url 93 | x-http-destinationurl 94 | proxy-connection 95 | x-uidh 96 | true-client-ip 97 | request-uri 98 | orig_path_info 99 | client-ip 100 | x-real-ip 101 | x-originating-ip 102 | cf-ipcountry 103 | cf-visitor 104 | remote-userhttps 105 | server-software 106 | web-server-api 107 | remote-addr 108 | remote-host 109 | remote-user 110 | request-method 111 | script-name 112 | path-info 113 | unencoded-url 114 | x-arr-ssl 115 | x-arr-log-id 116 | soapaction 117 | x-original-http-command 118 | x-server-name 119 | x-server-port 120 | query-string 121 | auth-password 122 | auth-type 123 | auth-user 124 | cert-cookie 125 | cert-flags 126 | cert-issuer 127 | cert-keysize 128 | cert-secretkeysize 129 | cert-serialnumber 130 | cert-server-issuer 131 | cert-server-subject 132 | cert-subject 133 | cf-template-path 134 | context-path 135 | gateway-interface 136 | https-keysize 137 | https-secretkeysize 138 | https-server-issuer 139 | https-server-subject 140 | http-accept 141 | http-accept-encoding 142 | http-accept-language 143 | http-connection 144 | http-cookie 145 | http-host 146 | http-referer 147 | http-url 148 | http-user-agent 149 | local-addr 150 | path-translated 151 | server-name 152 | server-port 153 | server-port-secure 154 | server-protocol 155 | cloudfront-viewer-country 156 | x-scheme 157 | x-cascade 158 | x-http-method-override 159 | x-http-path-override 160 | x-http-host-override 161 | x-http-method 162 | x-method-override 163 | x-cf-url 164 | php-auth-user 165 | php-auth-pw 166 | error 167 | post-vars 168 | raw-post-data 169 | proxy-request-fulluri 170 | request 171 | server-varsabantecart 172 | accept-application 173 | accept-encodxng 174 | accept-version 175 | action 176 | admin 177 | akamai-origin-hop 178 | app 179 | app-key 180 | apply-to-redirect-ref 181 | atcept-language 182 | auth-digest-ie 183 | auth-key 184 | auth-realm 185 | base-url 186 | bearer-indication 187 | browser-user-agent 188 | case-files 189 | category 190 | ch 191 | challenge-response 192 | charset 193 | client-address 194 | client-bad-request 195 | client-conflict 196 | client-error-connect 197 | client-expectation-failed 198 | client-forbidden 199 | client-gone 200 | client-length-required 201 | client-method-not-allowed 202 | client-not-acceptable 203 | client-not-found 204 | client-payment-required 205 | client-precondition-failed 206 | client-proxy-auth-required 207 | client-quirk-mode 208 | client-requested-range-not-possible 209 | client-request-timeout 210 | client-request-too-large 211 | client-request-uri-too-large 212 | client-unauthorized 213 | client-unsupported-media-type 214 | cloudinary-name 215 | cloudinary-public-id 216 | cloudinaryurl 217 | cloudinary-version 218 | compress 219 | connection-type 220 | content 221 | content-type-xhtml 222 | cookies 223 | core-base 224 | credentials-filepath 225 | curl 226 | curl-multithreaded 227 | custom-secret-header 228 | dataserviceversion 229 | destroy 230 | devblocksproxybase 231 | devblocksproxyhost 232 | devblocksproxyssl 233 | digest 234 | dir 235 | dir-name 236 | dir-resource 237 | disable-gzip 238 | dkim-signature 239 | download-bad-url 240 | download-cut-short 241 | download-mime-type 242 | download-no-server 243 | download-size 244 | download-status-not-found 245 | download-status-server-error 246 | download-status-unauthorized 247 | download-status-unknown 248 | download-url 249 | env-silla-environment 250 | espo-authorization 251 | espo-cgi-auth 252 | eve-charid 253 | eve-charname 254 | eve-solarsystemid 255 | eve-solarsystemname 256 | ex-copy-movie 257 | ext 258 | fake-header 259 | fastly-client-ip 260 | fb-appid 261 | fb-secret 262 | filename 263 | file-not-found 264 | files 265 | files-vars 266 | foo-bar 267 | force-language 268 | force-local-xhprof 269 | forwarded-proto 270 | fromlink 271 | givenname 272 | global-all 273 | global-cookie 274 | global-get 275 | global-post 276 | google-code-project-hosting-hook-hmac 277 | h0st 278 | home 279 | host-liveserver 280 | host-name 281 | host-unavailable 282 | http-authorization 283 | if-modified-since-version 284 | if-posted-before 285 | if-unmodified-since-version 286 | images 287 | info 288 | ischedule-version 289 | iv-groups 290 | iv-user 291 | jenkins 292 | kiss-rpc 293 | last-event-id 294 | local-dir 295 | mail 296 | max-conn 297 | maxdataserviceversion 298 | max-request-size 299 | max-uri-length 300 | message 301 | message-b 302 | mode 303 | mod-env 304 | mod-security-message 305 | module-class 306 | module-class-path 307 | module-name 308 | ms-asprotocolversion 309 | msisdn 310 | my-header 311 | mysqlport 312 | native-sockets 313 | nonce 314 | not-exists 315 | notification-template 316 | onerror-return 317 | organizer 318 | params-get-catid 319 | params-get-currentday 320 | params-get-disposition 321 | params-get-downwards 322 | params-get-givendate 323 | params-get-lang 324 | params-get-type 325 | passkey 326 | path-base 327 | path-themes 328 | phpthreads 329 | portsensor-auth 330 | post-error 331 | postredir-301 332 | postredir-302 333 | postredir-all 334 | protocol 335 | protocols 336 | proxy-agent 337 | proxy-http-1-0 338 | proxy-pwd 339 | proxy-socks4a 340 | proxy-socks5-hostname 341 | proxy-url 342 | pull 343 | querystring 344 | real-ip 345 | real-method 346 | reason 347 | reason-phrase 348 | redirected-accept-language 349 | redirection-found 350 | redirection-multiple-choices 351 | redirection-not-modified 352 | redirection-permanent 353 | redirection-see-other 354 | redirection-temporary 355 | redirection-unused 356 | redirection-use-proxy 357 | redirect-problem-withoutwww 358 | redirect-problem-withwww 359 | ref 360 | referer 361 | refresh 362 | remix-hash 363 | remote-host-wp 364 | request-method- 365 | response 366 | rest-key 367 | returned-error 368 | rlnclientipaddr 369 | safe-ports-list 370 | safe-ports-ssl-list 371 | schedule-reply 372 | sec-websocket-accept 373 | sec-websocket-extensions 374 | sec-websocket-key1 375 | sec-websocket-key2 376 | sec-websocket-origin 377 | sec-websocket-protocol 378 | sec-websocket-version 379 | self 380 | send-x-frame-options 381 | server-bad-gateway 382 | server-error 383 | server-gateway-timeout 384 | server-internal 385 | server-not-implemented 386 | server-service-unavailable 387 | server-unsupported-version 388 | session-id-tag 389 | shib- 390 | shib-identity-provider 391 | shib-logouturl 392 | shopilex 393 | sn 394 | socketlog 395 | somevar 396 | sp-client 397 | ssl-offloaded 398 | sslsessionid 399 | ssl-session-id 400 | status- 401 | status-403 402 | status-403-admin-del 403 | status-404 404 | status-code 405 | status-platform-403 406 | success-accepted 407 | success-created 408 | success-no-content 409 | success-non-authoritative 410 | success-ok 411 | success-partial-content 412 | success-reset-content 413 | test 414 | test-config 415 | test-server-path 416 | test-something-anything 417 | ticket 418 | time-out 419 | tmp 420 | translate 421 | ua-color 422 | ua-resolution 423 | ua-voice 424 | unit-test-mode 425 | upgrade 426 | uri 427 | url-sanitize-path 428 | use-gzip 429 | useragent-via 430 | user-email 431 | user-id 432 | user-photos 433 | util 434 | verbose 435 | versioncode 436 | x-aastra-expmod1 437 | x-aastra-expmod2 438 | x-aastra-expmod3 439 | x-accel-mapping 440 | x-advertiser-id 441 | x-ajax-real-method 442 | x-alto-ajax-keyz 443 | x-api-signature 444 | x-api-timestamp 445 | x-apple-client-application 446 | x-apple-store-front 447 | x-authentication 448 | x-authentication-key 449 | x-auth-mode 450 | x-authorization 451 | x-auth-password 452 | x-auth-service-provider 453 | x-auth-token 454 | x-auth-userid 455 | x-auth-username 456 | x-avantgo-screensize 457 | x-azc-remote-addr 458 | x-bear-ajax-request 459 | x-bluecoat-via 460 | x-browser-height 461 | x-browser-width 462 | x-cept-encoding 463 | x-chrome-extension 464 | x-cisco-bbsm-clientip 465 | x-client-host 466 | x-client-id 467 | x-clientip 468 | x-client-key 469 | x-client-os 470 | x-client-os-ver 471 | x-collect-coverage 472 | x-credentials-request 473 | x-csrf-crumb 474 | x-cuid 475 | x-custom 476 | x-dagd-proxy 477 | x-davical-testcase 478 | x-debug-test 479 | x-dialog 480 | x-drestcg 481 | x-dsid 482 | x-enable-coverage 483 | x-environment-override 484 | x-experience-api-version 485 | x-fb-user-remote-addr 486 | x-file-id 487 | x-file-resume 488 | x-foo-bar 489 | x-forwarded-for-original 490 | x-forwarder-for 491 | x-forward-proto 492 | x-from 493 | x-gb-shared-secret 494 | x-geoip-country 495 | x-get-checksum 496 | x-helpscout-event 497 | x-hgarg- 498 | x-host 499 | x-https 500 | x-htx-agent 501 | x-if-unmodified-since 502 | x-imbo-test-config 503 | x-insight 504 | x-ip 505 | x-ip-trail 506 | x-iwproxy-nesting 507 | x-jphone-color 508 | x-jphone-geocode 509 | x-kaltura-remote-addr 510 | x-known-signature 511 | x-known-username 512 | x-litmus-second 513 | x-machine 514 | x-mandrill-signature 515 | x-mobile-ua 516 | x-mosso-dt 517 | x-msisdn 518 | x-ms-policykey 519 | x-myqee-system-debug 520 | x-myqee-system-hash 521 | x-myqee-system-isadmin 522 | x-myqee-system-isrest 523 | x-myqee-system-pathinfo 524 | x-myqee-system-project 525 | x-myqee-system-rstr 526 | x-myqee-system-time 527 | x-network-info 528 | x-nfsn-https 529 | x-ning-request-uri 530 | x-nokia-connection-mode 531 | x-nokia-msisdn 532 | x-nokia-wia-accept-original 533 | x-nokia-wtls 534 | x-nuget-apikey 535 | x-opera-info 536 | x-operamini-features 537 | x-orchestra-scheme 538 | x-orig-client 539 | x-original-host 540 | x-originally-forwarded-for 541 | x-originally-forwarded-proto 542 | x-original-remote-addr 543 | x-overlay 544 | x-pagelet-fragment 545 | x-password 546 | xpdb-debugger 547 | x-phabricator-csrf 548 | x-phpbb-using-plupload 549 | xproxy 550 | x-proxy-url 551 | x-pswd 552 | x-qafoo-profiler 553 | x-remote-protocol 554 | x-render-partial 555 | x-request 556 | x-request-id 557 | x-request-start 558 | x-response-format 559 | x-rest-cors 560 | x-sakura-forwarded-for 561 | x-scalr-auth-key 562 | x-scalr-auth-token 563 | x-scalr-env-id 564 | x-screen-height 565 | x-screen-width 566 | x-sendfile-type 567 | x-serialize 568 | x-serial-number 569 | x-server-id 570 | x-sina-proxyuser 571 | x-skyfire-screen 572 | x-ssl 573 | x-subdomain 574 | x-teamsite-preremap 575 | x-test-session-id 576 | x-tine20-jsonkey 577 | x-tine20-request-type 578 | x-tomboy-client 579 | x-tor 580 | x-twilio-signature 581 | x-uniquewcid 582 | x-up-calling-line-id 583 | x-up-devcap-screendepth 584 | x-upload-maxresolution 585 | x-upload-name 586 | x-upload-size 587 | x-upload-type 588 | x-user-agent 589 | x-username 590 | x-verify-credentials-authorization 591 | x-wap-client-sdu-size 592 | x-wap-gateway 593 | x-wap-network-client-ip 594 | x-wap-network-client-msisdn 595 | x-wap-proxy-cookie 596 | x-wap-session-id 597 | x-wap-tod 598 | x-wap-tod-coded 599 | x-wikimedia-debug 600 | x-wp-pjax-prefetch 601 | x-ws-api-key 602 | x-xc-schema-version 603 | x-xhprof-debug 604 | x-xhr-referer 605 | x-xmlhttprequest 606 | x-xpid 607 | xxx-real-ip 608 | xxxxxxxxxxxxxxx 609 | x-zikula-ajax-token 610 | x-zotero-version 611 | x-ztgo-bearerinfo 612 | y 613 | zotero-api-version 614 | zotero-write-token 615 | access-token 616 | ajax 617 | app-env 618 | bae-env-addr-bcms 619 | bae-env-addr-bus 620 | bae-env-addr-channel 621 | bae-logid 622 | basic 623 | catalog 624 | clientip 625 | debug 626 | delete 627 | enable-gzip 628 | enable-no-cache-headers 629 | error-1 630 | error-2 631 | error-3 632 | error-4 633 | eve-trusted 634 | fire-breathing-dragon 635 | format 636 | gzip-level 637 | head 638 | hosti 639 | htaccess 640 | image 641 | incap-client-ip 642 | local-content-sha1 643 | on-behalf-of 644 | options 645 | password 646 | pink-pony 647 | proxy-password 648 | put 649 | request2-tests-base-url 650 | request2-tests-proxy-host 651 | request-timeout 652 | rest-sign 653 | root 654 | support-events 655 | token 656 | user 657 | useragent 658 | user-mail 659 | user-name 660 | version-none 661 | viad 662 | x 663 | x-access-token 664 | x-amz-date 665 | x-auth-key 666 | x-auth-user 667 | x-confirm-delete 668 | x-do-not-track 669 | x-elgg-nonce 670 | x-expected-entity-length 671 | x-filename 672 | x-flash-version 673 | x-flx-consumer-key 674 | x-flx-consumer-secret 675 | x-flx-redirect-url 676 | x-forwarded-scheme 677 | x-jphone-msname 678 | x-options 679 | x-os-prefs 680 | x-pjax-container 681 | x-request-timestamp 682 | x-rest-password 683 | x-rest-username 684 | x-te 685 | x-unique-id 686 | x-up-devcap-iscolor 687 | accesskey 688 | auth-any 689 | auth-basic 690 | auth-digest 691 | auth-gssneg 692 | auth-ntlm 693 | code 694 | cookie-httponly 695 | cookie-parse-raw 696 | cookie-secure 697 | deflate-level-def 698 | deflate-level-max 699 | deflate-level-min 700 | deflate-strategy-def 701 | deflate-strategy-filt 702 | deflate-strategy-fixed 703 | deflate-strategy-huff 704 | deflate-strategy-rle 705 | deflate-type-gzip 706 | deflate-type-raw 707 | deflate-type-zlib 708 | e-encoding 709 | e-header 710 | e-invalid-param 711 | e-malformed-headers 712 | e-message-type 713 | encoding-stream-flush-full 714 | encoding-stream-flush-none 715 | encoding-stream-flush-sync 716 | e-querystring 717 | e-request 718 | e-request-method 719 | e-request-pool 720 | e-response 721 | e-runtime 722 | e-socket 723 | e-url 724 | get 725 | header 726 | http-phone-number 727 | ipresolve-any 728 | ipresolve-v4 729 | ipresolve-v6 730 | link 731 | meth-acl 732 | meth-baseline-control 733 | meth-checkin 734 | meth-checkout 735 | meth-connect 736 | meth-copy 737 | meth-label 738 | meth-lock 739 | meth-merge 740 | meth-mkactivity 741 | meth-mkcol 742 | meth-mkworkspace 743 | meth-move 744 | meth-options 745 | meth-propfind 746 | meth-proppatch 747 | meth-report 748 | meth-trace 749 | meth-uncheckout 750 | meth-unlock 751 | meth-update 752 | meth-version-control 753 | msg-none 754 | msg-request 755 | msg-response 756 | oc-chunked 757 | ocs-apirequest 758 | params-allow-comma 759 | params-allow-failure 760 | params-default 761 | params-raise-error 762 | path 763 | phone-number 764 | pragma-no-cache 765 | proxy-http 766 | proxy-socks4 767 | proxy-socks5 768 | querystring-type-array 769 | querystring-type-bool 770 | querystring-type-float 771 | querystring-type-int 772 | querystring-type-object 773 | querystring-type-string 774 | redirect 775 | redirect-found 776 | redirect-perm 777 | redirect-post 778 | redirect-proxy 779 | redirect-temp 780 | refferer 781 | requesttoken 782 | sec-websocket-key 783 | sp-host 784 | ssl 785 | ssl-version-any 786 | status-bad-request 787 | status-forbidden 788 | support 789 | support-encodings 790 | support-magicmime 791 | support-requests 792 | support-sslrequests 793 | surrogate-capability 794 | ua 795 | upload-default-chmod 796 | url 797 | url-from-env 798 | verbose-throttle 799 | version-1-0 800 | version-1-1 801 | version-any 802 | webodf-member-id 803 | webodf-session-id 804 | webodf-session-revision 805 | work-directory 806 | x- 807 | x-api-key 808 | x-apitoken 809 | x-csrftoken 810 | x-elgg-apikey 811 | x-elgg-hmac 812 | x-elgg-hmac-algo 813 | x-elgg-posthash 814 | x-elgg-posthash-algo 815 | x-elgg-time 816 | x-foo 817 | x-forwarded-by 818 | x-json 819 | x-litmus 820 | x-locking 821 | x-oc-mtime 822 | x-remote-addr 823 | x-request-signature 824 | x-ua-device 825 | x-update-range 826 | x-varnish 827 | x-wp-nonce 828 | auth 829 | brief 830 | chunk-size 831 | client 832 | download-attachment 833 | download-bz2 834 | download-e-headers-sent 835 | download-e-invalid-archive-type 836 | download-e-invalid-content-type 837 | download-e-invalid-file 838 | download-e-invalid-param 839 | download-e-invalid-request 840 | download-e-invalid-resource 841 | download-e-no-ext-mmagic 842 | download-e-no-ext-zlib 843 | download-inline 844 | download-tar 845 | download-tgz 846 | download-zip 847 | header-lf 848 | header-status-client-error 849 | header-status-informational 850 | header-status-redirect 851 | header-status-server-error 852 | header-status-successful 853 | https-from-lb 854 | meth-delete 855 | meth-head 856 | meth-post 857 | multipart-boundary 858 | originator 859 | php 860 | recipient 861 | request-error 862 | request-vars 863 | secretkey 864 | status-ok 865 | xauthorization 866 | x-codeception-codecoverage 867 | x-codeception-codecoverage-config 868 | x-codeception-codecoverage-debug 869 | x-codeception-codecoverage-suite 870 | x-csrf-token 871 | x-dokuwiki-do 872 | x-helpscout-signature 873 | x-nokia-bearer 874 | xonnection 875 | x-purpose 876 | xroxy-connection 877 | x-user 878 | bae-env-appid 879 | catalog-server 880 | cookie-path 881 | custom-header 882 | forwarded-for-ip 883 | meth-get 884 | meth-put 885 | opencart 886 | unless-modified-since 887 | www-address 888 | x-content-type 889 | x-hub-signature 890 | x-signature 891 | bae-env-addr-sql-ip 892 | bae-env-addr-sql-port 893 | cache-info 894 | client-error-cannot-access-local-file 895 | client-error-cannot-connect 896 | client-error-communication-failure 897 | client-error-invalid-parameters 898 | client-error-invalid-server-address 899 | client-error-no-error 900 | client-error-protocol-failure 901 | client-error-unspecified-error 902 | error-formatting-html 903 | lock-token 904 | onerror-continue 905 | onerror-die 906 | overwrite 907 | prefer 908 | shib-application-id 909 | x-fireloggerauth 910 | cookie-domain 911 | https 912 | meth- 913 | modauth 914 | port 915 | post 916 | read-state-begin 917 | read-state-body 918 | read-state-headers 919 | socket-connection-err 920 | str-match 921 | transport-err 922 | coming-from 923 | nl 924 | ua-pixels 925 | x-coming-from 926 | x-jphone-display 927 | x-up-devcap-screenpixels 928 | x-whatever 929 | appname 930 | proxy-port 931 | version 932 | x-forward-for 933 | proxy-user 934 | x-em-uid 935 | x-file-type 936 | bar 937 | proxy 938 | timeout 939 | referrer 940 | x-forwarded-ssl 941 | x-jphone-uid 942 | x-file-size 943 | accepted 944 | appcookie 945 | bad-gateway 946 | bae-env-addr-bcs 947 | conflict 948 | continue 949 | created 950 | expectation-failed 951 | failed-dependency 952 | gateway-time-out 953 | gone 954 | insufficient-storage 955 | internal-server-error 956 | length-required 957 | locked 958 | method-not-allowed 959 | moved-permanently 960 | moved-temporarily 961 | multiple-choices 962 | multi-status 963 | no-content 964 | non-authoritative 965 | not-acceptable 966 | not-extended 967 | not-implemented 968 | not-modified 969 | partial-content 970 | payment-required 971 | precondition-failed 972 | processing 973 | proxy-authentication-required 974 | range-not-satisfiable 975 | request-entity-too-large 976 | request-time-out 977 | request-uri-too-large 978 | reset-content 979 | see-other 980 | service-unavailable 981 | switching-protocols 982 | temporary-redirect 983 | unprocessable-entity 984 | unsupported-media-type 985 | upgrade-required 986 | use-proxy 987 | variant-also-varies 988 | version-not-supported 989 | x-operamini-phone 990 | bad-request 991 | forbidden 992 | unauthorized 993 | user-agent-via 994 | appversion 995 | not-found 996 | url-strip- 997 | x-pjax 998 | cf-connecting-ip 999 | x-dcmguid 1000 | foo 1001 | info-download-size 1002 | info-download-time 1003 | info-return-code 1004 | info-total-request-stat 1005 | info-total-response-stat 1006 | x-firelogger 1007 | content-md5 1008 | x-up-subno 1009 | bae-env-ak 1010 | bae-env-sk 1011 | if 1012 | ok 1013 | url-join-path 1014 | url-join-query 1015 | url-replace 1016 | url-strip-all 1017 | url-strip-auth 1018 | url-strip-fragment 1019 | url-strip-pass 1020 | url-strip-path 1021 | url-strip-port 1022 | url-strip-query 1023 | url-strip-user 1024 | depth 1025 | x-file-name 1026 | x-moz 1027 | x-ucbrowser-device-ua 1028 | device-stock-ua 1029 | mod-rewrite 1030 | x-nokia-ipaddress 1031 | x-bolt-phone-ua 1032 | x-original-user-agent 1033 | x-skyfire-phone 1034 | title 1035 | ssl-https 1036 | request-error-file 1037 | request-error-gzip-crc 1038 | request-error-gzip-data 1039 | request-error-gzip-method 1040 | request-error-gzip-read 1041 | request-error-proxy 1042 | request-error-redirects 1043 | request-error-response 1044 | request-error-url 1045 | slug 1046 | x-att-deviceid 1047 | authentication 1048 | x-firephp-version 1049 | x-mobile-gateway 1050 | request-mbstring 1051 | x-device-user-agent 1052 | x-huawei-userid 1053 | x-orange-id 1054 | x-vodafone-3gpdpcontext 1055 | x-wap-clientid 1056 | ua-cpu 1057 | wap-connection 1058 | x-nokia-gateway-id 1059 | ua-os 1060 | body-maxlength 1061 | body-truncated 1062 | max-forwards 1063 | mimetype 1064 | verify-cert 1065 | request-http-ver-1-0 1066 | request-http-ver-1-1 1067 | request-method-delete 1068 | request-method-get 1069 | request-method-head 1070 | request-method-options 1071 | request-method-post 1072 | request-method-put 1073 | request-method-trace 1074 | x-operamini-phone-ua 1075 | status 1076 | x-update 1077 | method 1078 | forwarded-for 1079 | x-forwarded 1080 | scheme 1081 | x-forwarded-server 1082 | origin 1083 | x-client-ip 1084 | x-prototype-version 1085 | clientaddress 1086 | base 1087 | pc-remote-addr 1088 | post-files 1089 | session-vars 1090 | cookie-vars 1091 | env-vars 1092 | get-vars 1093 | server-vars 1094 | x-forwarded-host 1095 | x-requested-with 1096 | referer 1097 | host 1098 | alt-used 1099 | x-original-url~/%s 1100 | x-rewrite-url~/%s 1101 | command 1102 | __requesturi 1103 | __requestverb 1104 | x-http-status-code-override 1105 | x-amzn-remapped-host 1106 | x-amz-website-redirect-location 1107 | x-up-devcap-post-charset 1108 | http_sm_authdirname 1109 | http_sm_authdirnamespace 1110 | http_sm_authdiroid 1111 | http_sm_authdirserver 1112 | http_sm_authreason 1113 | http_sm_authtype 1114 | http_sm_dominocn 1115 | http_sm_realm 1116 | http_sm_realmoid 1117 | http_sm_sdomain 1118 | http_sm_serveridentityspec 1119 | http_sm_serversessionid 1120 | http_sm_serversessionspec 1121 | http_sm_sessiondrift 1122 | http_sm_timetoexpire 1123 | http_sm_transactionid 1124 | http_sm_universalid 1125 | http_sm_user 1126 | http_sm_userdn 1127 | http_sm_usermsg 1128 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'param-miner' 2 | -------------------------------------------------------------------------------- /src/burp/BurpExtender.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.databind.exc.MismatchedInputException; 5 | import com.google.gson.JsonElement; 6 | import com.google.gson.JsonParseException; 7 | import com.google.gson.JsonParser; 8 | import org.apache.commons.lang3.StringEscapeUtils; 9 | import org.apache.commons.lang3.StringUtils; 10 | 11 | import javax.swing.*; 12 | import java.io.ByteArrayOutputStream; 13 | import java.io.PrintStream; 14 | import java.net.URL; 15 | import java.util.*; 16 | import java.util.concurrent.*; 17 | 18 | import static burp.Keysmith.getHtmlKeys; 19 | import static burp.Keysmith.getWords; 20 | 21 | 22 | 23 | 24 | public class BurpExtender implements IBurpExtender, IExtensionStateListener { 25 | private static final String name = "Param Miner"; 26 | private static final String version = "1.31"; 27 | private ThreadPoolExecutor taskEngine; 28 | static ParamGrabber paramGrabber; 29 | static SettingsBox configSettings = new SettingsBox(); 30 | static SettingsBox guessSettings = new SettingsBox(); 31 | 32 | @Override 33 | public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks) { 34 | 35 | new Utilities(callbacks, new HashMap<>(), name); 36 | 37 | // config only (currently param-guess displays everything) 38 | configSettings.register("Add 'fcbz' cachebuster", false); 39 | configSettings.register("Add dynamic cachebuster", false); 40 | configSettings.register("Add header cachebuster", false); 41 | configSettings.register("learn observed words", false); 42 | configSettings.register("enable auto-mine", false); 43 | configSettings.register("auto-mine headers", false); 44 | configSettings.register("auto-mine cookies", false); 45 | configSettings.register("auto-mine params", false); 46 | configSettings.register("auto-nest params", false); 47 | 48 | // param-guess only 49 | //guessSettings.importSettings(globalSettings); 50 | guessSettings.register("learn observed words", false); 51 | guessSettings.register("skip boring words", true); 52 | guessSettings.register("only report unique params", false); 53 | guessSettings.register("response", true); 54 | guessSettings.register("request", true); 55 | guessSettings.register("use basic wordlist", true); 56 | guessSettings.register("use bonus wordlist", false); 57 | guessSettings.register("use assetnote params", false); 58 | guessSettings.register("use custom wordlist", false); 59 | guessSettings.register("custom wordlist path", "/usr/share/dict/words"); 60 | guessSettings.register("bruteforce", false); 61 | guessSettings.register("skip uncacheable", false); 62 | guessSettings.register("dynamic keyload", false); 63 | guessSettings.register("max one per host", false); 64 | guessSettings.register("max one per host+status", false); 65 | guessSettings.register("probe identified params", true); 66 | guessSettings.register("scan identified params", false); 67 | guessSettings.register("fuzz detect", false); 68 | guessSettings.register("carpet bomb", false); 69 | guessSettings.register("try cache poison", true); 70 | guessSettings.register("twitchy cache poison", false); 71 | guessSettings.register("try method flip", false); 72 | guessSettings.register("identify smuggle mutations", true); 73 | guessSettings.register("try -_ bypass", false); 74 | guessSettings.register("rotation interval", 200); 75 | guessSettings.register("rotation increment", 4); 76 | guessSettings.register("force bucketsize", -1); 77 | guessSettings.register("max bucketsize", 65536); 78 | guessSettings.register("max param length", 32); 79 | guessSettings.register("lowercase headers", true); 80 | guessSettings.register("name in issue", false); 81 | guessSettings.register("canary", "zwrtxqva"); 82 | guessSettings.register("force canary", ""); 83 | guessSettings.register("poison only", false); 84 | guessSettings.register("tunnelling retry count", 20); 85 | guessSettings.register("abort on tunnel failure", true); 86 | 87 | loadWordlists(); 88 | BlockingQueue tasks; 89 | if (Utilities.globalSettings.getBoolean("enable auto-mine")) { 90 | tasks = new PriorityBlockingQueue<>(1000, new RandomComparator()); 91 | } 92 | else { 93 | tasks = new LinkedBlockingQueue<>(); 94 | } 95 | 96 | Utilities.globalSettings.registerSetting("thread pool size", 8); 97 | taskEngine = new ThreadPoolExecutor(Utilities.globalSettings.getInt("thread pool size"), Utilities.globalSettings.getInt("thread pool size"), 10, TimeUnit.MINUTES, tasks); 98 | Utilities.globalSettings.registerListener("thread pool size", value -> { 99 | Utilities.out("Updating active thread pool size to "+value); 100 | try { 101 | taskEngine.setCorePoolSize(Integer.parseInt(value)); 102 | taskEngine.setMaximumPoolSize(Integer.parseInt(value)); 103 | } catch (IllegalArgumentException e) { 104 | taskEngine.setMaximumPoolSize(Integer.parseInt(value)); 105 | taskEngine.setCorePoolSize(Integer.parseInt(value)); 106 | } 107 | }); 108 | 109 | callbacks.setExtensionName(name); 110 | 111 | try { 112 | StringUtils.isNumeric("1"); 113 | } catch (java.lang.NoClassDefFoundError e) { 114 | Utilities.out("Failed to import the Apache Commons Lang library. You can get it from http://commons.apache.org/proper/commons-lang/"); 115 | throw new NoClassDefFoundError(); 116 | } 117 | 118 | try { 119 | callbacks.getHelpers().analyzeResponseVariations(); 120 | } catch (java.lang.NoSuchMethodError e) { 121 | Utilities.out("This extension requires Burp Suite Pro 1.7.10 or later"); 122 | throw new NoSuchMethodError(); 123 | } 124 | 125 | paramGrabber = new ParamGrabber(taskEngine); 126 | callbacks.registerContextMenuFactory(new OfferParamGuess(callbacks, paramGrabber, taskEngine)); 127 | 128 | if(Utilities.isBurpPro()) { 129 | callbacks.registerScannerCheck(new GrabScan(paramGrabber)); 130 | } 131 | 132 | callbacks.registerHttpListener(paramGrabber); 133 | callbacks.registerProxyListener(paramGrabber); 134 | 135 | SwingUtilities.invokeLater(new ConfigMenu()); 136 | 137 | new HeaderPoison("Header poison"); 138 | new PortDOS("port-DoS"); 139 | //new ValueScan("param-value probe"); 140 | new UnkeyedParamScan("Unkeyed param"); 141 | new FatGet("fat GET"); 142 | new NormalisedParamScan("normalised param"); 143 | new NormalisedPathScan("normalised path"); 144 | new RailsUtmScan("rails param cloaking scan"); 145 | new HeaderMutationScan("identify header smuggling mutations"); 146 | 147 | 148 | new BulkScanLauncher(BulkScan.scans); 149 | 150 | Utilities.callbacks.registerExtensionStateListener(this); 151 | 152 | Utilities.out("Loaded " + name + " v" + version); 153 | } 154 | 155 | 156 | private void loadWordlists() { 157 | Scanner s = new Scanner(getClass().getResourceAsStream("/functions")); 158 | while (s.hasNext()) { 159 | Utilities.phpFunctions.add(s.next()); 160 | } 161 | s.close(); 162 | 163 | Scanner params = new Scanner(getClass().getResourceAsStream("/params")); 164 | while (params.hasNext()) { 165 | Utilities.paramNames.add(params.next()); 166 | } 167 | params.close(); 168 | 169 | Scanner headers = new Scanner(getClass().getResourceAsStream("/boring_headers")); 170 | while (headers.hasNext()) { 171 | Utilities.boringHeaders.add(headers.next().toLowerCase()); 172 | } 173 | } 174 | 175 | public void extensionUnloaded() { 176 | Utilities.log("Aborting all attacks"); 177 | Utilities.unloaded.set(true); 178 | taskEngine.getQueue().clear(); 179 | taskEngine.shutdown(); 180 | } 181 | 182 | } 183 | 184 | 185 | 186 | 187 | 188 | class RequestWithOffsets { 189 | private byte[] request; 190 | private int[] offsets; 191 | 192 | public RequestWithOffsets(byte[] request, int[] offsets) { 193 | this.request = request; 194 | this.offsets = offsets; 195 | } 196 | } 197 | 198 | class ParamInsertionPoint implements IScannerInsertionPoint { 199 | byte[] request; 200 | String name; 201 | String value; 202 | byte type; 203 | 204 | ParamInsertionPoint(byte[] request, String name, String value, byte type) { 205 | this.request = request; 206 | this.name = name; 207 | this.value = value; 208 | this.type = type; 209 | } 210 | 211 | String calculateValue(String unparsed) { 212 | return unparsed; 213 | } 214 | 215 | @Override 216 | public String getInsertionPointName() { 217 | return name; 218 | } 219 | 220 | @Override 221 | public String getBaseValue() { 222 | return value; 223 | } 224 | 225 | @Override 226 | public byte[] buildRequest(byte[] payload) { 227 | IParameter newParam = Utilities.helpers.buildParameter(name, Utilities.encodeParam(Utilities.helpers.bytesToString(payload)), type); 228 | return Utilities.helpers.updateParameter(request, newParam); 229 | } 230 | 231 | @Override 232 | public int[] getPayloadOffsets(byte[] payload) { 233 | //IParameter newParam = Utilities.helpers.buildParameter(name, Utilities.encodeParam(Utilities.helpers.bytesToString(payload)), type); 234 | return new int[]{0, 0}; 235 | //return new int[]{newParam.getValueStart(), newParam.getValueEnd()}; 236 | } 237 | 238 | @Override 239 | public byte getInsertionPointType() { 240 | return type; 241 | //return IScannerInsertionPoint.INS_PARAM_BODY; 242 | // return IScannerInsertionPoint.INS_EXTENSION_PROVIDED; 243 | } 244 | } 245 | 246 | class ParamNameInsertionPoint extends ParamInsertionPoint { 247 | String attackID; 248 | String defaultPrefix; 249 | String host; 250 | HashMap present; 251 | 252 | ParamNameInsertionPoint(byte[] request, String name, String value, byte type, String attackID) { 253 | super(request, name, value, type); 254 | this.attackID = attackID; 255 | 256 | ArrayList keys = Keysmith.getAllKeys(request, new HashMap<>()); 257 | HashMap freq = new HashMap<>(); 258 | for (String key: keys) { 259 | if (key.contains(":")) { 260 | String object = key.split(":")[0]; 261 | freq.put(object, freq.getOrDefault(object, 0) + 1); 262 | } 263 | } 264 | 265 | String maxKey = null; 266 | 267 | if (Utilities.globalSettings.getBoolean("auto-nest params")) { 268 | int max = 0; 269 | for (Map.Entry entry : freq.entrySet()) { 270 | if (entry.getValue() > max) { 271 | maxKey = entry.getKey(); 272 | max = entry.getValue(); 273 | } 274 | } 275 | } 276 | defaultPrefix = maxKey; 277 | 278 | if (maxKey != null) { 279 | Utilities.out("Selected default key: "+maxKey); 280 | } 281 | else { 282 | Utilities.log("No default key available"); 283 | } 284 | 285 | present = new HashMap<>(); 286 | List headers = Utilities.analyzeRequest(request).getHeaders(); 287 | for (String header: headers) { 288 | if (header.startsWith("Host: ")) { 289 | host = header.split(": ", 2)[1]; 290 | } 291 | header = header.split(": ", 2)[0]; 292 | if (Utilities.globalSettings.getBoolean("lowercase headers")) { 293 | present.put(header.toLowerCase(), header); 294 | } 295 | else { 296 | present.put(header, header); 297 | } 298 | } 299 | } 300 | 301 | String calculateValue(String unparsed) { 302 | String canary = Utilities.globalSettings.getString("force canary"); 303 | if (!"".equals(canary)) { 304 | return canary; 305 | } 306 | return Utilities.toCanary(unparsed) + attackID + value + Utilities.fuzzSuffix(); 307 | } 308 | 309 | @Override 310 | public byte[] buildRequest(byte[] payload) { 311 | String bulk = Utilities.helpers.bytesToString(payload); 312 | String[] params = bulk.split("[|]"); 313 | ArrayList preppedParams = new ArrayList<>(); 314 | for(String key: params) { 315 | if (defaultPrefix != null && !key.contains(":")) { 316 | key = defaultPrefix + ":" + key; 317 | } 318 | preppedParams.add(Keysmith.unparseParam(key)); 319 | } 320 | 321 | if(type == IParameter.PARAM_URL || type == IParameter.PARAM_BODY || type == IParameter.PARAM_COOKIE || type == Utilities.PARAM_HEADER) { 322 | return buildBulkRequest(preppedParams); 323 | } 324 | 325 | return buildBasicRequest(preppedParams); 326 | } 327 | 328 | public byte[] buildBulkRequest(ArrayList params) { 329 | String merged = prepBulkParams(params); 330 | String replaceKey = "TCZqBcS13SA8QRCpW"; 331 | IParameter newParam = Utilities.helpers.buildParameter(replaceKey, "", type); 332 | byte[] built = Utilities.helpers.updateParameter(request, newParam); 333 | return Utilities.fixContentLength(Utilities.replace(built, Utilities.helpers.stringToBytes(replaceKey+"="), Utilities.helpers.stringToBytes(merged))); 334 | } 335 | 336 | String prepBulkParams(ArrayList params) { 337 | ArrayList preppedParams = new ArrayList<>(); 338 | 339 | String equals; 340 | String join; 341 | String trail; 342 | if(type == IParameter.PARAM_COOKIE) { 343 | equals = "="; 344 | join = "; "; 345 | trail = ";"; 346 | } 347 | else if (type == Utilities.PARAM_HEADER) { 348 | equals = ": "; 349 | join ="\r\n"; 350 | trail = ""; // \r\n 351 | } 352 | else { 353 | equals = "="; 354 | join = "&"; 355 | trail = ""; 356 | } 357 | 358 | 359 | for (String param: params) { 360 | String fullParam[] = getValue(param); 361 | if ("".equals(fullParam[0])) { 362 | continue; 363 | } 364 | if (type == Utilities.PARAM_HEADER) { 365 | preppedParams.add(fullParam[0] + equals + fullParam[1]); 366 | } 367 | else { 368 | preppedParams.add(Utilities.encodeParam(fullParam[0]) + equals + Utilities.encodeParam(fullParam[1])); 369 | } 370 | } 371 | 372 | return String.join(join, preppedParams) + trail; 373 | } 374 | 375 | String[] getValue(String name) { 376 | if (name.contains("~")) { 377 | String[] parts = name.split("~", 2); 378 | parts[1] = parts[1].replace("%s", calculateValue(name)); 379 | parts[1] = parts[1].replace("%h", host); 380 | return new String[]{parts[0], String.valueOf(Utilities.invert(parts[1]))}; 381 | } 382 | else { 383 | return new String[]{name, calculateValue(name)}; 384 | } 385 | } 386 | 387 | byte[] buildBasicRequest(ArrayList params) { 388 | byte[] built = request; 389 | for (String name: params) { 390 | String[] param = getValue(name); 391 | IParameter newParam = Utilities.helpers.buildParameter(param[0], Utilities.encodeParam(param[1]), type); 392 | built = Utilities.helpers.updateParameter(built, newParam); 393 | } 394 | return built; 395 | } 396 | } 397 | 398 | class HeaderNameInsertionPoint extends ParamNameInsertionPoint { 399 | 400 | public HeaderNameInsertionPoint(byte[] request, String name, String value, byte type, String attackID) { 401 | super(request, name, value, type, attackID); 402 | } 403 | 404 | public byte[] buildBulkRequest(ArrayList params) { 405 | String merged = prepBulkParams(params); 406 | Iterator dupeCheck= params.iterator(); 407 | byte[] body = Utilities.getBodyBytes(request); 408 | 409 | boolean fooReq = false; 410 | if (Utilities.containsBytes(body, "FOO BAR AAH\r\n".getBytes())) { 411 | fooReq = true; 412 | } 413 | 414 | if (fooReq || Utilities.containsBytes(body, " HTTP/1.1\r\n".getBytes())) { 415 | Utilities.chopNestedResponses = true; 416 | 417 | boolean usingCorrectContentLength = true; 418 | 419 | try { 420 | if (body.length != Integer.parseInt(Utilities.getHeader(request, "Content-Length"))) { 421 | usingCorrectContentLength = false; 422 | } 423 | } catch (Exception e) { 424 | 425 | } 426 | 427 | while (dupeCheck.hasNext()) { 428 | String param = dupeCheck.next().split("~", 2)[0]; 429 | byte[] toReplace = ("\n"+param+": ").getBytes(); 430 | if (Utilities.containsBytes(body, toReplace)) { 431 | body = Utilities.replace(body, toReplace, ("\nold"+param+": ").getBytes()); 432 | } 433 | } 434 | 435 | byte[] newBody; 436 | if (fooReq) { 437 | newBody = Utilities.replaceFirst(body, "FOO BAR AAH\r\n", "GET http://"+Utilities.getHeader(request, "Host")+"/ HTTP/1.1\r\n"+merged+"\r\n"); 438 | } 439 | else { 440 | newBody = Utilities.replaceFirst(body, "HTTP/1.1", "HTTP/1.1\r\n"+merged); 441 | } 442 | 443 | byte[] finalRequest = Utilities.setBody(request, new String(newBody)); 444 | if (usingCorrectContentLength) { 445 | finalRequest = Utilities.fixContentLength(finalRequest); 446 | } 447 | 448 | finalRequest = Utilities.addOrReplaceHeader(finalRequest, "X-Mine-Nested-Request", "1"); 449 | 450 | return finalRequest; 451 | } 452 | 453 | String replaceKey = "TCZqBcS13SA8QRCpW"; 454 | byte[] built = Utilities.addOrReplaceHeader(request, replaceKey, "foo"); 455 | 456 | if (params.isEmpty() || "".equals(merged)) { 457 | return built; 458 | } 459 | 460 | while (dupeCheck.hasNext()) { 461 | String param = dupeCheck.next().split("~", 2)[0]; 462 | if (present.containsKey(param)) { 463 | String toReplace = present.get(param)+": "; 464 | built = Utilities.replace(built, toReplace.getBytes(), ("old"+toReplace).getBytes()); 465 | } 466 | } 467 | 468 | return Utilities.setHeader(built, replaceKey, "x\r\n"+merged); 469 | } 470 | } 471 | 472 | class JsonParamNameInsertionPoint extends ParamInsertionPoint { 473 | byte[] headers; 474 | byte[] body; 475 | String baseInput; 476 | String attackID; 477 | JsonElement root; 478 | 479 | public JsonParamNameInsertionPoint(byte[] request, String name, String value, byte type, String attackID) { 480 | super(request, name, value, type); // Utilities.encodeJSON(value) 481 | int start = Utilities.getBodyStart(request); 482 | this.attackID = attackID; 483 | headers = Arrays.copyOfRange(request, 0, start); 484 | body = Arrays.copyOfRange(request, start, request.length); 485 | baseInput = Utilities.helpers.bytesToString(body); 486 | root = new JsonParser().parse(baseInput); 487 | } 488 | 489 | private Object makeNode(ArrayList keys, int i, Object paramValue) { 490 | if (i+1 == keys.size()) { 491 | return paramValue; 492 | } 493 | else if (Utilities.parseArrayIndex(keys.get(i+1)) != -1) { 494 | return new ArrayList(Utilities.parseArrayIndex(keys.get(i+1))); 495 | } 496 | else { 497 | return new HashMap(); 498 | } 499 | } 500 | 501 | String calculateValue(String unparsed) { 502 | return Utilities.toCanary(unparsed) + attackID + value + Utilities.fuzzSuffix(); 503 | } 504 | 505 | 506 | @Override 507 | @SuppressWarnings("unchecked") 508 | public byte[] buildRequest(byte[] payload) throws RuntimeException { 509 | String[] params = Utilities.helpers.bytesToString(payload).split("[|]"); 510 | String lastBuild = baseInput; 511 | 512 | try { 513 | for (String unparsed: params) { 514 | 515 | Object paramValue; 516 | if (unparsed.contains("~")) { 517 | String[] parts = unparsed.split("~", 2); 518 | unparsed = parts[0]; 519 | paramValue = Utilities.invert(parts[1]); 520 | } else { 521 | paramValue = calculateValue(unparsed); 522 | } 523 | 524 | ArrayList keys = new ArrayList<>(Arrays.asList(unparsed.split(":"))); 525 | 526 | boolean isArray = Utilities.parseArrayIndex(keys.get(0)) != -1; 527 | Object base; 528 | if (isArray) { 529 | try { 530 | base = new ObjectMapper().readValue(lastBuild, ArrayList.class); 531 | } 532 | catch (MismatchedInputException e) { 533 | base = new ArrayList(); 534 | } 535 | } else { 536 | try { 537 | base = new ObjectMapper().readValue(lastBuild, HashMap.class); 538 | } 539 | catch (MismatchedInputException e) { 540 | base = new HashMap(); 541 | } 542 | } 543 | 544 | Object next = base; 545 | for (int i = 0; i < keys.size(); i++) { 546 | 547 | try { 548 | String key = keys.get(i); 549 | boolean setValue = i + 1 == keys.size(); 550 | 551 | int index = Utilities.parseArrayIndex(key); 552 | if (index != -1) { 553 | ArrayList injectionPoint = (ArrayList) next; 554 | if (injectionPoint.size() < index + 1) { 555 | for (int k = injectionPoint.size(); k < index; k++) { 556 | injectionPoint.add(Utilities.generateCanary()); 557 | } 558 | injectionPoint.add(makeNode(keys, i, paramValue)); 559 | } else if (injectionPoint.get(index) == null || setValue) { 560 | injectionPoint.set(index, makeNode(keys, i, paramValue)); 561 | } 562 | next = injectionPoint.get(index); 563 | } else { 564 | HashMap injectionPoint = (HashMap) next; 565 | if (!injectionPoint.containsKey(key) || setValue) { 566 | injectionPoint.put(key, makeNode(keys, i, paramValue)); 567 | } 568 | next = injectionPoint.get(key); 569 | } 570 | } catch(ClassCastException e) { 571 | //Utilities.out("Cast error"); // todo figure out a sensible action to stop this form occuring 572 | } 573 | } 574 | 575 | lastBuild = new ObjectMapper().writeValueAsString(base); 576 | } 577 | 578 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 579 | outputStream.write(headers); 580 | outputStream.write(Utilities.helpers.stringToBytes(lastBuild)); 581 | return Utilities.fixContentLength(outputStream.toByteArray()); 582 | } catch (Exception e) { 583 | Utilities.out("Error with " + String.join(":", params)); 584 | e.printStackTrace(new PrintStream(Utilities.callbacks.getStdout())); 585 | return buildRequest(Utilities.helpers.stringToBytes("error_" + String.join(":", params).replace(":", "_"))); 586 | // throw new RuntimeException("Request creation unexpectedly failed: "+e.getMessage()); 587 | } 588 | } 589 | } 590 | 591 | 592 | 593 | 594 | 595 | 596 | -------------------------------------------------------------------------------- /src/burp/FatGet.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | import java.util.List; 3 | 4 | public class FatGet extends ParamScan { 5 | 6 | FatGet(String name) { 7 | super(name); 8 | } 9 | 10 | @Override 11 | List doScan(IHttpRequestResponse baseRequestResponse, IScannerInsertionPoint insertionPoint) { 12 | // don't scan POST 13 | if (baseRequestResponse.getRequest()[0] == 'P') { 14 | return null; 15 | } 16 | 17 | // set value to canary 18 | String canary = Utilities.generateCanary(); 19 | 20 | String fullValue = insertionPoint.getBaseValue()+canary; 21 | byte[] poison = insertionPoint.buildRequest(fullValue.getBytes()); 22 | 23 | // convert to POST 24 | poison = Utilities.helpers.toggleRequestMethod(poison); 25 | 26 | poison = Utilities.fixContentLength(Utilities.replaceFirst(poison, canary.getBytes(), (canary).getBytes())); 27 | 28 | // convert method back to GET 29 | poison = Utilities.setMethod(poison, "GET"); 30 | 31 | poison = Utilities.addOrReplaceHeader(poison, "X-HTTP-Method-Override", "POST"); 32 | poison = Utilities.addOrReplaceHeader(poison, "X-HTTP-Method", "POST"); 33 | poison = Utilities.addOrReplaceHeader(poison, "X-Method-Override", "POST"); 34 | 35 | poison = Utilities.addCacheBuster(poison, Utilities.generateCanary()); 36 | 37 | IHttpService service = baseRequestResponse.getHttpService(); 38 | 39 | Resp resp = request(service, poison); 40 | byte[] response = resp.getReq().getResponse(); 41 | 42 | if (Utilities.containsBytes(response, canary.getBytes())) { 43 | 44 | recordCandidateFound(); 45 | 46 | // report("Fat-GET body reflection", canary, resp); 47 | for (int i=0; i<5; i++) { 48 | request(service, poison); 49 | } 50 | 51 | //String toReplace = insertionPoint.getInsertionPointName()+"="+fullValue; 52 | String toReplace = canary; 53 | 54 | byte[] getPoison = Utilities.fixContentLength(Utilities.replaceFirst(poison, toReplace.getBytes(), "".getBytes())); 55 | // byte[] getPoison = baseRequestResponse.getRequest(); 56 | // getPoison = Utilities.appendToQuery(getPoison, "x="+cacheBuster); 57 | 58 | 59 | Resp poisonedResp = request(service, getPoison); 60 | if (Utilities.containsBytes(poisonedResp.getReq().getResponse(), canary.getBytes())) { 61 | report("Web Cache Poisoning via Fat GET", "The application lets users pass parameters in the body of GET requests, but does not include them in the cache key. This was confirmed by injecting the value "+canary+" using the "+insertionPoint.getInsertionPointName()+" parameter, then replaying the request without the injected value, and confirming it still appears in the response.

For further information on this technique, please refer to https://portswigger.net/research/web-cache-entanglement", resp, poisonedResp); 62 | } 63 | } 64 | 65 | return null; 66 | } 67 | 68 | @Override 69 | List doScan(byte[] baseReq, IHttpService service) { 70 | return null; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/burp/GrabScan.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class GrabScan implements IScannerCheck { 7 | 8 | private ParamGrabber paramGrabber; 9 | 10 | GrabScan(ParamGrabber paramGrabber) { 11 | this.paramGrabber = paramGrabber; 12 | } 13 | 14 | @Override 15 | public List doActiveScan(IHttpRequestResponse baseRequestResponse, IScannerInsertionPoint insertionPoint) { 16 | return new ArrayList<>(); 17 | } 18 | 19 | @Override 20 | public List doPassiveScan(IHttpRequestResponse baseRequestResponse) { 21 | if (Utilities.globalSettings.getBoolean("learn observed words")) { 22 | paramGrabber.saveParams(baseRequestResponse); 23 | } 24 | return new ArrayList<>(); 25 | } 26 | 27 | @Override 28 | public int consolidateDuplicateIssues(IScanIssue existingIssue, IScanIssue newIssue) { 29 | if (existingIssue.getIssueName().equals(newIssue.getIssueName()) && existingIssue.getIssueDetail().equals(newIssue.getIssueDetail())) 30 | return -1; 31 | else return 0; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/burp/HeaderMutationGuesser.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import org.graalvm.compiler.core.common.util.Util; 4 | 5 | import java.nio.charset.StandardCharsets; 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.Iterator; 9 | 10 | public class HeaderMutationGuesser { 11 | private ConfigurableSettings config; 12 | private IHttpRequestResponse req; 13 | private IHttpService service; 14 | public HashMap evidence; 15 | private String[][] testHeaders; 16 | 17 | HeaderMutationGuesser(IHttpRequestResponse req, ConfigurableSettings config) { 18 | this.req = req; 19 | this.config = config; 20 | this.service = req.getHttpService(); 21 | this.evidence = new HashMap(); 22 | 23 | this.testHeaders = new String[][]{ 24 | {"Content-Length: 0", "Content-Length: z"} 25 | }; 26 | } 27 | 28 | // Returns the mutation names used by HeaderMutator 29 | public ArrayList guessMutations() { 30 | byte[] baseReq = this.removeHeader(this.req.getRequest(), "Content-Length"); 31 | ArrayList ret = new ArrayList(); 32 | HeaderMutator mutator = new HeaderMutator(); 33 | 34 | // Test all the mutations to find back-end errors 35 | for (int i = 0; i< this.testHeaders.length; i++) { 36 | Iterator iterator = mutator.mutations.iterator(); 37 | String testHeaderValid = this.testHeaders[i][0]; 38 | String testHeaderInvalid = this.testHeaders[i][1]; 39 | 40 | // Get the front-end error 41 | IHttpRequestResponse frontErrReq = this.requestHeader(baseReq, testHeaderInvalid); 42 | byte[] frontError = frontErrReq.getResponse(); 43 | 44 | // Check we've managed to generate an error 45 | IHttpRequestResponse noErrReq = this.requestHeader(baseReq, testHeaderValid); 46 | byte[] noErr = noErrReq.getResponse(); 47 | if (this.requestMatch(frontError, noErr)) { 48 | continue; 49 | } 50 | 51 | if (frontError.length == 0 || noErr.length == 0) { 52 | String host = frontErrReq.getHttpService().getHost(); 53 | Utilities.out("Failed to fetch request while guessing mutations " + host); 54 | continue; 55 | } 56 | 57 | while (iterator.hasNext()) { 58 | String mutation = iterator.next(); 59 | if (ret.contains(mutation)) { 60 | continue; 61 | } 62 | byte[] mutated = mutator.mutate(testHeaderInvalid, mutation); 63 | IHttpRequestResponse testReqResp = this.requestHeader(baseReq, mutated); 64 | byte[] testReq = testReqResp.getResponse(); 65 | 66 | // Check that: 67 | // 1. We have a different error than the front-end error 68 | // 2. We have an error at all (i.e. not the same as the base request 69 | // In this case, confirm that we get no error (i.e. the base response) with mutation(CL: 0) 70 | if (!this.requestMatch(frontError, testReq) && !this.requestMatch(noErr, testReq)) { 71 | mutated = mutator.mutate(testHeaderValid, mutation); 72 | IHttpRequestResponse validReqResp = this.requestHeader(baseReq, mutated); 73 | byte[] validResp = validReqResp.getResponse(); 74 | if (this.requestMatch(noErr, validResp)) { 75 | ret.add(mutation); 76 | IHttpRequestResponse[] reqs = new IHttpRequestResponse[4]; 77 | reqs[0] = frontErrReq; 78 | reqs[1] = noErrReq; 79 | reqs[2] = testReqResp; 80 | reqs[3] = validReqResp; 81 | this.evidence.put(mutation, reqs); 82 | } 83 | } 84 | } 85 | } 86 | 87 | // TODO: Maybe re-check mutations to deal with inconsistent servers? 88 | return ret; 89 | } 90 | 91 | public void reportMutations(ArrayList mutations) { 92 | Iterator iterator = mutations.iterator(); 93 | while (iterator.hasNext()) { 94 | String mutation = iterator.next(); 95 | String urlStr = Utilities.getURL(this.req).toString(); 96 | Utilities.out("Found mutation against " + urlStr + ": " + mutation); 97 | IHttpRequestResponse[] evidence = this.evidence.get(mutation); 98 | IHttpService service = evidence[0].getHttpService(); 99 | Utilities.callbacks.addScanIssue(new CustomScanIssue( 100 | service, 101 | Utilities.helpers.analyzeRequest(service, evidence[0].getRequest()).getUrl(), 102 | evidence, 103 | "Header mutation found", 104 | "Headers can be snuck to a back-end server using the '" + mutation + "' mutation.", 105 | "Information", 106 | "Firm", 107 | "This issue is not exploitable on its own, but interesting headers may be able to be snuck through to backend servers." 108 | )); 109 | } 110 | } 111 | 112 | private IHttpRequestResponse requestHeader(byte[] baseReq, String header) { 113 | return this.requestHeader(baseReq, header.getBytes(StandardCharsets.UTF_8)); 114 | } 115 | 116 | private IHttpRequestResponse requestHeader(byte[] baseReq, byte[] header) { 117 | byte[] req = this.addHeader(baseReq, header); 118 | req = Utilities.addCacheBuster(req, Utilities.generateCanary()); 119 | return Utilities.attemptRequest(this.service, req); 120 | } 121 | 122 | private byte[] removeHeader(byte[] req, String headerName) { 123 | int[] offsets = Utilities.getHeaderOffsets(req, headerName); 124 | if (offsets == null) { 125 | return req; 126 | } 127 | int start = offsets[0]; 128 | int end = offsets[2] + 2; 129 | byte[] ret = new byte[req.length - (end - start)]; 130 | // TODO: sometimes getting null point exceptions from this line 131 | System.arraycopy(req, 0, ret, 0, start); 132 | System.arraycopy(req, end, ret, start, req.length - end); 133 | return ret; 134 | } 135 | 136 | private boolean requestMatch(byte[] resp1, byte[] resp2) { 137 | IResponseInfo info1 = Utilities.helpers.analyzeResponse(resp1); 138 | IResponseInfo info2 = Utilities.helpers.analyzeResponse(resp2); 139 | if (info1.getStatusCode() != info2.getStatusCode()) { 140 | return false; 141 | } 142 | 143 | // If we have a body length, use that as a comparison. Otherwise, use the total length 144 | int length1 = resp1.length - info1.getBodyOffset(); 145 | int length2 = resp2.length - info2.getBodyOffset(); 146 | if (length1 == 0 || length2 == 0) { 147 | length1 = resp1.length; 148 | length2 = resp2.length; 149 | } 150 | int lower = (9 * length1) / 10; 151 | int upper = (11 * length1) / 10; 152 | 153 | if (length2 <= lower || length2 >= upper) { 154 | return false; 155 | } 156 | 157 | return true; 158 | } 159 | 160 | private byte[] addHeader(byte[] baseReq, byte[] header) { 161 | IRequestInfo info = Utilities.analyzeRequest(baseReq); 162 | int offset = info.getBodyOffset() - 2; 163 | byte[] ret = new byte[baseReq.length + header.length + 2]; 164 | byte[] crlf = "\r\n".getBytes(StandardCharsets.UTF_8); 165 | 166 | System.arraycopy(baseReq, 0, ret, 0, offset); 167 | System.arraycopy(header, 0, ret, offset, header.length); 168 | int newOffset = offset + header.length; 169 | System.arraycopy(crlf, 0, ret, newOffset, 2); 170 | newOffset += 2; 171 | System.arraycopy(baseReq, offset, ret, newOffset, baseReq.length - offset); 172 | 173 | return ret; 174 | } 175 | } -------------------------------------------------------------------------------- /src/burp/HeaderMutationScan.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class HeaderMutationScan extends Scan { 7 | HeaderMutationScan(String name) { 8 | super(name); 9 | } 10 | 11 | 12 | @Override 13 | List doScan(IHttpRequestResponse req) { 14 | //new ParamGuesser(req, false, Utilities.PARAM_HEADER, BurpExtender.paramGrabber, null, 2147483647, Utilities.globalSettings).run(); 15 | HeaderMutationGuesser guesser = new HeaderMutationGuesser(req, Utilities.globalSettings); 16 | ArrayList mutations = guesser.guessMutations(); 17 | guesser.reportMutations(mutations); 18 | return null; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/burp/HeaderPoison.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import java.util.List; 4 | 5 | public class HeaderPoison extends Scan { 6 | 7 | HeaderPoison(String name) { 8 | super(name); 9 | scanSettings.importSettings(BurpExtender.guessSettings); 10 | } 11 | 12 | @Override 13 | List doScan(IHttpRequestResponse req) { 14 | new ParamGuesser(req, false, Utilities.PARAM_HEADER, BurpExtender.paramGrabber, null, 2147483647, Utilities.globalSettings).run(); 15 | return null; 16 | } 17 | } -------------------------------------------------------------------------------- /src/burp/Keysmith.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonParseException; 6 | import com.google.gson.JsonParser; 7 | import org.jsoup.Jsoup; 8 | import org.jsoup.nodes.Document; 9 | import org.jsoup.nodes.Element; 10 | import org.jsoup.select.Elements; 11 | 12 | import java.util.*; 13 | import java.util.regex.Matcher; 14 | import java.util.regex.Pattern; 15 | 16 | /** 17 | * Created by james on 06/09/2017. 18 | */ 19 | public class Keysmith { 20 | 21 | static ArrayList getJsonKeys(JsonElement json, HashMap witnessedParams){ 22 | try { 23 | return getJsonKeys(json, null, witnessedParams); 24 | } 25 | catch (JsonParseException e) { 26 | return new ArrayList<>(); 27 | } 28 | 29 | } 30 | 31 | static ArrayList getAllKeys(byte[] resp, HashMap witnessedParams){ 32 | if (!"".equals(Utilities.getBody(resp))) { 33 | try { 34 | return getJsonKeys(new JsonParser().parse(Utilities.getBody(resp)), witnessedParams); 35 | } 36 | catch (JsonParseException e) { 37 | 38 | } 39 | } 40 | 41 | if(Utilities.isResponse(resp)) { 42 | return getHtmlKeys(Utilities.getBody(resp)); 43 | } 44 | else { 45 | return getParamKeys(resp, new HashSet<>()); 46 | } 47 | 48 | } 49 | 50 | static ArrayList getParamKeys(byte[] resp, HashSet types) { 51 | ArrayList keys = new ArrayList<>(); 52 | 53 | List currentParams = Utilities.helpers.analyzeRequest(resp).getParameters(); 54 | 55 | for (IParameter param : currentParams) { 56 | String parsedParam = parseParam(param.getName().replace(':', ';')); 57 | if(types.isEmpty() || types.contains(param.getType())) { 58 | keys.add(parsedParam); 59 | Utilities.log(parsedParam); 60 | } 61 | } 62 | return keys; 63 | } 64 | 65 | static String parseParam(String param) { 66 | param = param.replace("%5B", "[").replace("%5D", "]"); 67 | StringBuilder parsed = new StringBuilder(); 68 | for (String e: param.split("\\[")) { 69 | parsed.append(":"); 70 | parsed.append(e.replace("]", "")); 71 | } 72 | 73 | if (parsed.length() == 0) { 74 | return ""; 75 | } 76 | 77 | return parsed.toString().substring(1); 78 | } 79 | 80 | static String unparseParam(String param) { 81 | String[] presplit = param.split("~", 2); 82 | StringBuilder unparsed = new StringBuilder(); 83 | String[] split = presplit[0].split(":", -1); 84 | unparsed.append(split[0]); 85 | for (int i=1;i 1) { 92 | output = output + "~" + presplit[1]; 93 | } 94 | 95 | return output; 96 | } 97 | 98 | static HashSet getWords(String body) { 99 | HashSet longWords = new HashSet<>(Arrays.asList(body.split("[^.a-zA-Z0-9_-]"))); 100 | longWords.addAll(Arrays.asList(body.split("[^a-zA-Z0-9]"))); 101 | return longWords; 102 | } 103 | 104 | 105 | static ArrayList getHtmlKeys(String body) { 106 | HashSet params = new HashSet<>(); 107 | Document doc = Jsoup.parse(body); 108 | Elements links = doc.select("a[href]"); 109 | for(Element link: links) { 110 | String url = link.attr("href"); 111 | if(url.contains("?")) { 112 | url = url.split("[?]", 2)[1]; 113 | String[] chunks = url.split("&"); 114 | for (String chunk: chunks) { 115 | String[] keyvalue = chunk.split("=", 2); 116 | String key = keyvalue[0]; 117 | if (keyvalue.length > 1 && Utilities.invertable(keyvalue[1])) { 118 | key = key + "~" + keyvalue[1]; 119 | } 120 | params.add(key); 121 | //Utilities.out("HTML PARAM: "+chunk.split("=", 2)[0]); 122 | } 123 | } 124 | } 125 | Elements inputs = doc.select("input[name]"); 126 | for(Element input: inputs) { 127 | String key= input.attr("name"); 128 | if (Utilities.invertable(input.attr("value"))) { 129 | key = key + "~" + input.attr("value"); 130 | } 131 | params.add(key); 132 | } 133 | 134 | Elements scripts = doc.select("script"); 135 | for(Element script: scripts) { 136 | String content = script.html(); 137 | Matcher matched = Pattern.compile("\"([a-zA-Z0-9_]+)\":").matcher(content); 138 | while(matched.find()) { 139 | params.add(matched.group(1)); 140 | } 141 | } 142 | 143 | return new ArrayList(params); 144 | } 145 | 146 | // fixme still returns keys starting with ':' sometimes 147 | private static ArrayList getJsonKeys(JsonElement json, String prefix, HashMap witnessedParams) { 148 | ArrayList keys = new ArrayList<>(); 149 | 150 | if (json.isJsonObject()) { 151 | for (Map.Entry entry: json.getAsJsonObject().entrySet()) { 152 | if (witnessedParams.containsKey(entry.getKey())) { 153 | //Utilities.out("Recognised '"+entry.getKey()+", replacing prefix '"+prefix+"' with '"+ witnessedParams.get(entry.getKey())+"'"); 154 | if(witnessedParams.get(entry.getKey()).equals("")) { 155 | prefix = null; 156 | } 157 | else { 158 | prefix = witnessedParams.get(entry.getKey()); 159 | } 160 | break; 161 | } 162 | } 163 | 164 | for (Map.Entry entry: json.getAsJsonObject().entrySet()) { 165 | String tempPrefix = entry.getKey(); 166 | if (prefix != null) { 167 | tempPrefix = prefix+":"+tempPrefix; 168 | } 169 | keys.addAll(getJsonKeys(entry.getValue(), tempPrefix, witnessedParams)); 170 | } 171 | 172 | if(prefix != null) { 173 | keys.add(prefix); 174 | } 175 | 176 | } else if (json.isJsonArray()) { 177 | JsonArray hm = json.getAsJsonArray(); 178 | int i = 0; 179 | for (JsonElement x: hm) { 180 | String tempPrefix = "[" + Integer.toString(i++)+"]"; 181 | if (prefix != null) { 182 | tempPrefix = prefix+":"+tempPrefix; 183 | } 184 | keys.addAll(getJsonKeys(x, tempPrefix, witnessedParams)); 185 | } 186 | } 187 | 188 | 189 | else { 190 | if (prefix != null) { 191 | 192 | try { 193 | if (!json.getAsJsonPrimitive().isJsonNull()) { 194 | String val = json.getAsString(); 195 | if (Utilities.invertable(val)) { 196 | prefix = prefix + "~" + val; 197 | } 198 | } 199 | } catch (java.lang.IllegalStateException e) { 200 | 201 | } 202 | 203 | 204 | keys.add(prefix); // todo append value here 205 | } 206 | } 207 | 208 | return keys; 209 | } 210 | 211 | 212 | static String[] parseKey(String entry) { 213 | int keyStart = entry.lastIndexOf(':'); 214 | String prefix; 215 | String key; 216 | if (keyStart != -1) { 217 | prefix = entry.substring(0, keyStart); 218 | key = entry.substring(keyStart + 1); 219 | } 220 | else { 221 | prefix = ""; 222 | key = entry; 223 | } 224 | String[] parsed = new String[2]; 225 | parsed[0] = prefix; 226 | parsed[1] = key; 227 | return parsed; 228 | } 229 | 230 | static String getKey(String param) { 231 | String[] keys = param.split(":"); 232 | for (int i=keys.length-1; i>=0; i--) { 233 | if (Utilities.parseArrayIndex(keys[i]) == -1) { 234 | return keys[i]; 235 | } 236 | } 237 | return param; 238 | } 239 | 240 | static String permute(String fullparam) { 241 | return permute(fullparam, false); 242 | } 243 | 244 | static String permute(String fullparam, boolean allowValueChange) { 245 | String[] params = fullparam.split("[|]"); 246 | ArrayList out = new ArrayList<>(); 247 | for (String eachparam: params) { 248 | if (allowValueChange && eachparam.contains("~") && !eachparam.contains("%")) { 249 | String[] param = eachparam.split("~", 2); 250 | out.add(param[0] + "~" + Utilities.invert(param[1])); 251 | } else { 252 | String[] keys = eachparam.split(":"); 253 | String[] param = null; 254 | if (eachparam.contains("~")) { 255 | param = eachparam.split("~", 2); 256 | keys = param[0].split(":"); 257 | } 258 | for (int i = keys.length - 1; i >= 0; i--) { 259 | if (Utilities.parseArrayIndex(keys[i]) == -1) { 260 | keys[i] += Utilities.randomString(6); 261 | break; 262 | } 263 | } 264 | 265 | String tempOut = String.join(":", keys); 266 | if (eachparam.contains("~")) { 267 | tempOut += "~" + param[1]; 268 | } 269 | out.add(tempOut); 270 | } 271 | } 272 | 273 | return String.join("|", out); 274 | 275 | } 276 | 277 | } 278 | -------------------------------------------------------------------------------- /src/burp/NormalisedParamScan.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import java.util.List; 4 | 5 | public class NormalisedParamScan extends ParamScan { 6 | 7 | NormalisedParamScan(String name) { 8 | super(name); 9 | } 10 | 11 | @Override 12 | List doScan(IHttpRequestResponse baseRequestResponse, IScannerInsertionPoint insertionPoint) { 13 | String canary = "kkvjq%61mdk"; 14 | byte[] poisonReq = Utilities.addCacheBuster(insertionPoint.buildRequest(canary.getBytes()), Utilities.generateCanary()); 15 | byte[] victimReq = Utilities.replaceFirst(poisonReq, "kkvjq%61".getBytes(), "kkvjqa".getBytes()); 16 | 17 | IHttpService service = baseRequestResponse.getHttpService(); 18 | 19 | Resp resp = request(service, poisonReq); 20 | if (Utilities.containsBytes(resp.getReq().getResponse(), canary.getBytes())) { 21 | BulkScanLauncher.getTaskEngine().candidates.incrementAndGet(); 22 | 23 | for (int i=0; i<5; i++) { 24 | request(service, poisonReq); 25 | } 26 | 27 | Resp victimResp = request(service, victimReq); 28 | if (Utilities.containsBytes(victimResp.getReq().getResponse(), canary.getBytes())) { 29 | report("URL-decoded parameter", "The application appears to URL-decode parameters before placing them in the cache key, which may enable DoS attacks and also makes other vulnerabilities more exploitable. This was confirmed using the "+insertionPoint.getInsertionPointName()+" parameter.
For further information on this technique, please refer to https://portswigger.net/research/web-cache-entanglement", resp, victimResp); 30 | } 31 | } 32 | 33 | return null; 34 | } 35 | 36 | @Override 37 | List doScan(byte[] baseReq, IHttpService service) { 38 | return null; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/burp/NormalisedPathScan.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import java.util.List; 4 | 5 | public class NormalisedPathScan extends Scan { 6 | 7 | NormalisedPathScan(String name) { 8 | super(name); 9 | } 10 | 11 | @Override 12 | List doScan(byte[] baseReq, IHttpService service) { 13 | baseReq = Utilities.appendToQuery(baseReq, "cb="+Utilities.generateCanary()); 14 | 15 | Resp base = request(service, Utilities.appendToQuery(baseReq, "cbx=zxcv")); 16 | short baseCode = base.getStatus(); 17 | 18 | byte[] poisonReq = Utilities.replaceFirst(baseReq, "?".getBytes(), "%3f".getBytes()); 19 | 20 | Resp resp = request(service, poisonReq); 21 | short poisonedCode = resp.getStatus(); 22 | 23 | if (baseCode != poisonedCode) { 24 | BulkScanLauncher.getTaskEngine().candidates.incrementAndGet(); 25 | 26 | for (int i=0; i<5; i++) { 27 | request(service, poisonReq); 28 | } 29 | 30 | Resp victimResp = request(service, baseReq); 31 | short victimCode = victimResp.getStatus(); 32 | 33 | if (victimCode == poisonedCode) { 34 | report("Web Cache Poisoning: URL-decoded path", "The application appears to URL-decode the path before placing it in the cache key, which may enable DoS attacks and also makes other vulnerabilities more exploitable.
For further information on this technique, please refer to https://portswigger.net/research/web-cache-entanglement", base, resp, victimResp); 35 | } 36 | } 37 | 38 | 39 | return null; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/burp/OfferParamGuess.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import javax.swing.*; 4 | import java.util.ArrayList; 5 | import java.util.HashSet; 6 | import java.util.List; 7 | import java.util.concurrent.ThreadPoolExecutor; 8 | 9 | class OfferParamGuess implements IContextMenuFactory { 10 | private IBurpExtenderCallbacks callbacks; 11 | private ParamGrabber paramGrabber; 12 | private ThreadPoolExecutor taskEngine; 13 | 14 | public OfferParamGuess(final IBurpExtenderCallbacks callbacks, ParamGrabber paramGrabber, ThreadPoolExecutor taskEngine) { 15 | this.taskEngine = taskEngine; 16 | this.callbacks = callbacks; 17 | this.paramGrabber = paramGrabber; 18 | } 19 | 20 | @Override 21 | public List createMenuItems(IContextMenuInvocation invocation) { 22 | IHttpRequestResponse[] reqs = invocation.getSelectedMessages(); 23 | List options = new ArrayList<>(); 24 | 25 | if(reqs == null || reqs.length == 0) { 26 | if (invocation.getSelectedIssues().length > 0) { 27 | ArrayList newReqs = new ArrayList<>(); 28 | for(IScanIssue issue: invocation.getSelectedIssues()){ 29 | newReqs.add(issue.getHttpMessages()[0]); 30 | 31 | } 32 | reqs = newReqs.toArray(new IHttpRequestResponse[0]); 33 | } 34 | else { 35 | return options; 36 | } 37 | } 38 | 39 | JMenu scanMenu = new JMenu("Guess params"); 40 | 41 | JMenuItem allButton = new JMenuItem("Guess everything!"); 42 | allButton.addActionListener(new TriggerParamGuesser(reqs, false, IParameter.PARAM_URL, paramGrabber, taskEngine)); 43 | 44 | JMenuItem probeButton = new JMenuItem("Guess GET parameters"); 45 | probeButton.addActionListener(new TriggerParamGuesser(reqs, false, IParameter.PARAM_URL, paramGrabber, taskEngine)); 46 | allButton.addActionListener(new TriggerParamGuesser(reqs, false, IParameter.PARAM_URL, paramGrabber, taskEngine)); 47 | scanMenu.add(probeButton); 48 | 49 | JMenuItem cookieProbeButton = new JMenuItem("Guess cookie parameters"); 50 | cookieProbeButton.addActionListener(new TriggerParamGuesser(reqs, false, IParameter.PARAM_COOKIE, paramGrabber, taskEngine)); 51 | allButton.addActionListener(new TriggerParamGuesser(reqs, false, IParameter.PARAM_COOKIE, paramGrabber, taskEngine)); 52 | scanMenu.add(cookieProbeButton); 53 | 54 | JMenuItem headerProbeButton = new JMenuItem("Guess headers"); 55 | headerProbeButton.addActionListener(new TriggerParamGuesser(reqs, false, Utilities.PARAM_HEADER, paramGrabber, taskEngine)); 56 | allButton.addActionListener(new TriggerParamGuesser(reqs, false, Utilities.PARAM_HEADER, paramGrabber, taskEngine)); 57 | scanMenu.add(headerProbeButton); 58 | 59 | // if (invocation.getSelectionBounds() != null && reqs.length == 1) { 60 | // JMenuItem valueProbeButton = new JMenuItem("Guess value"); 61 | // valueProbeButton.addActionListener(new ValueGuesser(reqs, invocation.getSelectionBounds())); 62 | // options.add(valueProbeButton); 63 | // } 64 | 65 | 66 | if (reqs.length == 1 && reqs[0] != null) { 67 | IHttpRequestResponse req = reqs[0]; 68 | byte[] resp = req.getRequest(); 69 | if (Utilities.countMatches(resp, Utilities.helpers.stringToBytes("%253c%2561%2560%2527%2522%2524%257b%257b%255c")) > 0) { 70 | JMenuItem backendProbeButton = new JMenuItem("*Identify backend parameters*"); 71 | backendProbeButton.addActionListener(new TriggerParamGuesser(reqs, true, IParameter.PARAM_URL, paramGrabber, taskEngine)); 72 | allButton.addActionListener(new TriggerParamGuesser(reqs, true, IParameter.PARAM_URL, paramGrabber, taskEngine)); 73 | scanMenu.add(backendProbeButton); 74 | } 75 | 76 | // if (Utilities.containsBytes(resp, "HTTP/1.1".getBytes())) { 77 | // JMenuItem tunHeaderProbeButton = new JMenuItem("Guess tunneled headers"); 78 | // tunHeaderProbeButton.addActionListener(new TriggerParamGuesser(reqs, false, Utilities.PARAM_HEADER_TUNNELED, paramGrabber, taskEngine)); 79 | // allButton.addActionListener(new TriggerParamGuesser(reqs, false, Utilities.PARAM_HEADER_TUNNELED, paramGrabber, taskEngine)); 80 | // options.add(tunHeaderProbeButton); 81 | // } 82 | 83 | if (resp != null && resp.length > 0 && resp[0] == 'P') { 84 | IRequestInfo info = Utilities.helpers.analyzeRequest(req); 85 | List params = info.getParameters(); 86 | 87 | HashSet paramTypes = new HashSet<>(); 88 | for (IParameter param : params) { 89 | if (param.getType() != IParameter.PARAM_URL) { 90 | paramTypes.add(param.getType()); 91 | } 92 | } 93 | 94 | for (Byte type : paramTypes) { 95 | String humanType = "Unknown"; 96 | switch(type) { 97 | case 0: 98 | humanType = "URL"; 99 | break; 100 | case 1: 101 | humanType = "body"; 102 | break; 103 | case 2: 104 | humanType = "cookie"; 105 | continue; 106 | case 3: 107 | humanType = "XML"; 108 | break; 109 | case 4: 110 | humanType = "XML attribute"; 111 | break; 112 | case 5: 113 | humanType = "multipart"; 114 | break; 115 | case 6: 116 | humanType = "JSON"; 117 | break; 118 | } 119 | 120 | JMenuItem postProbeButton = new JMenuItem("Guess " + humanType + " parameter"); 121 | postProbeButton.addActionListener(new TriggerParamGuesser(reqs, false, type, paramGrabber, taskEngine)); 122 | allButton.addActionListener(new TriggerParamGuesser(reqs, false, type, paramGrabber, taskEngine)); 123 | scanMenu.add(postProbeButton); 124 | } 125 | } 126 | } 127 | 128 | scanMenu.add(allButton); 129 | options.add(scanMenu); 130 | return options; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/burp/ParamAttack.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonParser; 5 | import org.apache.commons.collections4.queue.CircularFifoQueue; 6 | 7 | import java.util.*; 8 | 9 | import static java.lang.Math.min; 10 | 11 | class ParamAttack { 12 | 13 | CircularFifoQueue recentParams; 14 | HashSet alreadyReported; 15 | ArrayList params; 16 | ArrayList valueParams; 17 | int seed = -1; 18 | boolean started; 19 | 20 | private WordProvider bonusParams; 21 | private HashMap requestParams; 22 | private ParamHolder paramBuckets; 23 | private int bucketSize = 1; 24 | private IHttpRequestResponse baseRequestResponse; 25 | private PayloadInjector injector; 26 | private String attackID; 27 | private Attack base; 28 | private String targetURL; 29 | private Attack altBase; 30 | private boolean tryMethodFlip; 31 | private final ParamInsertionPoint insertionPoint; 32 | final byte type; 33 | private ConfigurableSettings config; 34 | private ArrayList headerMutations; 35 | 36 | int getStop() { 37 | return stop; 38 | } 39 | 40 | void incrStop() { 41 | stop += config.getInt("rotation increment"); 42 | } 43 | 44 | private int stop; 45 | 46 | WordProvider getBonusParams() { 47 | return bonusParams; 48 | } 49 | 50 | HashMap getRequestParams() { 51 | return requestParams; 52 | } 53 | 54 | String getTargetURL() { 55 | return targetURL; 56 | } 57 | 58 | ParamInsertionPoint getInsertionPoint() { 59 | return insertionPoint; 60 | } 61 | 62 | byte[] getInvertedBase() { 63 | return invertedBase; 64 | } 65 | 66 | private byte[] invertedBase; 67 | 68 | Attack getAltBase() { 69 | return altBase; 70 | } 71 | 72 | boolean shouldTryMethodFlip() { 73 | return tryMethodFlip; 74 | } 75 | 76 | Attack getBase() { 77 | return base; 78 | } 79 | 80 | String getAttackID() { 81 | return attackID; 82 | } 83 | 84 | PayloadInjector getInjector() { 85 | return injector; 86 | } 87 | 88 | ParamHolder getParamBuckets() { 89 | return paramBuckets; 90 | } 91 | 92 | int getBucketSize() { 93 | return bucketSize; 94 | } 95 | 96 | IHttpRequestResponse getBaseRequestResponse() { 97 | return baseRequestResponse; 98 | } 99 | 100 | ArrayList getHeaderMutations() { return headerMutations; } 101 | 102 | void setHeaderMutations(ArrayList mutations) { this.headerMutations = mutations; } 103 | 104 | 105 | ParamAttack(IHttpRequestResponse baseRequestResponse, byte type, ParamGrabber paramGrabber, int stop, ConfigurableSettings config) { 106 | started = false; 107 | this.type = type; 108 | this.stop = stop; 109 | this.config = config; 110 | this.baseRequestResponse = baseRequestResponse; 111 | targetURL = baseRequestResponse.getHttpService().getHost(); 112 | params = calculatePayloads(baseRequestResponse, paramGrabber, type); 113 | valueParams = new ArrayList<>(); 114 | for(int i = 0; i< params.size(); i++) { 115 | String candidate = params.get(i); 116 | if(candidate.contains("~")) { 117 | params.set(i, candidate.split("~", 2)[0]); 118 | if (!valueParams.contains(candidate)) { 119 | valueParams.add(candidate); 120 | } 121 | } 122 | } 123 | 124 | // prevents attack cross-talk with stored input detection 125 | attackID = Utilities.mangle(Arrays.hashCode(baseRequestResponse.getRequest())+"|"+System.currentTimeMillis()).substring(0,2); 126 | 127 | requestParams = new HashMap<>(); 128 | for (String entry: Keysmith.getAllKeys(baseRequestResponse.getRequest(), new HashMap<>())) { 129 | String[] parsed = Keysmith.parseKey(entry); 130 | requestParams.putIfAbsent(parsed[1], parsed[0]); 131 | } 132 | 133 | final String payload = ""; // formerly " baselines = new HashMap<>(); 144 | //baselines.put(ref, new Attack(baseRequestResponse)); 145 | invertedBase = null; 146 | altBase = null; 147 | tryMethodFlip = false; 148 | 149 | int longest = config.getInt("max param length"); 150 | 151 | // fixme this may exceed the max bucket size 152 | calculateBucketSize(type, longest); 153 | 154 | if (!Utilities.globalSettings.getBoolean("carpet bomb")) { 155 | StringBuilder basePayload = new StringBuilder(); 156 | for (int i = 1; i < min(8, bucketSize); i++) { 157 | basePayload.append("|"); 158 | basePayload.append(Utilities.randomString(longest)); 159 | if (i % 4 == 0) { 160 | base.addAttack(injector.probeAttack(basePayload.toString())); 161 | } 162 | } 163 | } 164 | 165 | // calculateBucketSize(type, longest); was here 166 | 167 | recentParams = new CircularFifoQueue<>(bucketSize *3); 168 | Utilities.log("Selected bucket size: "+ bucketSize + " for "+ targetURL); 169 | 170 | if(baseRequestResponse.getRequest()[0] != 'G') { 171 | invertedBase = Utilities.helpers.toggleRequestMethod(baseRequestResponse.getRequest()); 172 | altBase = new Attack(Utilities.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), invertedBase)); 173 | if(Utilities.helpers.analyzeResponse(altBase.getFirstRequest().getResponse()).getStatusCode() != 404 && Utilities.globalSettings.getBoolean("try method flip")) { 174 | altBase.addAttack(new Attack(Utilities.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), invertedBase))); 175 | altBase.addAttack(new Attack(Utilities.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), invertedBase))); 176 | altBase.addAttack(new Attack(Utilities.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), invertedBase))); 177 | tryMethodFlip = true; 178 | } 179 | } 180 | 181 | // put the params into buckets 182 | paramBuckets = new ParamHolder(type, bucketSize); 183 | paramBuckets.addParams(valueParams, false); 184 | paramBuckets.addParams(params, false); 185 | 186 | if (!config.getBoolean("dynamic keyload")) { 187 | params = null; 188 | valueParams = null; 189 | } 190 | 191 | alreadyReported = getBlacklist(type); 192 | //Utilities.log("Trying " + (valueParams.size()+ params.size()) + " params in ~"+ paramBuckets.size() + " requests. Going from "+start + " to "+stop); 193 | } 194 | 195 | private void calculateBucketSize(byte type, int longest) { 196 | if (config.getInt("force bucketsize") != -1) { 197 | bucketSize = config.getInt("force bucketsize"); 198 | return; 199 | } 200 | 201 | switch(type) { 202 | case IParameter.PARAM_BODY: 203 | bucketSize = 128; 204 | break; 205 | case Utilities.PARAM_HEADER: 206 | bucketSize = 8; 207 | case IParameter.PARAM_URL: 208 | bucketSize = 16; 209 | break; 210 | default: 211 | bucketSize = 32; 212 | } 213 | 214 | while (true) { 215 | Utilities.log("Trying bucket size: "+ bucketSize); 216 | long start = System.currentTimeMillis(); 217 | StringBuilder trialPayload = new StringBuilder(); 218 | trialPayload.append(Utilities.randomString(longest)); 219 | for (int i = 0; i < bucketSize; i++) { 220 | trialPayload.append("|"); 221 | trialPayload.append(Utilities.randomString(longest)); 222 | } 223 | 224 | Attack trial = injector.probeAttack(trialPayload.toString()); 225 | if (!Utilities.similar(base, trial)) { 226 | trial.addAttack(injector.probeAttack(trialPayload.toString())); 227 | trial.addAttack(injector.probeAttack(trialPayload.toString())); 228 | if (!Utilities.similar(base, trial)) { 229 | bucketSize = bucketSize / 2; 230 | break; 231 | } 232 | } 233 | 234 | long end = System.currentTimeMillis(); 235 | if (end - start > 5000) { 236 | bucketSize = bucketSize / 2; 237 | Utilities.out("Setting bucketSize to "+bucketSize+" due to slow response"); 238 | break; 239 | } 240 | 241 | if (bucketSize >= Utilities.globalSettings.getInt("max bucketsize")) { 242 | break; 243 | } 244 | 245 | bucketSize = bucketSize * 2; 246 | } 247 | } 248 | 249 | private HashSet getBlacklist(byte type) { 250 | HashSet blacklist = new HashSet<>(); 251 | switch(type) { 252 | case IParameter.PARAM_COOKIE: 253 | blacklist.add("__cfduid"); 254 | blacklist.add("PHPSESSID"); 255 | blacklist.add("csrftoken"); 256 | blacklist.addAll(Keysmith.getParamKeys(baseRequestResponse.getRequest(), new HashSet<>(IParameter.PARAM_COOKIE))); 257 | break; 258 | case IParameter.PARAM_URL: 259 | blacklist.add("lang"); 260 | blacklist.addAll(Keysmith.getParamKeys(baseRequestResponse.getRequest(), new HashSet<>(IParameter.PARAM_URL, IParameter.PARAM_BODY))); 261 | break; 262 | case IParameter.PARAM_BODY: 263 | blacklist.addAll(Keysmith.getParamKeys(baseRequestResponse.getRequest(), new HashSet<>(IParameter.PARAM_URL, IParameter.PARAM_BODY))); 264 | break; 265 | case Utilities.PARAM_HEADER: 266 | if (Utilities.globalSettings.getBoolean("skip boring words")) { 267 | blacklist.addAll(Utilities.boringHeaders); 268 | } 269 | break; 270 | default: 271 | Utilities.out("Unrecognised type: "+type); 272 | break; 273 | } 274 | 275 | if (Utilities.globalSettings.getBoolean("only report unique params")) { 276 | blacklist.addAll(Utilities.reportedParams); 277 | } 278 | 279 | return blacklist; 280 | } 281 | 282 | Attack updateBaseline() { 283 | this.base = this.injector.probeAttack(Utilities.randomString(6)); 284 | for(int i=0; i<4; i++) { 285 | base.addAttack(this.injector.probeAttack(Utilities.randomString((i+1)*(i+1)))); 286 | } 287 | if (bucketSize > 1) { 288 | base.addAttack(this.injector.probeAttack(Utilities.randomString(6) + "|" + Utilities.randomString(12))); 289 | } 290 | return base; 291 | } 292 | 293 | 294 | private static ParamInsertionPoint getInsertionPoint(IHttpRequestResponse baseRequestResponse, byte type, String payload, String attackID) { 295 | switch(type) { 296 | case IParameter.PARAM_JSON: 297 | return new JsonParamNameInsertionPoint(baseRequestResponse.getRequest(), "guesser", payload, type, attackID); 298 | case Utilities.PARAM_HEADER: 299 | return new HeaderNameInsertionPoint(baseRequestResponse.getRequest(), "guesser", payload, type, attackID); 300 | default: 301 | return new ParamNameInsertionPoint(baseRequestResponse.getRequest(), "guesser", payload, type, attackID); 302 | } 303 | } 304 | 305 | ArrayList calculatePayloads(IHttpRequestResponse baseRequestResponse, ParamGrabber paramGrabber, byte type) { 306 | ArrayList params = new ArrayList<>(); 307 | 308 | // collect keys in request, for key skipping, matching and re-mapping 309 | HashMap requestParams = new HashMap<>(); 310 | for (String entry: Keysmith.getAllKeys(baseRequestResponse.getRequest(), new HashMap<>())) { // todo give precedence to shallower keys 311 | String[] parsed = Keysmith.parseKey(entry); 312 | Utilities.log("Request param: " +parsed[1]); 313 | requestParams.putIfAbsent(parsed[1], parsed[0]); 314 | } 315 | 316 | // add JSON from response 317 | params.addAll(Keysmith.getAllKeys(baseRequestResponse.getResponse(), requestParams)); 318 | 319 | // add JSON from method-flip response 320 | if(baseRequestResponse.getRequest()[0] != 'G') { 321 | IHttpRequestResponse getreq = Utilities.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), 322 | Utilities.helpers.toggleRequestMethod(baseRequestResponse.getRequest())); 323 | params.addAll(Keysmith.getAllKeys(getreq.getResponse(), requestParams)); 324 | } 325 | 326 | 327 | // add JSON from elsewhere 328 | HashMap> responses = new HashMap<>(); 329 | 330 | Iterator savedJson = paramGrabber.getSavedJson().iterator(); 331 | while (savedJson.hasNext()) { 332 | IHttpRequestResponse resp = null; // todo record resp 333 | try { 334 | resp = savedJson.next(); 335 | } 336 | catch (NoSuchElementException e) { 337 | break; 338 | } 339 | 340 | JsonParser parser = new JsonParser(); 341 | JsonElement json = parser.parse(Utilities.getBody(resp.getResponse())); 342 | HashSet keys = new HashSet<>(Keysmith.getJsonKeys(json, requestParams)); 343 | int matches = 0; 344 | for (String requestKey: keys) { 345 | if (requestParams.containsKey(requestKey) || requestParams.containsKey(Keysmith.parseKey(requestKey)[1])) { 346 | matches++; 347 | } 348 | } 349 | 350 | // if there are no matches, don't bother with prefixes 351 | // todo use root (or non-leaf) objects only 352 | if(matches < 1) { 353 | //Utilities.out("No matches, discarding prefix"); 354 | HashSet filteredKeys = new HashSet<>(); 355 | for(String key: keys) { 356 | String lastKey = Keysmith.parseKey(key)[1]; 357 | if (Utilities.parseArrayIndex(lastKey) < 3) { 358 | filteredKeys.add(Keysmith.parseKey(key)[1]); 359 | } 360 | } 361 | keys = filteredKeys; 362 | } 363 | 364 | Integer matchKey = matches; 365 | if(responses.containsKey(matchKey)) { 366 | responses.get(matchKey).addAll(keys); 367 | } 368 | else { 369 | responses.put(matchKey, keys); 370 | } 371 | } 372 | 373 | 374 | final TreeSet sorted = new TreeSet<>(Collections.reverseOrder()); 375 | sorted.addAll(responses.keySet()); 376 | for(Integer key: sorted) { 377 | Utilities.log("Loading keys with "+key+" matches"); 378 | ArrayList sortedByLength = new ArrayList<>(responses.get(key)); 379 | sortedByLength.sort(new LengthCompare()); 380 | params.addAll(sortedByLength); 381 | } 382 | 383 | if (params.size() > 0) { 384 | Utilities.log("Loaded " + new HashSet<>(params).size() + " params from response"); 385 | } 386 | 387 | params.addAll(Keysmith.getWords(Utilities.helpers.bytesToString(baseRequestResponse.getResponse()))); 388 | 389 | if (config.getBoolean("request")) { 390 | params.addAll(Keysmith.getWords(Utilities.helpers.bytesToString(baseRequestResponse.getRequest()))); 391 | } 392 | 393 | // todo move this stuff elsewhere - no need to load it into memory in advance 394 | params.addAll(paramGrabber.getSavedGET()); 395 | 396 | params.addAll(paramGrabber.getSavedWords()); 397 | 398 | // de-dupe without losing the ordering 399 | params = new ArrayList<>(new LinkedHashSet<>(params)); 400 | 401 | bonusParams = new WordProvider(); 402 | 403 | if (config.getBoolean("use custom wordlist")) { 404 | bonusParams.addSource(config.getString("custom wordlist path")); 405 | } 406 | 407 | if (config.getBoolean("use assetnote params")) { 408 | bonusParams.addSource("/assetnote-params"); 409 | } 410 | 411 | 412 | if (type == Utilities.PARAM_HEADER && config.getBoolean("use basic wordlist")) { 413 | bonusParams.addSource("/headers"); 414 | } 415 | 416 | if (config.getBoolean("response")) { 417 | if (type == Utilities.PARAM_HEADER) { 418 | params.replaceAll(x -> x.toLowerCase().replaceAll("[^a-z0-9_-]", "")); 419 | params.replaceAll(x -> x.replaceFirst("^[_-]+", "")); 420 | params.remove(""); 421 | } 422 | 423 | params.replaceAll(x -> x.substring(0, min(x.length(), config.getInt("max param length")))); 424 | 425 | bonusParams.addSource(String.join("\n", params)); 426 | } 427 | 428 | if (type != Utilities.PARAM_HEADER && config.getBoolean("use basic wordlist")) { 429 | bonusParams.addSource("/params"); 430 | } 431 | 432 | if (config.getBoolean("use bonus wordlist")) { 433 | bonusParams.addSource("/functions"); 434 | if (type != Utilities.PARAM_HEADER) { 435 | bonusParams.addSource("/headers"); 436 | } 437 | else { 438 | bonusParams.addSource("/params"); 439 | } 440 | bonusParams.addSource("/words"); 441 | } 442 | 443 | // only use keys if the request isn't JSON 444 | // todo accept two levels of keys if it's using [] 445 | //if (type != IParameter.PARAM_JSON) { 446 | // for(int i=0;i(); 454 | } 455 | } 456 | -------------------------------------------------------------------------------- /src/burp/ParamGrabber.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonParseException; 5 | import com.google.gson.JsonParser; 6 | 7 | import java.util.*; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | import java.util.concurrent.ThreadPoolExecutor; 10 | import java.util.stream.Collectors; 11 | import java.util.zip.CRC32; 12 | 13 | import static burp.Keysmith.getHtmlKeys; 14 | import static burp.Keysmith.getWords; 15 | 16 | 17 | public class ParamGrabber implements IProxyListener, IHttpListener { 18 | 19 | private Set savedJson = ConcurrentHashMap.newKeySet(); 20 | private HashSet> done = new HashSet<>(); 21 | private Set savedGET = ConcurrentHashMap.newKeySet(); 22 | private Set savedWords = ConcurrentHashMap.newKeySet(); 23 | private HashSet alreadyScanned = new HashSet<>(); 24 | private ThreadPoolExecutor taskEngine; 25 | 26 | ParamGrabber(ThreadPoolExecutor taskEngine) { 27 | this.taskEngine = taskEngine; 28 | } 29 | 30 | Set getSavedJson() { 31 | return savedJson; 32 | } 33 | Set getSavedGET() { 34 | return savedGET; 35 | } 36 | 37 | public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) { 38 | if (messageIsRequest && toolFlag != IBurpExtenderCallbacks.TOOL_EXTENDER) { 39 | addCacheBusters(messageInfo); 40 | } 41 | } 42 | 43 | public void processProxyMessage(boolean messageIsRequest, IInterceptedProxyMessage messageInfo) { 44 | if (!messageIsRequest) { 45 | saveParams(messageInfo.getMessageInfo()); 46 | launchScan(messageInfo.getMessageInfo()); 47 | } 48 | } 49 | 50 | Set getSavedWords() { 51 | return savedWords; 52 | } 53 | 54 | void saveParams(IHttpRequestResponse baseRequestResponse) { 55 | // todo also use observed requests 56 | String body = Utilities.getBody(baseRequestResponse.getResponse()); 57 | if (!body.equals("")) { 58 | savedWords.addAll(getWords(Utilities.helpers.bytesToString(baseRequestResponse.getResponse()))); 59 | savedGET.addAll(getHtmlKeys(body)); 60 | try { 61 | JsonParser parser = new JsonParser(); 62 | JsonElement json = parser.parse(body); 63 | ArrayList keys = Keysmith.getJsonKeys(json, new HashMap<>()); 64 | if (!done.contains(keys)) { 65 | //Utilities.out("Importing observed data..."); 66 | done.add(keys); 67 | savedJson.add(Utilities.callbacks.saveBuffersToTempFiles(baseRequestResponse)); 68 | } 69 | } catch (JsonParseException e) { 70 | 71 | } 72 | } 73 | } 74 | 75 | private void addCacheBusters(IHttpRequestResponse messageInfo) { 76 | byte[] placeHolder = Utilities.helpers.stringToBytes("$randomplz"); 77 | if (Utilities.countMatches(messageInfo.getRequest(), placeHolder) > 0) { 78 | messageInfo.setRequest( 79 | Utilities.fixContentLength(Utilities.replace(messageInfo.getRequest(), placeHolder, Utilities.helpers.stringToBytes(Utilities.generateCanary()))) 80 | ); 81 | } 82 | 83 | byte[] req = messageInfo.getRequest(); 84 | String cacheBuster = null; 85 | if (Utilities.globalSettings.getBoolean("Add dynamic cachebuster")) { 86 | cacheBuster = Utilities.generateCanary(); 87 | } 88 | else if (Utilities.globalSettings.getBoolean("Add 'fcbz' cachebuster")) { 89 | cacheBuster = "fcbz"; 90 | } 91 | 92 | if (cacheBuster != null) { 93 | req = Utilities.addCacheBuster(req, cacheBuster); 94 | } 95 | 96 | messageInfo.setRequest(req); 97 | } 98 | 99 | private void launchScan(IHttpRequestResponse messageInfo) { 100 | if (!Utilities.globalSettings.getBoolean("enable auto-mine")) { 101 | return; 102 | } 103 | 104 | IRequestInfo reqInfo = Utilities.helpers.analyzeRequest(messageInfo.getHttpService(), messageInfo.getRequest()); 105 | if (!Utilities.callbacks.isInScope(reqInfo.getUrl())) { 106 | return; 107 | } 108 | 109 | IResponseInfo respInfo = Utilities.helpers.analyzeResponse(messageInfo.getResponse()); 110 | StringBuilder codeBuidler = new StringBuilder(); 111 | String contentType = respInfo.getStatedMimeType(); 112 | 113 | codeBuidler.append(reqInfo.getUrl().getHost()); 114 | codeBuidler.append(contentType); 115 | 116 | String broadCode = codeBuidler.toString(); 117 | if (!alreadyScanned.contains(broadCode)){ 118 | //Utilities.out("Queueing headers+cookies on "+reqInfo.getUrl()); 119 | if (Utilities.globalSettings.getBoolean("auto-mine headers")) { 120 | taskEngine.execute(new ParamGuesser(Utilities.callbacks.saveBuffersToTempFiles(messageInfo), false, Utilities.PARAM_HEADER, this, taskEngine, Utilities.globalSettings.getInt("rotation interval"), Utilities.globalSettings)); 121 | } 122 | if (Utilities.globalSettings.getBoolean("auto-mine cookies")) { 123 | taskEngine.execute(new ParamGuesser(Utilities.callbacks.saveBuffersToTempFiles(messageInfo), false, IParameter.PARAM_COOKIE, this, taskEngine, Utilities.globalSettings.getInt("rotation interval"), Utilities.globalSettings)); 124 | } 125 | 126 | alreadyScanned.add(broadCode); 127 | } 128 | 129 | if (!Utilities.globalSettings.getBoolean("auto-mine params")) { 130 | return; 131 | } 132 | 133 | codeBuidler.append( 134 | reqInfo.getParameters().stream() 135 | .map(IParameter::getName) 136 | .collect(Collectors.joining(" ")) 137 | ); 138 | 139 | 140 | if(contentType.equals("JSON") || contentType.equals("HTML")) { 141 | codeBuidler.append(reqInfo.getUrl().getPath()); 142 | } 143 | 144 | String paramCode = codeBuidler.toString(); 145 | if (alreadyScanned.contains(paramCode)) { 146 | return; 147 | } 148 | 149 | byte guessType = IParameter.PARAM_URL; 150 | if (reqInfo.getMethod().equals("POST")) { 151 | guessType = IParameter.PARAM_BODY; 152 | } 153 | 154 | Utilities.out("Queueing params on "+reqInfo.getUrl()); 155 | taskEngine.execute(new ParamGuesser(Utilities.callbacks.saveBuffersToTempFiles(messageInfo), false, guessType, this, taskEngine, Utilities.globalSettings.getInt("rotation interval"), Utilities.globalSettings)); 156 | alreadyScanned.add(paramCode); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/burp/ParamGuesser.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import org.apache.commons.collections4.queue.CircularFifoQueue; 4 | 5 | import java.util.*; 6 | import java.util.concurrent.ThreadPoolExecutor; 7 | 8 | 9 | class SimpleScan implements Runnable, IExtensionStateListener { 10 | 11 | public void run() { 12 | 13 | } 14 | 15 | public void extensionUnloaded() { 16 | Utilities.log("Aborting param bruteforce"); 17 | Utilities.unloaded.set(true); 18 | } 19 | 20 | } 21 | /** 22 | * Created by james on 30/08/2017. 23 | */ 24 | class ParamGuesser implements Runnable { 25 | 26 | private IHttpRequestResponse req; 27 | private boolean backend; 28 | private byte type; 29 | private ThreadPoolExecutor taskEngine; 30 | private int stop; 31 | private ParamGrabber paramGrabber; 32 | private ParamAttack attack; 33 | private ConfigurableSettings config; 34 | 35 | private byte[] staticCanary; 36 | 37 | ParamGuesser(IHttpRequestResponse req, boolean backend, byte type, ParamGrabber paramGrabber, ThreadPoolExecutor taskEngine, int stop, ConfigurableSettings config) { 38 | this.paramGrabber = paramGrabber; 39 | this.req = req; 40 | this.backend = backend; 41 | this.type = type; 42 | this.stop = stop; 43 | this.taskEngine = taskEngine; 44 | this.config = config; 45 | staticCanary = config.getString("canary").getBytes(); 46 | } 47 | 48 | ParamGuesser(ParamAttack attack, ThreadPoolExecutor taskEngine, ConfigurableSettings config) { 49 | this.attack = attack; 50 | this.req = attack.getBaseRequestResponse(); 51 | this.taskEngine = taskEngine; 52 | this.config = config; 53 | staticCanary = config.getString("canary").getBytes(); 54 | } 55 | 56 | public void run() { 57 | try { 58 | if (this.attack == null) { 59 | if (req.getResponse() == null) { 60 | Utilities.log("Baserequest has no response - fetching..."); 61 | try { 62 | req = Utilities.callbacks.makeHttpRequest(req.getHttpService(), req.getRequest()); 63 | } catch (RuntimeException e) { 64 | Utilities.out("Aborting attack due to failed lookup"); 65 | return; 66 | } 67 | if (req == null) { 68 | Utilities.out("Aborting attack due to null response"); 69 | return; 70 | } 71 | } 72 | this.attack = new ParamAttack(req, type, paramGrabber, stop, config); 73 | } 74 | 75 | // Check for mutations 76 | if (this.type == Utilities.PARAM_HEADER && config.getBoolean("identify smuggle mutations")) { 77 | HeaderMutationGuesser mutationGuesser = new HeaderMutationGuesser(req, this.config); 78 | ArrayList mutations = mutationGuesser.guessMutations(); 79 | this.attack.setHeaderMutations(mutations); 80 | 81 | // Report if required 82 | if (mutations != null) { 83 | mutationGuesser.reportMutations(mutations); 84 | } 85 | } 86 | 87 | ArrayList paramGuesses = guessParams(attack); 88 | if (!paramGuesses.isEmpty()) { 89 | Utilities.callbacks.addScanIssue(Utilities.reportReflectionIssue(paramGuesses.toArray((new Attack[paramGuesses.size()])), req, "", "")); 90 | } 91 | } catch (Exception e) { 92 | Utilities.out("Attack aborted by exception"); 93 | Utilities.showError(e); 94 | throw e; 95 | } 96 | 97 | // if(backend) { 98 | // IRequestInfo info = Utilities.helpers.analyzeRequest(req); 99 | // List params = info.getParameters(); 100 | // for (IParameter param : params) { 101 | // String key = null; 102 | // String[] keys = {"%26zq=%253c", "!zq=%253c"}; 103 | // for (String test : keys) { 104 | // if (param.getValue().contains(test)) { 105 | // key = test; 106 | // break; 107 | // } 108 | // } 109 | // 110 | // if (key != null) { 111 | // String originalValue = param.getValue().substring(0, param.getValue().indexOf(key)); 112 | // ParamInsertionPoint insertionPoint = new ParamInsertionPoint(req.getRequest(), param.getName(), originalValue, param.getType()); 113 | // ArrayList paramGuesses = guessBackendParams(req, insertionPoint); 114 | // if (!paramGuesses.isEmpty()) { 115 | // Utilities.callbacks.addScanIssue(Utilities.reportReflectionIssue(paramGuesses.toArray((new Attack[paramGuesses.size()])), req)); 116 | // } 117 | // break; 118 | // } 119 | // 120 | // } 121 | // } 122 | } 123 | 124 | private ArrayList guessParams(ParamAttack state) { 125 | final int bucketSize = state.getBucketSize(); 126 | final IHttpRequestResponse baseRequestResponse = state.getBaseRequestResponse(); 127 | final IHttpService service = baseRequestResponse.getHttpService(); 128 | final PayloadInjector injector = state.getInjector(); 129 | final String attackID = state.getAttackID(); 130 | final String targetURL = state.getTargetURL(); 131 | final boolean tryMethodFlip = state.shouldTryMethodFlip(); 132 | final ParamInsertionPoint insertionPoint = state.getInsertionPoint(); 133 | final HashMap requestParams = state.getRequestParams(); 134 | final WordProvider bonusParams = state.getBonusParams(); 135 | final byte type = state.type; 136 | ArrayList headerMutations = state.getHeaderMutations(); 137 | 138 | ArrayList attacks = new ArrayList<>(); 139 | int completedAttacks = 0; 140 | int start = 0; // todo could manually set this 141 | int stop = state.getStop(); 142 | Attack base = state.getBase(); 143 | byte[] invertedBase = state.getInvertedBase(); 144 | Attack altBase = state.getAltBase(); 145 | ParamHolder paramBuckets = state.getParamBuckets(); 146 | 147 | if (Utilities.globalSettings.getBoolean("carpet bomb")) { 148 | Utilities.out("Warning: carpet bomb mode is on, so no parameters will be detected."); 149 | } 150 | 151 | if (!state.started) { 152 | Utilities.out("Initiating "+Utilities.getNameFromType(type)+" bruteforce on "+ targetURL); 153 | state.started = true; 154 | } 155 | else { 156 | Utilities.out("Resuming "+Utilities.getNameFromType(type)+" bruteforce at "+state.seed+" on "+ targetURL); 157 | } 158 | 159 | 160 | while (completedAttacks++ < stop) { 161 | if (paramBuckets.size() == 0) { 162 | ArrayList newParams = new ArrayList<>(); 163 | int i = 0; 164 | if (state.seed == -1) { 165 | while (i++ < bucketSize) { 166 | String next = bonusParams.getNext(); 167 | if (next == null) { 168 | state.seed = 0; 169 | break; 170 | } 171 | newParams.add(next); 172 | } 173 | } else { 174 | if (!config.getBoolean("bruteforce")) { 175 | Utilities.out("Completed attack on " + targetURL); 176 | if (taskEngine != null) { 177 | Utilities.out("Completed " + (taskEngine.getCompletedTaskCount() + 1) + "/" + (taskEngine.getTaskCount())); 178 | } 179 | return attacks; 180 | } 181 | state.seed = Utilities.generate(state.seed, bucketSize, newParams); 182 | } 183 | newParams.removeAll(state.alreadyReported); 184 | paramBuckets.addParams(newParams, true); 185 | } 186 | 187 | ArrayList candidates; 188 | try { 189 | candidates = paramBuckets.pop(); 190 | Iterator iterator = candidates.iterator(); 191 | } catch (NoSuchElementException e) { 192 | continue; 193 | } 194 | 195 | if (completedAttacks < start) { 196 | continue; 197 | } 198 | 199 | //candidates.remove(""); 200 | candidates.removeAll(state.alreadyReported); 201 | candidates.removeIf((String candidate) -> (candidate.contains("_") && state.alreadyReported.contains(candidate.replace('_', '-')))); 202 | candidates.removeIf((String candidate) -> (candidate.contains("~") && state.alreadyReported.contains(candidate.split("~", 2)[0]))); 203 | if (candidates.isEmpty()) { 204 | continue; 205 | } 206 | 207 | String submission = String.join("|", candidates); 208 | if (headerMutations == null) { 209 | headerMutations = new ArrayList(); 210 | } 211 | 212 | // Ensure that the identity mutation is scanned 213 | if (headerMutations.size() == 0 || headerMutations.get(0) != null) { 214 | headerMutations.add(0, null); 215 | } 216 | Iterator iterator = headerMutations.iterator(); 217 | while (iterator.hasNext()) { 218 | String mutation = iterator.next(); 219 | Attack paramGuess = injector.probeAttack(submission, mutation); 220 | 221 | if (!candidates.contains("~")) { 222 | if (findPersistent(baseRequestResponse, paramGuess, attackID, state.recentParams, candidates, state.alreadyReported)) { 223 | state.updateBaseline(); 224 | } 225 | state.recentParams.addAll(candidates); // fixme this results in params being found multiple times 226 | } 227 | 228 | Attack localBase; 229 | if (submission.contains("~")) { 230 | localBase = new Attack(); 231 | localBase.addAttack(base); 232 | } else { 233 | localBase = base; 234 | } 235 | 236 | if (!Utilities.globalSettings.getBoolean("carpet bomb") && !Utilities.similar(localBase, paramGuess)) { 237 | Attack confirmParamGuess = injector.probeAttack(submission, mutation); 238 | 239 | Attack failAttack = injector.probeAttack(Keysmith.permute(submission), mutation); 240 | 241 | // this to prevent error messages obscuring persistent inputs 242 | findPersistent(baseRequestResponse, failAttack, attackID, state.recentParams, null, state.alreadyReported); 243 | localBase.addAttack(failAttack); 244 | 245 | if (!Utilities.similar(localBase, confirmParamGuess)) { 246 | if (candidates.size() > 1) { 247 | Utilities.log("Splitting " + submission); 248 | ArrayList left = new ArrayList<>(candidates.subList(0, candidates.size() / 2)); 249 | Utilities.log("Got " + String.join("|", left)); 250 | ArrayList right = new ArrayList<>(candidates.subList(candidates.size() / 2, candidates.size())); 251 | Utilities.log("Got " + String.join("|", right)); 252 | paramBuckets.push(left); 253 | paramBuckets.push(right); 254 | } else { 255 | if (state.alreadyReported.contains(submission)) { 256 | Utilities.out("Ignoring reporting of submission " + submission + " using mutation " + mutation + " as already reported."); 257 | continue; 258 | } 259 | 260 | Attack WAFCatcher = new Attack(Utilities.attemptRequest(service, Utilities.addOrReplaceHeader(baseRequestResponse.getRequest(), "junk-header", submission))); 261 | WAFCatcher.addAttack(new Attack(Utilities.attemptRequest(service, Utilities.addOrReplaceHeader(baseRequestResponse.getRequest(), "junk-head", submission)))); 262 | if (!Utilities.similar(WAFCatcher, confirmParamGuess)) { 263 | Probe validParam = new Probe("Found unlinked param: " + submission, 4, submission); 264 | validParam.setEscapeStrings(Keysmith.permute(submission), Keysmith.permute(submission, false)); 265 | validParam.setRandomAnchor(false); 266 | validParam.setPrefix(Probe.REPLACE); 267 | ArrayList confirmed = injector.fuzz(localBase, validParam, mutation); 268 | if (!confirmed.isEmpty()) { 269 | state.alreadyReported.add(submission); 270 | Utilities.reportedParams.add(submission); 271 | Utilities.out("Identified parameter on " + targetURL + ": " + submission); 272 | 273 | boolean cacheSuccess = false; 274 | if (type == Utilities.PARAM_HEADER || type == IParameter.PARAM_COOKIE) { 275 | cacheSuccess = cachePoison(injector, submission, failAttack.getFirstRequest()); 276 | } 277 | if (!Utilities.globalSettings.getBoolean("poison only")) { 278 | String title = "Secret input: " + Utilities.getNameFromType(type); 279 | if (!cacheSuccess && canSeeCache(paramGuess.getFirstRequest().getResponse())) { 280 | title = "Secret uncached input: " + Utilities.getNameFromType(type); 281 | } 282 | if (Utilities.globalSettings.getBoolean("name in issue")) { 283 | title += ": " + submission.split("~")[0]; 284 | } 285 | Utilities.callbacks.addScanIssue(Utilities.reportReflectionIssue(confirmed.toArray(new Attack[2]), baseRequestResponse, title, "Unlinked parameter identified.")); 286 | if (type != Utilities.PARAM_HEADER || Utilities.containsBytes(paramGuess.getFirstRequest().getResponse(), staticCanary)) { 287 | scanParam(insertionPoint, injector, submission.split("~", 2)[0]); 288 | } 289 | 290 | base = state.updateBaseline(); 291 | } 292 | 293 | //Utilities.callbacks.doPassiveScan(service.getHost(), service.getPort(), service.getProtocol().equals("https"), paramGuess.getFirstRequest().getRequest(), paramGuess.getFirstRequest().getResponse()); 294 | 295 | if (config.getBoolean("dynamic keyload")) { 296 | ArrayList newWords = new ArrayList<>(Keysmith.getWords(Utilities.helpers.bytesToString(paramGuess.getFirstRequest().getResponse()))); 297 | addNewKeys(newWords, state, bucketSize, paramBuckets, candidates, paramGuess); 298 | } 299 | } else { 300 | Utilities.out(targetURL + " questionable parameter: " + candidates); 301 | } 302 | } 303 | } 304 | } else{ 305 | Utilities.log(targetURL + " couldn't replicate: " + candidates); 306 | base.addAttack(paramGuess); 307 | } 308 | 309 | if (config.getBoolean("dynamic keyload")) { 310 | addNewKeys(Keysmith.getAllKeys(paramGuess.getFirstRequest().getResponse(), requestParams), state, bucketSize, paramBuckets, candidates, paramGuess); 311 | } 312 | 313 | } else if (tryMethodFlip) { 314 | Attack paramGrab = new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase)); 315 | findPersistent(baseRequestResponse, paramGrab, attackID, state.recentParams, null, state.alreadyReported); 316 | 317 | if (!Utilities.similar(altBase, paramGrab)) { 318 | Utilities.log("Potential GETbase param: " + candidates); 319 | injector.probeAttack(Keysmith.permute(submission), mutation); 320 | altBase.addAttack(new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase))); 321 | injector.probeAttack(submission, mutation); 322 | 323 | paramGrab = new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase)); 324 | if (!Utilities.similar(altBase, paramGrab)) { 325 | 326 | if (candidates.size() > 1) { 327 | Utilities.log("Splitting " + submission); 328 | ArrayList left = new ArrayList<>(candidates.subList(0, candidates.size() / 2)); 329 | ArrayList right = new ArrayList<>(candidates.subList(candidates.size() / 2 + 1, candidates.size())); 330 | paramBuckets.push(left); 331 | paramBuckets.push(right); 332 | } else { 333 | Utilities.out("Confirmed GETbase param: " + candidates); 334 | IHttpRequestResponse[] evidence = new IHttpRequestResponse[3]; 335 | evidence[0] = altBase.getFirstRequest(); 336 | evidence[1] = paramGuess.getFirstRequest(); 337 | evidence[2] = paramGrab.getFirstRequest(); 338 | Utilities.callbacks.addScanIssue(new CustomScanIssue(service, Utilities.getURL(baseRequestResponse), evidence, "Secret parameter", "Parameter name: '" + candidates + "'. Review the three requests attached in chronological order.", "Medium", "Tentative", "Investigate")); 339 | 340 | altBase = new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase)); 341 | altBase.addAttack(new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase))); 342 | altBase.addAttack(new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase))); 343 | altBase.addAttack(new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase))); 344 | } 345 | } 346 | } 347 | } 348 | } 349 | } 350 | 351 | 352 | state.incrStop(); 353 | taskEngine.execute(new ParamGuesser(state, taskEngine, config)); 354 | 355 | return attacks; 356 | } 357 | 358 | 359 | 360 | private boolean cachePoison(PayloadInjector injector, String param, IHttpRequestResponse baseResponse) { 361 | if (!Utilities.globalSettings.getBoolean("try cache poison")) { 362 | return false; 363 | } 364 | 365 | try { 366 | IHttpRequestResponse base = injector.getBase(); 367 | PayloadInjector altInject = new PayloadInjector(base, new ParamNameInsertionPoint(base.getRequest(), "guesser", "", IParameter.PARAM_URL, "repliblah")); 368 | Probe validParam = new Probe("Potentially swappable param: " + param, 5, param); 369 | validParam.setEscapeStrings(Keysmith.permute(param), Keysmith.permute(param, false)); 370 | validParam.setRandomAnchor(false); 371 | validParam.setPrefix(Probe.REPLACE); 372 | Attack paramBase = new Attack(); 373 | paramBase.addAttack(altInject.probeAttack(Utilities.generateCanary())); 374 | paramBase.addAttack(altInject.probeAttack(Utilities.generateCanary())); 375 | ArrayList confirmed = altInject.fuzz(paramBase, validParam); 376 | if (!confirmed.isEmpty()) { 377 | Utilities.callbacks.addScanIssue(Utilities.reportReflectionIssue(confirmed.toArray(new Attack[2]), base, "Potentially swappable param", "")); 378 | } 379 | 380 | byte[] testReq = injector.getInsertionPoint().buildRequest(Utilities.helpers.stringToBytes(param)); 381 | testReq = Utilities.addCacheBuster(testReq, Utilities.generateCanary()); 382 | 383 | int attackDedication; 384 | if (canSeeCache(base.getResponse())) { 385 | attackDedication = 10; 386 | } 387 | else { 388 | attackDedication = 5; 389 | for (int i=0;i<5;i++) { 390 | IHttpRequestResponse base2 = Utilities.attemptRequest(injector.getService(), testReq); 391 | if (canSeeCache(base2.getResponse())) { 392 | attackDedication = 30; 393 | break; 394 | } 395 | } 396 | } 397 | 398 | 399 | 400 | 401 | String pathCacheBuster = Utilities.generateCanary() + ".jpg"; 402 | 403 | //String path = Utilities.getPathFromRequest(base.getRequest()); 404 | //byte[] base404 = Utilities.replaceFirst(base.getRequest(), path.getBytes(), (path+pathCacheBuster).getBytes()); 405 | byte[] base404 = Utilities.appendToPath(base.getRequest(), pathCacheBuster); 406 | 407 | 408 | IHttpRequestResponse get404 = Utilities.attemptRequest(injector.getService(), base404); 409 | short get404Code = Utilities.helpers.analyzeResponse(get404.getResponse()).getStatusCode(); 410 | 411 | 412 | IHttpRequestResponse testResp = Utilities.attemptRequest(injector.getService(), testReq); 413 | 414 | boolean reflectPoisonMightWork = Utilities.containsBytes(testResp.getResponse(), staticCanary); 415 | boolean statusPoisonMightWork = Utilities.helpers.analyzeResponse(baseResponse.getResponse()).getStatusCode() != Utilities.helpers.analyzeResponse(testResp.getResponse()).getStatusCode(); 416 | 417 | 418 | ArrayList suffixes = new ArrayList<>(); 419 | ArrayList suffixesWhich404 = new ArrayList<>(); 420 | String[] potentialSuffixes = new String[]{"index.php/zxcvk.jpg", "zxcvk.jpg"}; 421 | 422 | suffixes.add(""); 423 | if (reflectPoisonMightWork) { 424 | for (String suffix : potentialSuffixes) { 425 | testResp = Utilities.attemptRequest(injector.getService(), Utilities.appendToPath(testReq, suffix)); 426 | if (Utilities.containsBytes(testResp.getResponse(), staticCanary)) { 427 | if (Utilities.helpers.analyzeResponse(testResp.getResponse()).getStatusCode() == 200) { 428 | suffixes.add(suffix); 429 | } else { 430 | suffixesWhich404.add(suffix); 431 | } 432 | } 433 | if (attackDedication == 2 && canSeeCache(testResp.getResponse())) { 434 | attackDedication = 7; 435 | } 436 | } 437 | } 438 | 439 | if (suffixes.size() == 1) { 440 | if (!suffixesWhich404.isEmpty()) { 441 | suffixes.add(suffixesWhich404.get(suffixesWhich404.size() - 1)); 442 | } 443 | } 444 | 445 | Utilities.log("Dedicated: "+attackDedication); 446 | for (int i = 1; i < attackDedication; i++) { 447 | 448 | if (reflectPoisonMightWork) { 449 | for (String suffix : suffixes) { 450 | if (tryReflectCache(injector, param, base, attackDedication, i, suffix)) return true; 451 | } 452 | } 453 | 454 | if (statusPoisonMightWork) { 455 | //if (tryStatusCache(injector, param, attackDedication, pathCacheBuster, base404, get404Code, i)) 456 | if (tryStatusCache(injector, param, attackDedication, get404Code)) 457 | return true; 458 | } 459 | 460 | if (!reflectPoisonMightWork && !statusPoisonMightWork && Utilities.globalSettings.getBoolean("twitchy cache poison")) { 461 | if (tryDiffCache(injector, param, attackDedication)) { 462 | return true; 463 | } 464 | } 465 | } 466 | 467 | Utilities.log("Failed cache poisoning check"); 468 | } 469 | catch (java.lang.Exception e) { 470 | Utilities.err(e.getMessage()+"\n\n"+e.getStackTrace()[0]); 471 | } 472 | return false; 473 | } 474 | 475 | private String addStatusPayload(String paramName) { 476 | if (paramName.contains("~")) { 477 | return paramName; 478 | } 479 | else if (paramName.equals("x-original-url")) { 480 | return paramName+"~/"; 481 | } 482 | else { 483 | return paramName; 484 | } 485 | } 486 | 487 | private boolean tryDiffCache(PayloadInjector injector, String param, int attackDedication) { 488 | String canary = Utilities.generateCanary()+".jpg"; 489 | byte[] setPoison200Req = injector.getInsertionPoint().buildRequest(Utilities.helpers.stringToBytes(param)); 490 | setPoison200Req = Utilities.appendToPath(setPoison200Req, canary); 491 | for(int j=0; j diffed = new HashSet<>(); 504 | for(int i=0; i<10; i++) { 505 | diffed.clear(); 506 | diff = false; 507 | byte[] fakePoisonReq = Utilities.appendToPath(getPoisonReq, Utilities.generateCanary()+".jpg"); 508 | resp = Utilities.attemptRequest(injector.getService(), fakePoisonReq); 509 | baseline.updateWith(resp.getResponse()); 510 | for (String attribute: baseline.getInvariantAttributes()) { 511 | if (baseline.getAttributeValue(attribute, 0) != poisoned.getAttributeValue(attribute, 0)) { 512 | diff = true; 513 | diffed.add(attribute); 514 | } 515 | } 516 | if (!diff) { 517 | break; 518 | } 519 | } 520 | 521 | if (diff) { 522 | IHttpRequestResponse[] attachedRequests = new IHttpRequestResponse[2]; 523 | attachedRequests[0] = resp; 524 | attachedRequests[1] = getPoisoned; 525 | Utilities.callbacks.addScanIssue(new CustomScanIssue(getPoisoned.getHttpService(), Utilities.getURL(getPoisoned), attachedRequests, "Attribute-diff cache poisoning: "+param, "Cache poisoning: '" + param + "'. Diff based cache poisoning. Good luck confirming "+diffed, "High", "Tentative", "Investigate")); 526 | return true; 527 | } 528 | 529 | return false; 530 | } 531 | 532 | private boolean tryStatusCache(PayloadInjector injector, String param, int attackDedication, short get404Code) { 533 | String canary = Utilities.generateCanary()+".jpg"; 534 | byte[] setPoison200Req = injector.getInsertionPoint().buildRequest(Utilities.helpers.stringToBytes(addStatusPayload(param))); 535 | setPoison200Req = Utilities.appendToPath(setPoison200Req, canary); 536 | 537 | byte[] getPoison200Req = injector.getInsertionPoint().buildRequest(Utilities.helpers.stringToBytes(addStatusPayload("xyz"+param+"z"))); 538 | getPoison200Req = Utilities.appendToPath(getPoison200Req, canary); 539 | 540 | for(int j=0; j keys, ParamAttack state, int bucketSize, ParamHolder paramBuckets, ArrayList candidates, Attack paramGuess) { 625 | if (!config.getBoolean("dynamic keyload")) { 626 | return; 627 | } 628 | ArrayList discoveredParams = new ArrayList<>(); 629 | for (String key : keys) { 630 | String[] parsed = Keysmith.parseKey(key); 631 | if (!(state.valueParams.contains(key) || state.params.contains(key) || candidates.contains(parsed[1]) || candidates.contains(key))) { // || params.contains(parsed[1]) 632 | Utilities.log("Found new key: " + key); 633 | state.valueParams.add(key); 634 | discoveredParams.add(key); // fixme probably adds the key in the wrong format 635 | paramGrabber.saveParams(paramGuess.getFirstRequest()); 636 | } 637 | } 638 | 639 | paramBuckets.addParams(discoveredParams, true); 640 | } 641 | 642 | private void scanParam(ParamInsertionPoint insertionPoint, PayloadInjector injector, String scanBasePayload) { 643 | 644 | try { 645 | IHttpRequestResponse scanBaseAttack = injector.probeAttack(scanBasePayload).getFirstRequest(); 646 | byte[] req = scanBaseAttack.getRequest(); 647 | byte[] scanBaseGrep = Utilities.helpers.stringToBytes(insertionPoint.calculateValue(scanBasePayload)); 648 | 649 | int start = Utilities.helpers.indexOf(req, scanBaseGrep, true, 0, req.length); 650 | int end = start + scanBaseGrep.length; 651 | 652 | // todo test this 653 | // todo make separate option for core scan vs param scan 654 | ArrayList offsets = new ArrayList<>(); 655 | offsets.add(new int[]{start, end}); 656 | IHttpService service = scanBaseAttack.getHttpService(); 657 | 658 | if (Utilities.globalSettings.getBoolean("probe identified params") && insertionPoint.type != Utilities.PARAM_HEADER) { 659 | IScannerInsertionPoint valueInsertionPoint = new RawInsertionPoint(req, scanBasePayload, start, end); 660 | for (Scan scan : BulkScan.scans) { 661 | if (scan instanceof ParamScan) { 662 | ((ParamScan) scan).doActiveScan(scanBaseAttack, valueInsertionPoint); 663 | } 664 | } 665 | } 666 | 667 | if (!Utilities.globalSettings.getBoolean("scan identified params")) { 668 | return; 669 | } 670 | 671 | if (!Utilities.isBurpPro()) { 672 | Utilities.out("Can't autoscan identified parameter - requires pro edition"); 673 | return; 674 | } 675 | 676 | Utilities.callbacks.doActiveScan(service.getHost(), service.getPort(), Utilities.isHTTPS(service), req, offsets); 677 | //ValueGuesser.guessValue(scanBaseAttack, start, end); 678 | 679 | } catch (Exception e) { 680 | // don't let a broken scan take out the param-miner thread 681 | Utilities.showError(e); 682 | } 683 | } 684 | 685 | private boolean findPersistent(IHttpRequestResponse baseRequestResponse, Attack paramGuess, String attackID, CircularFifoQueue recentParams, ArrayList currentParams, HashSet alreadyReported) { 686 | if (currentParams == null) { 687 | currentParams = new ArrayList<>(); 688 | } 689 | 690 | byte[] failResp = paramGuess.getFirstRequest().getResponse(); 691 | if (failResp == null) { 692 | return false; 693 | } 694 | 695 | if (!Utilities.containsBytes(failResp, staticCanary)) { 696 | return false; 697 | } 698 | 699 | byte[] req = paramGuess.getFirstRequest().getRequest(); 700 | 701 | for(Iterator params = recentParams.iterator(); params.hasNext();) { 702 | String param = params.next(); 703 | if(currentParams.contains(param) || alreadyReported.contains(param)) { 704 | continue; 705 | } 706 | 707 | byte[] canary = Utilities.helpers.stringToBytes(Utilities.toCanary(param.split("~", 2)[0]) + attackID); 708 | if (Utilities.containsBytes(failResp, canary) && !Utilities.containsBytes(req, canary)){ 709 | Utilities.out("Identified persistent parameter on "+Utilities.getURL(baseRequestResponse) + ":" + param); 710 | params.remove(); 711 | Utilities.callbacks.addScanIssue(new CustomScanIssue(baseRequestResponse.getHttpService(), Utilities.getURL(baseRequestResponse), paramGuess.getFirstRequest(), "Secret parameter", "Found persistent parameter: '"+param+"'. Disregard the request and look for " + Utilities.helpers.bytesToString(canary) + " in the response", "High", "Firm", "Investigate")); 712 | alreadyReported.add(param); 713 | return true; 714 | } 715 | } 716 | return false; 717 | } 718 | 719 | 720 | // static ArrayList guessBackendParams(IHttpRequestResponse baseRequestResponse, IScannerInsertionPoint insertionPoint) { 721 | // 722 | // String baseValue = insertionPoint.getBaseValue(); 723 | // PayloadInjector injector = new PayloadInjector(baseRequestResponse, insertionPoint); 724 | // String targetURL = baseRequestResponse.getHttpService().getHost(); 725 | // Utilities.log("Initiating parameter name bruteforce on " + targetURL); 726 | // 727 | // final String breaker = "=%3c%61%60%27%22%24%7b%7b%5c"; 728 | // Attack base = injector.buildAttack(baseValue+"&"+Utilities.randomString(6)+ breaker, false); 729 | // 730 | // for(int i=0; i<4; i++) { 731 | // base.addAttack(injector.buildAttack(baseValue+"&"+Utilities.randomString((i+1)*(i+1))+ breaker, false)); 732 | // } 733 | // 734 | // ArrayList attacks = new ArrayList<>(); 735 | // try { 736 | // for (int i = 0; i < Utilities.paramNames.size(); i++) { // i confirmed = injector.fuzz(base, validParam); 747 | // if (!confirmed.isEmpty()) { 748 | // Utilities.out("Identified backend parameter: " + candidate); 749 | // attacks.addAll(confirmed); 750 | // } 751 | // } else { 752 | // base.addAttack(paramGuess); 753 | // } 754 | // } 755 | // 756 | // } 757 | // Utilities.log("Parameter name bruteforce complete: "+targetURL); 758 | // } 759 | // catch (RuntimeException e) { 760 | // Utilities.log("Parameter name bruteforce aborted: "+targetURL); 761 | // } 762 | // 763 | // return attacks; 764 | // } 765 | 766 | } 767 | 768 | class LengthCompare implements Comparator { 769 | public int compare(String o1, String o2) { 770 | return Integer.compare(o1.length(), o2.length()); 771 | } 772 | } 773 | 774 | -------------------------------------------------------------------------------- /src/burp/ParamHolder.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import java.util.ArrayDeque; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.Deque; 7 | 8 | class ParamHolder { 9 | private Deque> paramBuckets; 10 | private byte type; 11 | private int bucketSize; 12 | 13 | ParamHolder(byte type, int bucketSize) { 14 | this.type = type; 15 | this.bucketSize = bucketSize; 16 | paramBuckets = new ArrayDeque<>(); 17 | } 18 | 19 | int size() { 20 | return paramBuckets.size(); 21 | } 22 | 23 | ArrayList pop() { 24 | return paramBuckets.pop(); 25 | } 26 | 27 | void push(ArrayList e) { 28 | paramBuckets.push(e); 29 | } 30 | 31 | void addParams(ArrayList params, boolean topup) { 32 | removeBadEntries(params); 33 | 34 | if(type == Utilities.PARAM_HEADER) { 35 | int max = params.size(); 36 | for (int i=0; i last = paramBuckets.getLast(); 63 | while(last.size() < bucketSize && i < params.size()) { 64 | last.add(params.get(i++)); 65 | } 66 | 67 | if (i == params.size()) { 68 | return; 69 | } 70 | } 71 | 72 | for (int i = 0; i bucket = new ArrayList<>(); 74 | for(int k = 0; k< bucketSize && i+k < limit; k++) { 75 | String param = params.get(i+k); 76 | bucket.add(param); 77 | } 78 | paramBuckets.add(bucket); 79 | } 80 | } 81 | 82 | private void removeBadEntries(ArrayList params) { 83 | params.removeAll(Arrays.asList("")); 84 | 85 | if (type == Utilities.PARAM_HEADER) { 86 | params.removeIf(x -> Character.isDigit(x.charAt(0))); 87 | if (Utilities.globalSettings.getBoolean("lowercase headers")) { 88 | params.replaceAll(String::toLowerCase); 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/burp/PartialParam.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | class PartialParam implements IParameter { 4 | 5 | private int valueStart, valueEnd; 6 | private String name; 7 | private byte type; 8 | 9 | PartialParam(String name, int valueStart, int valueEnd) { 10 | this(name, valueStart, valueEnd, IParameter.PARAM_COOKIE); 11 | } 12 | 13 | PartialParam(String name, int valueStart, int valueEnd, byte type) { 14 | this.name = name; 15 | this.valueStart = valueStart; 16 | this.valueEnd = valueEnd; 17 | this.type = type; 18 | } 19 | 20 | 21 | 22 | @Override 23 | public byte getType() { 24 | return type; 25 | } 26 | 27 | @Override 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | @Override 33 | public String getValue() { 34 | return null; 35 | } 36 | 37 | @Override 38 | public int getNameStart() { 39 | return 0; 40 | } 41 | 42 | @Override 43 | public int getNameEnd() { 44 | return 0; 45 | } 46 | 47 | @Override 48 | public int getValueStart() { 49 | return valueStart; 50 | } 51 | 52 | @Override 53 | public int getValueEnd() { 54 | return valueEnd; 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /src/burp/PortDOS.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import java.util.List; 4 | 5 | public class PortDOS extends Scan { 6 | 7 | PortDOS(String name) { 8 | super(name); 9 | } 10 | 11 | @Override 12 | List doScan(byte[] baseReq, IHttpService service) { 13 | baseReq = Utilities.addCacheBuster(baseReq, Utilities.generateCanary()); 14 | 15 | String canary = "41810"; 16 | byte[] poisonReq = Utilities.addOrReplaceHeader(baseReq, "Host", service.getHost()+":"+canary); 17 | 18 | if (Utilities.containsBytes(baseReq, canary.getBytes())) { 19 | return null; 20 | } 21 | 22 | 23 | Resp resp = request(service, poisonReq); 24 | if (Utilities.containsBytes(resp.getReq().getResponse(), canary.getBytes())) { 25 | recordCandidateFound(); 26 | 27 | for (int i=0; i<5; i++) { 28 | request(service, poisonReq); 29 | } 30 | 31 | Resp victimResp = request(service, baseReq); 32 | if (Utilities.containsBytes(victimResp.getReq().getResponse(), canary.getBytes())) { 33 | report("Web Cache Poisoning: unkeyed port", "The application does not include the port in the host header in the cache key. This may enable a single-request DoS attack. More serious attacks may be possible depending on how much validation is applied to the port.
For further information on this technique, please refer to https://portswigger.net/research/web-cache-entanglement", resp, victimResp); 34 | } 35 | } 36 | 37 | 38 | return null; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/burp/Probe.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.HashSet; 6 | 7 | /** 8 | * Created by james on 24/11/2016. 9 | */ 10 | class Probe { 11 | public static byte APPEND = 0; 12 | public static byte PREPEND = 1; 13 | public static byte REPLACE = 2; 14 | 15 | private String base = "'"; 16 | private String name; 17 | 18 | private String tip = ""; 19 | private int severity; 20 | private ArrayList breakStrings = new ArrayList<>(); 21 | private ArrayList escapeStrings = new ArrayList<>(); 22 | private byte prefix = APPEND; 23 | private boolean randomAnchor = true; 24 | private boolean useCacheBuster = false; 25 | private int nextBreak = -1; 26 | private int nextEscape = -1; 27 | 28 | public boolean getRequireConsistentEvidence() { 29 | return requireConsistentEvidence; 30 | } 31 | 32 | public void setRequireConsistentEvidence(boolean requireConsistentEvidence) { 33 | this.requireConsistentEvidence = requireConsistentEvidence; 34 | } 35 | 36 | private boolean requireConsistentEvidence = false; 37 | 38 | 39 | public boolean useCacheBuster() { 40 | return useCacheBuster; 41 | } 42 | 43 | 44 | 45 | public Probe(String name, int severity, String... breakStrings) { 46 | this.name = name; 47 | this.severity = severity; 48 | this.breakStrings = new ArrayList<>(Arrays.asList(breakStrings)); 49 | } 50 | 51 | public String getTip() { 52 | return tip; 53 | } 54 | 55 | public void setTip(String tip) { 56 | this.tip = tip; 57 | } 58 | 59 | public byte getPrefix() { 60 | return prefix; 61 | } 62 | 63 | public void setPrefix(byte prefix) { 64 | this.prefix = prefix; 65 | } 66 | 67 | public boolean getRandomAnchor() { 68 | return randomAnchor; 69 | } 70 | 71 | public void setRandomAnchor(boolean randomAnchor) { 72 | this.randomAnchor = randomAnchor; 73 | useCacheBuster = !randomAnchor; 74 | } 75 | 76 | public void setUseCacheBuster(boolean useCacheBuster) { 77 | this.useCacheBuster = useCacheBuster; 78 | } 79 | 80 | 81 | public String getBase() { 82 | return base; 83 | } 84 | 85 | public void setBase(String base) { 86 | this.base = base; 87 | } 88 | 89 | public void setEscapeStrings(String... args) { 90 | for (String arg : args) { 91 | escapeStrings.add(new String[]{arg}); 92 | } 93 | } 94 | 95 | // args is a list of alternatives 96 | public void addEscapePair(String... args) { 97 | escapeStrings.add(args); 98 | } 99 | 100 | public String getNextBreak() { 101 | nextBreak++; 102 | return breakStrings.get(nextBreak % breakStrings.size()); 103 | } 104 | 105 | public String[] getNextEscapeSet() { 106 | nextEscape++; 107 | return escapeStrings.get(nextEscape % escapeStrings.size()); 108 | } 109 | 110 | public String getName() { 111 | return name; 112 | } 113 | 114 | public int getSeverity() { 115 | return severity; 116 | } 117 | 118 | static class ProbeResults { 119 | public HashSet interesting = new HashSet<>(); 120 | public HashSet boring = new HashSet<>(); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/burp/RailsUtmScan.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | 4 | import java.util.List; 5 | 6 | public class RailsUtmScan extends ParamScan { 7 | 8 | RailsUtmScan(String name) { 9 | super(name); 10 | } 11 | 12 | @Override 13 | List doScan(byte[] baseReq, IHttpService service) { 14 | return null; 15 | } 16 | 17 | @Override 18 | List doScan(IHttpRequestResponse baseRequestResponse, IScannerInsertionPoint insertionPoint) { 19 | // don't scan POST 20 | if (baseRequestResponse.getRequest()[0] == 'P') { 21 | return null; 22 | } 23 | 24 | IHttpService service = baseRequestResponse.getHttpService(); 25 | 26 | // set value to canary 27 | String canary = "akzldka"; 28 | String cacheBuster = Utilities.generateCanary(); 29 | 30 | 31 | byte[] poison = insertionPoint.buildRequest((insertionPoint.getBaseValue()+"&utm_content=x;"+insertionPoint.getInsertionPointName()+"="+canary).getBytes()); 32 | 33 | poison = Utilities.addCacheBuster(poison, cacheBuster); 34 | 35 | // confirm we have input reflection 36 | Resp resp = request(service, poison); 37 | if (!Utilities.containsBytes(resp.getReq().getResponse(), canary.getBytes())) { 38 | // todo try path-busting 39 | return null; 40 | } 41 | 42 | // try to apply poison 43 | for (int i=0; i<5; i++) { 44 | request(service, poison); 45 | } 46 | 47 | // see if the poison stuck 48 | byte[] victim = insertionPoint.buildRequest(insertionPoint.getBaseValue().getBytes()); 49 | victim = Utilities.addCacheBuster(victim, cacheBuster); 50 | Resp poisoned = request(service, victim); 51 | if (!Utilities.containsBytes(poisoned.getReq().getResponse(), canary.getBytes())) { 52 | return null; 53 | } 54 | 55 | report("Web Cache Poisoning: Parameter Cloaking", "The application can be manipulated into excluding the "+insertionPoint.getInsertionPointName()+" parameter from the cache key, by disguising it as utm_content.
For further information on this technique, please refer to https://portswigger.net/research/web-cache-entanglement", resp, poisoned); 56 | 57 | 58 | return null; 59 | } 60 | } -------------------------------------------------------------------------------- /src/burp/RandomComparator.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import java.util.Comparator; 4 | 5 | class RandomComparator implements Comparator { 6 | @Override 7 | public int compare(Object o1, Object o2) { 8 | int h1 = o1.hashCode(); 9 | int h2 = o2.hashCode(); 10 | if (h1 < h2) { 11 | return -1; 12 | } 13 | else if (h1 == h2) { 14 | return 0; 15 | } 16 | return 1; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/burp/TriggerParamGuesser.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import org.apache.commons.collections4.queue.CircularFifoQueue; 4 | import org.graalvm.compiler.core.common.util.Util; 5 | 6 | import java.awt.event.ActionEvent; 7 | import java.awt.event.ActionListener; 8 | import java.util.*; 9 | import java.util.concurrent.ThreadPoolExecutor; 10 | 11 | import static java.lang.Math.min; 12 | import static org.apache.commons.lang3.math.NumberUtils.max; 13 | 14 | 15 | class TriggerParamGuesser implements ActionListener, Runnable { 16 | private IHttpRequestResponse[] reqs; 17 | private boolean backend; 18 | private byte type; 19 | private ParamGrabber paramGrabber; 20 | private ThreadPoolExecutor taskEngine; 21 | private ConfigurableSettings config; 22 | 23 | TriggerParamGuesser(IHttpRequestResponse[] reqs, boolean backend, byte type, ParamGrabber paramGrabber, ThreadPoolExecutor taskEngine) { 24 | this.taskEngine = taskEngine; 25 | this.paramGrabber = paramGrabber; 26 | this.backend = backend; 27 | this.reqs = reqs; 28 | this.type = type; 29 | } 30 | 31 | public void actionPerformed(ActionEvent e) { 32 | ConfigurableSettings config = Utilities.globalSettings.showSettings(); 33 | if (config != null) { 34 | this.config = config; 35 | //Runnable runnable = new TriggerParamGuesser(reqs, backend, type, paramGrabber, taskEngine, config); 36 | (new Thread(this)).start(); 37 | } 38 | } 39 | 40 | public void run() { 41 | int queueSize = taskEngine.getQueue().size(); 42 | Utilities.log("Adding "+reqs.length+" tasks to queue of "+queueSize); 43 | queueSize += reqs.length; 44 | int thread_count = taskEngine.getCorePoolSize(); 45 | 46 | int stop = config.getInt("rotation interval"); 47 | if (queueSize < thread_count) { 48 | stop = 256; 49 | } 50 | 51 | ArrayList reqlist = new ArrayList<>(Arrays.asList(reqs)); 52 | Collections.shuffle(reqlist); 53 | 54 | int cache_size = thread_count; 55 | if (config.getBoolean("max one per host")) { 56 | cache_size = queueSize; 57 | } 58 | 59 | Set keyCache = new HashSet<>(); 60 | boolean useKeyCache = config.getBoolean("max one per host+status"); 61 | 62 | Queue cache = new CircularFifoQueue<>(cache_size); 63 | HashSet remainingHosts = new HashSet<>(); 64 | 65 | boolean canSkip = false; 66 | byte[] noCache = "no-cache".getBytes(); 67 | if (config.getBoolean("skip uncacheable") && (type == IParameter.PARAM_COOKIE || type == Utilities.PARAM_HEADER)) { 68 | canSkip = true; 69 | } 70 | 71 | 72 | int i = 0; 73 | int queued = 0; 74 | // every pass adds at least one item from every host 75 | while(!reqlist.isEmpty()) { 76 | Utilities.log("Loop "+i++); 77 | Iterator left = reqlist.iterator(); 78 | while (left.hasNext()) { 79 | IHttpRequestResponse req = left.next(); 80 | 81 | String host = req.getHttpService().getHost(); 82 | String key = req.getHttpService().getProtocol()+host; 83 | if (req.getResponse() != null) { 84 | if (canSkip && Utilities.containsBytes(req.getResponse(), noCache)) { 85 | continue; 86 | } 87 | 88 | IResponseInfo info = Utilities.helpers.analyzeResponse(req.getResponse()); 89 | key = key + info.getStatusCode() + info.getInferredMimeType(); 90 | } 91 | 92 | if (useKeyCache && keyCache.contains(key)) { 93 | left.remove(); 94 | continue; 95 | } 96 | 97 | if (!cache.contains(host)) { 98 | cache.add(host); 99 | keyCache.add(key); 100 | left.remove(); 101 | Utilities.log("Adding request on "+host+" to queue"); 102 | queued++; 103 | taskEngine.execute(new ParamGuesser(Utilities.callbacks.saveBuffersToTempFiles(req), backend, type, paramGrabber, taskEngine, stop, config)); 104 | } else { 105 | remainingHosts.add(host); 106 | } 107 | } 108 | 109 | if(config.getBoolean("max one per host")) { 110 | break; 111 | } 112 | 113 | if (remainingHosts.size() <= 1 && !useKeyCache) { 114 | left = reqlist.iterator(); 115 | while (left.hasNext()) { 116 | queued++; 117 | taskEngine.execute(new ParamGuesser(Utilities.callbacks.saveBuffersToTempFiles(left.next()), backend, type, paramGrabber, taskEngine, stop, config)); 118 | } 119 | break; 120 | } 121 | else { 122 | cache = new CircularFifoQueue<>(max(min(remainingHosts.size()-1, thread_count), 1)); 123 | } 124 | } 125 | 126 | Utilities.out("Queued " + queued + " attacks"); 127 | 128 | } 129 | } -------------------------------------------------------------------------------- /src/burp/UnkeyedParamScan.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | 4 | import java.util.List; 5 | 6 | public class UnkeyedParamScan extends ParamScan { 7 | 8 | UnkeyedParamScan(String name) { 9 | super(name); 10 | } 11 | 12 | @Override 13 | List doScan(byte[] baseReq, IHttpService service) { 14 | return null; 15 | } 16 | 17 | @Override 18 | List doScan(IHttpRequestResponse baseRequestResponse, IScannerInsertionPoint insertionPoint) { 19 | // don't scan POST 20 | if (baseRequestResponse.getRequest()[0] == 'P') { 21 | return null; 22 | } 23 | 24 | IHttpService service = baseRequestResponse.getHttpService(); 25 | 26 | // set value to canary 27 | String canary = "akzldka"; 28 | String cacheBuster = Utilities.generateCanary(); 29 | 30 | byte[] poison = insertionPoint.buildRequest(canary.getBytes()); 31 | 32 | poison = Utilities.addCacheBuster(poison, cacheBuster); 33 | 34 | // confirm we have input reflection 35 | Resp resp = request(service, poison); 36 | if (!Utilities.containsBytes(resp.getReq().getResponse(), canary.getBytes())) { 37 | // todo try path-busting 38 | return null; 39 | } 40 | 41 | // try to apply poison 42 | for (int i=0; i<5; i++) { 43 | request(service, poison); 44 | } 45 | 46 | // see if the poison stuck 47 | String victimCanary = "zzmkdfq"; 48 | byte[] victim = insertionPoint.buildRequest(victimCanary.getBytes()); 49 | victim = Utilities.addCacheBuster(victim, cacheBuster); 50 | Resp poisoned = request(service, victim); 51 | if (!Utilities.containsBytes(poisoned.getReq().getResponse(), canary.getBytes())) { 52 | return null; 53 | } 54 | 55 | if (Utilities.containsBytes(poisoned.getReq().getResponse(), victimCanary.getBytes())) { 56 | report("Internal cache poisoning?", "The second response contains elements of the previous request and the victim request, suggesting it may be vulnerable to internal cache poisoning.
For further information on this technique, please refer to https://portswigger.net/research/web-cache-entanglement", resp, poisoned); 57 | } 58 | 59 | // identify whether the URL-based cachebuster is necessary 60 | byte[] victim2 = Utilities.replace(victim, cacheBuster, cacheBuster+"2"); 61 | Resp poisonedDueToUnkeyedQuery = request(service, victim2); 62 | 63 | if (Utilities.containsBytes(poisonedDueToUnkeyedQuery.getReq().getResponse(), canary.getBytes())) { 64 | report("Web Cache Poisoning: Query string unkeyed?", "The application does not include the query string in the cache key. This was confirmed by injecting the value '"+canary+"' using the "+insertionPoint.getInsertionPointName()+" parameter, then replaying the request without the injected value, and confirming it still appears in the response.
For further information on this technique, please refer to https://portswigger.net/research/web-cache-entanglement", resp, poisonedDueToUnkeyedQuery); 65 | } 66 | else { 67 | report("Web Cache Poisoning: Query param blacklist ", "The application excludes certain parameters from the cache key. This was confirmed by injecting the value '"+canary+"' using the "+insertionPoint.getInsertionPointName()+" parameter, then replaying the request without the injected value, and confirming it still appears in the response.
For further information on this technique, please refer to https://portswigger.net/research/web-cache-entanglement", resp, poisoned); 68 | } 69 | 70 | return null; 71 | } 72 | } -------------------------------------------------------------------------------- /src/burp/ValueGuesser.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | import java.awt.event.ActionEvent; 7 | import java.awt.event.ActionListener; 8 | import java.util.ArrayList; 9 | import java.util.Base64; 10 | import java.util.List; 11 | 12 | class ValueGuesser implements Runnable, ActionListener { 13 | private IHttpRequestResponse[] reqs; 14 | private int[] selection; 15 | 16 | ValueGuesser(IHttpRequestResponse[] reqs, int[] selection) { 17 | this.reqs = reqs; 18 | this.selection = selection; 19 | } 20 | 21 | public void actionPerformed(ActionEvent e) { 22 | ConfigurableSettings config = Utilities.globalSettings.showSettings(); 23 | if (config != null) { 24 | (new Thread(this)).start(); 25 | } 26 | } 27 | 28 | static void guessValue(IHttpRequestResponse req, int start, int end) { 29 | IScannerInsertionPoint valueInsertionPoint = new RawInsertionPoint(req.getRequest(), "name", start, end); 30 | guessValue(req, valueInsertionPoint); 31 | } 32 | 33 | 34 | static void guessValue(IHttpRequestResponse req, IScannerInsertionPoint valueInsertionPoint) { 35 | PayloadInjector valueInjector = new PayloadInjector(req, valueInsertionPoint); 36 | IHttpService service = req.getHttpService(); 37 | String domain = service.getHost(); 38 | 39 | Attack randBase = valueInjector.probeAttack(Utilities.generateCanary()); 40 | randBase.addAttack(valueInjector.probeAttack(Utilities.generateCanary())); 41 | randBase.addAttack(valueInjector.probeAttack(Utilities.generateCanary())); 42 | randBase.addAttack(valueInjector.probeAttack(Utilities.generateCanary())); 43 | 44 | String baseValue = valueInsertionPoint.getBaseValue(); 45 | ArrayList potentialValues = new ArrayList<>(); 46 | 47 | // todo try observed values, wordlists etc 48 | // todo multi-step exploration? number->observed numbers 49 | potentialValues.add("z"); // false positive catcher 50 | 51 | if (!StringUtils.isNumeric(baseValue)) { 52 | potentialValues.add("1"); 53 | //potentialValues.add("0"); 54 | } 55 | 56 | if (!baseValue.equals("true") && !baseValue.equals("false")) { 57 | potentialValues.add("true"); 58 | //potentialValues.add("false"); 59 | } 60 | 61 | if (!baseValue.startsWith("/") && !baseValue.startsWith("http")) { 62 | potentialValues.add("/cow"); 63 | potentialValues.add("https://"+domain+"/"); 64 | } 65 | 66 | if (!baseValue.contains("@")) { 67 | potentialValues.add("test@" + domain); 68 | } 69 | 70 | if (!baseValue.startsWith("{") && !baseValue.startsWith("[")) { 71 | potentialValues.add("{}"); 72 | potentialValues.add("[]"); 73 | } 74 | 75 | potentialValues.add("`z'z\"${{\\"); // removed % because it isn't getting URL-encoded 76 | 77 | 78 | ArrayList attacks = new ArrayList<>(); 79 | attacks.add(new Resp(randBase.getFirstRequest())); 80 | 81 | boolean launchedScan = false; 82 | String title = "Alternative code path"; 83 | for (String potentialValue : potentialValues) { 84 | int count = 0; 85 | 86 | Attack potentialBase = null; 87 | for(;count<5;count++) { 88 | potentialBase = valueInjector.probeAttack(potentialValue); 89 | if (Utilities.similar(randBase, potentialBase)) { 90 | break; 91 | } 92 | randBase.addAttack(valueInjector.probeAttack(Utilities.generateCanary())); 93 | if (Utilities.similar(randBase, potentialBase)) { 94 | break; 95 | } 96 | 97 | Object status = potentialBase.getPrint().get("status_code"); 98 | if(status != null && "400".equals(status.toString())) { 99 | break; 100 | } 101 | 102 | } 103 | 104 | if (count == 5) { 105 | 106 | 107 | baseValue = potentialValue; 108 | Utilities.out("Alternative code path triggered by value '"+baseValue+"'"); 109 | IHttpRequestResponse altBase = valueInjector.buildRequest(potentialValue, false);//potentialBase.getFirstRequest(); 110 | attacks.add(new Resp(altBase)); 111 | 112 | if (potentialValue.equals("z")) { 113 | title = "Fake code path"; 114 | break; 115 | } 116 | 117 | 118 | 119 | if (!launchedScan) { 120 | // scan this insertion point with our new base value 121 | // Utilities.doActiveScan(Utilities.attemptRequest(service, newBaseRequest), valueInsertionPoint.getPayloadOffsets(baseValue.getBytes())); 122 | 123 | // scan the entire request with our new base value 124 | title = "Alternative code path: "+potentialValue; 125 | Utilities.callbacks.doActiveScan(domain, service.getPort(), Utilities.isHTTPS(service), altBase.getRequest()); 126 | launchedScan = true; 127 | } 128 | } 129 | } 130 | 131 | if (false && attacks.size() > 1) { 132 | title += "#"+(attacks.size()-1); 133 | Scan.report(title, "details", attacks.toArray(new Resp[0])); 134 | } 135 | } 136 | 137 | @Override 138 | public void run() { 139 | guessValue(reqs[0], selection[0], selection[1]); 140 | } 141 | } 142 | 143 | class ValueScan extends ParamScan { 144 | 145 | ValueScan(String name) { 146 | super(name); 147 | } 148 | 149 | @Override 150 | List doScan(byte[] baseReq, IHttpService service) { 151 | return null; 152 | } 153 | 154 | @Override 155 | List doScan(IHttpRequestResponse baseReq, IScannerInsertionPoint insertionPoint) { 156 | ValueGuesser.guessValue(baseReq, insertionPoint); 157 | return null; 158 | } 159 | } -------------------------------------------------------------------------------- /src/burp/WordProvider.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import java.io.File; 4 | import java.io.FileNotFoundException; 5 | import java.util.ArrayDeque; 6 | import java.util.Scanner; 7 | 8 | class WordProvider { 9 | 10 | private Scanner currentSource; 11 | private ArrayDeque sources = new ArrayDeque<>(); 12 | 13 | void addSource(String source) { 14 | sources.add(source); 15 | } 16 | 17 | String getNext() { 18 | getNextSource(); 19 | if (currentSource == null || !currentSource.hasNextLine()){ 20 | return null; 21 | } 22 | return currentSource.nextLine(); 23 | } 24 | 25 | private void getNextSource() { 26 | if (currentSource != null && currentSource.hasNextLine()) { 27 | return; 28 | } 29 | 30 | while (!sources.isEmpty()) { 31 | String filename = sources.removeFirst(); 32 | try { 33 | currentSource = new Scanner(getClass().getResourceAsStream(filename)); 34 | if (currentSource.hasNextLine()) { 35 | return; 36 | } 37 | } catch (NullPointerException e) { 38 | try { 39 | currentSource = new Scanner(new File(filename)); 40 | if (currentSource.hasNextLine()) { 41 | return; 42 | } 43 | } 44 | catch (FileNotFoundException f) { 45 | if (filename.contains("\n")) { 46 | currentSource = new Scanner(filename); 47 | return; 48 | } 49 | 50 | } 51 | } 52 | } 53 | 54 | currentSource = null; 55 | } 56 | 57 | } 58 | --------------------------------------------------------------------------------