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

index.astro (11146B)


      1 ---
      2 import Base from '../../layouts/Base.astro';
      3 import SectionBanner from '../../components/SectionBanner.astro';
      4 import RecordRow from '../../components/RecordRow.astro';
      5 import CategoryNav from '../../components/CategoryNav.astro';
      6 import { CATEGORIES } from '../../lib/taxonomy';
      7 import type { CategoryDef } from '../../lib/taxonomy';
      8 import { sheetsByCategory, sheetHref, type Sheet } from '../../lib/sheets';
      9 import { url } from '../../lib/url';
     10 import pentestDownloads from '../../data/pentest-workflow-downloads.json';
     11 
     12 export async function getStaticPaths() {
     13   const groups = await sheetsByCategory();
     14   return CATEGORIES.map((c, i) => ({
     15     params: { category: c.slug },
     16     props: {
     17       cat: c,
     18       items: groups.find((g) => g.slug === c.slug)?.items ?? [],
     19       n: String(i + 1).padStart(2, '0'),
     20     },
     21   }));
     22 }
     23 interface Props { cat: CategoryDef; items: Sheet[]; n: string }
     24 const { cat, items, n } = Astro.props;
     25 
     26 /* Kill-chain order for the sub-sections. A sheet whose subcategory isn't
     27    listed falls to the end under its own name; a category with no
     28    subcategories at all (every domain except Active Directory today) renders
     29    one flat index, exactly as before. */
     30 const SUBCAT_ORDER = [
     31   'CPTS Attack Flow',
     32   'Companion Guides',
     33   'General CPTS Cheatsheets',
     34   'Credential Access',
     35   'Kerberos & Delegation',
     36   'ACL Abuse',
     37   'ADCS & Certificates',
     38   'Domain Controller Attacks',
     39   'Privilege & Group Abuse',
     40   'Lateral Movement',
     41   'Persistence',
     42   'Trust Abuse',
     43   'Advanced & Post-Exploitation',
     44   'Tooling & Recon',
     45 ];
     46 
     47 /* Within a section: numbered techniques first (attack-2 before attack-40,
     48    esc1 before esc10 — numeric, not lexical), then everything else A→Z. The
     49    prefix rank keeps all attack-* together, then all esc-*, and so on. */
     50 const PFX_RANK: Record<string, number> = { attack: 0, esc: 1, persist: 2, theft: 3, dpersist: 4 };
     51 function sortKey(entry: Sheet): [number, number, number, string] {
     52   const slug = entry.id.split('/').pop() ?? '';
     53   if (entry.data.order !== undefined) return [-1, 0, entry.data.order, slug];
     54   const m = slug.match(/^([a-z]+)-?(\d+)/);
     55   if (m && m[1] in PFX_RANK) return [0, PFX_RANK[m[1]], parseInt(m[2], 10), slug];
     56   return [1, 0, 0, (entry.data.title || slug).toLowerCase()];
     57 }
     58 function cmp(a: Sheet, b: Sheet) {
     59   const ka = sortKey(a), kb = sortKey(b);
     60   for (let i = 0; i < ka.length; i++) {
     61     if (ka[i] < kb[i]) return -1;
     62     if (ka[i] > kb[i]) return 1;
     63   }
     64   return 0;
     65 }
     66 
     67 const hasSubcats = items.some((s) => s.data.subcategory);
     68 type Group = { name: string; items: Sheet[] };
     69 let groups: Group[] = [];
     70 if (hasSubcats) {
     71   const buckets = new Map<string, Sheet[]>();
     72   for (const s of items) {
     73     const key = s.data.subcategory ?? 'Other';
     74     (buckets.get(key) ?? buckets.set(key, []).get(key)!).push(s);
     75   }
     76   const ordered = [
     77     ...SUBCAT_ORDER.filter((k) => buckets.has(k)),
     78     ...[...buckets.keys()].filter((k) => !SUBCAT_ORDER.includes(k)).sort(),
     79   ];
     80   groups = ordered.map((name) => ({ name, items: [...buckets.get(name)!].sort(cmp) }));
     81 }
     82 
     83 const byDifficulty = (level: string) => items.filter((s) => s.data.difficulty === level).length;
     84 const tools = new Set(items.flatMap((s) => s.data.tools));
     85 const bannerStats = [
     86   { label: 'Sheets', value: String(items.length).padStart(2, '0'), accent: true },
     87   ...(hasSubcats
     88     ? [{ label: 'Sections', value: String(groups.length).padStart(2, '0') }]
     89     : [{ label: 'Tools covered', value: String(tools.size).padStart(2, '0') }]),
     90   { label: 'Beginner / adv', value: `${byDifficulty('beginner')} / ${byDifficulty('advanced')}` },
     91 ];
     92 
     93 /* Section accents cycle the palette so the sub-headers read as a colour
     94    run down the page, the same key the masthead and marquee use. */
     95 const SECTION_ACCENTS = ['iris', 'foam', 'love', 'gold', 'rose', 'pine'];
     96 const downloads = cat.slug === 'pentest-workflow' ? pentestDownloads : [];
     97 const downloadBytes = downloads.reduce((sum, item) => sum + item.size, 0);
     98 const downloadSize = `${(downloadBytes / 1024 / 1024).toFixed(1)} MiB`;
     99 ---
    100 <Base title={`${cat.title} — DÆMON//SEC`} description={cat.blurb}>
    101   <SectionBanner n={n} label={cat.tag} title={cat.title} blurb={cat.blurb} accent={cat.accent} stats={bannerStats} />
    102 
    103   <div class="wrap">
    104     <nav class="breadcrumb" style="margin-top:1.6rem;">
    105       <a href={url('')}>~</a><span class="sep">/</span>
    106       <span style={`color: var(--${cat.accent})`}>{cat.tag.toLowerCase()}</span>
    107     </nav>
    108 
    109     <div style="margin:1.4rem 0 2rem;"><CategoryNav active={cat.slug} /></div>
    110 
    111     {items.length === 0 && (
    112       <div class="slab corners" style="margin:1.5rem 0 3rem; padding:1.2rem 1.3rem;">
    113         <p class="muted mono" style="font-size:0.85rem;">
    114           No sheets in this domain yet. More landing soon —
    115           <a class="accent-text" href={url('')}>back to the vault</a>.
    116         </p>
    117       </div>
    118     )}
    119 
    120     {items.length > 0 && !hasSubcats && (
    121       <div class="index" style="margin-bottom:3rem;">
    122         {items.map((entry, i) => (
    123           <RecordRow
    124             href={url(sheetHref(entry))}
    125             n={String(i + 1).padStart(2, '0')}
    126             title={entry.data.title}
    127             kind={entry.data.difficulty}
    128             accent={cat.accent}
    129             meta={entry.data.updated ?? entry.id.split('/').pop()}
    130           />
    131         ))}
    132       </div>
    133     )}
    134 
    135     {items.length > 0 && hasSubcats && (
    136       <div style="margin-bottom:3rem;">
    137         {groups.map((g, gi) => {
    138           const acc = SECTION_ACCENTS[gi % SECTION_ACCENTS.length];
    139           return (
    140             <section class="subcat" id={`s-${gi}`}>
    141               <div class="section-head" style="margin-bottom:0.9rem;">
    142                 <div>
    143                   <p class="eyebrow" style={`--acc: var(--${acc});`}>
    144                     <span class="eyebrow__n">{String(gi + 1).padStart(2, '0')}</span>
    145                     <span class="eyebrow__mark">^:</span>
    146                     <span>{g.items.length} {g.items.length === 1 ? 'sheet' : 'sheets'}</span>
    147                     <span class="eyebrow__rule" aria-hidden="true"></span>
    148                   </p>
    149                   <h2>{g.name}</h2>
    150                 </div>
    151               </div>
    152               <div class="index">
    153                 {g.items.map((entry, i) => (
    154                   <RecordRow
    155                     href={url(sheetHref(entry))}
    156                     n={String(i + 1).padStart(2, '0')}
    157                     title={entry.data.title}
    158                     kind={entry.data.difficulty}
    159                     accent={acc}
    160                     meta={entry.data.updated ?? entry.id.split('/').pop()}
    161                   />
    162                 ))}
    163               </div>
    164             </section>
    165           );
    166         })}
    167       </div>
    168     )}
    169 
    170     {downloads.length > 0 && (
    171       <section class="download-library" id="signed-downloads" aria-labelledby="signed-downloads-title">
    172         <div class="download-library__head">
    173           <div>
    174             <p class="eyebrow" style="--acc: var(--gold);">
    175               <span class="eyebrow__n">DL</span>
    176               <span class="eyebrow__mark">^:</span>
    177               <span>{downloads.length} signed files · {downloadSize}</span>
    178               <span class="eyebrow__rule" aria-hidden="true"></span>
    179             </p>
    180             <h2 id="signed-downloads-title">Signed attachment library</h2>
    181             <p class="muted download-library__lede">
    182               Every attachment referenced by the CPTS sheets is mirrored here. Each download has an individual
    183               SHA-256 checksum and detached OpenPGP signature; the combined manifest covers the complete set.
    184             </p>
    185           </div>
    186           <div class="download-library__manifest">
    187             <a class="btn" href={url('downloads/pentest-workflow/SHA256SUMS')} download>SHA256SUMS</a>
    188             <a class="btn btn--ghost" href={url('downloads/pentest-workflow/SHA256SUMS.asc')} download>Manifest signature</a>
    189           </div>
    190         </div>
    191 
    192         <div class="download-index" role="list">
    193           {downloads.map((item, i) => (
    194             <article class="download-row corners" role="listitem">
    195               <span class="download-row__n">{String(i + 1).padStart(2, '0')}</span>
    196               <div class="download-row__main">
    197                 <a class="download-row__file" href={url(item.href)} download>{item.name}</a>
    198                 <span class="download-row__hash" title={item.sha256}>{item.sha256}</span>
    199               </div>
    200               <span class="download-row__size">{item.sizeLabel}</span>
    201               <div class="download-row__proof">
    202                 <a href={url(item.checksumHref)} download>SHA-256</a>
    203                 <a href={url(item.signatureHref)} download>GPG</a>
    204               </div>
    205             </article>
    206           ))}
    207         </div>
    208       </section>
    209     )}
    210   </div>
    211 </Base>
    212 
    213 <style>
    214   .subcat + .subcat { margin-top: clamp(2.2rem, 4.5vw, 3.4rem); }
    215   .download-library {
    216     margin: clamp(2.8rem, 6vw, 5rem) 0 4rem;
    217     scroll-margin-top: 6rem;
    218   }
    219   .download-library__head {
    220     display: grid;
    221     gap: 1.2rem 2rem;
    222     align-items: end;
    223     margin-bottom: 1.2rem;
    224     padding-bottom: 1rem;
    225     border-bottom: 1px solid var(--rule);
    226   }
    227   .download-library__head h2 {
    228     margin-top: 0.35rem;
    229     font-family: var(--font-display);
    230     font-size: clamp(1.7rem, 4vw, 2.7rem);
    231     letter-spacing: -0.035em;
    232   }
    233   .download-library__lede { max-width: 72ch; margin-top: 0.55rem; }
    234   .download-library__manifest { display: flex; flex-wrap: wrap; gap: 0.55rem; }
    235   .download-index { display: grid; gap: 0.45rem; }
    236   .download-row {
    237     position: relative;
    238     display: grid;
    239     grid-template-columns: 2.2rem minmax(0, 1fr) auto;
    240     gap: 0.55rem 0.9rem;
    241     align-items: center;
    242     border: 1px solid var(--rule);
    243     background: color-mix(in srgb, var(--surface) 76%, transparent);
    244     padding: 0.75rem 0.85rem;
    245   }
    246   .download-row__n,
    247   .download-row__size,
    248   .download-row__hash,
    249   .download-row__proof {
    250     font-family: var(--font-mono);
    251     font-size: var(--step-micro);
    252     letter-spacing: var(--track-micro);
    253     text-transform: uppercase;
    254   }
    255   .download-row__n { color: var(--gold); }
    256   .download-row__main { min-width: 0; }
    257   .download-row__file {
    258     display: block;
    259     overflow-wrap: anywhere;
    260     color: var(--fg);
    261     font-weight: 650;
    262   }
    263   .download-row__file:hover { color: var(--love); }
    264   .download-row__hash {
    265     display: block;
    266     max-width: 100%;
    267     margin-top: 0.2rem;
    268     overflow: hidden;
    269     color: var(--fg-faint);
    270     text-overflow: ellipsis;
    271     text-transform: none;
    272     white-space: nowrap;
    273   }
    274   .download-row__size { color: var(--fg-muted); white-space: nowrap; }
    275   .download-row__proof {
    276     grid-column: 2 / -1;
    277     display: flex;
    278     gap: 0.75rem;
    279   }
    280   .download-row__proof a { color: var(--iris); }
    281   .download-row__proof a:last-child { color: var(--foam); }
    282   @media (min-width: 720px) {
    283     .download-library__head { grid-template-columns: minmax(0, 1fr) auto; }
    284     .download-library__manifest { justify-content: flex-end; }
    285     .download-row { grid-template-columns: 2.2rem minmax(0, 1fr) 5.5rem 8rem; }
    286     .download-row__proof { grid-column: auto; justify-content: flex-end; }
    287   }
    288 </style>