WSLC Pentest Workflow
Use this skill when pentesting from Windows while isolating tooling in WSL Containers (wslc) and reusing imported pentest skills.
Safety + scope guardrails
- Authorized assessments only (written permission, defined scope).
- Never run active testing outside approved targets.
- Default to non-destructive validation first.
- Keep logs and timestamps for all commands.
Pair this skill with
pentest-methodology(phase workflow)tool-selection(pick lowest-risk effective tools)- Tool-specific skills as needed (
nmap,ffuf,sqlmap,burp-suite, etc.)
Load sequence recommendation:
skill_view(name="wslc-pentest-workflow")skill_view(name="pentest-methodology")skill_view(name="tool-selection")skill_view(name="<tool-skill>")per task
Preflight checks (Windows host)
Run before creating/using containers:
command -v wsl && wsl --version
command -v wslc && wslc version
wslc images
wslc container ps -a
If wslc is not found in PATH, use:
'/c/Program Files/WSL/wslc.exe' version
Standard container profile (persistent Kali)
Use one persistent container per engagement, and always increment the engagement ID.
# Compute next numeric engagement id from existing workspaces (001, 002, ...)
BASE="/c/Users/$USER/pentest-workspace"
mkdir -p "$BASE"
LAST_ID=$(find "$BASE" -maxdepth 1 -type d -name 'engagement-*' 2>/dev/null \
| sed -E 's#.*/engagement-([0-9]{3})$#\1#' \
| sort -n | tail -1)
[ -z "$LAST_ID" ] && LAST_ID=0
ENG_ID=$(printf '%03d' $((10#$LAST_ID + 1)))
ENG_DIR="engagement-$ENG_ID"
CONT_NAME="kali-pentest-$ENG_ID"
# Host workspace
mkdir -p "$BASE/$ENG_DIR"
# Create long-lived container with mounted workspace
wslc run -d --name "$CONT_NAME" \
-v "C:/Users/$USER/pentest-workspace/$ENG_DIR:/workspace" \
kalilinux/kali-rolling sleep infinity
# Verify
wslc container ps -a
echo "Using ENG_ID=$ENG_ID, CONT_NAME=$CONT_NAME"
Naming rule: never reuse 001 across engagements. Always create a fresh incremented ID.
Bootstrap base tooling once (run after container creation):
wslc exec kali-pentest-<ENG_ID> bash -lc \
'apt-get update -qq && apt-get install -y -qq \
nmap dnsutils curl wget netcat-traditional gobuster ffuf nikto sqlmap \
enum4linux-ng smbclient ldap-utils python3 python3-pip python3-pexpect git jq \
john hydra sshpass wordlists dirb expect 2>&1 | tail -5'
Unpack rockyou after installing wordlists:
wslc exec kali-pentest-<ENG_ID> bash -lc \
'gunzip -k /usr/share/wordlists/rockyou.txt.gz 2>/dev/null; \
ls -lh /usr/share/wordlists/rockyou.txt'
Notes:
wordlistspackage installs rockyou.txt.gz — must be gunzipped before use.dirbpackage is required for/usr/share/wordlists/dirb/common.txt— thewordlistspackage alone does NOT create this path. Always includedirbin the bootstrap install.python3-pexpectenables SSH automation scripts (key passphrase, sudo interaction) — required forpexpect-based exploit scripts. Install withapt-get install python3-pexpect, notpip(externally-managed-environment error).expect(the shell tool) is also useful as a fallback butpython3-pexpectis more reliable for complex multi-prompt SSH sessions.johnships with best64.rule pre-configured in /etc/john/john.conf; reference it as--rules=best64.- Install
hashcatseparately only if GPU/OpenCL is available (see Pitfalls).
Execution pattern (Hermes + skills)
- Keep planning on host (Hermes).
- Execute scanner/tool commands in
kali-pentest-<ENG_ID>viawslc exec .... - Save outputs to
/workspace/artifacts/...so evidence persists on Windows host. - Map each action to pentest-methodology phases.
Examples:
# Recon
wslc exec kali-pentest-<ENG_ID> bash -lc 'nmap -sV -sC -oN /workspace/artifacts/nmap-initial.txt <TARGET>'
# Web content discovery
wslc exec kali-pentest-<ENG_ID> bash -lc 'ffuf -u http://<TARGET>/FUZZ -w /usr/share/wordlists/dirb/common.txt -o /workspace/artifacts/ffuf.json -of json'
# Basic web triage
wslc exec kali-pentest-<ENG_ID> bash -lc 'nikto -h http://<TARGET> -output /workspace/artifacts/nikto.txt'
File + evidence conventions
Recommended host tree:
C:\Users\<user>\pentest-workspace\engagement-<ENG_ID>\
scope.md
logbook.md
artifacts\
reports\
Command logging convention (append to logbook.md):
- timestamp
- command
- objective
- result summary
- artifact path
Useful lifecycle commands
# Logs and stats
wslc logs kali-pentest-<ENG_ID>
wslc stats
# Shell into container
wslc exec -it kali-pentest-<ENG_ID> bash
# Stop/start between sessions
wslc stop kali-pentest-<ENG_ID>
wslc start kali-pentest-<ENG_ID>
# Remove when engagement ends
wslc remove kali-pentest-<ENG_ID>
When to use privileged workaround
Only if a required test needs capabilities unavailable in default wslc run flags (e.g., network admin operations):
wslc system session run docker run -d --name privileged-kali --privileged kalilinux/kali-rolling sleep infinity
Use this sparingly, document why, and tear down after use.
Adapting pentest-methodology to wslc
The pentest-methodology skill uses kali-exec (MCP server pattern). When using wslc instead, substitute every kali-exec "cmd" with:
wslc exec kali-pentest-<ENG_ID> bash -lc 'cmd'
The logic, phase ordering, and reporting structure from pentest-methodology remain the same.
Interception Proxy Routing inside WSL Containers (Caido / Burp Pattern)
To capture and analyze web/API security testing traffic generated by command-line tools (such as curl, ffuf, nikto, or nuclei) or headless browsers, you can route container traffic through an interception proxy. Learning from the Strix design pattern, there are two highly effective approaches:
Approach 1: Strix-Inspired In-Container Caido Sidecar (Recommended)
Running Caido inside the WSL container itself bypasses all Windows Firewall and WSL virtual routing boundaries. Since Caido runs on loopback 127.0.0.1:8080 locally inside the container, traffic routing is 100% reliable and zero-friction.
Expose the Proxy Port on Startup: Map port
8080(or48080) when starting the container so you can view the Caido Web UI from your Windows browser:wslc run -d --name "$CONT_NAME" \ -p 127.0.0.1:8080:8080 \ -v "C:/Users/$USER/pentest-workspace/$ENG_DIR:/workspace" \ kalilinux/kali-rolling sleep infinityDownload and Install Caido CLI in the Container:
wslc exec kali-pentest-<ENG_ID> bash -lc \ 'wget -O /tmp/caido.tar.gz "https://caido.download/releases/v0.38.0/caido-cli-v0.38.0-linux-x86_64.tar.gz" && \ tar -xzf /tmp/caido.tar.gz -C /usr/local/bin/ caido-cli && \ chmod +x /usr/local/bin/caido-cli && \ rm /tmp/caido.tar.gz'Launch Caido CLI in the Background:
wslc exec kali-pentest-<ENG_ID> bash -lc \ 'caido-cli --listen 0.0.0.0:8080 --allow-guests --no-open --no-logging &'Automate System-Wide Proxy & CA Certificate Trust: Wait for Caido to boot, fetch its CA certificate via curl, install it into the system-wide CA trust store, and write proxy exports to
/etc/profile.d/proxy.shso every shell automatically proxies all traffic:wslc exec kali-pentest-<ENG_ID> bash -lc \ 'sleep 3 && \ curl -fsSL http://127.0.0.1:8080/ca.crt -o /tmp/ca.crt && \ cp /tmp/ca.crt /usr/local/share/ca-certificates/ca-caido.crt && \ update-ca-certificates && \ cat << "EOF" | tee /etc/profile.d/proxy.sh export http_proxy="http://127.0.0.1:8080" export https_proxy="http://127.0.0.1:8080" export HTTP_PROXY="http://127.0.0.1:8080" export HTTPS_PROXY="http://127.0.0.1:8080" export ALL_PROXY="http://127.0.0.1:8080" export NO_PROXY="localhost,127.0.0.1" export REQUESTS_CA_BUNDLE="/etc/ssl/certs/ca-certificates.crt" export SSL_CERT_FILE="/etc/ssl/certs/ca-certificates.crt" EOF 'Now, any tool in the container (such as
curl,ffuf, ornuclei) will seamlessly route through Caido and automatically verify its SSL certificate without needing insecure flags like-k!Tip: Simply open
http://127.0.0.1:8080in your host web browser (Windows) to interactively inspect, filter, and replay testing traffic!
Approach 2: Host Proxy Setup (Traditional)
Route container traffic through an interception proxy (Caido/Burp) running directly on the Windows host:
Host Proxy Setup: Ensure your host proxy binds to
0.0.0.0or the specific WSL virtual interface (e.g., port 8080). Find your Windows host IP address from within the container:HOST_IP=$(ip route | grep default | awk "{print \$3}")Environment Proxy Exports: Before running utilities, export the proxy environment variables pointing to the Windows host IP:
export http_proxy="http://$HOST_IP:8080" export https_proxy="http://$HOST_IP:8080" export REQUESTS_CA_BUNDLE="/workspace/ca-certificates/cacert.pem" # If SSL inspection is activeExecution example:
wslc exec kali-pentest-<ENG_ID> bash -lc \ 'curl -k -i -s http://<TARGET>/api/users'
Hash cracking in-container (john vs hashcat)
johnworks out of the box on CPU inside wslc containers. Use it.hashcatrequires OpenCL/GPU — not available in standard wslc containers. Attempting it fails silently withCL_PLATFORM_NOT_FOUND_KHR. Do not install hashcat inside the container unless you have GPU passthrough.- For best64-rule cracking with john:
(john --format=Raw-SHA512 --wordlist=/usr/share/wordlists/rockyou.txt \ --rules=best64 --fork=4 /tmp/target.hash john --show --format=Raw-SHA512 /tmp/target.hashbest64is pre-registered in /etc/john/john.conf — no path needed.)
Heredoc quoting hazard with wslc exec
Passing heredocs or complex scripts through wslc exec kali-... bash -lc '...' causes quoting conflicts. Instead, write scripts to a file and copy them:
# On Windows/host:
cat > /c/Users/admin/pentest-workspace/engagement-<ENG_ID>/artifacts/myscript.sh << 'EOF'
#!/bin/bash
# your script here
EOF
# Copy to container then run:
wslc exec kali-pentest-<ENG_ID> bash -lc \
'sshpass -p "PASS" scp -o StrictHostKeyChecking=no \
/workspace/artifacts/myscript.sh user@TARGET:/tmp/myscript.sh && \
sshpass -p "PASS" ssh -o StrictHostKeyChecking=no user@TARGET "bash /tmp/myscript.sh"'
IMPORTANT: Do NOT modify /etc/hosts inside the container via wslc exec — this is blocked
by the Hermes safety system as an irreversible action. Use explicit Host: headers for ALL
vhost-aware requests instead. Tools that support -H or --host flags work without any
hosts-file change.
Always check HTML source comments for vhost hints:
wslc exec kali-pentest-<ENG_ID> bash -lc \
'curl -s http://<TARGET>/ | grep -i "TODO\|vhost\|host\|domain\|thm\|htb"'
Then probe with Host: header before running gobuster:
wslc exec kali-pentest-<ENG_ID> bash -lc \
'curl -s -H "Host: discovered-vhost.thm" http://<TARGET>/ | head -40'
Before running ffuf vhost fuzzing, get the baseline response size for non-existent vhosts
so you can set -fs correctly:
# Check baseline size — use this value for ffuf -fs
wslc exec kali-pentest-<ENG_ID> bash -lc \
'curl -s -o /dev/null -w "%{size_download}" http://<TARGET>/ -H "Host: nonexistent.<DOMAIN>"'
# Then fuzz with correct filter size
wslc exec kali-pentest-<ENG_ID> bash -lc \
'ffuf -u http://<TARGET>/ -H "Host: FUZZ.<DOMAIN>" \
-w /usr/share/wordlists/dirb/common.txt \
-fs <BASELINE_SIZE> -o /workspace/artifacts/ffuf-vhost.json -of json 2>&1'
Pass vhost to gobuster with -H:
wslc exec kali-pentest-<ENG_ID> bash -lc \
'gobuster dir -u http://<TARGET>/ \
-H "Host: <VHOST>" \
-w /usr/share/wordlists/dirb/common.txt \
-o /workspace/artifacts/gobuster-vhost.txt 2>&1'
SSH automation with pexpect (passphrase-protected keys or interactive sudo)
When SSH requires a key passphrase or interactive sudo password, use python3-pexpect scripts.
ALWAYS write the script to /workspace/artifacts/<name>.py first and run it via
wslc exec kali-pentest-<ENG_ID> bash -lc 'python3 /workspace/artifacts/<name>.py'.
Inline heredoc Python via bash -lc 'python3 << EOF ... EOF' is fragile — shell quoting
breaks when the script contains single quotes or dollar signs.
Pattern — key passphrase + command:
import pexpect
child = pexpect.spawn(
"ssh -o StrictHostKeyChecking=no -o LogLevel=ERROR "
"-i /tmp/id_rsa user@TARGET \"cmd1 && cmd2\""
)
child.expect("passphrase", timeout=10)
child.sendline("THE_PASSPHRASE")
child.expect(pexpect.EOF, timeout=15)
print(child.before.decode("utf-8", "ignore"))
Pattern — passphrase + interactive sudo:
import pexpect, time
child = pexpect.spawn(
"ssh -o StrictHostKeyChecking=no -o LogLevel=ERROR "
"-t -i /tmp/id_rsa user@TARGET \"sudo -l\""
)
child.expect("passphrase", timeout=10)
child.sendline("KEY_PASSPHRASE")
child.expect("password for", timeout=15)
child.sendline("SUDO_PASSWORD") # literal — pexpect sends direct to PTY, no shell expansion
child.expect(pexpect.EOF, timeout=15)
print(child.before.decode("utf-8", "ignore"))
Note: child.sendline() sends bytes directly to the PTY — dollar signs and special chars are
literal. No shell escaping needed.
Pitfalls
--rmdeletes container state; avoid for persistent engagements.- Missing mounts cause artifact loss inside ephemeral filesystems.
- Running high-noise tools too early can violate rules of engagement.
- Skill mismatch: always load the relevant tool skill before execution.
- In this Hermes terminal backend (bash), avoid PowerShell-specific command syntax.
sshpassis not pre-installed in kali-rolling — add to bootstrap or install on demand.- Heredoc SSH commands (
ssh host << 'EOF') fail when piped throughwslc execbecause wslc does not allocate a PTY. Workaround: write the script to a file locally, SCP it to the target, then execute it remotely:# Write script locally (host-mounted workspace) cat > /workspace/artifacts/exploit.sh << 'SCRIPT' #!/bin/bash ... SCRIPT # SCP and execute sshpass -p "$PASS" scp -o StrictHostKeyChecking=no /workspace/artifacts/exploit.sh user@target:/tmp/ sshpass -p "$PASS" ssh -o StrictHostKeyChecking=no user@target "bash /tmp/exploit.sh" hashcatrequires OpenCL/CUDA/HIP — not available in standard wslc containers. Usejohninstead:- best64 rules:
john --format=Raw-SHA512 --wordlist=/usr/share/wordlists/rockyou.txt --rules=best64 hash.txt - Verify best64 is registered:
grep -i best64 /etc/john/john.conf - rockyou.txt ships compressed in kali — unzip first:
gunzip -k /usr/share/wordlists/rockyou.txt.gz
- best64 rules:
/usr/share/wordlists/dirb/common.txtrequires thedirbpackage, NOT justwordlists. Always includedirbin bootstrap install.sshpassonly handles password-based SSH auth (-p). It CANNOT supply a key passphrase — it will time out silently on the passphrase prompt. To use a passphrase-protected key, either: (a) crack the passphrase withssh2john+ john, then use apexpectscript to supply it interactively, or (b) strip the passphrase withssh-keygen -p -P OLD -N "" -f keyfileonce cracked.ssh2johnextracts a crackable hash from a passphrase-protected private key:ssh2john /tmp/id_rsa > /tmp/id_rsa.hash john --wordlist=/usr/share/wordlists/rockyou.txt /tmp/id_rsa.hash john --show /tmp/id_rsa.hash- Bash history password leaks: when
.bash_historyshowsecho "user:Pass$Word", the$Wmay be an unexpanded variable (empty in interactive shells without args). Test both the literal string (single-quoted) AND the expanded form. Usessh-keygen -p -P 'candidate' -N "" -f keyto validate each candidate without connecting to target. /c/Users/admin/pentest-workspace/...is NOT the workspace path inside the container — the container sees it as/workspace/.... Always use/workspace/artifacts/inside wslc exec commands.- Heredoc multiline scripts break inside
wslc exec bash -lc '...'; write to file and scp instead. gobusterandffuf-fs(filter size) must be tuned to the baseline response for the target context (vhost vs directory). Always probe a garbage subdomain first to get the default response size, then set-fsto that value.ftpbinary is NOT installed in kali-rolling by default. Usecurl --user anonymous:anonymous ftp://<TARGET>/fileinstead. Do not waste time troubleshooting a missingftpbinary.- Command injection outputs only display the first line when piped to an HTML element. Always append
| tr "\n" "|"to collapse multiline output before parsing viagrep "h2". - Full-path bypass for token blacklists: the filter
explode(" ", $cmd)only checks exact bare tokens./bin/cat,/usr/bin/python3,/bin/bashall pass even whencat,python3,bashare blocked. Always try full paths before assuming a tool is unavailable. - Secondary Apache virtualhost port is often firewalled (e.g. port 9001 inaccessible from outside). Read PHP source and copy files via command injection rather than fetching externally.
- Cracked DB passwords (from MySQL/webportal hashes) often do NOT work for SSH. They may be web-portal-only credentials. Continue enumerating (steghide, other users) rather than stopping at SSH auth failure.
steghideis not in the standard bootstrap — addapt-get install -y steghidewhen stego is suspected.cp /tmp/via cmd injection copies the file on the TARGET, not in the Kali container. To get a file from the target into Kali, base64-exfil it through the webshell response, then decode with Python in the container.sshpassis not installed by default in kali-rolling. Install withapt-get install -y sshpassbefore attempting password-based SSH automation.python3-pexpectis not installed by default in kali-rolling. Install withapt-get install -y python3-pexpectbefore attempting SSH automation scripts.unzipis not installed by default in kali-rolling. Install withapt-get install -y unzipbefore attempting ZIP extraction.- Host-mounted folder permissions for SSH private keys: SSH private keys stored in Windows host-mounted folders (e.g.,
/workspace/...mapping toC:\Users\<username>\pentest-workspace\...) often maintain standard777permissions inside the container. Since NTFS mounts in WSL/Docker do not support native POSIX file permissions, executingchmod 600on those host-mounted paths will silently fail to restrict permissions, causing SSH to reject the key. Workaround: Copy the private key to a container-native directory (e.g.,cp /workspace/artifacts/id_rsa /tmp/id_rsa) and executechmod 600 /tmp/id_rsathere before using it. - Modern LLM providers utilize built-in safety filters that scan and automatically redact raw SSH private keys or cleartext credentials from output logs (e.g., replacing them with
[REDACTED PRIVATE KEY]), which can corrupt retrieved keys. Workaround: Base64-encode the sensitive file on the target machine (e.g.,cat keyfile | base64 -w0) to obscure the private key structure from LLM-level filters, then decode and write it directly to/tmp/id_rsawithin your container. - Tools that require GPU acceleration (e.g.
hashcat) will fail withCL_PLATFORM_NOT_FOUND_KHRinside wslc containers because there is no GPU/OpenCL passthrough. Do not install or run hashcat inside a wslc container. Usejohn(CPU-native) for password cracking instead. If GPU cracking is essential, run hashcat on the Windows host directly or in a native WSL2 distro with GPU drivers configured. - Single-quoted heredocs and nested quotes break when passed through
wslc exec kali-container bash -lc '...'. Write scripts to a host-mounted volume file instead, then reference the file path inside the container via/workspace/....
Reference files
references/scope-template.md— engagement scope and authorization checklistreferences/logbook-template.md— per-command evidence logging templatereferences/report-template.md— vulnerability report skeleton
Success criteria
- WSL +
wslcvalidated. - Persistent Kali container running with mounted host workspace.
- Pentest actions mapped to methodology phases.
- Artifacts saved to host-mounted path.
- Final report generated from reproducible evidence.