Skip to content

Latest commit

 

History

History
192 lines (138 loc) · 6 KB

File metadata and controls

192 lines (138 loc) · 6 KB

05. Script Security Best Practices

Status Module Bash

🔐 Why Security in Shell Scripts?

Shell scripts often run with elevated privileges, process untrusted input, and manage system resources. A poorly written script can be exploited to execute arbitrary commands, leak sensitive data, or destroy systems.

This lesson covers the most critical security practices that separate a safe script from a dangerous one.


☣️ Command Injection

Command injection is the #1 vulnerability in shell scripts. It occurs when user-supplied data is used in a command without proper sanitization.

#!/usr/bin/env bash
# VULNERABLE — NEVER do this
read -p "Enter filename to search: " filename
grep "error" $filename    # What if user enters: '; rm -rf /'

The user could enter ; rm -rf / and it would execute as:

grep "error" ; rm -rf /

Safe Fix: Always Quote Variables

# SAFE — always double-quote variable expansions
grep "error" "$filename"

With double quotes, the entire value of $filename is treated as a single argument — shell metacharacters inside it have no special meaning.


✅ Input Validation

Never trust user input. Always validate it against a strict whitelist before use.

#!/usr/bin/env bash
read -p "Enter environment (dev/staging/prod): " env

# Whitelist validation — only allow known safe values
case "$env" in
    dev|staging|prod) ;;  # Valid — continue
    *)
        echo "Error: Invalid environment '$env'" >&2
        exit 1
        ;;
esac

# Regex validation for structured input
read -p "Enter port number: " port
if [[ ! "$port" =~ ^[0-9]{1,5}$ ]] || (( port < 1 || port > 65535 )); then
    echo "Error: '$port' is not a valid port number." >&2
    exit 1
fi

# Validate a filename — no slashes, dots at start, or special chars
read -p "Enter filename: " fname
if [[ ! "$fname" =~ ^[a-zA-Z0-9_.-]+$ ]]; then
    echo "Error: Filename contains invalid characters." >&2
    exit 1
fi

🛡️ Principle of Least Privilege

Your script should request only the permissions it absolutely needs:

# Check if root is actually required — only then demand it
if [[ $EUID -ne 0 ]] && needs_root; then
    echo "Error: This script must be run as root." >&2
    exit 1
fi

# Run specific commands as a less-privileged user
sudo -u www-data php artisan cache:clear

# Drop privileges after setup (don't run the whole script as root)
setup_as_root() { ... }
run_as_user()   { ... }

setup_as_root
exec sudo -u appuser bash -c "$(declare -f run_as_user); run_as_user"

🔑 Handling Secrets — Never Hardcode

# BAD — secret is visible in the script and process list
DB_PASSWORD="s3cr3tpassword"
mysql -u root -p"$DB_PASSWORD" mydb

# GOOD — read from environment variable
DB_PASSWORD="${DB_PASSWORD:?Error: DB_PASSWORD must be set}"
mysql -u root -p"$DB_PASSWORD" mydb

# BETTER — read from a secure file with restricted permissions
#   chmod 600 /etc/myapp/db.conf
source /etc/myapp/db.conf   # File contains: DB_PASSWORD="..."

Environment variables vs. files:

  • ✅ Environment variables are not written to disk.
  • ✅ Files with chmod 600 restrict access but leave traces.
  • ❌ Command-line arguments are visible in ps aux to any user — never pass secrets this way.

🚧 Safe Temporary Files

Predictable temporary file names can be exploited (TOCTOU race conditions). Always use mktemp:

# BAD — predictable, exploitable
tmpfile="/tmp/myapp_$$"

# GOOD — unpredictable, atomic creation
tmpfile=$(mktemp) || { echo "Failed to create tmpfile"; exit 1; }
tmpdir=$(mktemp -d)

# Always clean up, even on error
trap 'rm -rf "$tmpfile" "$tmpdir"' EXIT

🔬 ShellCheck — Your Static Analysis Tool

ShellCheck is a free, open-source tool that finds common bugs and security issues in shell scripts. It is the shell equivalent of a linter.

# Install
apt install shellcheck          # Debian/Ubuntu
brew install shellcheck         # macOS

# Run against your script
shellcheck myscript.sh

# Common findings:
# SC2086: Double quote to prevent word splitting: echo $var → echo "$var"
# SC2010: Don't use ls | grep: use a glob or find instead
# SC2046: Quote to prevent word splitting: cmd $(subcmd) → cmd "$(subcmd)"
# SC2001: Prefer bash variable operators over sed for simple patterns

💡 Run ShellCheck before every commit. Integrate it into your CI/CD pipeline or add a Git pre-commit hook.


📋 Security Checklist

Practice Why It Matters
✅ Always quote "$variables" Prevents word splitting and injection
✅ Validate all input with a whitelist Prevents unexpected values from causing harm
✅ Use set -euo pipefail Prevents silent failures from cascading
✅ Use mktemp for temp files Prevents race conditions and predictable path attacks
✅ Never hardcode secrets Prevents credential exposure in logs/code
✅ Run with minimum privilege Limits damage if something goes wrong
✅ Run shellcheck regularly Catches common bugs and anti-patterns automatically
✅ Use readonly for constants Prevents accidental modification
✅ Redirect stderr to log file Prevents sensitive info from leaking to terminal

🏆 Summary

Security is not an afterthought — it is designed in from the first line. The three habits that will protect you from 90% of shell script vulnerabilities are:

  1. Always quote your variables: "$var" not $var
  2. Validate all input: whitelists over blacklists
  3. Run ShellCheck: let a tool catch what your eyes miss

👉 Next: Capstone Project — System Health Monitor