paths.nix (10716B)
1 # modules/features/pentest/paths.nix — ~/pentesting, the variables that point 2 # into it, and `htbpaths` to find your way around. 3 # 4 # ~/pentesting/ the arsenal, one directory per job 5 # privesc/ creds/ ad/ pivoting/ recon/ shells/ webshells/ scripts/ 6 # exploits/ sharpcollection/ wordlists/ -> read-only, from the Nix store 7 # local/ -> yours, writable, never touched 8 # 9 # htbpaths every variable, where it points, how full it is 10 # htbpaths potatoes just the path: cd "$(htbpaths potatoes)" 11 # htbpaths find godpotato where a file is, and the URL payload-serve gives it 12 # htbpaths tree [name] a two-level tree 13 # htbpaths env `export` lines, for a shell started before login 14 # 15 # The variables are lowercase because that is how they are typed all day 16 # ($privesc, $potatoes, $ligolo). None of them is a name zsh or bash treats 17 # specially ($path, $status, $prompt… are avoided on purpose). $PAYLOADS and 18 # $WORDLISTS stay as uppercase aliases for scripts that already use them. 19 # 20 # Why symlinks into the store and not a download script: every file is pinned 21 # by hash in _pkgs/ and payloads.nix, so ~/pentesting rolls back with the 22 # system generation and is identical on a reinstall. The category links are 23 # re-pointed by systemd-tmpfiles on every switch; `local/` is a real directory 24 # and is only ever created, never cleaned. 25 # 26 # ONE table below feeds the variables, the tmpfiles links, htbpaths and its 27 # completion, so a new category is one line. 28 { lib, ... }: 29 let 30 # name -> { rel = path under ~/pentesting; desc = what it is for } 31 # Order is the order `htbpaths` prints in. 32 table = [ 33 { name = "pentesting"; rel = ""; desc = "the arsenal root"; } 34 { name = "wordlists"; rel = "wordlists"; desc = "seclists, rockyou, usernames"; } 35 { name = "seclists"; rel = "wordlists/seclists"; desc = "SecLists"; } 36 { name = "rockyou"; rel = "wordlists/rockyou.txt"; desc = "rockyou.txt (plain text)"; } 37 { name = "usernames"; rel = "wordlists/seclists/Usernames"; desc = "SecLists username lists"; } 38 { name = "privesc"; rel = "privesc"; desc = "privilege escalation, per OS"; } 39 { name = "winprivesc"; rel = "privesc/windows"; desc = "winPEAS, PrivescCheck, FullPowers, PowerUp"; } 40 { name = "potatoes"; rel = "privesc/windows/potatoes"; desc = "God/Sigma/Juicy/Rogue/PrintSpoofer… (SeImpersonate)"; } 41 { name = "linprivesc"; rel = "privesc/linux"; desc = "linpeas, lse, pspy, traitor…"; } 42 { name = "creds"; rel = "creds"; desc = "credential dumping"; } 43 { name = "mimikatz"; rel = "creds/mimikatz"; desc = "mimikatz, every build and arch"; } 44 { name = "ad"; rel = "ad"; desc = "SharpHound CE, Rubeus, Certify, Certipy, bloodyAD…"; } 45 { name = "sharpcollection"; rel = "sharpcollection"; desc = "the full SharpCollection, every framework"; } 46 { name = "sharp"; rel = "sharpcollection/NetFramework_4.7_x64"; desc = "SharpCollection, .NET 4.7 x64"; } 47 { name = "pivoting"; rel = "pivoting"; desc = "tunnels and port forwards, every OS/arch"; } 48 { name = "ligolo"; rel = "pivoting/ligolo-ng"; desc = "ligolo-ng agent + proxy, every OS/arch"; } 49 { name = "chisel"; rel = "pivoting/chisel"; desc = "chisel, every OS/arch"; } 50 { name = "recon"; rel = "recon"; desc = "scanners to run FROM a foothold"; } 51 { name = "nmapbin"; rel = "recon/nmap"; desc = "static nmap (linux) + portable nmap (windows)"; } 52 { name = "fscan"; rel = "recon/fscan"; desc = "fscan, every OS/arch"; } 53 { name = "shells"; rel = "shells"; desc = "nc, RunasCs, nishang, reverse shells"; } 54 { name = "webshells"; rel = "webshells"; desc = "php/asp/aspx/jsp webshells"; } 55 { name = "scripts"; rel = "scripts"; desc = "PowerView, PowerSploit, printer bug, nishang"; } 56 { name = "exploits"; rel = "exploits"; desc = "public exploits staged for targets"; } 57 { name = "mytools"; rel = "local"; desc = "YOUR tools — writable, never touched by Nix"; } 58 ]; 59 60 # The store-backed categories linked into ~/pentesting (wordlists is linked 61 # from its own tree; local is a real directory). 62 linked = [ "privesc" "creds" "ad" "sharpcollection" "pivoting" "recon" 63 "shells" "webshells" "scripts" "exploits" "INVENTORY.txt" ]; 64 in 65 { 66 flake.nixosModules.pentest-paths = 67 { config, pkgs, lib, user, ... }: 68 let 69 cfg = config.daemon.pentest; 70 on = cfg.enable && cfg.payloads.enable; 71 home = config.users.users.${user}.home; 72 group = config.users.users.${user}.group; 73 root = "${home}/pentesting"; 74 abs = rel: if rel == "" then root else "${root}/${rel}"; 75 76 # name|path|desc, one per line: the data htbpaths reads. 77 tsv = pkgs.writeText "htbpaths.tsv" 78 (lib.concatMapStrings (e: "${e.name}\t${abs e.rel}\t${e.desc}\n") table); 79 80 htbpaths = pkgs.writeShellScriptBin "htbpaths" '' 81 set -uo pipefail 82 PATH=${lib.makeBinPath [ pkgs.coreutils pkgs.findutils pkgs.gawk pkgs.gnugrep pkgs.tree ]}:$PATH 83 TABLE=${tsv} 84 ROOT=${lib.escapeShellArg root} 85 86 if [ -t 1 ] && [ -z "''${NO_COLOR:-}" ]; then 87 B=$'\e[1m' D=$'\e[2m' M=$'\e[1;35m' R=$'\e[31m' Z=$'\e[0m' 88 else B="" D="" M="" R="" Z=""; fi 89 90 usage() { 91 printf '%s\n' \ 92 'usage: htbpaths every variable and where it points' \ 93 ' htbpaths <name> print one path (cd "$(htbpaths potatoes)")' \ 94 ' htbpaths find <pattern> locate a tool, with its payload-serve URL' \ 95 ' htbpaths tree [name] two-level tree' \ 96 ' htbpaths env export lines (eval "$(htbpaths env)")' \ 97 ' htbpaths names just the names (completion)' 98 } 99 100 lookup() { awk -F'\t' -v n="$1" '$1==n {print $2; f=1} END {exit !f}' "$TABLE"; } 101 102 # The whole output is built first and written once, then exit 0: a 103 # reader like `htbpaths | grep -q x` closes the pipe early, and a 104 # write after that would turn EPIPE into this script's exit status. 105 case "''${1:-}" in 106 "") 107 out=$(awk -F'\t' -v B="$B" -v D="$D" -v M="$M" -v R="$R" -v Z="$Z" ' 108 { n=$1; p=$2; d=$3 109 cmd = "test -e \"" p "\""; ok = (system(cmd) == 0) 110 cnt = "" 111 if (ok) { c = "find -L \"" p "\" -maxdepth 1 -mindepth 1 2>/dev/null | wc -l"; c | getline cnt; close(c); cnt = cnt+0 } 112 printf "%s%-17s%s %-52s %s%s%s\n", M, "$" n, Z, p (ok ? "" : R " (missing)" Z), D, d (cnt != "" && cnt > 0 ? " [" cnt "]" : ""), Z 113 }' "$TABLE") 114 printf '%s\n%s\n' "$out" "''${D}also: \$PAYLOADS / \$WORDLISTS (same files, store side) · \$BOXDIR = current box · htbpaths find <tool>''${Z}" || true 115 exit 0 ;; 116 names) cut -f1 "$TABLE" || true; exit 0 ;; 117 env) 118 awk -F'\t' '{printf "export %s=%s\n", $1, $2}' "$TABLE" 119 printf 'export PAYLOADS=%s\nexport WORDLISTS=%s\n' "$ROOT" "$ROOT/wordlists" 120 exit 0 ;; 121 find|f) 122 pat="''${2:-}" 123 [ -n "$pat" ] || { echo "usage: htbpaths find <pattern>" >&2; exit 2; } 124 # Wordlists are skipped unless asked for: seclists alone is tens of 125 # thousands of files and would bury the tool you are looking for. 126 res=$(find -L "$ROOT" \( -path "$ROOT/wordlists" -prune \) -o \ 127 -iname "*$pat*" -print 2>/dev/null | sort) 128 [ -n "$res" ] || { echo "htbpaths: nothing matching '$pat' (wordlists not searched — try: find -L \$wordlists -iname '*$pat*')" >&2; exit 1; } 129 out="" 130 while IFS= read -r f; do 131 rel=''${f#"$ROOT"/} 132 out="$out$f 133 ''${D}payload-serve → http://\$LHOST:8000/$rel''${Z} 134 " 135 done <<< "$res" 136 printf '%s' "$out" || true 137 exit 0 ;; 138 tree|t) 139 p=$ROOT 140 if [ -n "''${2:-}" ]; then p=$(lookup "$2") || { echo "htbpaths: unknown name: $2" >&2; exit 2; }; fi 141 tree -L 2 --noreport -l "$p" | head -200 || true 142 exit 0 ;; 143 -h|--help|help) usage; exit 0 ;; 144 *) 145 p=$(lookup "$1") || { echo "htbpaths: unknown name: $1 (htbpaths names)" >&2; exit 2; } 146 echo "$p"; exit 0 ;; 147 esac 148 ''; 149 150 completion = pkgs.writeTextDir "share/zsh/site-functions/_htbpaths" '' 151 #compdef htbpaths 152 local -a names 153 names=( ${lib.concatMapStringsSep " " (e: lib.escapeShellArg "${e.name}:${e.desc}") table} ) 154 if (( CURRENT == 2 )); then 155 _describe -t names 'path' names 156 _values 'command' find tree env names help 157 elif [[ $words[2] == (tree|t) ]]; then 158 _describe -t names 'path' names 159 fi 160 ''; 161 in 162 { 163 config = lib.mkIf on { 164 environment.systemPackages = [ htbpaths completion ]; 165 166 # Permanent: set for every login session (and so every shell, editor 167 # and script started from it). After the first switch, log out and in 168 # once, or `eval "$(htbpaths env)"` in a shell that predates it. 169 # 170 # Only the lowercase per-category variables are set here. $PAYLOADS and 171 # $WORDLISTS stay owned by payloads.nix and wordlists.nix (each pointing 172 # at its store tree), so they still resolve when this category is off — 173 # and so there is no mkForce-vs-mkForce conflict. The lowercase 174 # $wordlists points at the ~/pentesting symlink of the same tree. 175 environment.sessionVariables = 176 lib.listToAttrs (map (e: lib.nameValuePair e.name (abs e.rel)) table); 177 178 systemd.tmpfiles.rules = 179 [ 180 "d ${root} 0755 ${user} ${group} -" 181 "d ${root}/local 0755 ${user} ${group} -" 182 "L+ ${root}/wordlists - - - - ${cfg.payloads.wordlistsTree}" 183 ] 184 ++ map (n: "L+ ${root}/${n} - - - - ${cfg.payloads.tree}/${n}") linked; 185 }; 186 }; 187 }