Skip to content

feat(ldap): add ADCS collection support via CertiHound integration#1054

Open
0x0Trace wants to merge 6 commits intoPennyw0rth:mainfrom
0x0Trace:feature/adcs-collection
Open

feat(ldap): add ADCS collection support via CertiHound integration#1054
0x0Trace wants to merge 6 commits intoPennyw0rth:mainfrom
0x0Trace:feature/adcs-collection

Conversation

@0x0Trace
Copy link

@0x0Trace 0x0Trace commented Dec 31, 2025

Adds Active Directory Certificate Services (ADCS) enumeration to BloodHound collection, enabling security assessors to collect PKI-related attack paths alongside traditional AD data.

Key Features:

  • New -c ADCS collection method for certificate template and CA enumeration
  • ADCS data exported in BloodHound CE v6 format for native import
  • Optional dependency - gracefully skips if CertiHound not installed
  • ADCS-only mode works without bloodhound-python or DNS resolution

Motivation

ADCS misconfigurations (ESC1-ESC8) are among the most prevalent and impactful Active Directory attack vectors. Currently, NetExec users must run separate tools to collect this data. This integration brings ADCS collection directly into the existing --bloodhound workflow, producing a single unified zip file for BloodHound CE import.

Changes

File Change
pyproject.toml Add certihound as optional dependency (netexec[adcs])
nxc/protocols/ldap/proto_args.py Add ADCS to collection methods help text
nxc/protocols/ldap.py Add adcs to valid/all methods, implement _collect_adcs_for_bloodhound()

Installation

# New installations
pip install netexec[adcs]

# Existing installations
pip install certihound

Usage

# ADCS only (fast, no DNS required)
nxc ldap 192.168.1.10 -u user -p password --bloodhound -c ADCS

# Full collection including ADCS
nxc ldap 192.168.1.10 -u user -p password --bloodhound -c All

# Mixed collection
nxc ldap 192.168.1.10 -u user -p password --bloodhound -c Group,Trusts,ADCS

Example Output

LDAP        192.168.1.10    389    DC01    [*] Windows Server 2019 (name:DC01) (domain:corp.local)
LDAP        192.168.1.10    389    DC01    [+] corp.local\user:password
LDAP        192.168.1.10    389    DC01    [*] Resolved collection methods: adcs
LDAP        192.168.1.10    389    DC01    [*] Collecting ADCS data (CertiHound)...
LDAP        192.168.1.10    389    DC01    [*] Found 15 certificate templates
LDAP        192.168.1.10    389    DC01    [*] Found 2 Enterprise CAs
LDAP        192.168.1.10    389    DC01    [*] Compressing output into corp.local_bloodhound.zip

Screenshots

Terminal Output

image

ESC1 Vulnerability Detected

image

ESC4 Vulnerability Detected

image

BloodHound Output

The zip file includes standard BloodHound files plus ADCS data:

File Description
certtemplates.json Certificate templates with enrollment permissions
enterprisecas.json Enterprise CA configurations
rootcas.json Root CA certificates
ntauthstores.json NTAuth store entries
aiacas.json AIA CA entries

Test Plan

  • ADCS-only collection (-c ADCS) works without bloodhound-python
  • Combined collection (-c All) includes ADCS data
  • Mixed methods (-c Group,ADCS) work correctly
  • Graceful skip when CertiHound not installed
  • Output imports successfully into BloodHound CE
  • Kerberos authentication supported

Notes

  • I'm the author of CertiHound and will maintain this integration
  • CertiHound is MIT licensed and available on PyPI
  • No breaking changes to existing functionality
  • Follows existing NetExec code patterns and style

Related


Checklist:

  • Code follows project style guidelines
  • No breaking changes to existing functionality
  • Optional dependency (doesn't affect users who don't need ADCS)
  • Tested against lab environment

Add Active Directory Certificate Services (ADCS) enumeration to the
BloodHound collection workflow using CertiHound library.

