NixDaemon

NixOS pentest workstation as one flake — IceBreaker's successor
git clone https://git.daemon-sec.xyz/NixDaemon.git
Log | Files | Refs | README

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 }