A Pentester's Guide to Practical Shell Scripting

Contents

As a veteran cybersecurity professional, I'm reframing the foundational chapter on shell scripting through the lens of practical, real-world offensive and defensive operations. Here is how I would teach this essential skill to the next generation of security professionals.


From Command Line to Weapon: A Pentester's Guide to Practical Shell Scripting 

After decades in the trenches of digital warfare—from government red teaming to enterprise threat hunting—I can tell you that the most powerful weapon in a security professional's arsenal is often the simplest. It’s not some zero-day exploit or a million-dollar analysis platform. It's the humble shell script.

The academic foundation teaches you the syntax—the "what." My goal is to teach you the tradecraft—the "why" and "how" for using these building blocks in real-world security engagements.

Think of individual command-line tools like nmap, grep, and curl as individual soldiers. A shell script is the mission plan that coordinates them into an effective fighting force. It’s how you automate reconnaissance, chain together exploits, and parse mountains of data to find the needle in the haystack. It’s a force multiplier.

Let's translate these core concepts into the language of offensive and defensive security.

The Anatomy of a Security Script: Core Concepts in Practice

1. The Shebang and Execution: Your Operational Foundation

The book correctly points out two ways to run a script. In our field, the first method (bash myscript.sh) is for quick tests. The second method is for building real tools. #!/bin/bash (the "shebang") is non-negotiable for any script you intend to reuse or deploy on a target.

  • Why it Matters: When you gain execution on a target system, you can't be sure what the user's default shell is (sh, zsh, csh). The shebang ensures your script uses the interpreter it was written for (bash in this case), making your payload portable and reliable. An exploit that fails because of a syntax error is just noise.
  • Permissions (chmod +x): This is fundamental. Your script is just a text file until you tell the operating system it's an executable program.
# A simple script to check for network connectivity
# Filename: check_host.sh

#!/bin/bash
TARGET="8.8.8.8"
echo "Pinging ${TARGET}..."
ping -c 4 ${TARGET}

To run it:

$ chmod +x check_host.sh
$ ./check_host.sh

2. Variables: Your Operational Parameters

The book uses examples like CITY="Springfield". Let's think like an operator. Our variables will define the parameters of our mission.

Offensive Example: Defining target and attacker details.

TARGET_IP="10.129.42.15"
LHOST="10.10.14.2"  # Our attack machine's IP
LPORT="4444"        # Our listening port for a reverse shell
WORDLIST="/usr/share/wordlists/rockyou.txt"

Defensive Example: Setting up parameters for monitoring.

LOG_FILE="/var/log/auth.log"
SUSPICIOUS_IP="198.51.100.5"
ALERT_EMAIL="soc@mycompany.com"

This makes your scripts readable, maintainable, and easy to modify for the next engagement.

3. Command Substitution: Capturing Intelligence 

This is where scripting becomes truly powerful. Command substitution—$(command) or `command`—allows you to store the output of a command in a variable. This is how you chain tools together.

Pentester's Example: Let's find open web ports from an Nmap scan and store them in a variable.

#!/bin/bash
TARGET_IP="10.129.42.15"

echo "[*] Finding open web ports on ${TARGET_IP}..."
# Run a fast scan, grep for "http", and cut out the port number.
OPEN_PORTS=$(nmap -F ${TARGET_IP} | grep 'http' | cut -d'/' -f1)

if [ -z "$OPEN_PORTS" ]; then
    echo "[-] No open web ports found."
else
    echo "[+] Found open web ports: ${OPEN_PORTS}"
fi

Here, we captured the intelligence from one tool (nmap) to be used later in the script.

4. Positional Parameters ($1, $2, $@): Building Reusable Tools

Hardcoding TARGET_IP is fine for a one-off task. But real operators build tools they can reuse. Positional parameters let you pass arguments to your script from the command line.

Let's upgrade our recon script:

#!/bin/bash
# Filename: web_recon.sh

# Check if a target IP was provided.
if [ -z "$1" ]; then
    echo "Usage: $0 <TARGET_IP>"
    exit 1
fi

TARGET_IP=$1 # The first argument is our target

echo "[*] Finding open web ports on ${TARGET_IP}..."
OPEN_PORTS=$(nmap -F ${TARGET_IP} | grep 'http' | cut -d'/' -f1)

# ... rest of the script ...

Now you can run it against any target:

$ ./web_recon.sh 10.129.42.15
$ ./web_recon.sh 192.168.1.1

The special variable $? is your best friend. It holds the exit code of the last command. A 0 means success; anything else means failure. This is critical for error checking.

ping -c 1 $TARGET_IP > /dev/null 2>&1
if [ $? -eq 0 ]; then
    echo "[+] Host is up!"
else
    echo "[-] Host is down. Aborting."
    exit 1
fi

Control Flow: Making Decisions in the Field

if...then...else Statements