Changes:
- Add 'adcs' to valid collection methods in resolve_collection_methods()
- Add CertiHound as optional dependency (pip install netexec[adcs])
- Implement _collect_adcs_for_bloodhound() for ADCS data collection
- Update bloodhound() to handle ADCS-only and combined collections
- Export ADCS data in BloodHound CE v6 format
@XiaoliChan
Copy link
Contributor

Consider this PR has DUP with this module

https://github.com/Pennyw0rth/NetExec/blob/main/nxc/modules/certipy-find.py

@0x0Trace
Copy link
Author

0x0Trace commented Dec 31, 2025

Consider this PR has DUP with this module

https://github.com/Pennyw0rth/NetExec/blob/main/nxc/modules/certipy-find.py

Thanks for flagging this! I see how it might look like a duplicate at first glance, but they actually do different things:
certipy-find is for quick vulnerability scanning - it shows you what's misconfigured right in the terminal.
This PR is about getting ADCS data into BloodHound CE so you can see attack paths in the graph. It uses CertiHound to export in the right format for BloodHound ingestion.

@XiaoliChan
Copy link
Contributor

XiaoliChan commented Dec 31, 2025

Good... my idea is, because now there are lots of Certi* stuff, so I think we can keep using certipy, and check there are any parser in CertiHound can convert certipy json format result to BloodhoundCE accept format.

It's a good idea? how do you think?

If this works, then you can move certipy-find module to core func like --certipy and also add adcs in --bloodhound

@NeffIsBack
Copy link
Member

Thanks for the PR.

I agree with @XiaoliChan, there are a lot of ADCS modules now and we have to make sure that they don't do duplicate functionality. As far as I know certipy is also capable of outputting bloodhound data so we can just use that.

@NeffIsBack
Copy link
Member

@0x0Trace is certihound open source? Can't find the repo

@0x0Trace
Copy link
Author

@0x0Trace is certihound open source? Can't find the repo

Hello! Yes it is open source you can find it here : https://pypi.org/project/certihound/

@0x0Trace
Copy link
Author

Thanks for the suggestions! I looked into this , the issue is that certipy's JSON output doesn't contain the data structures BloodHound CE needs (like node/edge relationships, proper GUIDs, etc.). CertiHound isn't just a format converter - it collects the data differently to build the graph relationships BloodHound CE expects.

Also worth noting: certipy v5.x actually removed the -bloodhound flags entirely, so there's no BloodHound output in current certipy at all.
Happy to discuss other approaches if you have ideas!

@XiaoliChan
Copy link
Contributor

Hello! Yes it is open source you can find it here : https://pypi.org/project/certihound/

Great, please provide the source code on git repository, we can take a look TGT ^_^

@0x0Trace
Copy link
Author

Hello! Yes it is open source you can find it here : https://pypi.org/project/certihound/

Great, please provide the source code on git repository, we can take a look TGT ^_^

Of course i would appreciate it ! I just pushed it you can check it here : https://github.com/0x0Trace/Certihound

@XiaoliChan
Copy link
Contributor

Of course i would appreciate it ! I just pushed it you can check it here : https://github.com/0x0Trace/Certihound

Great job bro, BTW, Happy new year!

@0x0Trace
Copy link
Author

Of course i would appreciate it ! I just pushed it you can check it here : https://github.com/0x0Trace/Certihound

Great job bro, BTW, Happy new year!

Thank you ! Happy new year to you also :D

@0x0Trace
Copy link
Author

@NeffIsBack the source code for the library is here : https://github.com/0x0Trace/Certihound .Happy new year!

@NeffIsBack
Copy link
Member

@NeffIsBack the source code for the library is here : https://github.com/0x0Trace/Certihound .Happy new year!

Thanks and happy new year. I will take a look at it 👍
Fyi, both the "homepage" as well as the "repository" link of the pypi package are broken and point to the wrong/non-existing repository.

@0x0Trace
Copy link
Author

0x0Trace commented Jan 1, 2026

@NeffIsBack thanks for noticing it !It is fixed now

@0x0Trace
Copy link
Author

@NeffIsBack did you managed to take a look at it?Thanks again for your time

@NeffIsBack
Copy link
Member

@NeffIsBack did you managed to take a look at it?Thanks again for your time

