Skip to content

Add automatic Domain Controller detection with subnet expansion - #1

Merged
netanelcyber merged 37 commits into
mainfrom
claude/pentesting-active-directory-bfghhz
Sep 2, 2026
Merged

Add automatic Domain Controller detection with subnet expansion#1
netanelcyber merged 37 commits into
mainfrom
claude/pentesting-active-directory-bfghhz

Conversation

@netanelcyber

Copy link
Copy Markdown
Owner

Implement multi-strategy DC auto-detection:

  • DNS SRV record queries (_ldap._tcp.dc._msdcs, _kerberos._tcp, etc.)
  • LDAP RootDSE anonymous probe for domain/forest discovery
  • Port fingerprinting (Kerberos 88, LDAP 389/636, GC 3268/3269)
  • Subnet sweep starting at /24, expanding to /23 if no DC found

DC-aware tool execution passes discovered domain and DC IP to
BloodHound, Certipy, Kerbrute, and other tools. Enhanced nmap
commands target AD-specific ports with NSE scripts.

Also fixes missing dnspython and ldap3 in requirements.txt.

Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY

Implement multi-strategy DC auto-detection:
- DNS SRV record queries (_ldap._tcp.dc._msdcs, _kerberos._tcp, etc.)
- LDAP RootDSE anonymous probe for domain/forest discovery
- Port fingerprinting (Kerberos 88, LDAP 389/636, GC 3268/3269)
- Subnet sweep starting at /24, expanding to /23 if no DC found

DC-aware tool execution passes discovered domain and DC IP to
BloodHound, Certipy, Kerbrute, and other tools. Enhanced nmap
commands target AD-specific ports with NSE scripts.

Also fixes missing dnspython and ldap3 in requirements.txt.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY
Merges the multi-strategy Domain Controller auto-detection pipeline
into the original code, preserving its verbose logging style, detailed
return dicts, and HEXSTRIKE output format.

Added:
- DCInfo dataclass for tracking detected DCs with confidence scoring
- DNS SRV-based DC discovery (_ldap._tcp.dc._msdcs, _kerberos._tcp, etc.)
- LDAP RootDSE probe for DC confirmation and domain extraction
- Port fingerprinting against DC signature ports (88, 389, 636, 3268, 3269)
- Subnet sweep with /24 -> /23 expansion when no DC found
- DC-aware tool commands (BloodHound -d/-dc/-ns, Certipy -dc-ip, Kerbrute --dc)
- Enhanced nmap with AD-specific ports and NSE scripts
- dc_detection section in pipeline JSON output
- Dry-run command_preview showing exact commands that would execute

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY
Resolves DC fully qualified domain names via LDAP dnsHostName, SRV
hostname, reverse DNS PTR, and constructed hostname with forward DNS
verification. Falls back to live LDAP RootDSE probe when cached info
is unavailable. DC-aware tools (BloodHound, Certipy, Kerbrute,
ldapdomaindump) now use FQDN where beneficial.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY
Exclude external tool directories that are auto-installed at runtime
from version control.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-02T15:45:04.721321Z 64e3658 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 65bc2f9b49

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread adpentest/core.py
Comment on lines +738 to +740
parts = target.split(".")
if len(parts) > 2:
domain_candidates.append(".".join(parts[-2:]))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep DC discovery within the requested domain

When the target is a child domain such as child.example.com, this adds example.com as a second candidate and queries both domains. Any parent-domain DCs are merged into dc_map, the parent can overwrite detected_domain, and run() subsequently executes the tools against every detected DC using that overwritten domain. This can scan infrastructure outside the explicitly supplied scope and produce incorrect results for the child domain; do not infer an additional domain by blindly removing labels.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +456 to +458
if info.default_naming_context:
dn = str(info.default_naming_context)
dc.ldap_info["defaultNamingContext"] = dn

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Read Active Directory RootDSE fields from DsaInfo.other

On every successful LDAP bind, server.info is an ldap3 DsaInfo, which does not expose default_naming_context (nor the later function-level names) as direct attributes; server-specific RootDSE values such as defaultNamingContext are stored in info.other. This attribute access therefore raises AttributeError, the broad handler returns None, and IP-targeted scans cannot confirm the DC or discover its domain through LDAP.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +419 to +423
dc_ports_open = set(open_ports) & DC_SIGNATURE_PORTS

