Skip to content

Commit 6149f51

Browse files
committed
Land rapid7#9256, Add aux module to discover WSDD enabled devices
Land rapid7#9256
2 parents fdd4fc1 + 7755292 commit 6149f51

File tree

2 files changed

+176
-0
lines changed

2 files changed

+176
-0
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
## Vulnerable Application
2+
3+
[Web Services Dynamic Discovery (WS-Discovery)](https://en.wikipedia.org/wiki/WS-Discovery) is a multicast discovery protocol utilising SOAP over UDP to locate web services on a local network.
4+
5+
Web service enabled devices typically include printers, scanners and file shares.
6+
7+
The reply from some devices may include optional vendor extensions. This data may include network information such as the device MAC address and hostname, or hardware information such as the serial number, make, and model.
8+
9+
10+
## Verification Steps
11+
12+
1. Start `msfconsole`
13+
2. Do: `use auxiliary/scanner/wsdd/wsdd_query`
14+
3. Do: `set RHOSTS [IP]` (Default: `239.255.255.250`)
15+
4. Do: `run`
16+
17+
18+
## Scenarios
19+
20+
```
21+
msf > use auxiliary/scanner/wsdd/wsdd_query
22+
msf auxiliary(wsdd_query) > set rhosts 239.255.255.250
23+
rhosts => 239.255.255.250
24+
msf auxiliary(wsdd_query) > run
25+
26+
[*] Sending WS-Discovery probe to 1 hosts
27+
[+] 10.1.1.184 responded with:
28+
Address: http://10.1.1.184:3911/
29+
Types: wsdp:Device, wprt:PrintDeviceType, wscn:ScanDeviceType, hpd:hpDevice
30+
Vendor Extensions: {"HardwareAddress"=>"123456789ABC", "UUID"=>"12345678-1234-1234-abcd-123456789abc", "IPv4Address"=>"10.1.1.123", "Hostname"=>"HP09AAFB", "DeviceId"=>"MFG:HP;MDL:Photosmart 5520 series;DES:CX042A;", "DeviceIdentification"=>{"MakeAndModel"=>"Photosmart 5520 series", "MakeAndModelBase"=>"Photosmart 5520 series"}, "SerialNumber"=>"123456", "Services"=>" Print9100 SclScan RESTScan CIFS DOT4 LEDM", "AdapterType"=>"WifiEmbedded"}
31+
[*] Scanned 1 of 1 hosts (100% complete)
32+
[*] Auxiliary module execution completed
33+
```
34+
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)