Not fully, but I took a short peek. I am still torn between "it would be really cool to have ADCS collection" and "oh no, yet another dependency with sub dependencies that have to be managed".

The problem is that there are already certipy and bloodhound which both would be a great fit for such functionality, without much initial and long term work required for it to be integrated. If we decide to integrate your tool (which is really cool btw!) this should not be an optional dependency, but a normal one. Meaning, your tool as well as all sub dependencies must be checked and uploaded into the kali repositories. That is one of the reasons I am hesitating a bit to give it the approval, besides the normal dependency management that gets harder and harder the more there are (e.g. upgrading to new python versions is often a pain).

One question: what is the reason ldap3 is used as a baseline for the tool? Why not impacket? And I have seen you have build an adapter for impacket connections, does this mean impacket ldap is used at the end or is this just used to create the ldap3 connection for querying?

@0x0Trace
Copy link
Author

Hey @NeffIsBack, thanks for taking the time to look into this , I really appreciate the feedback.

Let me address the ldap3 vs impacket question directly:

The ImpacketLDAPAdapter doesn't actually use impacket's LDAP client for queries - it's just a thin translation layer (~100 lines). Here's how it works:

  1. CertiHound passes NetExec's existing self.search() method to the adapter
  2. All LDAP queries go through NetExec's existing impacket connection (no new connections)
  3. The adapter simply converts impacket-format results → ldap3-compatible format for CertiHound's parsers

Why ldap3 as the baseline? CertiHound needs to work standalone (CLI mode) without requiring the full impacket stack. But when integrated with NetExec, ldap3 is not needed at runtime - the adapter handles all the translation. This keeps it portable for other tools too.

Regarding dependencies for Kali packaging - I checked and the actual new dependencies CertiHound would bring to NetExec are ldap3, click, and pydantic, all of which are already packaged in Kali/Debian repos. No extra sub-dependencies that would need uploading.

I've also set it up as an optional dependency (pip install netexec[adcs]) so it doesn't bloat the base install.

Totally understand the hesitation around dependency management though - it's a real concern for maintainability. Happy to help with the Kali packaging process if this moves forward, or answer any other questions about the architecture.

@NeffIsBack
Copy link
Member

NeffIsBack commented Jan 26, 2026

The ImpacketLDAPAdapter doesn't actually use impacket's LDAP client for queries - it's just a thin translation layer (~100 lines). Here's how it works:

  1. CertiHound passes NetExec's existing self.search() method to the adapter
  2. All LDAP queries go through NetExec's existing impacket connection (no new connections)
  3. The adapter simply converts impacket-format results → ldap3-compatible format for CertiHound's parsers

Actually sounds pretty cool. The reason i asked is my long long term goal would be to fully remove ldap3 from the project and solely rely on impackets ldap implementation. However, this will probably not happen for the next few years because there are so many projects using ldap3 instead of impackets ldap, so this additional dependency doesn't matter for now.

Regarding dependencies for Kali packaging - I checked and the actual new dependencies CertiHound would bring to NetExec are ldap3, click, and pydantic, all of which are already packaged in Kali/Debian repos. No extra sub-dependencies that would need uploading.

That is good2know and definitely makes things much easier.

So i have talked a bit with the others and i think for now this (CertiHound) is the best and easiest way to collect ADCS data, thanks for your work! So please:

  • Add your project to the default dependencies in pyproject.toml file so it is automatically installed
  • Change the code so that it assumes that certihound is installed because it will be required as a dependency

@NeffIsBack NeffIsBack added the enhancement New feature or request label Jan 26, 2026
Move CertiHound from optional to default dependencies and remove
conditional import handling since it will always be installed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@0x0Trace
Copy link
Author

Hey @NeffIsBack , thanks for the green light! Really appreciate you and the team giving this a shot.

I've made the changes you requested:

  • Moved CertiHound to default dependencies in pyproject.toml
  • Removed the conditional import in ldap.py - the code now assumes CertiHound is installed

Pretty minimal diff, just a few lines each. Let me know if you want me to adjust anything else or if there's anything I missed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants