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

stamp-subcategory.py (3647B)


      1 #!/usr/bin/env python3
      2 """
      3 Stamp a `subcategory` onto Active Directory sheets so the category page can
      4 group its 130+ entries instead of listing them flat.
      5 
      6 The grouping is not invented — it is the taxonomy the source vault already
      7 uses. Every sheet carries a `source: "vault:…"` path, and the AD-Attack set
      8 is filed under Category-One … Category-Ten, the ADCS work under
      9 ACL-ESC-Techniques, the ticket work under Kerberos. This maps those source
     10 folders to kill-chain-ordered names; anything loose in ActiveDirectory/ is
     11 tooling and recon.
     12 
     13 Idempotent: re-running rewrites the same `subcategory:` line. Only sheets
     14 under src/content/sheets/active-directory are touched.
     15 
     16 Usage: python3 scripts/stamp-subcategory.py
     17 """
     18 import os, re
     19 
     20 REPO = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
     21 AD = os.path.join(REPO, "src", "content", "sheets", "active-directory")
     22 
     23 # Source folder → subcategory. The AD-Attack categories collapse a couple of
     24 # ways: Category-Four (ESC attacks) merges with the ACL-ESC-Techniques folder,
     25 # and Category-Two (tickets/delegation) merges with the Kerberos folder, so a
     26 # reader sees one "ADCS & Certificates" section rather than two half-sections.
     27 def subcat_for(source):
     28     s = source.replace("vault:", "")
     29     m = re.search(r"AD-Attack/Category-(\w+)", s)
     30     if m:
     31         return {
     32             "One": "Credential Access",
     33             "Two": "Kerberos & Delegation",
     34             "Three": "ACL Abuse",
     35             "Four": "ADCS & Certificates",
     36             "Five": "Domain Controller Attacks",
     37             "Six": "Privilege & Group Abuse",
     38             "Seven": "Lateral Movement",
     39             "Eight": "Persistence",
     40             "Nine": "Trust Abuse",
     41             "Ten": "Advanced & Post-Exploitation",
     42         }.get(m.group(1), "Advanced & Post-Exploitation")
     43     if "ACL-ESC-Techniques" in s:
     44         return "ADCS & Certificates"
     45     if "/Kerberos/" in s:
     46         return "Kerberos & Delegation"
     47     return "Tooling & Recon"
     48 
     49 def read_source(fm):
     50     m = re.search(r'^source:\s*"?(?:source:\s*)?"?([^"\n]+)"?\s*$', fm, flags=re.M)
     51     # Some early files double-wrote the key ("source: source: \"vault:…\"");
     52     # this tolerates both. Fall back to a looser grab of the vault path.
     53     if m and "vault:" in m.group(0):
     54         vm = re.search(r"vault:[^\"\n]+", m.group(0))
     55         if vm:
     56             return vm.group(0)
     57     vm = re.search(r"vault:[^\"\n]+", fm)
     58     return vm.group(0) if vm else ""
     59 
     60 def main():
     61     changed = 0
     62     counts = {}
     63     for f in sorted(os.listdir(AD)):
     64         if not f.endswith(".md"):
     65             continue
     66         path = os.path.join(AD, f)
     67         txt = open(path, encoding="utf-8").read()
     68         if not txt.startswith("---"):
     69             continue
     70         end = txt.find("\n---", 3)
     71         if end == -1:
     72             continue
     73         fm, body = txt[:end], txt[end:]
     74 
     75         source = read_source(fm)
     76         sub = subcat_for(source) if source else "Tooling & Recon"
     77         counts[sub] = counts.get(sub, 0) + 1
     78 
     79         line = f'subcategory: "{sub}"'
     80         if re.search(r"^subcategory:.*$", fm, flags=re.M):
     81             fm = re.sub(r"^subcategory:.*$", line, fm, count=1, flags=re.M)
     82         else:
     83             # Insert right after the category line so frontmatter stays tidy.
     84             fm = re.sub(r"^(category:.*)$", r"\1\n" + line, fm, count=1, flags=re.M)
     85 
     86         new = fm + body
     87         if new != txt:
     88             open(path, "w", encoding="utf-8").write(new)
     89             changed += 1
     90 
     91     print(f"stamped {changed} AD sheets")
     92     for k in sorted(counts):
     93         print(f"  {k:32} {counts[k]}")
     94 
     95 if __name__ == "__main__":
     96     main()