Skip to content

Commit 283b7c5

Browse files
committed
Add WS-Discovery Information Discovery module
1 parent 65412cd commit 283b7c5

File tree

1 file changed

+142
-0
lines changed

1 file changed

+142
-0
lines changed
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Auxiliary
7+
include Msf::Auxiliary::Report
8+
include Msf::Auxiliary::UDPScanner
9+
10+
def initialize
11+
super(
12+
'Name' => 'WS-Discovery Information Discovery',
13+
'Description' => %q{
14+
Discover information from Web Services Dynamic Discovery (WS-Discovery)
15+
enabled systems.
16+
},
17+
'Author' => 'Brendan Coles <bcoles[at]gmail.com>',
18+
'License' => MSF_LICENSE,
19+
'References' =>
20+
[
21+
['URL', 'https://msdn.microsoft.com/en-us/library/windows/desktop/bb513684(v=vs.85).aspx'],
22+
['URL', 'http://specs.xmlsoap.org/ws/2005/04/discovery/ws-discovery.pd'],
23+
['URL', 'https://en.wikipedia.org/wiki/Web_Services_for_Devices'],
24+
['URL', 'https://en.wikipedia.org/wiki/WS-Discovery'],
25+
['URL', 'https://en.wikipedia.org/wiki/Zero-configuration_networking#WS-Discovery']
26+
]
27+
)
28+
register_options [
29+
Opt::RPORT(3702),
30+
OptAddressRange.new('RHOSTS', [true, 'The multicast address or CIDR range of targets to query', '239.255.255.250'])
31+
]
32+
end
33+
34+
def rport
35+
datastore['RPORT']
36+
end
37+
38+
def wsdd_probe
39+
probe = '<?xml version="1.0" encoding="utf-8" ?>'
40+
probe << '<soap:Envelope'
41+
probe << ' xmlns:soap="http://www.w3.org/2003/05/soap-envelope"'
42+
probe << ' xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"'
43+
probe << ' xmlns:wsd="http://schemas.xmlsoap.org/ws/2005/04/discovery"'
44+
probe << ' xmlns:wsdp="http://schemas.xmlsoap.org/ws/2006/02/devprof">'
45+
46+
probe << '<soap:Header>'
47+
# WS-Discovery
48+
probe << '<wsa:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>'
49+
# Action (Probe)
50+
probe << "<wsa:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action>"
51+
# Message identifier (unique GUID)
52+
probe << "<wsa:MessageID>urn:uuid:#{SecureRandom.uuid}</wsa:MessageID>"
53+
probe << '</soap:Header>'
54+
55+
probe << '<soap:Body>'
56+
probe << '<wsd:Probe/>' # WS-Discovery type (blank)
57+
probe << '</soap:Body>'
58+
probe << '</env:Envelope>'
59+
60+
probe
61+
end
62+
63+
def scanner_prescan(batch)
64+
print_status "Sending WS-Discovery probe to #{batch.length} hosts"
65+
@results = {}
66+
end
67+
68+
def scan_host(ip)
69+
vprint_status "#{ip}:#{rport} - Sending WS-Discovery probe"
70+
scanner_send wsdd_probe, ip, datastore['RPORT']
71+
end
72+
73+
def scanner_postscan(_batch)
74+
if @results.empty?
75+
print_status 'No WS-Discovery endpoints found.'
76+
return
77+
end
78+
79+
found = {}
80+
@results.each_pair do |ip, responses|
81+
responses.uniq.each do |res|
82+
found[ip] ||= {}
83+
next if found[ip][res]
84+
85+
response_info = parse_wsdd_response res
86+
87+
if response_info.nil?
88+
print_error "#{ip} responded with malformed data"
89+
next
90+
end
91+
92+
msg = []
93+
msg << "Address: #{response_info['Address']}"
94+
msg << "Types: #{response_info['Types'].to_s.split(/\s+/).join(', ')}"
95+
msg << "Vendor Extensions: #{response_info['VendorExtension']}" unless response_info['VendorExtension'].nil?
96+
97+
print_good "#{ip} responded with:\n#{msg.join("\n")}"
98+
99+
report_service(host: ip, port: rport, proto: 'udp', name: 'wsdd', info: response_info)
100+
found[ip][res] = true
101+
end
102+
end
103+
end
104+
105+
def parse_wsdd_response(wsdd_res)
106+
info = {}
107+
108+
# Validate ProbeMatches SOAP response contains a ProbeMatch
109+
begin
110+
soap = ::Nokogiri::XML wsdd_res
111+
return nil if soap.xpath('//soap:Body//wsd:ProbeMatches//wsd:ProbeMatch').empty?
112+
rescue
113+
return nil
114+
end
115+
116+
# Convert SOAP response to Hash
117+
begin
118+
res = Hash.from_xml wsdd_res
119+
rescue REXML::ParseException
120+
return nil
121+
end
122+
123+
# Use the first ProbeMatch
124+
probe_match = res['Envelope']['Body']['ProbeMatches'].first
125+
return nil unless probe_match[0].eql? 'ProbeMatch'
126+
return nil if probe_match[1].nil? || probe_match[1].empty?
127+
match = probe_match[1]
128+
129+
# Device Address
130+
info['Address'] = match['XAddrs'] || ''
131+
132+
# Device Types
133+
info['Types'] = match['Types'] || ''
134+
135+
# Optional vendor extensions
136+
unless match['VendorExtension'].nil? || match['VendorExtension'].empty?
137+
info['VendorExtension'] = match['VendorExtension']
138+
end
139+
140+
info
141+
end
142+
end

0 commit comments

Comments
 (0)