This is your logic engine. It allows your script to react to changing conditions. We already used it above to check if a host is up or if a command-line argument was supplied.

Real-World Use Case: Checking for root privileges.

if [ $(id -u) -ne 0 ]; then
    echo "[-] This script requires root privileges. Please run with sudo."
    exit 1
fi
echo "[+] Running with root privileges."

for and while Loops: Automation in Action 

Loops are the heart of automation. They perform the same action on a list of items. This is how you scale your efforts from one target to one hundred.

for loop: Iterate through a list of targets.

# targets.txt contains one IP per line
# 10.10.1.1
# 10.10.1.5
# 10.10.1.12

for target in $(cat targets.txt); do
    echo "[*] Scanning ${target}..."
    nmap -T4 -F ${target}
done

while loop: Read a file line by line. This is often better for handling lines with spaces.

# Bruteforcing SSH with a list of passwords
while read -r pass; do
    echo "[*] Trying password: ${pass}"
    sshpass -p "${pass}" ssh user@target.com "whoami"
done < passwords.txt

Text Manipulation: The Hacker's Scalpel 

The book introduces grep, cut, sed, and tr. For a security professional, these aren't just text utilities; they are data-mining tools. You will use them every single day to parse logs, tool output, and configuration files.

  1. grep: Your searchlight. Find passwords in config files (grep 'password' config.xml), filter for specific events in logs (grep 'Failed password' /var/log/auth.log), or find interesting files (find / -name "*.bak" 2>/dev/null).
  2. cut: Your extractor. Pull usernames from /etc/passwd (cut -d':' -f1 /etc/passwd). We used it earlier to pull port numbers from Nmap output.
  3. sed: Your editor. Use it to craft payloads or clean up data. For example, creating a list of URLs from a file of directory names: sed 's#^#http://target.com/#' dirs.txt.
  4. awk: The powerhouse. While cut is simple, awk is a full-featured text-processing language. It's brilliant for parsing structured text. For example, to print the username and shell of everyone in /etc/passwd: awk -F: '{print $1 " uses " $7}' /etc/passwd.

Putting It All Together: A Simple, Practical Recon Script

Let's combine these concepts into a tool that's genuinely useful for an initial penetration test.

#!/bin/bash
# A simple script to perform initial recon on a target.

# --- Check for correct usage ---
if [ "$#" -ne 1 ]; then
    echo "Usage: $0 <Target IP>"
    exit 1
fi

# --- Variables ---
TARGET=$1
OUTPUT_DIR="recon_results/${TARGET}"
NMAP_FILE="${OUTPUT_DIR}/nmap_scan.txt"

# --- Setup ---
echo "[*] Starting reconnaissance on ${TARGET}"
mkdir -p ${OUTPUT_DIR}
echo "[+] Created output directory: ${OUTPUT_DIR}"

# --- Host Discovery ---
echo "[*] Checking if host is online..."
ping -c 1 ${TARGET} > /dev/null 2>&1
if [ $? -ne 0 ]; then
    echo "[-] Host ${TARGET} appears to be down. Aborting."
    exit 1
fi
echo "[+] Host is up!"

# --- Port Scanning ---
echo "[*] Performing a quick Nmap scan..."
# -F for fast scan (top 100 ports), -T4 for speed
nmap -F -T4 ${TARGET} -oN ${NMAP_FILE}

echo "[*] Nmap scan complete. Results saved to ${NMAP_FILE}"

# --- Service Enumeration ---
echo "[*] Enumerating open ports for more details..."
# Get open TCP ports from the Nmap file, format them for the next scan
PORTS=$(grep 'open' ${NMAP_FILE} | grep 'tcp' | cut -d'/' -f1 | tr '\n' ',')

if [ -z "$PORTS" ]; then
    echo "[-] No open TCP ports found to enumerate."
else
    echo "[+] Found open ports: ${PORTS}"
    echo "[*] Running detailed service scan on discovered ports..."
    nmap -sV -p${PORTS} ${TARGET} -oN "${OUTPUT_DIR}/service_scan.txt"
    echo "[+] Detailed scan complete."
fi

echo "[*] Reconnaissance finished."

The Blue Team Perspective: Scripting for Defense 

These same skills are indispensable for defense. As a threat hunter or incident responder, you'll use scripts to:

  • Hunt for IOCs: Write a for loop to SSH into 500 servers and grep for a malicious filename.
  • Parse Logs: Use grep, awk, and sort to find the top 10 IPs hitting your web server or the accounts with the most failed logins.
  • Automate Response: Create a script that, when given a malicious IP, automatically adds it to a firewall blocklist (iptables -A INPUT -s $MALICIOUS_IP -j DROP).

This is the duality of our craft. The same tool can be used to build or to break. Your job is to master it for both. Start simple, build on what you learn, and never stop practicing. The command line is your laboratory. 🧑‍💻