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/post/multi/recon/sudo_commands.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Post67include Msf::Post::File8include Msf::Post::Linux::Priv9include Msf::Auxiliary::Report1011def initialize(info = {})12super(13update_info(14info,15'Name' => 'Sudo Commands',16'Description' => %q{17This module examines the sudoers configuration for the session user18and lists the commands executable via sudo.1920This module also inspects each command and reports potential avenues21for privileged code execution due to poor file system permissions or22permitting execution of executables known to be useful for privesc,23such as utilities designed for file read/write, user modification,24or execution of arbitrary operating system commands.2526Note, you may need to provide the password for the session user.27},28'License' => MSF_LICENSE,29'Author' => [ 'bcoles' ],30'Platform' => [ 'bsd', 'linux', 'osx', 'solaris', 'unix' ],31'SessionTypes' => [ 'meterpreter', 'shell' ]32)33)34register_options [35OptString.new('SUDO_PATH', [ true, 'Path to sudo executable', '/usr/bin/sudo' ]),36OptString.new('PASSWORD', [ false, 'Password for the current user', '' ])37]38end3940def sudo_path41datastore['SUDO_PATH'].to_s42end4344def password45datastore['PASSWORD'].to_s46end4748def eop_bins49%w[50cat chgrp chmod chown cp echo find less ln mkdir more mv tail tar51usermod useradd userdel52env crontab53awk gdb gawk lua irb ld node perl php python python2 python3 ruby tclsh wish54ncat netcat netcat.traditional nc nc.traditional openssl socat telnet telnetd55ash bash csh dash ksh sh zsh56su sudo57expect ionice nice script setarch strace taskset time58wget curl ftp scp sftp ssh tftp59nmap60ed emacs man nano vi vim visudo61dpkg rpm rpmquery62]63end6465#66# Check if a sudo command offers prvileged code execution67#68def check_eop(cmd)69# drop args for simplicity (at the risk of false positives)70cmd = cmd.split(/\s/).first7172if cmd.eql? 'ALL'73print_good 'sudo any command!'74return true75end7677base_dir = File.dirname cmd78base_name = File.basename cmd7980if file_exist? cmd81if writable? cmd82print_good "#{cmd} is writable!"83return true84end85elsif writable? base_dir86print_good "#{cmd} does not exist and #{base_dir} is writable!"87return true88end8990if eop_bins.include? base_name91print_good "#{cmd} matches known privesc executable '#{base_name}' !"92return true93end9495false96end9798#99# Retrieve list of sudo commands for current session user100#101def sudo_list102# try non-interactive (-n) without providing a password103cmd = "#{sudo_path} -n -l"104vprint_status "Executing: #{cmd}"105output = cmd_exec(cmd).to_s106107if output.start_with?('usage:') || output.include?('illegal option') || output.include?('a password is required')108# try with a password from stdin (-S)109cmd = "echo #{password} | #{sudo_path} -S -l"110vprint_status "Executing: #{cmd}"111output = cmd_exec(cmd).to_s112end113114output115end116117#118# Format sudo output and extract permitted commands119#120def parse_sudo(sudo_data)121cmd_data = sudo_data.scan(/may run the following commands.*?$(.*)\z/m).flatten.first122123# remove leading whitespace from each line and remove linewraps124formatted_data = ''125cmd_data.split("\n").reject { |line| line.eql?('') }.each do |line|126formatted_line = line.gsub(/^\s*/, '').to_s127if formatted_line.start_with? '('128formatted_data << "\n#{formatted_line}"129else130formatted_data << " #{formatted_line}"131end132end133134formatted_data.split("\n").reject { |line| line.eql?('') }.each do |line|135run_as = line.scan(/^\((.+?)\)/).flatten.first136137if run_as.blank?138print_warning "Could not parse sudoers entry: #{line.inspect}"139next140end141142user = run_as.split(':')[0].to_s.strip || ''143group = run_as.split(':')[1].to_s.strip || ''144no_passwd = false145146cmds = line.scan(/^\(.+?\) (.+)$/).flatten.first147if cmds.start_with? 'NOPASSWD:'148no_passwd = true149cmds = cmds.gsub(/^NOPASSWD:\s*/, '')150end151152# Commands are separated by commas but may also contain commas (escaped with a backslash)153# so we temporarily replace escaped commas with some junk154# later, we'll replace each instance of the junk with a comma155junk = Rex::Text.rand_text_alpha(10)156cmds = cmds.gsub('\, ', junk)157158cmds.split(', ').each do |cmd|159cmd = cmd.gsub(junk, ', ').strip160161if cmd.start_with? '('162run_as = cmd.scan(/^\((.+?)\)/).flatten.first163164if run_as.blank?165print_warning "Could not parse sudo command: #{cmd.inspect}"166next167end168169user = run_as.split(':')[0].to_s.strip || ''170group = run_as.split(':')[1].to_s.strip || ''171cmd = cmd.scan(/^\(.+?\) (.+)$/).flatten.first172end173174msg = "Command: #{cmd.inspect}"175msg << " RunAsUsers: #{user}" unless user.eql? ''176msg << " RunAsGroups: #{group}" unless group.eql? ''177msg << ' without providing a password' if no_passwd178vprint_status msg179180eop = check_eop cmd181182@results << [cmd, user, group, no_passwd ? '' : 'True', eop ? 'True' : '']183end184end185rescue StandardError => e186print_error "Could not parse sudo output: #{e.message}"187end188189def run190if is_root?191fail_with Failure::BadConfig, 'Session already has root privileges'192end193194unless executable? sudo_path195print_error 'Could not find sudo executable'196return197end198199output = sudo_list200vprint_line output201vprint_line202203if output.include? 'Sorry, try again'204fail_with Failure::NoAccess, 'Incorrect password'205end206207if output =~ /^Sorry, .* may not run sudo/208fail_with Failure::NoAccess, 'Session user is not permitted to execute any commands with sudo'209end210211if output !~ /may run the following commands/212fail_with Failure::NoAccess, 'Incorrect password, or the session user is not permitted to execute any commands with sudo'213end214215@results = Rex::Text::Table.new(216'Header' => 'Sudo Commands',217'Indent' => 2,218'Columns' =>219[220'Command',221'RunAsUsers',222'RunAsGroups',223'Password?',224'Privesc?'225]226)227228parse_sudo output229230if @results.rows.empty?231print_status 'Found no sudo commands for the session user'232return233end234235print_line236print_line @results.to_s237238path = store_loot(239'sudo.commands',240'text/csv',241session,242@results.to_csv,243'sudo.commands.txt',244'Sudo Commands'245)246247print_good "Output stored in: #{path}"248end249end250251252