├── .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 | [](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 |
--------------------------------------------------------------------------------