Posted on
Artificial Intelligence

Bash Scripts That Explain Security Risks

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Bash Scripts That Explain Security Risks

Ever run a “quick” Bash one-liner from the web—or glued a few commands together to automate a task—only to wonder if it’s safe? The smallest mistake in a shell script can escalate fast: data loss, credential leaks, or remote code execution. This post uses short, readable Bash snippets to show common security pitfalls and how to fix them—so you can keep the power of Bash without the booby traps.

What you’ll get:

  • A mental model for why Bash security risks are real and worth your time

  • 5 focused examples of risky patterns, each with a safer alternative

  • A quick tooling tip to catch issues automatically

  • Distro-specific installation commands where tools are cited

Note: All examples are crafted to be safe to copy-paste in a test directory. They print or operate on /tmp paths, not on production data.


Why this matters

  • Bash is everywhere: CI/CD runners, cron jobs, Docker entrypoints, ad-hoc admin scripts, and “quick fixes” that stick around for years.

  • Shell syntax is sharp: quoting, word splitting, globbing, and environment inheritance are powerful—and easy to misuse.

  • Real incidents happen because of small mistakes: an unquoted variable, a predictable temp file, or an over-trusting eval can hand control to attackers.

  • Risk rises with privilege: many scripts run as root or on critical hosts. Low-probability mistakes + high-impact context = worth fixing.


Prerequisites (tooling)

We’ll use ShellCheck to statically lint our examples. If you don’t have it, install it with your distro’s package manager.

  • Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y shellcheck
  • Fedora/RHEL/CentOS Stream (dnf):
sudo dnf install -y ShellCheck
  • openSUSE/SLE (zypper):
sudo zypper refresh
sudo zypper install -y ShellCheck
# If that fails, try the lowercase name:
# sudo zypper install -y shellcheck

Run it like:

shellcheck your-script.sh

1) Unquoted variables cause word splitting and globbing

The risk:

  • Unquoted expansions (like $var) split on whitespace and expand globs (*, ?).

  • Attackers (or accidents) can change what your command means by adding spaces or glob characters.

Vulnerable:

#!/usr/bin/env bash
# demo1-bad.sh
src=$1
dest_dir=$2
mkdir -p $dest_dir
cp $src $dest_dir/

How it can go wrong:

  • If $src is report 2026*.txt, the * will expand inside the script and copy many files you didn’t intend.

Safer:

#!/usr/bin/env bash
# demo1-good.sh
set -euo pipefail
src=$1
dest_dir=$2
mkdir -p -- "$dest_dir"
cp -- "$src" "$dest_dir/"

Key takeaways:

  • Always quote variables: "$var".

  • Use -- to end option parsing when passing user input to commands.

  • Consider set -euo pipefail to fail early and avoid using unset variables.


2) Command injection via eval and unsafe composition

The risk:

  • Building shell commands from strings and running them with eval lets attackers smuggle in new commands.

Vulnerable:

#!/usr/bin/env bash
# demo2-bad.sh
pattern=$1
cmd="grep $pattern access.log"
eval "$cmd"

If pattern is foo; echo HACKED >&2, the echo will also run.

Safer (don’t use eval; pass args directly):

#!/usr/bin/env bash
# demo2-good.sh
set -euo pipefail
pattern=$1
grep -- "$pattern" access.log

Key takeaways:

  • Avoid eval for untrusted input. Prefer arrays or direct argument passing.

  • Many tools support -- to prevent input from being treated as options.


3) Insecure temporary files (symlink and race attacks)

The risk:

  • Writing to a predictable path in /tmp lets attackers pre-create a symlink to a sensitive file.

  • Result: your script overwrites something you never intended.

Vulnerable:

#!/usr/bin/env bash
# demo3-bad.sh
tmp=/tmp/myapp.log
echo "report at $(date)" > "$tmp"
echo "Wrote $tmp"

Safer:

#!/usr/bin/env bash
# demo3-good.sh
set -euo pipefail
umask 077  # Restrictive defaults: files 600, dirs 700
tmp=$(mktemp -t myapp.XXXXXX)
trap 'rm -f "$tmp"' EXIT
printf 'report at %s\n' "$(date)" > "$tmp"
echo "Wrote $tmp (will be removed on exit)"

Key takeaways:

  • Use mktemp for unique files/dirs (mktemp -d for directories).

  • Set a restrictive umask (e.g., 077) to avoid world-readable secrets.

  • Clean up with trap ... EXIT.


4) PATH hijacking and untrusted environment

The risk:

  • If your script trusts $PATH, an attacker can place a malicious binary earlier in the path (or in .) and your script will run it.

Vulnerable:

#!/usr/bin/env bash
# demo4-bad.sh
PATH=.:$PATH           # Don't do this
tar czf /tmp/backup.tgz /var/log

Safer (set a known-good PATH and/or use absolute paths):

#!/usr/bin/env bash
# demo4-good.sh
set -euo pipefail
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
TAR=$(command -v tar) || { echo "tar not found" >&2; exit 1; }
"$TAR" czf /tmp/backup.tgz -- /var/log

Key takeaways:

  • Never prepend . to PATH.

  • Set a minimal, known-safe PATH at script start.

  • Resolve tools with command -v or use absolute paths.


5) Leaking secrets via args and debug traces

The risk:

  • set -x (xtrace) prints commands and their expanded arguments, exposing tokens/passwords in logs.

  • Secrets in command-line args may be visible to other processes via ps (depending on system/policy).

Vulnerable:

#!/usr/bin/env bash
# demo5-bad.sh
set -x
token=$1
curl -H "Authorization: Bearer $token" https://api.example.com/me

Safer:

#!/usr/bin/env bash
# demo5-good.sh
set -euo pipefail
set +x  # ensure xtrace is off before handling secrets
token=$1

# Avoid logging the token; prefer config, files with 600 perms, or stdin where possible.
curl -sS -H "Authorization: Bearer $token" https://api.example.com/me > /tmp/me.json

# If you must re-enable xtrace later:
# set -x

Further hardening ideas:

  • Avoid placing secrets in command-line args; consider reading from stdin (read -rs secret) and passing via headers or files with chmod 600.

  • Keep debug logs separated from production runs; never enable set -x in secret-handling code paths.


Bonus: Guardrails to put in every script

Drop this near the top of your scripts:

#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

# Optional: minimal, known-safe PATH
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# Cleanup example
# tmp=$(mktemp -t myapp.XXXXXX); trap 'rm -f "$tmp"' EXIT

Why it helps:

  • -e stops on errors; -u fails on unset vars; pipefail propagates failures in pipelines.

  • A strict IFS reduces accidental word splitting.

  • A known PATH limits hijacking risk.


Linting: let ShellCheck catch mistakes early

Run:

shellcheck demo1-bad.sh

ShellCheck will point out:

  • Unquoted variables

  • Useless/unsafe eval

  • Word splitting issues

  • Globbing pitfalls

  • Many more best practices

This is the fastest, highest-ROI step you can take today.


Conclusion and next steps (CTA)

  • Pick one production script you rely on.

  • Add set -euo pipefail, quote every variable, lock down PATH, and fix temp file handling with mktemp.

  • Run shellcheck and address its warnings.

  • Repeat for your other scripts; make this your team’s standard.

Security in Bash isn’t about writing less shell—it’s about writing it deliberately. Start with the five pitfalls above, bake the guardrails into your templates, and let ShellCheck keep you honest. Your future self (and your incident report) will thank you.