{"id":7474,"date":"2021-12-27T19:04:56","date_gmt":"2021-12-28T02:04:56","guid":{"rendered":"https:\/\/webdev.securin.xyz\/?p=7474"},"modified":"2023-04-11T10:20:35","modified_gmt":"2023-04-11T17:20:35","slug":"patch-now-two-microsoft-active-directory-bugs-chained-to-takeover-windows-domain","status":"publish","type":"post","link":"https:\/\/webdev.securin.xyz\/articles\/patch-now-two-microsoft-active-directory-bugs-chained-to-takeover-windows-domain\/","title":{"rendered":"Patch Now: Two Microsoft Active Directory Bugs Chained to Takeover Windows Domain"},"content":{"rendered":"
Microsoft released a statement<\/a> to its customers to patch two Active Directory domain controller bugs following the release of the proof-of-concept<\/a> on December 11.<\/p>\n The vulnerabilities, tracked as CVE-2021-42287<\/a> and CVE-2021-42278<\/a>, can be chained to gain privileges that lead to an easy Windows domain takeover.<\/p>\n The two vulnerabilities allow an adversary with low-privileged domain user credentials to obtain a Kerberos Service Ticket<\/a> for a Domain Controller computer account, thereby allowing a normal user to control a domain controller. The flaw, according to Microsoft, stems from a KDC misconfiguration that allows any computer account to impersonate AD domains.<\/p>\n <\/p>\n Historically, it has been previously observed that Active Directory is extremely difficult to secure. Windows Active Directory servers played a part in the SolarWinds attacks<\/a> where the servers were hit by the FoggyWeb backdoor.<\/p>\n Get CSW\u00a0script to detect\u00a0Windows Active Directory\u00a0bugs here<\/a><\/p>\n CVE-2021-42287, an elevation of privilege vulnerability, affecting Windows Active Directory servers, has been assigned a CVSS v3 score of 8.8 (high) and the weakness enumeration, CWE-269, which leads to Improper Privilege Management.<\/p>\n<\/li>\n CVE-2021-42278, on the other hand, is a security bypass escalation of privilege vulnerability with the potential to let attackers impersonate domain controllers using sAMAccountName spoofing<\/a> techniques. CVE-2021-42278 has a CVSS v3 score of 8.8 (high) and is also pegged as an Improper Privilege Management software weakness (CWE-269).<\/p>\n<\/li>\n Attackers can use a vulnerability-chaining method to create a direct path to a domain admin user in any unpatched Active Directory environment, thereafter, allowing the full takeover of the machine.<\/p>\n<\/li>\n A proof-of-concept (PoC) tool<\/a> that can be used to exploit the bug was released publicly on GitHub, a few weeks after Microsoft released the Patch Tuesday Update for November 2021.<\/p>\n<\/li>\n Google Trends for most searches for CVE-2021-42287 between December 16-23 reveals it is trending in China, Switzerland, Israel, Austria, and Germany.<\/p>\n<\/li>\n<\/ul>\n <\/p>\n Similar search trends between December 16-23 for CVE-2021-42278 show a high number of searches in China with a lesser number of counts for Germany, Switzerland, Taiwan, and Denmark.<\/p>\n<\/li>\n<\/ul>\n <\/p>\n The patches for the two vulnerabilities were published in the November 2021 Patch Tuesday<\/a> release put out by Microsoft.<\/p>\n<\/li>\n<\/ul>\n Our security analysts took a deeper look at how the vulnerabilities affect the Active Directory server. Here is their analysis:<\/strong><\/p>\n All computer accounts usually have a trailing $ in their sAMAccountName attribute. However, since no validation processes exist to make sure of it, CVE-2021-42278 can be leveraged to allow attackers to impersonate domain controller accounts.<\/p>\n<\/li>\n CVE-2021-42287 comes in when requesting a Kerberos Service Ticket. Before one can request a Service Ticket, a Ticket Granting Ticket (TGT) is required by the Key Distribution Center (KDC). When the KDC is unable to find the requested service ticket, it automatically searches for a machine name with the trailing $. If a user is removed after obtaining the TGT, the user can request a service ticket meant for another user for himself by leveraging the obtained TGT, resulting in the KDC looking for user$ in Active Directory. If the domain controller account user$ exists, then the user basically obtained a service ticket for the domain controller account thereby completing the impersonation loop.<\/p>\n<\/li>\n<\/ul>\n Successful exploitation of the Windows Active Directory domain server would require the following steps:<\/strong><\/p>\n Add a new machine account to the domain.<\/p>\n<\/li>\n Rename the new machine account to match the sAMAccountName of an existing domain controller (without the \u2018$\u2019 that all computer names usually have)<\/p>\n<\/li>\n<\/ol>\n <\/p>\n Request a Kerberos TGT using the updated machine account name.<\/p>\n<\/li>\n Rename the new machine account once again to its original value or any other name.<\/p>\n<\/li>\n Use the S4U2self extension to request a Kerberos Service Ticket.<\/p>\n<\/li>\n<\/ol>\n The complete exploitation process can be found here<\/a>.<\/p>\n <\/p>\n Exploit of CVE-2021-42287 and CVE-2021-42278 in process<\/strong><\/p>\n CSW pentesters have provided a script<\/a> below to help detect any potential compromise of an unpatched Windows AD Domain server.<\/p>\n #!\/usr\/bin\/env python from impacket import version def TGT_size(options): # # Print TGT TGT_size, TGT_size_2 = len(tgt),len(tgt_2)<\/p>\n print(“<“*2+”-“*22+” TGT Size”+”-“*22+’>’*2) print(f”[+] Length of TGT size without PAC: {TGT_size_2} \\n”)<\/p>\n if TGT_size == TGT_size_2: # helper functions ldap connection return ldap_server, ldap_session<\/p>\n def init_ldap_session(args, domain, username, password):<\/p>\n # if args.k: ## checking MachineAccountQuota # Get domain policy # print(“<“*3+”-“*13+”Machine Account Quota “+”-“*13+’>’*3) #Conditional check # Process command-line arguments. parser = argparse.ArgumentParser()<\/p>\n parser.add_argument group = parser.add_argument_group(‘mandatory’) group.add_argument(‘-dc-ip’, action=’store’,required=True, metavar=”<IP address>”, if len(sys.argv)==1: options = parser.parse_args()<\/p>\n domain, username, password = parse_credentials(options.credentials) if domain is None: if password == ” and username != ” and options.hashes is None: if options.debug is True: try: except Exception as e: We also looked into the scanner details to see if the vulnerabilities had been missed by any major scanners. Here are the findings:<\/p>\nHow dangerous are these two vulnerabilities?<\/strong><\/h2>\n
\n
\n
\n
How does the exploit work?<\/strong><\/h2>\n
\n
\n
\n
<\/a>Detection<\/h2>\n
\nfrom __future__ import division
\nfrom __future__ import print_function
\nimport argparse
\nimport logging
\nimport sys
\nfrom binascii import unhexlify<\/p>\n
\nfrom impacket.examples import logger
\nfrom impacket.examples.utils import parse_credentials
\nfrom impacket.krb5 import constants
\nfrom impacket.krb5.kerberosv5 import getKerberosTGT
\nfrom impacket.krb5.types import Principal
\nimport ldapdomaindump
\nimport ldap3
\nfrom getpass import getpass<\/p>\n
\ndomain, username, password = parse_credentials(options.credentials)
\nuserName = Principal(username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
\nlmhash = ”
\nnthash = ”
\ntgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, unhexlify(lmhash), unhexlify(nthash),requestPAC=True)
\ntgt_2, cipher_2, oldSessionKey_2, sessionKey_2 = getKerberosTGT(userName, password, domain, unhexlify(lmhash), unhexlify(nthash),requestPAC=False)<\/p>\n
\n# print (tgt)
\n# print(“_”*50)
\n# print(tgt_2)<\/p>\n
\nprint(f”[+] Length of TGT size with PAC: {TGT_size} \\n”)<\/p>\n
\nprint( “[-] Not Vulnerable, PAC validated\\n”)
\nelse:
\nprint(“[!] Possbily vulnerable to CVE-2021-42287. \\n\\n[+] Apply Patches”)
\nprint(“<“*3+”-“*51+’>’*3)<\/p>\n
\ndef init_ldap_connection(target, tls_version, args, domain, username, password):
\nuser = ‘%s\\\\%s’ % (domain, username)
\nif tls_version is not None:
\nuse_ssl = True
\nport = 636
\ntls = ldap3.Tls(validate=ssl.CERT_NONE, version=tls_version)
\nelse:
\nuse_ssl = False
\nport = 389
\ntls = None
\nldap_server = ldap3.Server(target, get_info=ldap3.ALL, port=port, use_ssl=use_ssl, tls=tls)
\nldap_session = ldap3.Connection(ldap_server, user=user, password=password, authentication=ldap3.NTLM, auto_bind=True)<\/p>\n
\n# \u00a0 \u00a0 target = get_machine_name(args, domain)
\nif args.dc_ip is not None:
\ntarget = args.dc_ip
\nelse:
\ntarget = domain
\nreturn init_ldap_connection(target, None, args, domain, username, password)<\/p>\n
\ndef Check_quota(options):
\ndomain, username, password = parse_credentials(options.credentials)
\ndc_ip = options.dc_ip
\nldap_server, ldap_session = init_ldap_session(options, domain, username, password )
\ncnf = ldapdomaindump.domainDumpConfig()
\ncnf.basepath = None
\ndomain_dumper = ldapdomaindump.domainDumper(ldap_server, ldap_session, cnf)
\nMachineAccountQuota = None<\/p>\n
\ndd = domain_dumper.getDomainPolicy()
\nfor i in dd:
\nMachineAccountQuota = int(str(i[‘ms-DS-MachineAccountQuota’]))<\/p>\n
\nprint(“<“+”-“*16+” Machine Account Quota “+”-“*16+”>”)
\n# print(f'[-] Machine Account Quota \u00a0‘)
\nprint(f'[+] MachineAccountQuota = {MachineAccountQuota} \\n’)<\/p>\n
\nif MachineAccountQuota < 0:
\nprint(“[#] Not vulnerable \u00a0cannot proceed with Machine creation. \\n”)
\nelse:
\nprint(“[!] Possible Attack Vector, can be exploited further. \\n”)<\/p>\n
\nif __name__ == ‘__main__’:
\nlogger.init()
\nprint(version.BANNER)<\/p>\n
\nparser.add_argument(‘-debug’, action=’store_true’, help=’Turn DEBUG output ON’)<\/p>\n
\n## hashes are currently not supported
\n# group.add_argument(‘-hashes’, action=”store”, metavar = “LMHASH:NTHASH”, help=’NTLM hashes, format is LMHASH:NTHASH’)<\/p>\n
\nhelp=’IP of the domain controller to use. Useful if you can\\’t translate the FQDN.’
\n‘specified in the account parameter will be used’)
\ngroup.add_argument(‘-targetUser’, action=’store’, required=True,metavar=”<Target Username>”, help=’The target user to retrieve the PAC of’)
\ngroup.add_argument(‘credentials’, action=’store’, help=’domain\/username[:password]. Valid domain credentials to use ‘
\n‘for grabbing targetUser\\’s PAC \\n ‘)<\/p>\n
\nparser.print_help()
\nsys.exit(1)<\/p>\n
\ntarget_user = options.targetUser
\ndc_IP = options.dc_ip
\n# print(“<“*3+”-“*20+” Input Values “+”-“*20+’>’*3)
\nprint(f”[#] Input Values”)
\nprint(f”domain : {domain} \\nusername : {username} \\npassword: {password} \\ntarget_user: {target_user} \\ndc_ip : {dc_IP}”)<\/p>\n
\ndomain = ”<\/p>\n
\npassword = getpass(“Password:”)<\/p>\n
\nlogging.getLogger().setLevel(logging.DEBUG)
\n# Print the Library’s installation path
\nlogging.debug(version.getInstallationPath())
\nelse:
\nlogging.getLogger().setLevel(logging.INFO)<\/p>\n
\nprint(f”\\n[#] Initiating validations… \\n”)
\nCheck_quota(options)
\nTGT_size(options)<\/p>\n
\nif logging.getLogger().level == logging.DEBUG:
\nimport traceback
\ntraceback.print_exc()
\nlogging.error(str(e))<\/p>\n<\/div>\n