CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/parser/burp_session_document.rb
Views: 11623
1
# -*- coding: binary -*-
2
require "rex/parser/nokogiri_doc_mixin"
3
4
module Rex
5
module Parser
6
7
# If Nokogiri is available, define Burp Session document class.
8
#
9
# Burp Session XML files actually provide a lot, but since it also
10
# provides the originating url, we can pull most of the detail from
11
# the URI object.
12
load_nokogiri && class BurpSessionDocument < Nokogiri::XML::SAX::Document
13
14
include NokogiriDocMixin
15
16
# The resolver prefers your local /etc/hosts (or windows equiv), but will
17
# fall back to regular DNS. It retains a cache for the import to avoid
18
# spamming your network with DNS requests.
19
attr_reader :resolv_cache
20
21
# Since we try to resolve every time we hit a new web page, need to
22
# hang on to our misses. Presume that it's a permanent enough failure
23
# that it won't get fixed during this particular import
24
attr_reader :missed_cache
25
26
# If name resolution of the host fails out completely, you will not be
27
# able to import that Scan task. Other scan tasks in the same report
28
# should be unaffected.
29
attr_reader :parse_warning
30
31
def start_document
32
@parse_warnings = []
33
@parse_warned = []
34
@resolv_cache = {}
35
@missed_cache = []
36
end
37
38
def start_element(name=nil,attrs=[])
39
attrs = normalize_attrs(attrs)
40
block = @block
41
@state[:current_tag][name] = true
42
case name
43
when "host", "port", "protocol", "path"
44
@state[:has_text] = true
45
when "status"
46
@state[:has_text] = true
47
when "response"
48
@state[:has_text] = true
49
end
50
end
51
52
def end_element(name=nil)
53
block = @block
54
case name
55
when "item" # Wrap up this item, but keep resolved web sites
56
collect_uri
57
report_web_site(&block)
58
handle_parse_warnings(&block)
59
report_web_page(&block)
60
report_web_service_info
61
report_web_host_info
62
# Reset the state once we close a host
63
@state = @state.select {|k| [:current_tag, :web_sites].include? k}
64
when "host"
65
@state[:has_text] = false
66
collect_host
67
@text = nil
68
when "port"
69
@state[:has_text] = false
70
collect_port
71
@text = nil
72
when "protocol"
73
@state[:has_text] = false
74
collect_protocol
75
@text = nil
76
when "path"
77
@state[:has_text] = false
78
collect_path_and_query
79
@text = nil
80
when "status"
81
@state[:has_text] = false
82
collect_status
83
@text = nil
84
when "response"
85
@state[:has_text] = false
86
collect_response
87
@text = nil
88
end
89
@state[:current_tag].delete name
90
end
91
92
def collect_host
93
return unless in_item
94
return unless has_text
95
@state[:host] = @text
96
end
97
98
def collect_port
99
return unless in_item
100
return unless has_text
101
return unless @text.to_i.to_s == @text.to_s
102
@state[:port] = @text.to_i
103
end
104
105
def collect_protocol
106
return unless in_item
107
return unless has_text
108
@state[:protocol] = @text
109
end
110
111
def collect_path_and_query
112
return unless in_item
113
return unless has_text
114
path,query = @text.split(/\?+/,2)
115
return unless path
116
if query
117
@state[:query] = "?#{query}" # Can be nil
118
end
119
if path =~ /https?:[\x5c\x2f][\x5c\x2f]+[^\x5c\x2f][^\x5c\x2f]+([^?]+)/n
120
real_path = "/#{$1}"
121
else
122
real_path = path
123
end
124
@state[:path] = real_path
125
end
126
127
def collect_status
128
return unless in_item
129
return unless has_text
130
return unless @text.to_i.to_s == @text
131
@state[:status] = @text.to_i
132
end
133
134
def collect_uri
135
return unless in_item
136
return unless @state[:host]
137
return unless @state[:port]
138
return unless @state[:protocol]
139
return unless @state[:path]
140
url = @state[:protocol].to_s
141
url << "://"
142
url << @state[:host].to_s
143
url << ":"
144
url << @state[:port].to_s
145
url << @state[:path]
146
if @state[:query]
147
url << "?"
148
url << @state[:query]
149
end
150
@state[:uri] = URI.parse(url) rescue nil
151
end
152
153
def report_web_host_info
154
return unless @state[:web_site]
155
return unless @state[:uri].kind_of? URI::HTTP
156
return unless @state[:web_site].service.host.name.to_s.empty?
157
host_info = {:workspace => @args[:workspace]}
158
host_info[:address] = @state[:web_site].service.host.address
159
host_info[:name] = @state[:uri].host
160
db_report(:host, host_info)
161
end
162
163
def report_web_service_info
164
return unless @state[:web_site]
165
return unless @state[:service_info]
166
return unless @state[:web_site].service.info.to_s.empty?
167
service_info = {}
168
service_info[:host] = @state[:web_site].service.host
169
service_info[:port] = @state[:web_site].service.port
170
service_info[:proto] = @state[:web_site].service.proto
171
service_info[:info] = @state[:service_info]
172
db_report(:service, service_info)
173
end
174
175
def report_web_page(&block)
176
return unless @state[:uri].kind_of? URI::HTTP
177
return unless @state[:status]
178
return unless @state[:web_site]
179
return unless @state[:response_headers].kind_of? Hash
180
headers = {}
181
@state[:response_headers].each do |k,v|
182
headers[k.to_s.downcase] ||= []
183
headers[k.to_s.downcase] << v
184
end
185
if headers["server"].kind_of? Array
186
@state[:service_info] = headers["server"].first
187
end
188
return unless @state[:response_body]
189
web_page_info = {:workspace => @args[:workspace]}
190
web_page_info[:web_site] = @state[:web_site]
191
web_page_info[:code] = @state[:status]
192
web_page_info[:path] = @state[:uri].path
193
web_page_info[:headers] = headers
194
web_page_info[:body] = @state[:response_body]
195
web_page_info[:query] = @state[:uri].query
196
url = @state[:uri].to_s.gsub(/\?.*/,"")
197
db.emit(:web_page, url, &block) if block
198
db_report(:web_page, web_page_info)
199
end
200
201
def report_web_site(&block)
202
return unless @state[:uri].kind_of? URI::HTTP
203
vhost = @state[:uri].host
204
web_site_info = {:workspace => @args[:workspace]}
205
web_site_info[:vhost] = vhost
206
address = resolve_vhost_address(@state[:uri])
207
return unless address
208
web_site_info[:host] = address
209
web_site_info[:port] = @state[:uri].port
210
web_site_info[:ssl] = @state[:uri].kind_of? URI::HTTPS
211
web_site_obj = db_report(:web_site, web_site_info)
212
return unless web_site_obj
213
@state[:web_sites] ||= []
214
url = "#{@state[:uri].scheme}://#{@state[:uri].host}:#{@state[:uri].port}"
215
unless @state[:web_sites].include? web_site_obj
216
db.emit(:web_site, url, &block)
217
@state[:web_sites] << web_site_obj
218
end
219
@state[:web_site] = web_site_obj
220
end
221
222
def collect_response
223
return unless in_item
224
return unless has_text
225
response_text = @text.dup
226
response_header_text,response_body_text = response_text.split(/\r*\n\r*\n/n,2)
227
return unless response_header_text
228
response_header = Rex::Proto::Http::Packet::Header.new
229
response_header.from_s response_header_text
230
@state[:response_headers] = response_header
231
@state[:response_body] = response_body_text
232
end
233
234
def in_item
235
return false unless in_tag("item")
236
return false unless in_tag("items")
237
return true
238
end
239
240
def has_text
241
return false unless @text
242
return false if @text.strip.empty?
243
@text = @text.strip
244
end
245
246
def handle_parse_warnings(&block)
247
return if @parse_warnings.empty?
248
return unless block
249
@parse_warnings.each_with_index do |pwarn,i|
250
unless @parse_warned.include? i
251
db.emit(:warning, pwarn, &block)
252
@parse_warned << i
253
end
254
end
255
end
256
257
def resolve_address(host)
258
return @resolv_cache[host] if @resolv_cache[host]
259
return false if @missed_cache.include? host
260
address = Rex::Socket.resolv_to_dotted(host) rescue nil
261
@resolv_cache[host] = address
262
if address
263
block = @block
264
db.emit(:address, address, &block) if block
265
else
266
@missed_cache << host
267
end
268
return address
269
end
270
271
# Alias this
272
def resolve_vhost_address(uri)
273
if uri.host
274
address = resolve_address(uri.host)
275
case address
276
when false
277
return false
278
when nil
279
@parse_warnings << "Could not resolve address for '#{uri.host}', skipping."
280
end
281
else
282
@parse_warnings << "Could not determine a host for this import."
283
end
284
address
285
end
286
287
end
288
289
end
290
end
291
292
293