Last active
February 21, 2023 03:50
-
-
Save 3xocyte/8ad2d227d0906ea5ee294677508620f5 to your computer and use it in GitHub Desktop.
simple script for experimenting with machine account creation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import argparse | |
import sys | |
import string | |
import random | |
# https://support.microsoft.com/en-au/help/243327/default-limit-to-number-of-workstations-a-user-can-join-to-the-domain | |
# create machine account utility by @3xocyte | |
# with thanks to Kevin Robertson for https://github.com/Kevin-Robertson/Powermad/blob/master/Powermad.ps1 | |
from ldap3 import Server, Connection, ALL, NTLM | |
def get_base_dn(domain): | |
base_dn = '' | |
domain_parts = domain.split('.') | |
for i in domain_parts: | |
base_dn += 'DC=%s,' % i | |
# Remove last ',' | |
base_dn = base_dn[:-1] | |
return base_dn | |
def get_machine_name(): | |
machine_name = ''.join(random.choice(string.uppercase + string.digits) for _ in range(6)) | |
return machine_name + '$' | |
def get_unicode_password(password): | |
if password == '': | |
password = ''.join(random.choice(string.letters + string.digits + string.punctuation) for _ in range(20)) | |
encoded_password = '"{}"'.format(password).encode('utf-16-le') # https://github.com/cannatag/ldap3/issues/130#issuecomment-159641516 | |
return password, encoded_password | |
def ldaps_login(dc_ip, username, password, domain): | |
print "[*] connecting..." | |
s = Server(dc_ip, port = 636, use_ssl = True, get_info=ALL) | |
domain_user = "%s\\%s" % (domain, username) | |
try: | |
c = Connection(s, user = domain_user, password = password, authentication=NTLM) | |
if c.bind() == True: | |
print "[*] bound successfully" | |
except Exception, e: | |
print "[!] unable to connect: %s" % str(e) | |
sys.exit() | |
return c | |
def create_machine_account(ldap_connection, domain, machine_name, encoded_password, useraccountcontrol): | |
dn = "CN=%s,CN=Computers,%s" % (machine_name[:-1], get_base_dn(domain)) | |
dns_name = machine_name[:-1] + '.' + domain | |
try: | |
ldap_connection.add(dn, attributes={ | |
'objectClass':'Computer', | |
'SamAccountName': machine_name, | |
'userAccountControl': useraccountcontrol, | |
'DnsHostName': dns_name, | |
'ServicePrincipalName': [ | |
'HOST/' + dns_name, | |
'RestrictedKrbHost/' + dns_name, | |
'HOST/' + machine_name[:-1], | |
'RestrictedKrbHost/' + machine_name[:-1] | |
], | |
'unicodePwd':encoded_password | |
}) | |
if ldap_connection.result['description'] == 'success': | |
print "[*] added machine account: %s" % dns_name | |
else: | |
print "[!] failed to add machine account" | |
ldap_connection.unbind() | |
except Exception, e: | |
print "[!] exception raised: %s" % str(e) | |
ldap_connection.unbind() | |
sys.exit() | |
def main(): | |
# parser stuff | |
parser = argparse.ArgumentParser(add_help = True, description = "small utility to add a machine account to a domain, unprivileged users are limited by ms-DS-MachineAccountQuota (by default up to 10 concurrent machines may exist)") | |
parser.add_argument('-d', '--domain', action="store", default='', help='valid fully-qualified domain name', required=True) | |
parser.add_argument('-u', '--username', action="store", default='', help='valid username', required=True) | |
password_or_ntlm = parser.add_mutually_exclusive_group(required=True) | |
password_or_ntlm.add_argument('-p', '--password', action="store", default='', help='valid password') | |
password_or_ntlm.add_argument('-n', '--nthash', action="store", default='', help='valid ntlm hash') | |
dc_or_server_trust = parser.add_mutually_exclusive_group() | |
dc_or_server_trust.add_argument('--create-dc', action="store_true", help='set userAccountControl to "SERVER_TRUST_ACCOUNT | TRUSTED_FOR_DELEGATION" (privileged)') | |
dc_or_server_trust.add_argument('--create-server', action="store_true", help='set userAccountControl to "SERVER_TRUST_ACCOUNT" (privileged)') | |
parser.add_argument('--machine-name', action="store", help='name of computer account to create (default is random)') | |
parser.add_argument('--machine-pass', action="store", help='password of computer account to create (default is random)') | |
parser.add_argument('target_dc', help='ip address or hostname of dc') | |
options = parser.parse_args() | |
domain = options.domain | |
username = options.username | |
password = options.password | |
nthash = options.nthash | |
create_dc = options.create_dc | |
server_trust_account = options.create_server | |
dc_ip = options.target_dc | |
print "add a machine account to a domain (@3xocyte)\n" | |
# requires a dummy LM hash value | |
if nthash: | |
password = '00000000000000000000000000000000:%s' % nthash | |
useraccountcontrol = '4096' # 0x1000 (WORKSTATION_TRUST_ACCOUNT) | |
if create_dc: | |
useraccountcontrol = '532480' # 0x82000 (SERVER_TRUST_ACCOUNT | TRUSTED_FOR_DELEGATION) | |
print "[*] attempting to create a domain controller" | |
if server_trust_account: | |
useraccountcontrol = '8192' # 0x2000 (SERVER_TRUST_ACCOUNT) | |
print "[*] attempting to create a domain controller (just with SERVER_TRUST_ACCOUNT property)" | |
if options.machine_name: | |
machine_name = options.machine_name | |
if machine_name[-1:] != "$": | |
machine_name += "$" | |
else: | |
machine_name = get_machine_name() | |
if options.machine_pass: | |
machine_password, encoded_password = get_unicode_password(options.machine_pass) | |
else: | |
machine_password, encoded_password = get_unicode_password('') | |
print '[+] creating machine account "%s" with password "%s"' % (machine_name, machine_password) | |
ldap_connection = ldaps_login(dc_ip, username, password, domain) | |
create_machine_account(ldap_connection, domain, machine_name, encoded_password, useraccountcontrol) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment