noirello/bonsai

Any way to change ace.properties for an object?

iHaagcom opened this issue ยท 16 comments

using this:

from bonsai.active_directory import UserAccountControl
uac = UserAccountControl(entry['userAccountControl'][0])
print(uac.properties)

I can see 'passwd_cant_change': False, how to change this to true with bonsai?

Have you tried something like this?

uac.properties['passwd_cant_change'] = True
entry['userAccountControl'][0] = uac.value

How can I apply that to the object now?

I tried entry.apply but I get 'unwilling to perform' and cannot connect with ldaps so that could be the reason for the unwillingness?

This specific UAC PASSWD_CANT_CHANGE is a little challenging: https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties

Yes, it could be. The AD server usually requires secure connection for modifying specific attributes.

Unfortunately, I have got very little experience with Active Directory. If you can access the server logs it might have some better info about the unwillingness.

I did try LDAPS with your module but it wouldn't bind. Should I try using the port 636 instead? Was really hoping to make this change with python. https://docs.microsoft.com/en-us/answers/questions/948475/python-set-attributes-ldap.html

Would you know enough to help convert these? I was really hoping your module would be the answer :(

https://docs.microsoft.com/en-us/windows/win32/adsi/modifying-user-cannot-change-password-winnt-provider

I did try LDAPS with your module but it wouldn't bind. Should I try using the port 636 instead?

If you didn't specify a port in your connection URL, then using the ldaps:// will use port 636 by default. You have to set the proper CA cert (or lower the verification policy with the LDAPClient.set_cert_policy method), otherwise the connection will fail.

I dug in a little bit more based on the links you provided (mostly using this), but no luck denying password change.๐Ÿ˜ž

One important thing is that you should use LDAPEntry's change_attribute method for changing security related attributes (like userAccountControl or ntSecurityDescriptor). Simple assigning would not give you the proper LDAP modification, and it could also cause UnwillingToPerform error when try to modify the entry.

I share my code snippets which I was able to add the deny access control elements to the AD user.
The entry's passwd_cant_change was still False and I was still able to change the user's password after it, but maybe it could be useful to you somehow.

import bonsai
import bonsai.active_directory
import uuid

cli = bonsai.LDAPClient("ldap://ad-server.bonsai.test", tls=True)
cli.set_ca_cert("./tests/testenv/certs/cacert.pem")
cli.set_credentials("DIGEST-MD5", "admin", "password", "BONSAI.TEST")

def add_test_user(cli):
    entry = bonsai.LDAPEntry("cn=ad_user,dc=bonsai,dc=test")
    entry["objectclass"] = [
        "top",
        "inetOrgPerson",
        "person",
        "organizationalPerson",
        "user",
    ]
    entry["sn"] = "ad_user"
    entry["cn"] = "ad_user"
    entry["givenName"] = "AD"
    entry["userPrincipalName"] = "ad_user"
    entry["displayName"] = "ad_user"
    entry["sAMAccountName"] = "ad_user"
    entry["userPassword"] = "Sup3rP4ssW0rd123"
    entry["mail"] = "ad_user@bonsai.test"
    
    with cli.connect() as conn:
        conn.add(entry)

def deny_password_change(cli: LDAPClient, entry_dn: str):
    with cli.connect() as conn:
        entry = conn.search(
            entry_dn, 0, attrlist=["ntSecurityDescriptor", "userAccountControl"]
        )[0]
        uac = bonsai.active_directory.UserAccountControl(entry["userAccountControl"][0])
        sec_desc = bonsai.active_directory.SecurityDescriptor.from_binary(
            entry["ntSecurityDescriptor"][0]
        )
        new_dacl_aces = []
        for ace in sec_desc.dacl.aces:
            if ace.object_type == uuid.UUID("ab721a53-1e2f-11d0-9819-00aa0040529b"):
                # Find change password ACE and change it to deny.
                new_ace = bonsai.active_directory.ACE(
                    bonsai.active_directory.ACEType.ACCESS_DENIED_OBJECT,
                    ace.flags,
                    ace.mask,
                    ace.trustee_sid,
                    ace.object_type,
                    ace.inherited_object_type,
                    ace.application_data,
                )
                # Insert new deny ACEs to the front of the list.
                new_dacl_aces.insert(0, new_ace)
            else:
                new_dacl_aces.append(ace)
        # Adding the deny ACE for Everyone to the list. This ACE was missing from the original list 
        # only once while I was testing.
        #new_dacl_aces.insert(
        #    0,
        #    bonsai.active_directory.ACE(
        #        bonsai.active_directory.ACEType.ACCESS_DENIED_OBJECT,
        #        set(),
        #        bonsai.active_directory.ACERight.DS_CONTROL_ACCESS,
        #        bonsai.active_directory.SID("S-1-1-0"),
        #        uuid.UUID("ab721a53-1e2f-11d0-9819-00aa0040529b"),
        #        None,
        #        b"",
        #    ),
        #)
        # Copying every aspect of the old security descriptor except the DACL.
        new_dacl = bonsai.active_directory.ACL(sec_desc.dacl.revision, new_dacl_aces)
        new_sec_desc = bonsai.active_directory.SecurityDescriptor(
            sec_desc.control,
            sec_desc.owner_sid,
            sec_desc.group_sid,
            sec_desc.sacl,
            new_dacl,
            sec_desc.revision,
            sec_desc.sbz1,
        )
        entry.change_attribute(
            "ntSecurityDescriptor", bonsai.LDAPModOp.REPLACE, new_sec_desc.to_binary()
        )
        uac.properties["accountdisable"] = False
        entry.change_attribute("userAccountControl", bonsai.LDAPModOp.REPLACE, uac.value)
        entry.modify()

And this is the security settings for the user after I ran the code above:

access

Update: add dn parameter to deny_password_change function

Thank you for sharing, can you just modify the user not create a new user?? Did you say you were still able to change the password for the user (as the user on the domain?)?

Updated the function above with a dn parameter.

Did you say you were still able to change the password for the user (as the user on the domain?)?

Yes, I was able to bind with the user using the original password, then change it.

Can cer for the certificate be used or does it have to be pem?
Attempting with cer gives me: bonsai.errors.LDAPError: Local Error. (0x0052 [82])

It depends on the used tls implementation that the module is using. See: https://bonsai.readthedocs.io/en/latest/advanced.html#tls-settings

still unable to connect using the above method, i get bonsai.errors.LDAPError: Local Error. (0x0052 [82]) assuming that means unable over a vpn connection and must be local to the domain?

I exported the certificate this way, renamed it from cer to pem
cert-export

import bonsai
from bonsai import LDAPClient
from bonsai.active_directory import SecurityDescriptor
from bonsai.active_directory import UserAccountControl

#client = LDAPClient("ldap://IP_ADDRESS")
#client.set_credentials("SIMPLE", user="CN=GOD,OU=Admin,OU=Domain,DC=Domain,DC=lan", password="passwordforadmin!")
#conn = client.connect()

client = bonsai.LDAPClient("ldap://IP_ADDRESS", tls=True)
client.set_ca_cert("domainca.pem")
client.set_credentials("DIGEST-MD5", "GOD", "passwordforadmin", "domain.lan")
print("Authenticated")
entry = bonsai.LDAPEntry("CN=Test,OU=Users,OU=Domain,DC=domain,DC=lan")
#entry = client.search("CN=Test,OU=Users,OU=Domain,DC=domain,DC=lan"), 0, attrlist=["ntSecurityDescriptor", 'userAccountControl'])[0]
#sec_desc = SecurityDescriptor.from_binary(entry["ntSecurityDescriptor"][0])

with client.connect() as conn:
    entry = conn.search(
        entry.dn, 0, attrlist=["ntSecurityDescriptor", "userAccountControl"]
    )[0]
    uac = bonsai.active_directory.UserAccountControl(entry["userAccountControl"][0])
    sec_desc = bonsai.active_directory.SecurityDescriptor.from_binary(
        entry["ntSecurityDescriptor"][0]
    )
    new_dacl_aces = []
    for ace in sec_desc.dacl.aces:
        if ace.object_type == uuid.UUID("ab721a53-1e2f-11d0-9819-00aa0040529b"):
            # Find change password ACE and change it to deny.
            new_ace = bonsai.active_directory.ACE(
                bonsai.active_directory.ACEType.ACCESS_DENIED_OBJECT,
                ace.flags,
                ace.mask,
                ace.trustee_sid,
                ace.object_type,
                ace.inherited_object_type,
                ace.application_data,
            )
            # Insert new deny ACEs to the front of the list.
            new_dacl_aces.insert(0, new_ace)
        else:
            new_dacl_aces.append(ace)
    new_dacl = bonsai.active_directory.ACL(sec_desc.dacl.revision, new_dacl_aces)
    new_sec_desc = bonsai.active_directory.SecurityDescriptor(
        sec_desc.control,
        sec_desc.owner_sid,
        sec_desc.group_sid,
        sec_desc.sacl,
        new_dacl,
        sec_desc.revision,
        sec_desc.sbz1,
    )
    entry.change_attribute(
        "ntSecurityDescriptor", bonsai.LDAPModOp.REPLACE, new_sec_desc.to_binary()
    )
    uac.properties["accountdisable"] = False
    entry.change_attribute("userAccountControl", bonsai.LDAPModOp.REPLACE, uac.value)
    entry.modify()`

``


You can use bonsai.set_debug(True, -1) to get some debug information about the connection or try cli.set_cert_policy("ALLOW"), to not verify the server cert.

with this flag: cli.set_cert_policy("ALLOW") it at least tells me that:
bonsai.errors.AuthenticationError: Invalid Credentials. (0x0031 [49])

okay, i was able to connect using the old method (the hashed out stuff), and get the same result you do. deny is applied but uac flag is still false however, this changeUACattribute = {'userAccountControl': [('MODIFY_REPLACE', 66236)]} I think now ticks the box (just ran my old code)

FINALLY!!!!! The complete code (still have to pretty it up but the best thing is IT WORKS!!!!) THANK YOU THANK You Thank you!!

import bonsai
from bonsai import LDAPClient
from bonsai.active_directory import SecurityDescriptor
from bonsai.active_directory import UserAccountControl
import uuid

###debug####
#bonsai.set_debug(True, -1)
############
client = LDAPClient("ldap://IP_ADDRESS")
client.set_credentials("SIMPLE", user="CN=God,OU=Admin,OU=Domain,DC=domain,DC=lan", password="password_for_admin")
##### Secure if needed #####
#conn = client.connect()
#client = bonsai.LDAPClient("ldap://IP_ADDRESS", tls=True)
#client.set_ca_cert("ca.pem")
#client.set_cert_policy("ALLOW")
#client.set_credentials("DIGEST-MD5", "god", "password_for_admin", "domain.lan")
#############
entry = bonsai.LDAPEntry("CN=TEST,OU=Users,OU=Domain,DC=domain,DC=lan")
#entry = client.search("CN=TEST,OU=Users,OU=Domain,DC=domain,DC=lan", 0, attrlist=["ntSecurityDescriptor", 'userAccountControl'])[0]
#sec_desc = SecurityDescriptor.from_binary(entry["ntSecurityDescriptor"][0])

with client.connect() as conn:
    entry = conn.search(
        entry.dn, 0, attrlist=["ntSecurityDescriptor", "userAccountControl"]
    )[0]
    uac = bonsai.active_directory.UserAccountControl(entry["userAccountControl"][0])
    sec_desc = bonsai.active_directory.SecurityDescriptor.from_binary(
        entry["ntSecurityDescriptor"][0]
    )
    new_dacl_aces = []
    for ace in sec_desc.dacl.aces:
        if ace.object_type == uuid.UUID("ab721a53-1e2f-11d0-9819-00aa0040529b"):
            # Find change password ACE and change it to deny.
            new_ace = bonsai.active_directory.ACE(
                bonsai.active_directory.ACEType.ACCESS_DENIED_OBJECT,
                ace.flags,
                ace.mask,
                ace.trustee_sid,
                ace.object_type,
                ace.inherited_object_type,
                ace.application_data,
            )
            # Insert new deny ACEs to the front of the list.
            new_dacl_aces.insert(0, new_ace)
        else:
            new_dacl_aces.append(ace)
    new_dacl = bonsai.active_directory.ACL(sec_desc.dacl.revision, new_dacl_aces)
    new_sec_desc = bonsai.active_directory.SecurityDescriptor(
        sec_desc.control,
        sec_desc.owner_sid,
        sec_desc.group_sid,
        sec_desc.sacl,
        new_dacl,
        sec_desc.revision,
        sec_desc.sbz1,
    )
    entry.change_attribute(
        "ntSecurityDescriptor", bonsai.LDAPModOp.REPLACE, new_sec_desc.to_binary()
    )
    uac.properties["accountdisable"] = False
    entry.change_attribute("userAccountControl", bonsai.LDAPModOp.REPLACE, uac.value)
    entry.modify()
uac = UserAccountControl(entry['userAccountControl'][0])
print(uac.properties)
uac.properties['passwd_cant_change'] = True
entry['userAccountControl'][0] = uac.value
print(uac.properties)
#print(sec_desc.owner_sid)
#print(sec_desc.dacl)
print('Thank you Bonsai')

######
#Apply the Flag for 'PASSWD_CANNOT_CHANGE'
######
import ldap3
from ldap3 import Connection,Server,ALL,SUBTREE,MODIFY_REPLACE,NTLM
zid = input("username: ")
zid = str(zid).lower()
print(f'Searching for {zid}')
server = Server('ldaps://IP_ADDRESS', use_ssl=True, get_info=all) #port 636 for secure to solve {'result': 53, 'description': 'unwillingToPerform', 'dn': '', 'message': '0000001F: SvcErr: DSID-031A1236, problem 5003 (WILL_NOT_PERFORM), data 0\n\x00', 'referrals': None, 'type': 'modifyResponse'}
conn = Connection(server, user='Domain\\god', password='password_for_admin', auto_bind=True)
conn.bind()
Path_Root = "DC=Domain,DC=lan"
Filter = f'(&(objectclass=user)(&(sAMAccountName={zid})(!(objectclass=computer))))'
conn.search(search_base = Path_Root,
         search_filter = Filter,
         search_scope = SUBTREE,
         attributes = ["cn", "sAMAccountName", "displayName",'nTSecurityDescriptor','objectSid']
         )
if len(conn.entries) == 1:
    USER_DN = conn.response[0].get("dn")
    print(USER_DN)
changeUACattribute = {'userAccountControl': [('MODIFY_REPLACE', 66236)]}
conn.modify(USER_DN, changes=changeUACattribute)
print('THANK YOU Noirello')

I'm not sure yet, but i think this script may be applying the ace to the domain ou not the account? On your test account can you check if your script applies to "cn=ad_user,dc=bonsai,dc=test" or also changes the ",dc=bonsai,dc=test" permissions?

No, the top level permissions are not changed.