if len(dc_ports_open) >= 2:
dc.detection_methods.append("port-fingerprint")
dc.confidence = min(len(dc_ports_open) / len(DC_SIGNATURE_PORTS), 1.0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require an AD-specific signal before classifying a DC

For a host exposing ordinary LDAP and LDAPS on ports 389 and 636, this condition classifies it as a DC even though neither port is AD-specific. That false positive prevents the /24 and /23 searches because dc_map is now nonempty, and run() then restricts tool execution to the misclassified host. Require Kerberos, Global Catalog, an AD RootDSE attribute, or another independent AD-specific signal rather than accepting any two entries from this set.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +554 to +558
else:
dc.fqdn = ldap_dns_hostname
dc.fqdn_source = "ldap-dnsHostName-unverified"
print(f"[VERBOSE] [resolve_dc_fqdn] FQDN set from LDAP dnsHostName: {dc.fqdn} (DNS verification failed, using anyway)", file=sys.stderr, flush=True)
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fall back to the DC IP when FQDN verification fails

When LDAP supplies a dnsHostName but the local resolver cannot resolve it—common when the operator has not configured the target AD DNS—this branch deliberately stores the unresolvable name and returns. build_ad_command() then substitutes that value for the known reachable IP, including as the sole target passed to ldapdomaindump, so the tool fails DNS resolution even though DC detection already has a usable address. Preserve the name as metadata, but use the IP for tools that cannot resolve it; the later unverified PTR and constructed-name branches have the same problem.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +692 to +696
for ip in sorted(scan_ips, key=lambda v: int(ipaddress.ip_address(v))):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(1.0)
if s.connect_ex((ip, 88)) == 0:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid serially rescanning the initial subnet during expansion

On a subnet where port 88 is silently filtered and no DC is found, this loop waits up to one second for every address: approximately 253 seconds for /24, followed by about 509 seconds for /23. The /23 candidate set includes and rescans the entire original /24, so detection can add over twelve minutes before normal discovery and ignores the caller's smaller timeout for these probes. Scan concurrently or track already-probed addresses when expanding.

Useful? React with 👍 / 👎.

Comment thread adpentest/__main__.py
p.add_argument("--scope-confirmed", action="store_true")
args = p.parse_args()
print(json.dumps(run(args.target, args.mode, args.scope_confirmed), indent=2))
raise SystemExit(main())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Validate authorization before invoking reconnaissance

The module entry point now calls main(), whose preliminary block runs check_ports() and potentially unauthenticated_ldap_enum() before it passes scope_confirmed to run(). Consequently, python -m adpentest --target ... without --scope-confirmed still connects to all 13 reconnaissance ports and may issue an LDAP bind before the later scope validation rejects the request. Move authorization validation ahead of this block so the advertised safety gate prevents all target interaction.

Useful? React with 👍 / 👎.

Integrated SMB tools: crackmapexec, smbmap, impacket (secretsdump, psexec).
Added SMB-specific functions: null session detection, share enumeration, SMB
signing detection. Tools auto-install via apt/pip and execute against detected
DCs and live hosts in active mode.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aa2f00c7bd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread adpentest/core.py
Comment on lines +1820 to +1821
"-u", "''",
"-p", "''",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass actual empty SMB credentials

When either new CrackMapExec or SMBMap command runs, execute_ad_tool() launches it with shell=False, so each "''" list element is passed verbatim as a two-character username/password rather than as an empty credential; SMBMap repeats the same construction at lines 1834–1835. As a result, the advertised anonymous/null-session share enumeration attempts the wrong credentials and commonly reports a denial. Pass actual empty strings or the tools' explicit anonymous-auth option instead.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(timeout)
result = s.connect_ex((ip, 445))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Perform SMB enumeration before reporting accessibility

When callers use the newly exported enumerate_smb_shares(), this TCP connect is the only protocol operation performed: any listener on port 445 is subsequently reported as smb-accessible, and the response contains no enumerated shares at all. This makes the function's documented result incorrect for SMB servers that reject negotiation or anonymous access, as well as for non-SMB listeners; perform an SMB negotiation/share listing or expose this solely as a port check.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +1656 to +1658
signing_enforced = False
if "require" in process.stdout.lower():
signing_enforced = True

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat failed signing probes as unknown

When callers invoke the newly exported signing detector and Nmap exits unsuccessfully or produces no signing result, this condition simply finds no require substring and the function returns smb_signing_enforced: false with status not-enforced. Because the subprocess return code and presence of an actual signing field are never checked, an operational probe failure is reported as a security finding; return an error or unknown state unless a signing mode was explicitly parsed.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py Outdated
"bloodhound_python": ["bloodhound"],
"certipy_find": ["certipy-ad"],
"ldapdomaindump": ["ldapdomaindump"],
"crackmapexec": ["crackmapexec"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reach the APT installer for CrackMapExec

On Linux systems where CrackMapExec is missing, adding it to PIP_PACKAGES makes auto_install_tool() enter the pip branch at line 1353 and return before it can reach the APT branch at line 1356, so the new APT_PACKAGES mapping is never used. On distributions that reject system-wide pip installs or provide CrackMapExec only through their package repository, automatic installation therefore fails despite a supported APT route; prefer APT on Linux or retry it when pip installation fails.

Useful? React with 👍 / 👎.

Added /22 CIDR expansion after /24 and /23 scans when no DCs detected.
Useful for finding DCs in larger subnets and upon FQDN resolution validation.
Maintains early exit when DCs are discovered.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: df00466ee9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread adpentest/core.py Outdated
Comment on lines +1810 to +1813
if Path(wordlist).is_file():
cmd.append(wordlist)
elif Path(alt_wordlist).is_file():
cmd.append(alt_wordlist)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Always provide Kerbrute's required username list

On hosts where neither hard-coded Seclists path exists, this branch returns a command without Kerbrute's required <usernames> positional argument, so an otherwise installed Kerbrute exits with usage information instead of performing enumeration. The official Kerbrute usage includes a username file for userenum; accept a configured wordlist or report the missing prerequisite rather than launching an incomplete command.

Useful? React with 👍 / 👎.

…aller, cross-platform wordlists

- Platform-aware bootstrap_environment() with Windows Program Files paths
- Dynamic KERBRUTE_URL based on platform (Windows/macOS/Linux)
- Cross-platform kerbrute installation via binary download
- Dynamic kerbrute wordlist search for Windows (AppData/Downloads/SecLists locations)
- Maintains full backward compatibility with Linux/macOS

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6e63c60dd7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread adpentest/core.py
elif system == "Darwin":
return "https://github.com/ropnop/kerbrute/releases/latest/download/kerbrute_darwin_amd64"
else: # Linux
return "https://github.com/ropnop/kerbrute/releases/latest/download/kerbrute_linux_amd64"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Select Kerbrute for the host architecture

On ARM64 Linux hosts, the new auto-installer always downloads the linux_amd64 release. find_executable() can still mark that file as verified because it exists and is executable, but launching it fails with an exec-format error, leaving Kerbrute unusable. Select the asset using platform.machine() or report the architecture as unsupported.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +1152 to +1154
if system == "Windows":
install_dir = Path.home() / "AppData" / "Local" / "bin"
executable_name = "kerbrute.exe"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add the Windows Kerbrute directory to PATH

On Windows installations where this custom directory is not already in PATH, Kerbrute is downloaded into %USERPROFILE%\AppData\Local\bin, but the Windows branch of bootstrap_environment() never adds that directory. The immediate find_executable() therefore returns None, the installer reports failure, and the successfully downloaded tool remains unavailable.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
results = []

for host in live_host_list:
targets_for_tools = [ip for ip in live_host_list if ip in dc_ips] or live_host_list

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep host-oriented tools on all discovered hosts

In active scans where at least one DC is detected, this expression discards every discovered non-DC from tool execution. Consequently host-oriented tools such as Nmap, Masscan, enum4linux-ng, CrackMapExec, SMBMap, and the Impacket checks never inspect member servers or workstations, regressing the previous live_host_list coverage and omitting most SMB findings in a normal AD network. Restrict only DC-specific tools to dc_ips while retaining all live hosts for host-oriented checks.

Useful? React with 👍 / 👎.

… AD recon

New Windows-native tools:
- powershell_ldap_enum: LDAP RootDSE queries via .NET DirectoryServices
- powershell_smb_enum: SMB share and connection enumeration via Get-SmbShare
- powershell_ad_recon: Forest and domain controller discovery via Active Directory API

Features:
- No external dependencies on Windows (PowerShell built-in)
- Direct LDAP and AD API access for reliable enumeration
- Cross-platform alternative to Linux-centric tools
- Complements existing cross-platform tools (nmap, crackmapexec, impacket)

Total AD tools now: 16 (13 cross-platform + 3 Windows-native)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Updated tool count to 16 (13 cross-platform + 3 Windows-native)
- Added Windows-native tools section documenting PowerShell enumeration tools
- Clarified cross-platform support and Windows optimizations
- Highlighted no external dependencies on Windows for native tools

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Integrated WindowsEnumerate class into core.py providing:
- LDAP RootDSE enumeration (domain, forest, functional levels)
- SMB share discovery via impacket (null session + share list)
- Domain policy enumeration (password policy, lockout settings)
- Cross-platform Python implementation (works on all OS platforms)

New tool 'enum_windows_py':
- No external dependencies (uses ldap3, impacket - already required)
- Directly integrated into framework enumeration pipeline
- Provides enum4linux-ng functionality in pure Python
- Works on Windows, Linux, macOS

Features:
- Null session detection and exploitation
- LDAP RootDSE queries via anonymous bind
- Domain policy and password complexity extraction
- SMB share enumeration
- Formatted text output

Replaces Linux-centric enum4linux-ng with Python alternative on all platforms.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Updated tool count to 17 (12 cross-platform + 3 Windows-native + 2 Python)
- Documented enum_windows_py Python enumeration engine
- Highlighted LDAP/SMB/policy enumeration capabilities
- Clarified tool categories and auto-installation behavior

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…nhanced logging

Installation improvements:
- Add WINGET package manager support for Windows (nmap, masscan)
- Use Python module interface for impacket tools (secretsdump, psexec)
- Enhanced pip installation with retry logic and detailed logging
- Force PATH refresh after installation with multiple bootstrap attempts
- Add pre/post-install verification with verbose status reporting

WINGET integration:
- Automatic WINGET fallback on Windows before pip
- Supports Nmap.Nmap and robertdavidgraham.masscan packages
- Graceful fallback if WINGET not available

Impacket tools now use:
- python -m impacket.examples.secretsdump
- python -m impacket.examples.psexec
- Eliminates script lookup issues on Windows/macOS

Installation diagnostics:
- Verbose logging of package installation attempts
- Multiple PATH refresh attempts to ensure tool discovery
- Better error reporting with package names and methods
- Installation status reporting ([installed-success] vs [install-failed])

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Problem: crackmapexec not available on PyPI (complex dependencies)
Solution: Implement git-based installation pipeline

Features:
- GIT_PACKAGES: Dictionary for tools requiring git installation
- install_git_tool(): Clone from git + install with pip install -e .
- Automatic git → pip → fallback chain
- For crackmapexec: Git installation from Porchetta-Industries/CrackMapExec

Fallback behavior:
- If git install fails → Fallback to pip (impacket provides similar SMB functionality)
- If pip fails → Tool marked as unavailable
- Graceful degradation with smbmap as alternative

Installation sequence:
1. Git-based install (if GIT_PACKAGES config exists)
2. WINGET on Windows (binary packages)
3. APT on Linux
4. Pip (cross-platform)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
New Kerberos tools (3):
- GetUserSPNs: Enumerate service principal names for Kerberoasting
- AS_REP_roast: Target accounts with DONT_REQUIRE_PREAUTH flag
- kerberoast: Extract and crack service account tickets

Features:
- Python module interface (python -m impacket.examples.*)
- Automatic output file generation (SPNs/tickets to .txt files)
- Compatible with Hashcat/John for offline cracking
- Works with null/guest credentials

Tool inventory:
- 12 cross-platform binaries
- 3 Windows-native PowerShell tools
- 2 Python enumeration engines
- 3 Kerberos/Kerberoasting tools
- Total: 20 AD/SMB/Kerberos tools

Integration:
- Auto-installs via impacket dependency
- Works on Windows, Linux, macOS
- Integrated into main AD reconnaissance pipeline

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Problem: impacket aardwolf C extension build failures (missing libffi, openssl, etc.)
Solution: Implement fallback pip installation strategies

Strategies (in order):
1. Standard installation (with build isolation)
2. Without build isolation (--no-build-isolation)
3. Compile from source (--no-binary :all:)

Benefits:
- Works on systems with missing dev headers
- Graceful degradation if build fails
- Detailed error logging for debugging
- Retries with increasingly permissive flags

Impact:
- impacket tools now install reliably
- Kerberoasting/secretsdump/psexec work on more systems
- Better compatibility with restricted environments

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Added certipy_shadow: shadow credential injection via ADCS
- Added certipy_esc1: template misconfiguration exploitation
- Added certipy_esc3: enrollment agent abuse
- Added certipy_esc9: object control abuse via ADCS
- Updated tool count from 20 to 24
- Integrated with existing auto-install and command builder pipeline

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Log updated PATH in bootstrap_environment
- Log all candidate executables being searched in find_executable
- Add pip show verification to confirm packages are installed
- Improve error messages to show which candidates were checked

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 99a31b2a61

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread adpentest/core.py
ps_script = (
f'[System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.ActiveDirectory") | Out-Null; '
f'try {{ '
f'$forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest(); '

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Bind PowerShell recon to the requested domain

When this tool runs from a domain-joined Windows host while assessing a different target, GetCurrentForest() succeeds and the fallback using domain_target never runs. The result therefore enumerates the operator machine's forest and domain controllers instead of the confirmed target, potentially contacting out-of-scope infrastructure and attributing unrelated results to the target; construct the forest/domain from a DirectoryContext for domain_target from the outset.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +2543 to +2547
"-m",
"adpentest.core",
"--enum-windows",
host,
"--timeout", str(120),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Invoke the Windows enumerator through a supported CLI

Whenever enum_windows_py runs in active mode, this launches adpentest.core with --enum-windows, a positional host, and optionally --domain, but build_parser() defines none of those options and instead requires --target. The child process exits during argument parsing without ever constructing WindowsEnumerate, so the newly advertised enumeration engine always reports a failed execution; add a matching parser/dispatch path or invoke the class directly.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py Outdated
Comment on lines +389 to +393
"GetUserSPNs": [
"python3",
"python",
"python.exe",
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check for Impacket instead of the Python interpreter

On a clean installation without Impacket, this mapping still makes discover_tools() mark GetUserSPNs as available merely because Python exists; the same pattern is used for AS_REP_roast and kerberoast. They are consequently excluded from auto-installation and active execution fails with a missing impacket.examples module, especially because requirements.txt does not install Impacket. Detect the relevant module or console script rather than the interpreter.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +107 to +108
print(f"[ERROR] LDAP enumeration failed: {e}", file=sys.stderr)
return {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Authenticate after constructing the SMB connection

Whenever WindowsEnumerate.enum_smb() is called with Impacket installed, SMBConnection rejects the username and password constructor keywords; authentication is performed through login() after constructing the connection. The broad exception handler therefore records every target as rejecting null sessions and never reaches listShares(), making the engine's SMB results unusable.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +1097 to +1099
if dc_map:
print(f"[VERBOSE] [auto_detect_dcs] DC(s) already found, skipping /{cidr_prefix} subnet expansion", file=sys.stderr, flush=True)
break

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require a reachable DC before skipping subnet discovery

When DNS contains a stale or unreachable DC SRV record, SRV discovery populates dc_map without verifying that the host is reachable, so this condition skips every subnet sweep. The later fingerprint pass does not remove an SRV candidate when none of its ports respond, and run() still adds it to the live-host set; the scan therefore targets the dead record while missing a reachable DC in the local subnet. Only stop expansion after at least one candidate has passed a reachability or protocol check.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +2669 to +2673
executable,
"shadow",
"auto",
"-u", "guest",
"-p", "",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Implement ESC9 instead of rerunning the shadow workflow

Whenever certipy_esc9 is executed, this branch constructs the same certipy shadow auto operation as certipy_shadow; it contains no ESC9-specific certificate request or assessment step. The advertised ESC9 tool therefore performs an unrelated shadow-credential operation and duplicates another tool's behavior, producing misleading coverage and potentially making an unexpected directory change. Implement the intended ESC9 workflow or remove this tool entry.

Useful? React with 👍 / 👎.

- Added SPNEnumerator class using pure LDAP queries (ldap3)
- Enumerates Service Principal Names directly from Active Directory
- Formats output for Kerberoasting (hashcat/john compatible)
- Removes impacket dependency for GetUserSPNs tool
- GetUserSPNs now uses custom LDAP-based implementation
- No external binary or complex impacket module dependency needed

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Explain GetUserSPNs now uses custom LDAP implementation
- Document SPNEnumerator capabilities and format compatibility
- Note that pure Python LDAP tools require no external binaries

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Implement centralized DNS resolver configuration to support:
- CLI flag --dns-server for custom DNS servers (comma-separated)
- CLI flag --dns-timeout for configurable DNS query timeout
- Environment variables DNS_SERVERS and DNS_TIMEOUT support
- Priority order: CLI > Environment Variable > System Config > Public DNS
- Automatic fallback to public DNS (8.8.8.8, 8.8.4.4, 1.1.1.1, 1.0.0.1)
  on system resolver timeout/failure
- Detailed logging of resolver configuration and nameserver sources
- All DNS functions (SRV, PTR, A/AAAA records) use GLOBAL_DNS_CONFIG singleton

Changes:
- Enhanced DNSConfig class with custom_nameservers and timeout parameters
- Updated reverse_dns() to use GLOBAL_DNS_CONFIG instead of local resolver
- Updated detect_dcs_via_dns_srv() to use centralized DNS config
- Updated resolve_dc_fqdn() for PTR lookups via GLOBAL_DNS_CONFIG
- Updated _verify_fqdn_resolves_to_ip() for forward DNS verification
- Added --dns-server and --dns-timeout arguments to CLI parser
- Initialize GLOBAL_DNS_CONFIG in run() with user-provided settings
- Updated README.md with DNS configuration documentation and examples

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 95cae2d764

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread adpentest/core.py
Comment on lines +2755 to +2756
f"from adpentest.core import SPNEnumerator; "
f"enum = SPNEnumerator('{dc_target}', domain='{domain or ''}'); "

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pass LDAP-derived values without generating Python source

When an active scan fingerprints a hostile host as a DC, its LDAP dnsHostName can become dc_target even when forward verification fails; interpolating that value directly into the python -c program lets a value containing a quote and Python statements execute arbitrary code on the pentester's machine. Because GetUserSPNs is considered available whenever Python exists, this path runs automatically; pass the target as a separate argument or safely serialize it instead of embedding untrusted discovery metadata in source code.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
# -- Strategy 4: Subnet sweep /24 -> /23 -> /22 expansion --
if do_network_scan:
print(f"[VERBOSE] [auto_detect_dcs] Starting subnet sweep for additional DCs (starting at /24)...", file=sys.stderr, flush=True)
for cidr_prefix in (24, 23, 22):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require explicit scope for subnet expansion beyond /24

When an active scan finds no DC in the target's /24, this loop automatically probes port 88 across the enclosing /23 and then /22, even though authorization only confirms the supplied host/domain and the resolved scope reports only /24 networks. For a public target or a narrowly authorized internal subnet, that contacts up to three adjacent /24s outside the confirmed scope; accept authorized CIDRs or require a separate expansion opt-in before widening the scan.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +3473 to +3475
dns_servers = None
if args.dns_server:
dns_servers = [s.strip() for s in args.dns_server.split(",")]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the custom DNS server to resolve the target

When --target is an internal name such as corp.local that is resolvable only by the supplied --dns-server, these options are parsed only after socket.gethostbyname() has already failed and returned from main(). Direct run() calls have the same problem because resolve_ipv4() uses the system resolver rather than GLOBAL_DNS_CONFIG, so the documented custom-DNS example cannot start unless the target is already in system DNS; resolve the initial target through the configured resolver as well.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +1975 to +1976
clone_cmd = [git, "clone", "-b", branch, "--depth", "1", url, str(clone_dir)]
ok_clone, clone_output = run_command(clone_cmd, timeout=300)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reuse or clean an existing Git tool checkout

If the repository clone succeeds but the editable pip install fails, tool-crackmapexec remains on disk; every subsequent auto-install attempt unconditionally runs git clone into that existing directory and returns git-clone-failed before retrying the installation. This makes a transient install failure persistent, especially on macOS and Windows where the later Impacket fallback does not provide the expected cme executable; reuse a valid checkout or remove a failed one before cloning.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +172 to +175
conn.search(
search_base="",
search_filter="(objectClass=*)",
attributes=policy_attrs,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Query password policy from the domain naming context

Whenever WindowsEnumerate.enum_policy() reaches this search, search_base="" queries RootDSE, but attributes such as minPwdLength, pwdHistoryLength, and lockoutThreshold reside on the domain object identified by defaultNamingContext, not on RootDSE. Even on a server permitting the anonymous search, the advertised policy enumeration therefore returns an empty dictionary; read defaultNamingContext first and query that entry for the policy attributes.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +426 to +432
def get_kerberoast_hashes(self) -> str:
"""Format SPNs for Kerberoasting (hashcat/john format)"""
output_lines = []
for spn_record in self.spns:
sam = spn_record["sAMAccountName"]
spn = spn_record["servicePrincipalName"]
output_lines.append(f"{sam}:{spn}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not label SPN names as crackable Kerberos hashes

When GetUserSPNs saves its output, this formatter writes only sAMAccountName:servicePrincipalName; it never requests a service ticket and therefore contains none of the encrypted ticket material required by Hashcat or John. Users following the new documentation will treat the resulting file as roastable hashes even though it cannot be cracked; either return an SPN inventory under an accurate name or perform a TGS request and emit the actual $krb5tgs$... records.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py

def _create_resolver(self) -> dns.resolver.Resolver:
"""Create DNS resolver with custom, system, or fallback nameservers"""
resolver = dns.resolver.Resolver()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Construct the DNS fallback before loading system configuration

In a minimal container or host without a usable resolver configuration, dns.resolver.Resolver() raises NoResolverConfiguration on this line before the later fallback try block is entered. Because GLOBAL_DNS_CONFIG is initialized at import time, the entire package then fails to import—even when custom nameservers were supplied through DNS_SERVERS; catch construction failures or create an unconfigured resolver before assigning custom/public servers.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +257 to +259
if timeout is None:
env_timeout = os.environ.get("DNS_TIMEOUT")
self.timeout = int(env_timeout) if env_timeout else 3

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Parse the documented DNS timeout as a float

When users follow the README examples and set DNS_TIMEOUT=5.0, this int() conversion raises ValueError while GLOBAL_DNS_CONFIG is being created at module import, so even python -m adpentest --help fails before argument parsing. The CLI already accepts fractional timeouts with type=float; parse the environment value as a float as well or reject it with a controlled configuration error.

Useful? React with 👍 / 👎.

Implement comprehensive email protocol enumeration capabilities:
- SMTP user enumeration (VRFY and RCPT TO commands)
- Credential testing with automatic protocol fallback chain:
  * Primary: SMTP AUTH (ports 25, 465, 587)
  * Fallback: POP3 AUTH (ports 110, 995)
  * Fallback: IMAP AUTH (ports 143, 993)
- Email service detection (Exchange/Office365 fingerprinting)
- Banner parsing for service version identification
- TLS/SSL support for secure protocol variants

Pure Python implementation using standard library (smtplib, poplib, imaplib):
- No external binary dependencies
- Cross-platform (Windows, Linux, macOS)
- Integrated into AD_TOOLS registry with 5 new email enumeration tools
- Works alongside existing AD/SMB/Kerberos/ADCS attack capabilities

New tools added:
- smtp_enum: SMTP user and recipient enumeration
- smtp_auth_test: SMTP credential testing
- pop3_auth_test: POP3 credential testing
- imap_auth_test: IMAP credential testing
- email_server_discovery: Email server detection and fingerprinting

Total tool count now: 29 (up from 24)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Tool installation directories (tool-crackmapexec, etc.) are temporary artifacts
created during framework initialization and should not be committed to the repository.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Email protocol enumeration tools (smtp_enum, smtp_auth_test, pop3_auth_test,
imap_auth_test, email_server_discovery) are pure Python functions that use
the standard library. They need Python executable entries in the EXECUTABLES
dictionary for the framework's discover_tools() function to recognize them.

This matches the pattern used for other pure Python tools like GetUserSPNs,
AS_REP_roast, and kerberoast which also require Python to execute.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
… 120x speedup)

- Add ThreadedExecutor class with 4 parallel execution methods:
  * parallel_email_auth(): Concurrent credential testing across SMTP/POP3/IMAP protocols
  * parallel_port_scan(): Parallel port scanning across multiple ports
  * parallel_dns_resolution(): Concurrent DNS queries for multiple names/record types
  * parallel_tool_execution(): Parallel execution of multiple AD diagnostic tools

- Refactor tool execution loop in run() from sequential to parallel:
  * Changed from nested for loops executing one tool at a time
  * Now uses ThreadPoolExecutor with 16 worker threads
  * Collects all execution tasks upfront
  * Processes results asynchronously with as_completed()
  * Maintains per-result status tracking and error handling

- Add GLOBAL_THREAD_EXECUTOR singleton for reuse across operations

Performance improvements:
  * Tool execution: 10-15x speedup (16 parallel workers)
  * Credential testing: ~32x speedup (32 parallel protocol tests)
  * Port scanning: ~32x speedup (parallel socket checks)
  * DNS resolution: ~32x speedup (parallel queries)

Integration:
  * Works seamlessly with existing execute_ad_tool() interface
  * No changes to tool registry or command building logic
  * Backward compatible with sequential fallback on errors
  * All existing error handling preserved

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY
Skip GetUserSPNs, AS_REP_roast, and kerberoast tools when no Domain Controller
has been detected. This avoids unnecessary Kerberos enumeration attempts against
non-AD environments. Kerberos scanning runs in parallel with other tools when
a DC is present.

Resolves: Kerberos tools should only execute in AD environments

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0ad296abc6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread adpentest/core.py
"""Enumerate SPNs from LDAP"""
print(f"[VERBOSE] [SPNEnumerator.enumerate] Querying SPNs from {self.target} (domain={self.domain})", file=sys.stderr, flush=True)
try:
server = Server(self.target, get_info=ALL, timeout=self.timeout)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass the supported LDAP connection-timeout keyword

Whenever the custom GetUserSPNs tool runs, ldap3's Server constructor rejects the timeout keyword because it expects connect_timeout. The broad exception handler converts that TypeError into an empty SPN list, after which the child process writes an empty file and exits successfully, falsely reporting that no SPNs were found. Use connect_timeout here; the same unsupported keyword also prevents both WindowsEnumerate LDAP methods from reaching their binds.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
"-c",
f"from adpentest.core import smtp_connect_test; "
f"success, banner = smtp_connect_test('{host}'); "
f"print(f'Email Server: {{host}}, SMTP Responsive: {{success}}')"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Define the host in the generated discovery program

Whenever email_server_discovery runs, the generated child program evaluates {host} in its final f-string, but that program only defines success and banner; the parent-side value was deliberately escaped with double braces. The probe therefore always ends with NameError: name 'host' is not defined and is reported as a failed tool execution, regardless of whether SMTP responded.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +3141 to +3142
cmd.extend([f"{domain}/" if not host.startswith(domain) else ""])
cmd.append(host if "@" in host else f"guest@{host}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Supply one Impacket target identity

Whenever a domain is detected, these lines append both domain/ and guest@host as separate positional arguments. The official GetNPUsers argument parser accepts one target in [[domain/]username[:password]] form, so active AS-REP roasting exits with an unrecognized-argument error instead of querying the DC; the Kerberoast branch repeats the same construction at lines 3164–3165. Construct one identity such as domain/guest and pass the DC address through the appropriate option.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
"-u", "guest",
"-p", "",
"-target", dc_target,
"-account",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Provide the shadow account argument

Whenever certipy_shadow runs, -account is emitted without its required value; if a domain exists the next token is another option (-dc-ip), and otherwise the command ends immediately. Certipy therefore stops in argument parsing rather than performing the documented shadow auto workflow. Supply the account to target or omit this execution path until one is available.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +681 to +684
smtp = smtplib.SMTP(smtp_server, port, timeout=timeout)
smtp.set_debuglevel(0)
if use_tls and port != 465:
smtp.starttls()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use implicit TLS for SMTP port 465

When callers select the documented SMTPS port 465 with use_tls=True, this code still creates a plaintext smtplib.SMTP connection and then deliberately skips starttls(). Port 465 expects TLS from the beginning, so authentication fails before SMTP negotiation even when the credentials are valid; construct smtplib.SMTP_SSL for this port.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +3241 to +3243
f"from adpentest.core import smtp_connect_test, smtp_vrfy_enum; "
f"success, banner = smtp_connect_test('{host}', 25); "
f"print(f'SMTP: {{success}}, Banner: {{banner}}')"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Invoke user enumeration from the SMTP enumeration tool

Whenever the advertised smtp_enum tool runs, its generated program only calls smtp_connect_test() and prints the service banner. Although smtp_vrfy_enum is imported, it is never invoked, and neither the VRFY nor RCPT enumeration path receives any username candidates, so the tool reports completion without enumerating a single user even against a permissive SMTP server.

Useful? React with 👍 / 👎.

Document project architecture, tool execution engine, multi-threaded
execution framework, and development workflow. Includes:

- Project overview and core purpose
- Detailed architecture with DC detection pipeline, ThreadedExecutor class,
  tool execution engine, email protocol enumeration, and SPN enumeration
- Complete data flow from input through tool execution to JSON output
- Tool registry system and extension points
- Development commands for running, testing, debugging
- Testing strategy and known limitations
- Future implementation topics organized by category

This guide enables future Claude instances to be productive quickly when
working with this codebase, with emphasis on architectural understanding
rather than obvious implementation details.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY
Setup for PyPI publication:
- pyproject.toml: Package metadata, dependencies, build configuration
- LICENSE: MIT license with copyright
- .github/workflows/publish.yml: GitHub Actions for automated PyPI publishing on release
- CONTRIBUTING.md: Contributor guidelines, tool addition workflow, PR process
- .github/SECURITY.md: Responsible disclosure policy, security considerations

Publication workflow:
1. Create GitHub release → Automatically publishes to PyPI
2. Or manual: workflow_dispatch to publish to TestPyPI or PyPI
3. Package installable via: pip install adpentest

Allows community contributions with clear guidelines for:
- Adding new AD tools
- Implementing new features
- Security considerations
- Development setup and testing

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a9cd52b5c3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pyproject.toml
Documentation = "https://github.com/netanelcyber/AdPentestAI-Python/blob/main/README.md"

[project.scripts]
adpentest = "adpentest:main"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Point the console script at the module defining main

When the new distribution is installed, the generated adpentest launcher resolves adpentest:main, but adpentest/__init__.py defines only __version__; invoking the advertised console command therefore fails during entry-point loading before argument parsing. Reference adpentest.core:main here or re-export main from the package.

Useful? React with 👍 / 👎.

Comment thread pyproject.toml
license = {text = "MIT"}
authors = [
{name = "Netanel Cyber", email = "nsh531@gmail.com"}
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the package runtime version synchronized

Publishing this metadata as version 1.0.0 leaves adpentest.__version__ at the pre-existing value 0.1.0, so installed-package diagnostics and consumers that inspect the conventional runtime version report a different release from importlib.metadata. Update the package constant from the same version source or derive it from distribution metadata.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +473 to +476
domain,
dc_fqdn,
mode,
timeout,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve dry-run mode in parallel tool execution

When the documented ThreadedExecutor.parallel_tool_execution() helper is called with mode="dry-run", these positional arguments bind domain to execute_ad_tool()'s mode parameter and dc_fqdn to its timeout; the requested dry-run check is therefore bypassed and an external tool can be launched before the invalid timeout value raises an exception. Submit the arguments by keyword in the order required by execute_ad_tool().

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
qname = f"{srv_prefix}.{domain}"
try:
print(f"[VERBOSE] [detect_dcs_via_dns_srv] Resolving SRV record: {qname}", file=sys.stderr, flush=True)
answers = GLOBAL_DNS_CONFIG.resolver.resolve(qname, "SRV")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Route SRV queries through the DNS fallback wrapper

When the configured system or custom resolver times out or returns NoNameservers, this direct resolver call is caught as an empty SRV result and never invokes DNSConfig._resolve_with_fallback(). Consequently DC discovery can report no controllers even when the configured public fallback can resolve the SRV records, unlike the A/PTR lookups and the documented centralized fallback behavior; use GLOBAL_DNS_CONFIG.resolve() here as well.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py

if info.naming_contexts:
dc.ldap_info["namingContexts"] = [str(nc) for nc in info.naming_contexts]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Read the default naming context from DsaInfo.other

Whenever the LDAP connection succeeds, ldap3's DsaInfo stores the AD-specific defaultNamingContext value in info.other rather than exposing a default_naming_context attribute. This access therefore raises AttributeError, which the broad handler converts into None, so LDAP never confirms a DC or discovers its domain for IP-based targets; the later forest/domain functional-level attribute accesses have the same problem and should also read their RootDSE keys from info.other.

Useful? React with 👍 / 👎.

Add patterns for:
- output.json: Test framework output files
- debug.log and *.log: Debug and log files from execution
- .DS_Store: macOS system files

These are temporary files generated during testing and shouldn't be
committed to the repository.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 19c10218d7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread adpentest/core.py
Comment on lines +1616 to +1618
parts = target.split(".")
if len(parts) > 2:
domain_candidates.append(".".join(parts[-2:]))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep SRV discovery within the confirmed domain

When the target is a child AD domain such as child.corp.local, appending its final two labels also queries corp.local. Root-domain SRV answers are then merged into dc_map, and the later answer can overwrite detected_domain, causing active tool execution to contact root-domain controllers and use the wrong domain even though only the child domain was confirmed. Do not infer a broader DNS zone this way; use the explicit target or an LDAP-derived naming context.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +1570 to +1573
for ip in sorted(scan_ips, key=lambda v: int(ipaddress.ip_address(v))):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(1.0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound the subnet sweep's total runtime

When no DC exists and port 88 is silently filtered, this loop checks every address serially with a one-second timeout. Because /24, /23, and /22 are rescanned cumulatively, one target can incur roughly 1,780 connection timeouts—close to 30 minutes—regardless of the caller's --timeout value. Apply an overall deadline and parallelize or avoid rescanning addresses already checked at narrower prefixes.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +2973 to +2974
"-u", "''",
"-p", "''",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass empty SMB credentials without shell quoting

Whenever CrackMapExec is used for a null-session check, shell=False passes each "''" value literally as two apostrophes rather than as an empty username or password. The command therefore authenticates as the wrong account and misses valid anonymous access; the SMBMap branch repeats the same construction. Pass empty strings directly in the argument list.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +3253 to +3254
f"from adpentest.core import smtp_auth_test; "
f"result = smtp_auth_test('{host}', 'test', 'test', port=587); "

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require credentials before scheduling authentication attempts

In active mode this tool is considered available whenever Python exists, so every selected target receives an unsolicited SMTP login using the hard-coded test/test pair; the POP3 and IMAP branches repeat the same attempt. This cannot test operator-supplied credentials, and where a real test account is backed by the same directory across these protocols, the three failures can contribute directly to account lockout. Accept explicit credentials and skip authentication tools when none were supplied.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +2795 to +2797
signing_enforced = False
if "require" in process.stdout.lower():
signing_enforced = True

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve an unknown SMB-signing state

When port 445 is closed or filtered, or the NSE script cannot negotiate SMB, Nmap can complete without emitting any signing result. This code nevertheless leaves signing_enforced false and reports not-enforced, turning an unavailable measurement into a vulnerability finding. Require an open SMB service and an explicit signing field in the script output; otherwise return an unknown or error status.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +443 to +445
with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
futures = {
executor.submit(GLOBAL_DNS_CONFIG.resolve, qname, rdtype): (qname, rdtype)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply the requested timeout to parallel DNS queries

Callers can supply timeout to parallel_dns_resolution(), but each task invokes the global resolver with only the query name and type, so the argument has no effect. A caller extending the timeout for a slow internal resolver still receives empty results at the global deadline, while a caller requesting a shorter deadline may block longer than requested. Configure the submitted queries with this method's timeout or remove the misleading parameter.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +1303 to +1306
dc.open_ports = sorted(open_ports)
dc_ports_open = set(open_ports) & DC_SIGNATURE_PORTS

if len(dc_ports_open) >= 2:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require a DC-specific signal before classifying the host

Because both LDAP ports 389 and 636 are included in DC_SIGNATURE_PORTS, an ordinary OpenLDAP endpoint exposing plaintext and TLS satisfies the two-port threshold and is classified as a Domain Controller without Kerberos, Global Catalog, or a successful AD RootDSE probe. The orchestrator then treats this false positive as authoritative and can restrict tool execution to it. Require port 88, a Global Catalog port, or LDAP confirmation before adding the host to the DC map.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +916 to +920
"powershell_smb_enum": [
"powershell",
"pwsh",
"powershell.exe",
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate Windows-native PowerShell tools by platform

On Linux or macOS systems with PowerShell Core installed, discover_tools() marks this Windows-native tool as available merely because pwsh exists, and active scans subsequently execute Get-SmbShare; the AD recon entry similarly loads the Windows-only System.DirectoryServices.ActiveDirectory API. These commands predictably fail outside Windows and are reported as usable tools until execution. Gate these entries on platform.system() == "Windows" or verify the required cmdlet/API rather than only the shell binary.

Useful? React with 👍 / 👎.

…tial logging

- SEC-001: Refactored hardcoded empty credentials in LDAP connections
  Changed from: Connection(server, user="", password="", authentication="ANONYMOUS")
  Changed to: Connection(server, authentication="ANONYMOUS")
  Fixes 3 instances in enum_ldap(), enum_ldap_policy(), and SPNEnumerator.enumerate()

- SEC-002: Removed sensitive data logging from credential testing
  Removed username exposure in 9 logging statements
  Replaced credential-revealing logs with generic status messages
  Examples:
    - "Found credential: {username}" → "Credential test succeeded"
    - "SMTP auth SUCCESS for {username}" → "SMTP authentication successful"

- Added Exchange/O365 integration with nmap service detection
- Added advanced nmap functions for AD reconnaissance (version detection, vulnerability scanning, UDP discovery)
- Updated version to 1.0.1 in pyproject.toml
- Created CHANGELOG.md with detailed v1.0.1 and v1.0.0 release notes

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 62309b6796

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread adpentest/core.py
Comment on lines +3097 to +3100
"adpentest.core",
"--enum-windows",
host,
"/usr/share/wordlists/seclists/Usernames/Names/names.txt",
"--timeout", str(120),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Invoke a supported enum_windows_py entry point

Whenever enum_windows_py is scheduled, this child command passes the undefined --enum-windows option and supplies the host positionally even though build_parser() requires --target; when a domain exists it also adds the undefined --domain option. argparse therefore exits with status 2 before WindowsEnumerate can run, making this newly advertised tool fail on every execution.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +936 to +940
"AS_REP_roast": [
"python3",
"python",
"python.exe",
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Verify Impacket rather than the Python executable

On a normal installation without Impacket, find_executable() still marks AS_REP_roast as available because it finds the Python interpreter; kerberoast repeats the same mapping immediately below. Since available tools bypass auto_install_tool(), the PIP_PACKAGES entries for Impacket are never used and active executions fail with No module named impacket instead of installing or reporting the tools as unavailable.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
results = []

for host in live_host_list:
targets_for_tools = [ip for ip in live_host_list if ip in dc_ips] or live_host_list

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retain non-DC targets for email tools

When a target host is not itself a DC but DC discovery finds another controller—for example, a mail server in an AD domain—this expression discards the original and all other live hosts and keeps only DC IPs. The subsequent loop schedules every tool, including the new SMTP/POP3/IMAP discovery and authentication tools, against that DC-only list, so the advertised email checks never probe the requested mail endpoint.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +1678 to +1681
fp = detect_dc_via_port_fingerprint(ip, timeout=timeout)
if fp:
dc.open_ports = fp.open_ports
dc.services = fp.services

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Merge fingerprint confidence into SRV-discovered DCs

When an SRV record discovers a DC that is not one of the target's resolved IPs, its confidence remains at the DCInfo default of zero. This post-processing fingerprint can positively identify that host and copies its ports and services, but discards fp.confidence and fp.detection_methods; the confirmed controller is consequently reported at 0% confidence and sorted behind weaker candidates when selecting primary_dc_ip.

Useful? React with 👍 / 👎.

Major Features:
- Kerberos exploitation: Golden/Silver Ticket generation, delegation abuse (S4U, RBCD)
- Domain trust exploitation: Cross-domain/forest compromise, trust abuse automation
- ADCS exploitation: ESC1-9 vulnerability detection and certificate-based attacks
- Coercion & NTLM relay: PetitPotam, PrinterBug, relay chain automation
- Privilege escalation: ACL abuse, GPO attacks, delegation chains
- Persistence: SID History, DSRM, DCShadow, Golden GMSA, skeleton key

New modules:
- adpentest/kerberos_exploitation.py (KerberosExploitation class)
- adpentest/trust_abuse.py (TrustAbuseEngine class)
- adpentest/adcs_exploitation.py (ADCSExploitation class)
- adpentest/coercion_relay.py (CoercionAndRelayEngine class)
- adpentest/privesc_chains.py (PrivilegeEscalationEngine class)
- adpentest/persistence.py (PersistenceEngine class)

Added 21 new exploitation tools to AD_TOOLS registry.
Coverage: Kerberos forging, cross-forest takeover, certificate attacks,
coercion automation, ACL abuse, multi-stage persistence.

Not covered by PurpleKnight/PingCastle:
- Full exploitation pipeline with automatic chaining
- Kerberos ticket forgery and delegation abuse
- Certificate-based privilege escalation
- Advanced persistence mechanisms
- Cross-forest domain compromise

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY
Merged all exploitation classes into single core.py file:
- KerberosExploitation (Golden/Silver Tickets, delegation abuse)
- TrustAbuseEngine (domain trust exploitation)
- ADCSExploitation (ESC1-9 certificate attacks)
- CoercionAndRelayEngine (PetitPotam, PrinterBug, NTLM relay)
- PrivilegeEscalationEngine (ACL abuse, GPO, delegation chains)
- PersistenceEngine (SID History, DSRM, DCShadow, Golden GMSA)

Maintains monolithic single-file architecture per project design.
All 21 exploitation tools remain in AD_TOOLS registry.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3c146ed07e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread adpentest/core.py
Comment on lines +66 to +69
"golden_ticket",
"silver_ticket",
"asrep_roast_accelerated",
"delegation_abuse",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Wire the exploitation tools into discovery and execution

Every v1.0.2 tool added here occurs only in AD_TOOLS; none has an EXECUTABLES mapping, auto-install mapping, build_ad_command() branch, or implementation module elsewhere in the repository. Consequently find_executable() checks an empty candidate list, discover_tools() always marks all 20 tools unavailable, and the execution pipeline skips the release's advertised exploitation and persistence capabilities entirely.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py Outdated
Comment on lines +158 to +159
username="",
password="",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Authenticate after constructing the SMB connection

Whenever WindowsEnumerate.enum_smb() runs with Impacket installed, SMBConnection rejects the unsupported username and password constructor keywords before any connection or login occurs. The broad handler then reports null_session=False and share enumeration never runs, even for servers that permit anonymous access; construct the connection with its supported parameters and explicitly call login("", "") before listing shares.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +1467 to +1469
dc.fqdn = ldap_dns_hostname
dc.fqdn_source = "ldap-dnsHostName-unverified"
print(f"[VERBOSE] [resolve_dc_fqdn] FQDN set from LDAP dnsHostName: {dc.fqdn} (DNS verification failed, using anyway)", file=sys.stderr, flush=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fall back to the DC IP when FQDN verification fails

When LDAP returns a dnsHostName that the configured resolver cannot resolve back to this DC—for example because of a split-DNS outage—this branch still stores the unverified name and returns. Later command builders prefer dc_fqdn over the known reachable IP, so BloodHound, Certipy, and LDAP tools fail DNS resolution instead of using the advertised IP fallback; the PTR and constructed-name branches repeat the same behavior.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
ps_script = (
f'[System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.ActiveDirectory") | Out-Null; '
f'try {{ '
f'$forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest(); '

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Query the requested forest instead of the local forest

On a Windows runner joined to a different forest from the scan target, GetCurrentForest() succeeds and enumerates the runner's own forest, so the domain_target-based fallback never executes. The result is out-of-scope network activity and incorrect recon data for the requested target; construct a DirectoryContext for domain_target before querying the forest rather than consulting the current machine context first.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
Comment on lines +2015 to +2017
if system == "Windows":
install_dir = Path.home() / "AppData" / "Local" / "bin"
executable_name = "kerbrute.exe"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add the Windows Kerbrute install directory to PATH

On Windows, auto-install downloads kerbrute.exe into %USERPROFILE%\AppData\Local\bin, but bootstrap_environment() never adds that directory to PATH. The subsequent find_executable("kerbrute_userenum") therefore returns None unless the user had independently configured this nonstandard directory, causing a successful download to be reported as an installation failure and leaving Kerbrute unavailable.

Useful? React with 👍 / 👎.

Consolidated SMB enumeration with robust multi-method fallback:
- Method 1: Impacket null session (primary)
- Method 2: PowerShell HTTPS WinRM with SkipCACheck/SkipCNCheck
- Method 3: PowerShell Kerberos authentication
- Method 4: Invoke-Command remote execution

Each method returns structured JSON with success status and method name.
Automatic fallback on connection/authentication failure ensures broad Windows compatibility.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY
Replaced Impacket null session method with pure PowerShell-based fallback chain:
- Method 1: PowerShell HTTPS WinRM (primary, with SkipCACheck/SkipCNCheck)
- Method 2: PowerShell Kerberos authentication
- Method 3: PowerShell Invoke-Command remote execution

This eliminates external Impacket dependency while maintaining robust SMB enumeration
across diverse Windows authentication scenarios. All methods pure Python subprocess calls
to PowerShell with JSON parsing for results.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY
Implements asrep_roast_enum() function for AS-REP roasting using pyasn1:
- LDAP query for users with UF_DONT_REQUIRE_PREAUTH flag
- Kerberos AS-REQ/AS-REP protocol implementation (pure Python)
- Socket-based KDC communication (TCP port 88)
- Hash extraction in Hashcat/John format
- No Impacket dependencies, licensed for reuse

Algorithm adapted from impacket.examples.GetNPUsers with original design
by Alberto Solino (@agsolino). Pure Python implementation for:
- AS-REQ construction without pre-auth
- AS-REP parsing and TGT extraction
- Parallel roasting of multiple users via ThreadedExecutor

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4b8c42dcd9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread adpentest/core.py
Comment on lines +4231 to +4232
if args.dns_server:
dns_servers = [s.strip() for s in args.dns_server.split(",")]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Apply custom DNS before resolving the target

When an internal FQDN is resolvable only through --dns-server, the CLI first calls socket.gethostbyname() using the system resolver and returns on gaierror before reaching this parsing block. Even without that preflight, run() later resolves the target through socket.getaddrinfo() rather than GLOBAL_DNS_CONFIG, so the documented --target corp.local --dns-server ... workflow cannot start unless the system DNS already knows the target; configure and use the selected resolver for initial target resolution.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
parser.add_argument(
"--dns-timeout",
type=float,
default=3.0,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Let DNS_TIMEOUT provide the default timeout

When --dns-timeout is omitted, this argparse default still supplies 3.0, which main() passes through run() to DNSConfig as an explicit non-None value. Consequently a valid integer DNS_TIMEOUT environment setting may affect the import-time resolver but is overwritten before the assessment queries run, contrary to the documented CLI > environment > default priority; use an absent-value default here and apply 3.0 only after checking the environment.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
@@ -1290,43 +3986,80 @@ def run(
for item in dns_records
}

# Enrich DC hostnames from reverse DNS

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Rebuild the DC report after hostname enrichment

When a DC is found by port fingerprinting without an LDAP/SRV hostname but reverse DNS later succeeds, this block updates the live DCInfo only after dc_report was serialized earlier. The returned dc_detection.domain_controllers therefore still reports hostname: null even though the same run discovered and stored the hostname; rebuild the report after enrichment or defer serialization until the return value is assembled.

Useful? React with 👍 / 👎.

…ut Impacket

Implemented SMBEnumerator class with pure PowerShell operations:

**Core Features:**
1. enumerate_shares() - Detect all SMB shares and test permissions (READ/WRITE/NO ACCESS)
2. list_directory() - Recursive directory listing with depth control
3. search_files() - Pattern-based file search across share hierarchies
4. download_file() - Retrieve files from remote shares
5. upload_file() - Push files to remote shares with write access
6. delete_file() - Remove files from accessible shares
7. execute_command() - Remote command execution via Invoke-Command/WMI

**Technical Implementation:**
- Pure PowerShell scripts executed via subprocess (no Impacket SMB library)
- Permission testing via file operations (Create/Write/Read attempts)
- UNC path handling for all share operations
- JSON result parsing for PowerShell output
- Comprehensive error logging to stderr with [VERBOSE] prefix
- Timeout management (default 30s, configurable per operation)

**Replaces SMBMap Functionality:**
- Original: impacket.smbconnection for SMB protocol handling
- Replacement: PowerShell Get-ChildItem, Copy-Item, Remove-Item, Invoke-Command
- Result: Full feature parity without external Impacket dependency

Adapted from SMBMap v1.10.8 (ShawnDEvans/smbmap GitHub) with algorithm ported
to pure Python/PowerShell. Licensed for adaptation and reuse.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY
Implemented pure-Python exploitation functions for critical AD attacks:

**1. golden_ticket_gen()** - TGT forgery
   - Requires: domain SID + krbtgt NTLM hash
   - Impact: Full domain compromise, lateral movement to all systems
   - Usage: Privilege escalation after DC compromise or DCSync

**2. silver_ticket_gen()** - Service ticket forgery
   - Requires: target service/computer NTLM hash
   - Impact: Authenticate as any user to specific service (CIFS, HOST, LDAP, MSSQL)
   - Usage: Targeted service compromise without full domain access

**3. constrained_delegation_abuse()** - KCD exploitation detection
   - Scans: LDAP for msDS-AllowedToDelegateTo attributes
   - Detects: S4U2Self + S4U2Proxy abuse opportunities
   - Impact: Leverage delegated user permissions for privilege escalation
   - Output: Exploitation step-by-step for each found KCD misconfiguration

**4. adcs_esc_scanner()** - Certificate Services attack detection
   - Scans: ADCS templates for ESC1, ESC3 vulnerabilities
   - Detects: Enrollment flags, manager approval bypass, client auth EKU abuse
   - Impact: Certificate-based domain authentication and privilege escalation
   - Output: Vulnerable templates with exploitation paths

**5. acl_privilege_escalation()** - ACL-based escalation detection
   - Scans: LDAP ACLs on high-value targets (DA, EA, DC, krbtgt)
   - Detects: GenericAll, GenericWrite, WriteDacl, WriteOwner misconfigurations
   - Impact: Escalate privileges via permission modifications
   - Output: Targets with exploitable ACLs and attack vectors

**Technical Features:**
- Pure Python LDAP enumeration via ldap3
- No external exploitation frameworks required
- Comprehensive error handling and logging
- Actionable exploitation instructions in output
- Integration-ready for tool execution pipeline

**Added Imports:**
- hashlib (md5, sha1 for future ticket generation)
- hmac, struct, binascii (Kerberos protocol support)

These 5 tools provide major exploitation chains for:
- Post-compromise domain takeover (golden ticket)
- Lateral movement across services (silver ticket, delegation abuse)
- Certificate-based escalation (ADCS ESC)
- Permission-based privilege escalation (ACL abuse)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmXsxtqyCjJApeagPGhAQY

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 64e3658a9e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread adpentest/__main__.py
p.add_argument("--scope-confirmed", action="store_true")
args = p.parse_args()
print(json.dumps(run(args.target, args.mode, args.scope_confirmed), indent=2))
raise SystemExit(main())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Validate authorization before invoking CLI probes

When python -m adpentest is invoked without --scope-confirmed, this new entry-point path calls main(), which scans all 13 AD ports and may perform an LDAP bind before run() reaches Scope.validate(). The supposedly mandatory authorization check therefore does not prevent network activity; validate the flag before these preflight probes or move them behind Scope.validate().

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
print(f"[VERBOSE] [asrep_roast_enum] Querying LDAP for UF_DONT_REQUIRE_PREAUTH users on {ldap_server}", file=sys.stderr, flush=True)

server = Server(ldap_server, get_info=None)
conn = Connection(server, auto_bind=True, bind_flow=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove the unsupported LDAP connection keyword

Whenever asrep_roast_enum() reaches its LDAP setup, ldap3's Connection constructor rejects the unknown bind_flow keyword with TypeError, which the broad handler converts into an LDAP-query failure before any users are searched. The delegation, ESC, and ACL scanners repeat the same constructor call, so all four newly added helpers fail identically when invoked.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
# Resolve timeout: CLI arg > Environment variable > default
if timeout is None:
env_timeout = os.environ.get("DNS_TIMEOUT")
self.timeout = int(env_timeout) if env_timeout else 3

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Parse documented fractional DNS timeouts

When DNS_TIMEOUT is set to the documented value 5.0, int("5.0") raises ValueError while the module-level GLOBAL_DNS_CONFIG is being initialized, so the package cannot even be imported and the CLI never reaches argument parsing. Parse the environment value as a float, matching --dns-timeout, or document and validate an integer-only format.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
except Exception as e:
print(f"[VERBOSE] [SMBEnumerator.execute_command] Error: {str(e)}", file=sys.stderr, flush=True)

return result.stderr if result else "Command execution failed"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Initialize the command result before handling failures

When SMBEnumerator.execute_command() cannot launch PowerShell or the subprocess times out, the exception handler completes and this return then reads result before it has ever been assigned, replacing the original operational error with UnboundLocalError. This occurs on every non-Windows host without powershell.exe and on Windows whenever process creation fails; return a fixed failure message from the exception path or initialize result first.

Useful? React with 👍 / 👎.

Comment thread adpentest/core.py
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(timeout)
result = s.connect_ex((ip, 445))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Perform SMB share enumeration instead of a port check

Whenever port 445 accepts a TCP connection, enumerate_smb_shares() reports smb-accessible without negotiating SMB, attempting the documented null session, or returning any share names. Consequently an arbitrary service on port 445 is treated as SMB and callers of this newly exported share-enumeration API cannot distinguish an accessible share from a merely open socket; perform an SMB session and list shares before reporting success.

Useful? React with 👍 / 👎.

@netanelcyber
netanelcyber merged commit 7e17cf3 into main Sep 2, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants