17 Sicherheit im Shell-Scripting

17.1 Häufige Sicherheitsrisiken im Shell-Scripting

Shell-Skripte sind mächtige Werkzeuge zur Systemadministration und Automatisierung, aber sie können bei unsachgemäßer Handhabung schwerwiegende Sicherheitsrisiken darstellen. In diesem Abschnitt werden die häufigsten Sicherheitsrisiken und -schwachstellen im Shell-Scripting behandelt, die jeder Entwickler kennen sollte.

17.1.1 Unsichere Verarbeitung von Benutzereingaben

Eine der größten Gefahren im Shell-Scripting ist die unzureichende Validierung und Verarbeitung von Benutzereingaben. Dies kann zu verschiedenen Angriffsszenarien führen:

17.1.1.1 Command Injection

Command Injection tritt auf, wenn Benutzereingaben direkt in Shell-Befehlen verwendet werden, ohne dass eine angemessene Validierung oder Escaping stattfindet:

#!/bin/bash
echo "Welche Datei möchten Sie anzeigen?"
read dateiname
cat $dateiname

Bei diesem Beispiel könnte ein Angreifer als Eingabe "/etc/passwd; rm -rf /" verwenden, was dazu führen würde, dass nicht nur die Passwortdatei angezeigt, sondern auch der gefährliche Löschbefehl ausgeführt wird.

17.1.1.2 Path Traversal

Path Traversal-Angriffe ermöglichen Angreifern den Zugriff auf Dateien außerhalb des vorgesehenen Verzeichnisses:

#!/bin/bash
CONFIG_DIR="/app/config"
cat "$CONFIG_DIR/$1"

Ein Angreifer könnte "../../../etc/shadow" als Parameter übergeben, um auf die Shadow-Datei zuzugreifen, die Passwort-Hashes enthält.

17.1.2 Unsichere Temporäre Dateien

Shell-Skripte verwenden häufig temporäre Dateien zur Zwischenspeicherung von Daten. Unsichere Implementierungen können zu Sicherheitslücken führen:

17.1.2.1 Vorhersehbare Dateinamen

Die Verwendung vorhersehbarer Namen für temporäre Dateien ist problematisch:

#!/bin/bash
TEMP_FILE="/tmp/myscript_temp.txt"
# Daten in TEMP_FILE schreiben

Diese vorhersehbaren Namen können zu Race Conditions führen, wenn mehrere Instanzen des Skripts gleichzeitig ausgeführt werden, oder zu symbolischen Link-Angriffen (Symlink-Attacken).

17.1.2.2 Unzureichende Berechtigungen

Zu freizügige Berechtigungen für temporäre Dateien können dazu führen, dass vertrauliche Daten offengelegt werden:

#!/bin/bash
echo "geheime_daten" > /tmp/temp_file
chmod 666 /tmp/temp_file  # Lesbar für alle Benutzer!

17.1.3 Umgang mit sensiblen Informationen

Der sorglose Umgang mit sensiblen Daten ist ein weiteres wesentliches Sicherheitsrisiko:

17.1.3.1 Hardcodierte Credentials

Die direkte Einbettung von Passwörtern oder API-Schlüsseln in Skripten ist ein schwerwiegender Fehler:

#!/bin/bash
# FALSCH: Hardcodierte Anmeldeinformationen
MYSQL_USER="admin"
MYSQL_PASS="super_secret_password"
mysql -u $MYSQL_USER -p$MYSQL_PASS -e "SELECT * FROM users"

17.1.3.2 Kommandozeilenargumente mit sensiblen Daten

Die Übergabe von Passwörtern als Kommandozeilenargumente kann problematisch sein, da sie in der Prozessliste sichtbar sind:

#!/bin/bash
# FALSCH: Passwort als Kommandozeilenargument
mysql -u root -p"mein_passwort" -e "SELECT * FROM users"

Jeder Benutzer im System kann diese Informationen mit Befehlen wie ps aux einsehen.

17.1.4 Unsichere Umgebungsvariablen

Shell-Skripte nutzen häufig Umgebungsvariablen, die eigene Risiken mit sich bringen:

17.1.4.1 Lokalisierungsprobleme

Die Nutzung von lokalisierten Umgebungsvariablen kann zu unerwarteten Ergebnissen führen:

#!/bin/bash
# Dieses Skript kann je nach Spracheinstellung unterschiedlich funktionieren
ls | grep -i $DATE_FORMAT

Je nach Wert von $LANG oder anderen Umgebungsvariablen könnten Befehle unterschiedliche Ausgaben erzeugen.

17.1.4.2 PATH-Manipulation

Angreifer können die PATH-Variable manipulieren, um bösartige Versionen von Standardbefehlen auszuführen:

#!/bin/bash
# Das Skript verlässt sich auf den Befehl 'ls' ohne absoluten Pfad
ls /tmp

Wenn ein Angreifer einen bösartigen ls-Befehl in einem vorrangigen Verzeichnis im PATH platziert, wird dieser statt des echten Befehls ausgeführt.

17.1.5 Unzureichende Fehlerbehandlung

Die mangelnde Behandlung von Fehlern kann nicht nur zu Funktionsproblemen, sondern auch zu Sicherheitslücken führen:

#!/bin/bash
# FALSCH: Keine Überprüfung, ob Befehle erfolgreich waren
rm -rf /pfad/zum/verzeichnis
# Weitere Befehle, die davon ausgehen, dass das Löschen erfolgreich war

Wenn der Löschbefehl fehlschlägt, könnten nachfolgende Operationen zu unerwarteten Ergebnissen führen oder Sicherheitslücken schaffen.

17.1.6 Risiken durch Globbing und Expansion

Die automatische Expansion von Dateinamen und Pfaden (Globbing) kann unerwartete Sicherheitsrisiken bergen:

#!/bin/bash
# Gefährlich, wenn Dateinamen Leerzeichen oder spezielle Zeichen enthalten
rm -rf /pfad/zum/verzeichnis/*

Wenn eine Datei namens -rf / im Verzeichnis existiert, könnte das zu katastrophalen Folgen führen.

17.1.7 Race Conditions

Race Conditions treten auf, wenn mehrere Prozesse gleichzeitig auf dieselben Ressourcen zugreifen:

#!/bin/bash
if [ ! -e "$file" ]; then
    # Ein Angreifer könnte hier eingreifen und eine symbolische Verknüpfung erstellen
    echo "Daten" > "$file"
fi

Zwischen der Überprüfung und dem Schreiben könnte ein Angreifer eine symbolische Verknüpfung erstellen, die auf eine kritische Systemdatei zeigt.

17.1.8 Unsicheres Entpacken von Archiven

Das unkontrollierte Entpacken von Archiven kann zu Sicherheitsproblemen führen:

#!/bin/bash
# FALSCH: Unsicheres Entpacken ohne Pfadvalidierung
tar -xf archiv.tar

Archive können absolute Pfade oder ../-Referenzen enthalten, die es ermöglichen, Dateien an beliebigen Stellen im Dateisystem zu erstellen oder zu überschreiben.

17.1.9 Fehlende Berechtigungsprüfungen

Die Nichtüberprüfung von Benutzerberechtigungen kann zu unbeabsichtigten Privilegienerhöhungen führen:

#!/bin/bash
# FALSCH: Keine Überprüfung, ob der Benutzer die erforderlichen Rechte hat
rm -rf /var/www/html/*

Wenn das Skript mit root-Rechten ausgeführt wird, aber eigentlich nur von bestimmten Benutzern verwendet werden sollte, kann dies zu Sicherheitsproblemen führen.

Die Kenntnis dieser häufigen Sicherheitsrisiken ist der erste Schritt zur Erstellung sicherer Shell-Skripte. In den folgenden Abschnitten werden wir Best Practices und konkrete Techniken zur Absicherung von Shell-Skripten behandeln.

17.2 Sichere Handhabung von Benutzereingaben

Die sichere Verarbeitung von Benutzereingaben ist eine der wichtigsten Maßnahmen, um Shell-Skripte vor Angriffen zu schützen. In diesem Abschnitt werden verschiedene Techniken und Best Practices vorgestellt, die helfen, Benutzereingaben sicher zu handhaben und Command-Injection-Angriffe zu verhindern.

17.2.1 Validierung von Benutzereingaben

Die erste Verteidigungslinie ist eine gründliche Validierung aller Eingabedaten:

17.2.1.1 Überprüfung auf erlaubte Zeichen

Eine einfache, aber wirksame Methode besteht darin, Eingaben anhand einer Liste erlaubter Zeichen zu filtern:

#!/bin/bash
# Nur alphanumerische Zeichen erlauben
read -p "Geben Sie einen Dateinamen ein: " dateiname
if [[ ! $dateiname =~ ^[a-zA-Z0-9]+$ ]]; then
    echo "Fehler: Nur alphanumerische Zeichen sind erlaubt"
    exit 1
fi

17.2.1.2 Datentyp-Validierung

Bei numerischen Eingaben sollte geprüft werden, ob tatsächlich eine Zahl eingegeben wurde:

#!/bin/bash
read -p "Geben Sie eine Zahl ein: " zahl
if [[ ! $zahl =~ ^[0-9]+$ ]]; then
    echo "Fehler: Bitte geben Sie eine gültige Zahl ein"
    exit 1
fi

17.2.1.3 Längen- und Bereichsprüfung

Die Überprüfung der Länge und des Wertebereichs kann zusätzlichen Schutz bieten:

#!/bin/bash
read -p "Geben Sie einen Port ein (1-65535): " port
if ! [[ "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
    echo "Fehler: Ungültiger Port. Bitte geben Sie einen Wert zwischen 1 und 65535 ein."
    exit 1
fi

17.2.2 Escaping und Quoting

Korrektes Escaping und Quoting sind entscheidend, um zu verhindern, dass Benutzereingaben als Befehle interpretiert werden:

17.2.2.1 Verwenden von Anführungszeichen

Variablen sollten konsequent in doppelte Anführungszeichen gesetzt werden, um die Expansion von Metazeichen zu verhindern:

#!/bin/bash
# Richtig: Verwenden von Anführungszeichen
file="$1"
cat "$file"

# Falsch: Fehlende Anführungszeichen
# file=$1
# cat $file

17.2.2.2 Escaping mit printf

Die printf-Funktion bietet eine sichere Möglichkeit, mit potenziell gefährlichen Zeichenketten umzugehen:

#!/bin/bash
read -p "Geben Sie einen Text ein: " eingabe
sicherer_text=$(printf '%q' "$eingabe")
echo "Escaped Text: $sicherer_text"

17.2.3 Verwendung spezieller Bash-Features

Die Bash bietet spezielle Funktionen, die bei der sicheren Handhabung von Eingaben helfen können:

17.2.3.1 Parameter-Expansion für sichere Standardwerte

Die Parameter-Expansion kann verwendet werden, um Standardwerte festzulegen oder Fehler auszulösen, wenn eine Variable leer ist:

#!/bin/bash
# Fehler, wenn USER_INPUT leer ist
: "${USER_INPUT:?Variable ist leer oder nicht gesetzt}"

# Standardwert setzen, wenn USER_INPUT leer ist
SAFE_INPUT="${USER_INPUT:-Standardwert}"

17.2.3.2 Strict Mode aktivieren

Der Strict Mode hilft, viele typische Shell-Fehler zu erkennen:

#!/bin/bash
# Aktiviert den "Strict Mode"
set -euo pipefail
IFS=$'\n\t'

# Restliches Skript...

Diese Einstellung bewirkt: - set -e: Beendet das Skript, wenn ein Befehl fehlschlägt - set -u: Betrachtet undefinierte Variablen als Fehler - set -o pipefail: Gibt den Exit-Code des letzten fehlgeschlagenen Befehls in einer Pipeline zurück - IFS=$'\n\t': Setzt das Internal Field Separator auf Zeilenumbruch und Tab

17.2.4 Alternativen zu direkter Befehlsausführung

Manchmal ist es besser, Alternativen zur direkten Befehlsausführung mit Benutzereingaben zu finden:

17.2.4.1 Verwendung von Fallabfragen (case)

Anstatt Benutzereingaben direkt in Befehle einzubauen, können vorgegebene Optionen mit einer case-Anweisung behandelt werden:

#!/bin/bash
read -p "Was möchten Sie tun? (dateien/prozesse/speicher): " auswahl

case "$auswahl" in
    dateien)
        ls -la
        ;;
    prozesse)
        ps aux
        ;;
    speicher)
        free -m
        ;;
    *)
        echo "Ungültige Auswahl"
        exit 1
        ;;
esac

17.2.4.2 Array-Mapping statt direkter Ausführung

Arrays können verwendet werden, um Benutzereingaben auf sichere Aktionen abzubilden:

#!/bin/bash
declare -A befehle=(
    ["dateien"]="ls -la"
    ["prozesse"]="ps aux"
    ["speicher"]="free -m"
)

read -p "Was möchten Sie tun? (dateien/prozesse/speicher): " auswahl

if [[ -v befehle["$auswahl"] ]]; then
    eval "${befehle["$auswahl"]}"
else
    echo "Ungültige Auswahl"
    exit 1
fi

17.2.5 Vermeidung gefährlicher Konstrukte

Einige Bash-Konstrukte sollten bei der Verarbeitung von Benutzereingaben generell vermieden werden:

17.2.5.1 Vorsicht mit eval

Die Verwendung von eval mit Benutzereingaben ist äußerst riskant und sollte vermieden werden:

#!/bin/bash
# GEFÄHRLICH: Direktes eval mit Benutzereingabe
read -p "Geben Sie einen Befehl ein: " befehl
eval "$befehl"  # Extrem unsicher!

Falls eval unbedingt erforderlich ist, müssen vorher strenge Validierungen durchgeführt werden.

17.2.5.2 Vorsicht mit Backticks und Befehlssubstitution

Auch bei Backticks und Befehlssubstitution ist Vorsicht geboten:

#!/bin/bash
# GEFÄHRLICH: Befehlssubstitution mit Benutzereingabe
read -p "Geben Sie einen Dateinamen ein: " dateiname
anzahl=$(wc -l < "$dateiname")  # Sicherer: Eingabe als Dateiname, nicht als Befehl

17.2.6 Praktische Beispiele für sichere Eingabeverarbeitung

17.2.6.1 Sicheres Dateioperationsskript

#!/bin/bash
set -euo pipefail

# Funktion zur Validierung des Dateinamens
validate_filename() {
    local filename="$1"
    if [[ ! "$filename" =~ ^[a-zA-Z0-9_.-]+$ ]]; then
        echo "Fehler: Der Dateiname enthält ungültige Zeichen" >&2
        return 1
    fi
    
    if [[ "$filename" == /* || "$filename" == ../* || "$filename" == */../* ]]; then
        echo "Fehler: Absolute Pfade oder Directory Traversal sind nicht erlaubt" >&2
        return 1
    fi
    
    return 0
}

# Funktion zum sicheren Lesen einer Datei
safe_read_file() {
    local filename="$1"
    
    if ! validate_filename "$filename"; then
        return 1
    fi
    
    if [[ ! -f "$filename" ]]; then
        echo "Fehler: Die Datei existiert nicht" >&2
        return 1
    fi
    
    cat "$filename"
}

# Hauptprogramm
read -p "Geben Sie einen Dateinamen ein: " user_input

if safe_read_file "$user_input"; then
    echo "Datei wurde erfolgreich gelesen."
else
    echo "Fehler beim Lesen der Datei."
    exit 1
fi

17.2.6.2 Sicheres Benutzeroperationsskript

#!/bin/bash
set -euo pipefail

# Funktion zur Validierung des Benutzernamens
validate_username() {
    local username="$1"
    
    # Prüfen, ob der Benutzername nur erlaubte Zeichen enthält
    if [[ ! "$username" =~ ^[a-z][-a-z0-9]*$ ]]; then
        echo "Fehler: Der Benutzername enthält ungültige Zeichen" >&2
        return 1
    fi
    
    # Prüfen, ob der Benutzername zu lang ist
    if [ ${#username} -gt 32 ]; then
        echo "Fehler: Der Benutzername ist zu lang (max. 32 Zeichen)" >&2
        return 1
    fi
    
    # Prüfen, ob der Benutzername auf der Blacklist steht
    local blacklist=("root" "admin" "sudo" "daemon" "nobody")
    for blocked in "${blacklist[@]}"; do
        if [ "$username" = "$blocked" ]; then
            echo "Fehler: Der Benutzername ist nicht erlaubt" >&2
            return 1
        fi
    done
    
    return 0
}

# Funktion zur Anzeige von Benutzerinformationen
show_user_info() {
    local username="$1"
    
    if ! validate_username "$username"; then
        return 1
    fi
    
    # Sichere Methode zur Überprüfung, ob der Benutzer existiert
    if ! id "$username" &>/dev/null; then
        echo "Fehler: Benutzer existiert nicht" >&2
        return 1
    fi
    
    # Jetzt können wir sicher die Benutzerinformationen anzeigen
    id "$username"
    grep "^$username:" /etc/passwd | cut -d: -f5
}

# Hauptprogramm
read -p "Geben Sie einen Benutzernamen ein: " user_input

if show_user_info "$user_input"; then
    echo "Benutzerinformationen wurden erfolgreich angezeigt."
else
    echo "Fehler beim Anzeigen der Benutzerinformationen."
    exit 1
fi

17.2.7 Prinzipien zur sicheren Eingabeverarbeitung

Die sichere Handhabung von Benutzereingaben ist eine wesentliche Voraussetzung für sichere Shell-Skripte. Die wichtigsten Prinzipien sind:

  1. Validieren Sie alle Eingaben: Überprüfen Sie Eingaben auf Datentyp, erlaubte Zeichen, Länge und Wertebereich.
  2. Verwenden Sie konsequent Anführungszeichen: Setzen Sie alle Variablen in doppelte Anführungszeichen, um unerwünschte Expansion zu verhindern.
  3. Vermeiden Sie gefährliche Konstrukte: Seien Sie besonders vorsichtig mit eval, Backticks und Befehlssubstitution.
  4. Setzen Sie Sicherheitsoptionen: Verwenden Sie set -euo pipefail, um das Skriptverhalten sicherer zu machen.
  5. Bevorzugen Sie Alternativen: Verwenden Sie case-Anweisungen oder Mapping-Arrays anstelle direkter Befehlsausführung mit Benutzereingaben.

17.3 Umgang mit sensiblen Daten und Passwörtern

Der sichere Umgang mit sensiblen Daten wie Passwörtern, API-Schlüsseln und Zugangsdaten ist ein kritischer Aspekt der Shell-Scripting-Sicherheit. In diesem Abschnitt werden Methoden und Best Practices vorgestellt, die helfen, vertrauliche Informationen in Shell-Skripten zu schützen.

17.3.1 Vermeidung von Hardcoding sensibler Daten

Eine der häufigsten und gleichzeitig gefährlichsten Praktiken ist das Hardcoding von Credentials direkt im Skriptcode:

#!/bin/bash
# FALSCH: Hardcodierte Credentials
DB_USER="admin"
DB_PASS="super_secret_password"
mysql -u "$DB_USER" -p"$DB_PASS" -e "SELECT * FROM users"

Dieses Vorgehen birgt mehrere Risiken: - Jeder mit Zugriff auf das Skript kann die Credentials lesen - Die Credentials werden Teil der Versionskontrolle - Passwortänderungen erfordern Änderungen am Skript selbst

17.3.2 Sichere Alternativen für die Handhabung sensibler Daten

17.3.2.1 Verwendung von Umgebungsvariablen

Umgebungsvariablen bieten eine erste Verbesserung gegenüber hardcodierten Credentials:

#!/bin/bash
# Besserer Ansatz: Umgebungsvariablen
mysql -u "$DB_USER" -p"$DB_PASS" -e "SELECT * FROM users"

Die Umgebungsvariablen können vor dem Aufruf des Skripts gesetzt werden:

export DB_USER="admin"
export DB_PASS="super_secret_password"
./mein_skript.sh

Oder sie können in einer separaten, nicht versionierten Datei definiert werden:

# Datei: .env (nicht in der Versionskontrolle)
export DB_USER="admin"
export DB_PASS="super_secret_password"

Die dann vor der Ausführung des Skripts geladen wird:

source .env
./mein_skript.sh

Wichtig: Stellen Sie sicher, dass die Datei mit den Umgebungsvariablen restriktive Berechtigungen hat:

chmod 600 .env  # Nur der Eigentümer kann lesen und schreiben

17.3.2.2 Verwendung externer Konfigurationsdateien

Eine weitere Möglichkeit besteht darin, Credentials in einer separaten Konfigurationsdatei zu speichern:

#!/bin/bash
# Konfigurationsdatei einlesen
CONFIG_FILE="$HOME/.myapp/config"

if [[ -r "$CONFIG_FILE" ]]; then
    source "$CONFIG_FILE"
else
    echo "Fehler: Konfigurationsdatei nicht gefunden oder nicht lesbar" >&2
    exit 1
fi

# Jetzt können die Variablen DB_USER und DB_PASS verwendet werden
mysql -u "$DB_USER" -p"$DB_PASS" -e "SELECT * FROM users"

Die Konfigurationsdatei sollte ebenfalls restriktive Berechtigungen haben:

mkdir -p "$HOME/.myapp"
chmod 700 "$HOME/.myapp"
touch "$HOME/.myapp/config"
chmod 600 "$HOME/.myapp/config"

17.3.2.3 Verwendung von Passwort-/Schlüsselmanagern

Für eine noch sicherere Lösung können spezialisierte Passwort- oder Schlüsselmanager verwendet werden:

#!/bin/bash
# Passwort aus pass (Unix-Passwortmanager) holen
DB_PASS=$(pass database/mysql)

# Oder mit keyring
DB_PASS=$(secret-tool lookup service mysql username "$DB_USER")

mysql -u "$DB_USER" -p"$DB_PASS" -e "SELECT * FROM users"

17.3.2.4 Nutzung von Systemdiensten wie systemd-creds

Auf neueren Systemen mit systemd können Credentials mithilfe von systemd-creds sicher gespeichert werden:

# Credential erstellen (einmalig)
sudo systemd-creds encrypt --name=mysql-password super_secret_password

# Im Skript oder Service-File verwenden
DB_PASS=$(systemd-creds cat mysql-password)

17.3.3 Sichere Eingabe von Passwörtern

Wenn die interaktive Eingabe von Passwörtern notwendig ist, sollte dies sicher erfolgen:

17.3.3.1 Verwendung von read -s

Die Option -s des read-Befehls verhindert, dass die Eingabe auf dem Bildschirm angezeigt wird:

#!/bin/bash
read -sp "Bitte geben Sie das Datenbankpasswort ein: " DB_PASS
echo  # Neue Zeile nach der Passwortabfrage

mysql -u "$DB_USER" -p"$DB_PASS" -e "SELECT * FROM users"

17.3.3.2 Vermeidung von Passwörtern auf der Kommandozeile

Die Übergabe von Passwörtern direkt als Kommandozeilenargumente sollte vermieden werden, da diese im Prozesslisting sichtbar sind:

# FALSCH: Passwort als Kommandozeilenargument
mysql -u root -p"mein_passwort" -e "SELECT * FROM users"

# RICHTIG: Passwort über STDIN oder Umgebungsvariable
MYSQL_PWD="$DB_PASS" mysql -u root -e "SELECT * FROM users"
# oder
echo "$DB_PASS" | mysql -u root -p -e "SELECT * FROM users"

Hinweis: Auch die Verwendung von Umgebungsvariablen wie MYSQL_PWD birgt Risiken, da diese in bestimmten Debugausgaben sichtbar sein können. Die beste Methode hängt vom spezifischen Anwendungsfall ab.

17.3.4 Verschlüsselung sensibler Daten

Für besonders schutzbedürftige Daten sollte eine Verschlüsselung in Betracht gezogen werden:

17.3.4.1 Verwendung von gpg zur Verschlüsselung

GNU Privacy Guard (GPG) kann zum Verschlüsseln und Entschlüsseln sensibler Daten verwendet werden:

#!/bin/bash
# Passwort entschlüsseln
DB_PASS=$(gpg --quiet --batch --decrypt "$HOME/.myapp/db_password.gpg")

mysql -u "$DB_USER" -p"$DB_PASS" -e "SELECT * FROM users"

Das Passwort kann zuvor verschlüsselt und in einer Datei gespeichert werden:

echo "super_secret_password" | gpg --encrypt --recipient your@email.com > "$HOME/.myapp/db_password.gpg"
chmod 600 "$HOME/.myapp/db_password.gpg"

17.3.4.2 Verwendung von openssl für einfache Verschlüsselung

Als Alternative kann openssl für eine passwortbasierte Verschlüsselung verwendet werden:

#!/bin/bash
# Passwort entschlüsseln
read -sp "Entschlüsselungspasswort eingeben: " DECRYPT_PASS
echo

DB_PASS=$(openssl enc -aes-256-cbc -d -in "$HOME/.myapp/db_password.enc" -pass pass:"$DECRYPT_PASS")

mysql -u "$DB_USER" -p"$DB_PASS" -e "SELECT * FROM users"

Zum Verschlüsseln des Passworts:

echo "super_secret_password" | openssl enc -aes-256-cbc -salt -out "$HOME/.myapp/db_password.enc"
chmod 600 "$HOME/.myapp/db_password.enc"

17.3.5 Speichermanagement für sensible Daten

Nach der Verwendung sensibler Daten sollten diese so schnell wie möglich aus dem Speicher entfernt werden:

#!/bin/bash
read -sp "Passwort eingeben: " DB_PASS
echo

# Passwort verwenden...
mysql -u "$DB_USER" -p"$DB_PASS" -e "SELECT * FROM users"

# Passwort aus dem Speicher entfernen
DB_PASS="$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64)"
DB_PASS=""

17.3.6 Vermeidung von Logging sensibler Daten

Shell-Skripte sollten keine sensiblen Daten in Log-Dateien schreiben:

#!/bin/bash
# FALSCH: Sensible Daten werden geloggt
echo "Verbinde mit dem Datenbankserver als $DB_USER mit Passwort $DB_PASS" >> /var/log/myapp.log

# RICHTIG: Keine sensiblen Daten im Log
echo "Verbinde mit dem Datenbankserver als $DB_USER" >> /var/log/myapp.log

17.3.7 Vermeidung der Shell-History

Die Bash speichert standardmäßig ausgeführte Befehle in der History-Datei. Bei der Eingabe sensibler Daten sollte dies verhindert werden:

17.3.7.1 Temporäres Deaktivieren der History

#!/bin/bash
# History temporär deaktivieren
HISTFILE=""
# oder
set +o history

# Sensible Operation durchführen...
read -sp "API-Key eingeben: " API_KEY

# History wieder aktivieren, falls gewünscht
set -o history

17.3.7.2 Verwendung von Leerzeichen vor dem Befehl

Befehle, die mit einem Leerzeichen beginnen, werden in der Standardkonfiguration nicht in der History gespeichert:

# Das führende Leerzeichen verhindert, dass der Befehl in die History aufgenommen wird
 export API_KEY="mein_geheimer_schlüssel"

17.3.8 Praktische Beispiele

17.3.8.1 Sicheres Datenbankabfrage-Skript

#!/bin/bash
set -euo pipefail

# Funktion zum sicheren Lesen des Datenbankpassworts
read_db_password() {
    local password_file="$HOME/.myapp/db_password.gpg"
    
    if [[ -f "$password_file" ]]; then
        # Verschlüsseltes Passwort aus Datei lesen
        if ! gpg --quiet --batch --decrypt "$password_file" 2>/dev/null; then
            echo "Fehler: Konnte das Datenbankpasswort nicht entschlüsseln" >&2
            return 1
        fi
    else
        # Interaktive Eingabe, wenn keine Passwortdatei vorhanden
        local password
        read -sp "Bitte geben Sie das Datenbankpasswort ein: " password
        echo  # Neue Zeile
        echo "$password"
    fi
}

# Konfiguration aus sicherer Quelle laden
CONFIG_FILE="$HOME/.myapp/config"
if [[ -r "$CONFIG_FILE" ]]; then
    source "$CONFIG_FILE"
else
    echo "Fehler: Konfigurationsdatei nicht gefunden" >&2
    exit 1
fi

# Überprüfen, ob erforderliche Variablen gesetzt sind
: "${DB_HOST:?ist nicht gesetzt}"
: "${DB_NAME:?ist nicht gesetzt}"
: "${DB_USER:?ist nicht gesetzt}"

# Passwort sicher lesen
DB_PASS=$(read_db_password) || exit 1

# Datenbank abfragen
result=$(MYSQL_PWD="$DB_PASS" mysql -h "$DB_HOST" -u "$DB_USER" -D "$DB_NAME" -sN -e "SELECT COUNT(*) FROM users")

# Ergebnis anzeigen und sensible Daten aus dem Speicher entfernen
echo "Anzahl der Benutzer: $result"
DB_PASS="$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64)"
DB_PASS=""

17.3.8.2 Sicheres API-Kommunikationsskript

#!/bin/bash
set -euo pipefail

# Konfiguration aus sicherer Quelle laden
if [[ -r "$HOME/.myapp/api_config" ]]; then
    source "$HOME/.myapp/api_config"
else
    # API-Schlüssel interaktiv abfragen, wenn keine Konfigurationsdatei vorhanden
    HISTFILE=""  # History temporär deaktivieren
    read -sp "Bitte geben Sie den API-Schlüssel ein: " API_KEY
    echo  # Neue Zeile
    
    read -p "Möchten Sie den API-Schlüssel für zukünftige Verwendung speichern? (j/n): " save_key
    if [[ "$save_key" == "j" ]]; then
        mkdir -p "$HOME/.myapp"
        chmod 700 "$HOME/.myapp"
        echo "API_KEY=\"$API_KEY\"" > "$HOME/.myapp/api_config"
        chmod 600 "$HOME/.myapp/api_config"
        echo "API-Schlüssel wurde sicher gespeichert."
    fi
fi

# Überprüfen, ob der API-Schlüssel gesetzt ist
: "${API_KEY:?ist nicht gesetzt}"

# API-Anfrage durchführen
response=$(curl -s -H "Authorization: Bearer $API_KEY" "https://api.example.com/data")

# Ergebnis anzeigen und sensible Daten aus dem Speicher entfernen
echo "API-Antwort erhalten (Länge: ${#response} Zeichen)"
API_KEY="$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64)"
API_KEY=""

17.3.9 Prinzipien

Der sichere Umgang mit sensiblen Daten in Shell-Skripten erfordert besondere Aufmerksamkeit und spezielle Techniken:

  1. Vermeiden Sie Hardcoding: Speichern Sie keine sensiblen Daten direkt im Skriptcode.
  2. Verwenden Sie spezialisierte Lösungen: Nutzen Sie Passwortmanager, verschlüsselte Dateien oder System-Credentials.
  3. Schützen Sie Konfigurationsdateien: Stellen Sie sicher, dass Dateien mit sensiblen Informationen restriktive Berechtigungen haben.
  4. Vermeiden Sie die Kommandozeile: Übergeben Sie Passwörter nicht als Kommandozeilenargumente.
  5. Bereinigen Sie den Speicher: Löschen Sie sensible Daten nach der Verwendung aus dem Speicher.
  6. Vermeiden Sie Logging: Schreiben Sie keine sensiblen Daten in Logs oder die Shell-History.

17.4 Berechtigungsmanagement in Skripten

Die korrekte Verwaltung von Zugriffsrechten ist ein entscheidender Aspekt für die Sicherheit von Shell-Skripten. In diesem Abschnitt werden wir untersuchen, wie Datei- und Ausführungsberechtigungen sicher gehandhabt werden können, wie man das Prinzip der geringsten Privilegien umsetzt und wie man sich vor unbefugtem Zugriff schützt.

17.4.1 Grundlagen der Unix-Berechtigungen

Bevor wir in die Details des Berechtigungsmanagements in Skripten einsteigen, ist es wichtig, die Grundlagen der Unix-Berechtigungen zu verstehen:

17.4.1.1 Berechtigungsarten

In Unix-basierten Systemen gibt es drei Arten von Berechtigungen:

Diese Berechtigungen werden für drei Kategorien von Benutzern definiert:

17.4.1.2 Berechtigungen anzeigen und setzen

Die Berechtigungen einer Datei können mit ls -l angezeigt werden:

$ ls -l mein_skript.sh
-rwxr-xr-- 1 benutzer gruppe 1234 Apr 13 10:00 mein_skript.sh

In diesem Beispiel hat der Eigentümer Lese-, Schreib- und Ausführungsrechte (rwx), die Gruppe hat Lese- und Ausführungsrechte (r-x), und andere Benutzer haben nur Leserechte (r--).

Mit dem chmod-Befehl können Berechtigungen gesetzt werden:

# Numerische Notation
chmod 750 mein_skript.sh  # rwxr-x---

# Symbolische Notation
chmod u=rwx,g=rx,o= mein_skript.sh  # Gleiches Ergebnis

17.4.2 Sichere Berechtigungen für Skriptdateien

Die Wahl der richtigen Berechtigungen für Shell-Skripte ist entscheidend für die Sicherheit:

17.4.2.1 Empfohlene Berechtigungen für Skriptdateien

Für die meisten Shell-Skripte sind folgende Berechtigungen empfehlenswert:

chmod 700 mein_skript.sh  # rwx------

Dies bedeutet: - Der Eigentümer kann das Skript lesen, ändern und ausführen - Niemand sonst hat Zugriff auf das Skript

Wenn das Skript von anderen Benutzern ausgeführt werden soll, aber nicht geändert werden darf:

chmod 755 mein_skript.sh  # rwxr-xr-x

Wenn das Skript vertrauliche Informationen enthält (z.B. Passwörter), sollte der Zugriff noch restriktiver sein:

chmod 700 mein_skript.sh  # rwx------

17.4.2.2 Vermeidung von zu freizügigen Berechtigungen

Zu freizügige Berechtigungen können schwerwiegende Sicherheitsrisiken darstellen:

# FALSCH: Zu freizügige Berechtigungen
chmod 777 mein_skript.sh  # rwxrwxrwx - Jeder kann die Datei ändern!

Solche Berechtigungen ermöglichen es jedem Benutzer, das Skript zu ändern und potenziell schädlichen Code einzufügen.

17.4.3 Umask-Einstellungen in Skripten

Die umask legt die Standardberechtigungen für neu erstellte Dateien und Verzeichnisse fest. In Shell-Skripten kann die umask angepasst werden, um sicherzustellen, dass neue Dateien mit geeigneten Berechtigungen erstellt werden:

#!/bin/bash

# Sichere umask setzen: Keine Rechte für "others"
umask 027  # Neue Dateien: rw-r-----

# Besonders restriktive umask
# umask 077  # Neue Dateien: rw-------

# Skript-Logik...

# Dateien erstellen
echo "Sensible Daten" > sensible_daten.txt  # Wird mit umask-Berechtigungen erstellt

17.4.4 Das Prinzip der geringsten Privilegien

Ein wichtiges Sicherheitsprinzip ist das “Principle of Least Privilege” (Prinzip der geringsten Privilegien), nach dem ein Prozess nur die Rechte haben sollte, die für seine Funktion unbedingt notwendig sind.

17.4.4.1 Vermeidung unnötiger root-Berechtigungen

Viele Aufgaben erfordern keine root-Rechte. Skripte sollten daher:

  1. Nicht mit root-Rechten ausgeführt werden, wenn es nicht notwendig ist
  2. Nur für bestimmte Befehle temporär auf höhere Rechte zugreifen

Ein Beispiel für die temporäre Erhöhung von Berechtigungen:

#!/bin/bash
# Dieses Skript führt die meisten Operationen als normaler Benutzer aus
# und verwendet sudo nur für den einen Befehl, der erhöhte Rechte benötigt

# Normale Operationen ohne erhöhte Rechte
echo "Starte Backup-Prozess..."
tar -czf "/tmp/backup_$(date +%F).tar.gz" "$HOME/wichtige_daten"

# Nur hier werden erhöhte Rechte verwendet
echo "Verschiebe Backup in das gesicherte Verzeichnis..."
sudo mv "/tmp/backup_$(date +%F).tar.gz" "/secure_backups/"

echo "Backup abgeschlossen."

17.4.4.2 Verwendung von sudo mit Einschränkungen

Der Einsatz von sudo kann weiter eingeschränkt werden, um nur bestimmte Befehle zu erlauben. Dies kann in der /etc/sudoers-Datei konfiguriert werden:

# Erlaubt dem Benutzer 'backup', nur das Skript backup.sh auszuführen
backup ALL=(root) NOPASSWD: /usr/local/bin/backup.sh

# Erlaubt Mitgliedern der Gruppe 'admins', nur bestimmte Befehle auszuführen
%admins ALL=(root) /sbin/restart apache2, /sbin/restart mysql

17.4.5 Überprüfung und Durchsetzung von Berechtigungen in Skripten

Shell-Skripte sollten die Berechtigungen überprüfen und durchsetzen, um sicherzustellen, dass sie in einer sicheren Umgebung ausgeführt werden:

17.4.5.1 Überprüfung der Benutzeridentität

#!/bin/bash

# Prüfen, ob das Skript als root ausgeführt wird
if [[ $EUID -ne 0 ]]; then
    echo "Dieses Skript muss als root ausgeführt werden." >&2
    exit 1
fi

# Skript-Logik für root...

Oder umgekehrt, um sicherzustellen, dass das Skript NICHT als root ausgeführt wird:

#!/bin/bash

# Prüfen, ob das Skript als normaler Benutzer ausgeführt wird
if [[ $EUID -eq 0 ]]; then
    echo "Dieses Skript darf nicht als root ausgeführt werden." >&2
    exit 1
fi

# Skript-Logik für normalen Benutzer...

17.4.5.2 Überprüfung und Durchsetzung von Dateiberechtigungen

#!/bin/bash

CONFIG_FILE="/etc/myapp/config.conf"

# Prüfen, ob die Konfigurationsdatei die richtigen Berechtigungen hat
if [[ "$(stat -c '%a' "$CONFIG_FILE")" != "600" ]]; then
    echo "Warnung: Die Konfigurationsdatei hat unsichere Berechtigungen. Korrigiere..." >&2
    chmod 600 "$CONFIG_FILE"
fi

# Prüfen, ob die Konfigurationsdatei dem richtigen Benutzer gehört
if [[ "$(stat -c '%U' "$CONFIG_FILE")" != "$(whoami)" ]]; then
    echo "Fehler: Die Konfigurationsdatei gehört nicht dem aktuellen Benutzer." >&2
    exit 1
fi

17.4.6 Setuid und Setgid: Mächtig, aber gefährlich

Die Setuid- und Setgid-Bits ermöglichen es, ein Programm mit den Rechten des Eigentümers oder der Gruppe (anstatt des ausführenden Benutzers) auszuführen. Diese Funktionen sind mächtig, aber auch gefährlich:

# Setuid-Bit setzen (Programm läuft als Eigentümer)
chmod u+s mein_programm

# Setgid-Bit setzen (Programm läuft mit der Gruppe der Datei)
chmod g+s mein_programm

Wichtig: Die Verwendung von Setuid für Shell-Skripte ist extrem gefährlich und wird von vielen Systemen nicht unterstützt. Stattdessen sollten Sie sudo mit entsprechenden Einschränkungen verwenden.

17.4.7 Berechtigungsprüfungen für Dateizugriffe

Ein sicheres Skript sollte die Berechtigungen für alle Dateizugriffe prüfen:

#!/bin/bash

INPUT_FILE="$1"
OUTPUT_FILE="$2"

# Prüfen, ob die Eingabedatei existiert und lesbar ist
if [[ ! -r "$INPUT_FILE" ]]; then
    echo "Fehler: Die Eingabedatei '$INPUT_FILE' existiert nicht oder ist nicht lesbar." >&2
    exit 1
fi

# Prüfen, ob die Ausgabedatei geschrieben werden kann
if [[ -e "$OUTPUT_FILE" && ! -w "$OUTPUT_FILE" ]]; then
    echo "Fehler: Die Ausgabedatei '$OUTPUT_FILE' existiert, ist aber nicht beschreibbar." >&2
    exit 1
fi

# Prüfen, ob das Verzeichnis der Ausgabedatei beschreibbar ist
output_dir="$(dirname "$OUTPUT_FILE")"
if [[ ! -w "$output_dir" ]]; then
    echo "Fehler: Das Verzeichnis '$output_dir' ist nicht beschreibbar." >&2
    exit 1
fi

# Verarbeitung durchführen...

17.4.8 Umgang mit speziellen Berechtigungen

17.4.8.1 Umgang mit Sticky Bit

Das Sticky Bit (t) wird oft für Verzeichnisse verwendet, um zu verhindern, dass Benutzer die Dateien anderer Benutzer löschen können:

# Sticky Bit für ein Verzeichnis setzen
chmod +t /shared/directory

# Überprüfen, ob ein Verzeichnis das Sticky Bit hat
if [[ "$(stat -c '%A' /tmp | cut -c10)" == "t" ]]; then
    echo "/tmp hat das Sticky Bit gesetzt."
fi

17.4.8.2 Access Control Lists (ACLs)

Für feinere Kontrolle über Berechtigungen können ACLs verwendet werden:

# ACL setzen
setfacl -m u:benutzer:rwx,g:gruppe:rx datei.txt

# ACL anzeigen
getfacl datei.txt

# Überprüfen, ob ein Benutzer eine bestimmte Berechtigung hat
if getfacl datei.txt | grep -q "^user:benutzer:rwx"; then
    echo "Benutzer hat Lese-, Schreib- und Ausführungsrechte."
fi

17.4.9 Sicherheitsrelevante Dateien und Verzeichnisse

Bestimmte Dateien und Verzeichnisse erfordern besondere Aufmerksamkeit:

17.4.9.1 Schutz von Konfigurationsdateien

Konfigurationsdateien sollten besonders geschützt werden:

#!/bin/bash

# Sicheres Erstellen einer Konfigurationsdatei
CONFIG_DIR="$HOME/.myapp"
CONFIG_FILE="$CONFIG_DIR/config.conf"

# Verzeichnis mit restriktiven Berechtigungen erstellen
mkdir -p "$CONFIG_DIR"
chmod 700 "$CONFIG_DIR"

# Konfigurationsdatei mit restriktiven Berechtigungen erstellen
echo "key=value" > "$CONFIG_FILE"
chmod 600 "$CONFIG_FILE"

17.4.9.2 Sichere Berechtigungen für Log-Dateien

Log-Dateien sollten so konfiguriert werden, dass sie geschrieben, aber nicht unbefugt gelesen werden können:

#!/bin/bash

LOG_DIR="/var/log/myapp"
LOG_FILE="$LOG_DIR/app.log"

# Verzeichnis mit geeigneten Berechtigungen erstellen
sudo mkdir -p "$LOG_DIR"
sudo chown "$(whoami):syslog" "$LOG_DIR"
sudo chmod 750 "$LOG_DIR"  # Eigentümer und Gruppe können lesen und durchsuchen

# Log-Datei mit geeigneten Berechtigungen erstellen
sudo touch "$LOG_FILE"
sudo chown "$(whoami):syslog" "$LOG_FILE"
sudo chmod 640 "$LOG_FILE"  # Eigentümer kann schreiben, Gruppe kann lesen

17.4.10 Praktische Beispiele

17.4.10.1 Skript mit sicherem Berechtigungsmanagement

#!/bin/bash
set -euo pipefail

# Konfiguration
APP_NAME="secureapp"
CONFIG_DIR="/etc/$APP_NAME"
DATA_DIR="/var/$APP_NAME"
LOG_FILE="/var/log/$APP_NAME.log"
SCRIPT_NAME="$(basename "$0")"

# Funktion zur Überprüfung der Root-Rechte
check_root() {
    if [[ $EUID -ne 0 ]]; then
        echo "$SCRIPT_NAME: Dieses Skript muss als root ausgeführt werden." >&2
        exit 1
    fi
}

# Funktion zur Überprüfung und Korrektur von Verzeichnisberechtigungen
ensure_dir_permissions() {
    local dir="$1"
    local owner="$2"
    local group="$3"
    local mode="$4"
    
    if [[ ! -d "$dir" ]]; then
        mkdir -p "$dir"
    fi
    
    # Berechtigungen korrigieren, wenn notwendig
    local current_owner="$(stat -c '%U' "$dir")"
    local current_group="$(stat -c '%G' "$dir")"
    local current_mode="$(stat -c '%a' "$dir")"
    
    if [[ "$current_owner" != "$owner" || "$current_group" != "$group" ]]; then
        chown "$owner:$group" "$dir"
    fi
    
    if [[ "$current_mode" != "$mode" ]]; then
        chmod "$mode" "$dir"
    fi
}

# Funktion zur Überprüfung und Korrektur von Dateiberechtigungen
ensure_file_permissions() {
    local file="$1"
    local owner="$2"
    local group="$3"
    local mode="$4"
    
    # Datei erstellen, wenn sie nicht existiert
    if [[ ! -f "$file" ]]; then
        touch "$file"
    fi
    
    # Berechtigungen korrigieren, wenn notwendig
    local current_owner="$(stat -c '%U' "$file")"
    local current_group="$(stat -c '%G' "$file")"
    local current_mode="$(stat -c '%a' "$file")"
    
    if [[ "$current_owner" != "$owner" || "$current_group" != "$group" ]]; then
        chown "$owner:$group" "$file"
    fi
    
    if [[ "$current_mode" != "$mode" ]]; then
        chmod "$mode" "$file"
    fi
}

# Hauptfunktion
main() {
    # Root-Rechte prüfen
    check_root
    
    # Sichere umask setzen
    umask 027
    
    # Verzeichnisse mit sicheren Berechtigungen erstellen/überprüfen
    ensure_dir_permissions "$CONFIG_DIR" "root" "root" "755"
    ensure_dir_permissions "$DATA_DIR" "$APP_NAME" "$APP_NAME" "750"
    
    # Log-Datei mit sicheren Berechtigungen erstellen/überprüfen
    ensure_file_permissions "$LOG_FILE" "$APP_NAME" "adm" "640"
    
    # Konfigurationsdatei mit sicheren Berechtigungen erstellen/überprüfen
    ensure_file_permissions "$CONFIG_DIR/settings.conf" "root" "$APP_NAME" "640"
    
    # Hier weitere Skript-Logik...
    echo "Alle Verzeichnisse und Dateien haben sichere Berechtigungen."
}

# Skript ausführen
main

17.4.10.2 Skript zum Überprüfen der Dateiberechtigungen

#!/bin/bash
set -euo pipefail

# Funktion zur Überprüfung von Dateiberechtigungen
check_file_permissions() {
    local file="$1"
    local expected_perms="$2"
    local actual_perms="$(stat -c '%a' "$file")"
    
    if [[ "$actual_perms" != "$expected_perms" ]]; then
        echo "WARNUNG: $file hat die Berechtigungen $actual_perms (erwartet: $expected_perms)"
        return 1
    fi
    
    return 0
}

# Funktion zur Überprüfung von Dateieigentümern
check_file_owner() {
    local file="$1"
    local expected_owner="$2"
    local actual_owner="$(stat -c '%U' "$file")"
    
    if [[ "$actual_owner" != "$expected_owner" ]]; then
        echo "WARNUNG: $file gehört $actual_owner (erwartet: $expected_owner)"
        return 1
    fi
    
    return 0
}

# Zielverzichnis aus Parameter oder aktuelles Verzeichnis
TARGET_DIR="${1:-.}"

# Kritische Dateien und ihre erwarteten Berechtigungen
declare -A expected_permissions=(
    ["config.conf"]="600"
    ["credentials.txt"]="600"
    ["private_key.pem"]="600"
    ["public_key.pem"]="644"
    ["script.sh"]="755"
)

# Kritische Dateien und ihre erwarteten Eigentümer
declare -A expected_owners=(
    ["config.conf"]="$(whoami)"
    ["credentials.txt"]="$(whoami)"
    ["private_key.pem"]="$(whoami)"
    ["public_key.pem"]="$(whoami)"
    ["script.sh"]="$(whoami)"
)

# Überprüfung durchführen
echo "Überprüfe Dateiberechtigungen in $TARGET_DIR..."
error_count=0

for file in "${!expected_permissions[@]}"; do
    if [[ -f "$TARGET_DIR/$file" ]]; then
        if ! check_file_permissions "$TARGET_DIR/$file" "${expected_permissions[$file]}"; then
            ((error_count++))
        fi
        
        if ! check_file_owner "$TARGET_DIR/$file" "${expected_owners[$file]}"; then
            ((error_count++))
        fi
    else
        echo "INFO: Datei $TARGET_DIR/$file existiert nicht"
    fi
done

# Ergebnis ausgeben
if [[ $error_count -eq 0 ]]; then
    echo "Alle überprüften Dateien haben die erwarteten Berechtigungen und Eigentümer."
    exit 0
else
    echo "Es wurden $error_count Probleme mit Dateiberechtigungen gefunden."
    exit 1
fi

17.4.11 Prinzipien der Berechtigungsmanagement

Das Berechtigungsmanagement ist ein wesentlicher Aspekt der Sicherheit von Shell-Skripten. Die wichtigsten Prinzipien sind:

  1. Verwenden Sie restriktive Berechtigungen: Datei- und Verzeichnisberechtigungen sollten so restriktiv wie möglich sein.
  2. Folgen Sie dem Prinzip der geringsten Privilegien: Führen Sie Skripte mit den geringstmöglichen Berechtigungen aus.
  3. Überprüfen Sie Berechtigungen: Implementieren Sie Berechtigungsprüfungen in Ihren Skripten.
  4. Seien Sie vorsichtig mit speziellen Berechtigungen: Vermeiden Sie Setuid/Setgid für Shell-Skripte.
  5. Schützen Sie sensible Dateien: Konfigurationsdateien, Credentials und Logs benötigen besondere Aufmerksamkeit.
  6. Verwenden Sie eine sichere umask: Setzen Sie eine angemessene umask, um sichere Standardberechtigungen zu gewährleisten.

17.5 Sichere temporäre Dateien und Verzeichnisse

Temporäre Dateien und Verzeichnisse sind in vielen Shell-Skripten unverzichtbar, bergen jedoch erhebliche Sicherheitsrisiken, wenn sie nicht korrekt gehandhabt werden. Dieser Abschnitt behandelt Techniken und Best Practices für die sichere Verwendung temporärer Ressourcen in Shell-Skripten.

17.5.1 Risiken bei der Verwendung temporärer Dateien

Die Verwendung temporärer Dateien birgt verschiedene Sicherheitsrisiken:

17.5.1.1 Race Conditions

Race Conditions entstehen, wenn zwischen der Prüfung, ob eine Datei existiert, und ihrer Erstellung oder Nutzung ein Zeitfenster liegt, in dem ein Angreifer eingreifen kann:

#!/bin/bash
# UNSICHER: Race Condition bei der Erstellung einer temporären Datei
temp_file="/tmp/myscript_temp.txt"

if [[ ! -e "$temp_file" ]]; then
    # Ein Angreifer könnte hier einen symbolischen Link einfügen
    # bevor die Datei erstellt wird
    echo "Sensible Daten" > "$temp_file"
fi

Ein Angreifer kann einen symbolischen Link mit dem Namen der erwarteten temporären Datei erstellen, der auf eine wichtige Systemdatei zeigt:

# Angreifer erstellt einen symbolischen Link
ln -s /etc/passwd /tmp/myscript_temp.txt

# Wenn das Skript nun in die Datei schreibt, wird die Passwortdatei überschrieben

17.5.1.3 Vorhersehbare Dateinamen

Die Verwendung vorhersehbarer Namen für temporäre Dateien erleichtert Angreifern das Ausnutzen von Schwachstellen:

#!/bin/bash
# UNSICHER: Vorhersehbarer Dateiname
temp_file="/tmp/backup_data.tmp"

17.5.1.4 Mangelnde Bereinigung

Wenn temporäre Dateien nach der Verwendung nicht ordnungsgemäß gelöscht werden, können sie vertrauliche Daten offenlegen:

#!/bin/bash
# UNSICHER: Keine Bereinigung der temporären Datei
temp_file="/tmp/sensitive_data.tmp"
echo "Passwort: secret123" > "$temp_file"
# Verarbeitung...
# Datei wird nicht gelöscht!

17.5.2 Sichere Erstellung temporärer Dateien

17.5.2.1 Verwendung von mktemp

Der mktemp-Befehl ist die sicherste Methode, um temporäre Dateien und Verzeichnisse zu erstellen:

#!/bin/bash
# SICHER: Verwendung von mktemp
temp_file=$(mktemp)
echo "Temporäre Datei: $temp_file"

# Datei verwenden...

# Bereinigen
rm -f "$temp_file"

Der mktemp-Befehl: - Generiert einen eindeutigen, nicht vorhersehbaren Dateinamen - Erstellt die Datei mit sicheren Berechtigungen (nur für den Benutzer lesbar/schreibbar) - Vermeidet Race Conditions, da die Datei atomisch erstellt wird

17.5.2.2 Anpassung des Dateinamens mit mktemp

Sie können mit mktemp auch ein Muster für den Dateinamen angeben:

#!/bin/bash
# Temporäre Datei mit spezifischem Präfix erstellen
temp_file=$(mktemp /tmp/myapp.XXXXXXXX)
echo "Temporäre Datei: $temp_file"

# Die Xs werden durch zufällige Zeichen ersetzt, z.B. /tmp/myapp.a1b2c3d4

17.5.2.3 Temporäre Verzeichnisse mit mktemp

Ähnlich können Sie sichere temporäre Verzeichnisse erstellen:

#!/bin/bash
# Temporäres Verzeichnis erstellen
temp_dir=$(mktemp -d)
echo "Temporäres Verzeichnis: $temp_dir"

# Verzeichnis verwenden...
touch "$temp_dir/file1.txt"
touch "$temp_dir/file2.txt"

# Bereinigen
rm -rf "$temp_dir"

17.5.3 Sicherstellung der Bereinigung temporärer Dateien

Ein häufiges Problem ist, dass temporäre Dateien nach der Beendigung eines Skripts nicht gelöscht werden, insbesondere wenn das Skript unerwartet beendet wird.

17.5.3.1 Verwendung von trap für die Bereinigung

Mit dem trap-Befehl können Sie sicherstellen, dass temporäre Dateien auch bei vorzeitiger Beendigung des Skripts gelöscht werden:

#!/bin/bash
set -euo pipefail

# Temporäre Datei erstellen
temp_file=$(mktemp)

# Bereinigungsfunktion definieren
cleanup() {
    echo "Bereinige temporäre Ressourcen..." >&2
    rm -f "$temp_file"
}

# Trap für normale Beendigung und Abbruch einrichten
trap cleanup EXIT INT TERM

# Skript-Logik...
echo "Arbeite mit temporärer Datei: $temp_file"
echo "Daten werden verarbeitet..." > "$temp_file"

# Skript endet hier, trap sorgt für die Bereinigung

17.5.3.2 Bereinigung mehrerer temporärer Dateien

Wenn Ihr Skript mehrere temporäre Dateien verwendet, können Sie diese in einem Array verwalten:

#!/bin/bash
set -euo pipefail

# Array für temporäre Dateien initialisieren
declare -a temp_files=()

# Bereinigungsfunktion definieren
cleanup() {
    echo "Bereinige temporäre Ressourcen..." >&2
    for file in "${temp_files[@]}"; do
        rm -f "$file"
    done
}

# Trap für Bereinigung einrichten
trap cleanup EXIT INT TERM

# Mehrere temporäre Dateien erstellen und dem Array hinzufügen
temp_file1=$(mktemp)
temp_files+=("$temp_file1")

temp_file2=$(mktemp)
temp_files+=("$temp_file2")

# Skript-Logik...
echo "Arbeite mit temporären Dateien: $temp_file1, $temp_file2"

# Skript endet hier, trap sorgt für die Bereinigung

17.5.4 Sicheres Berechtigungsmanagement für temporäre Dateien

Selbst mit mktemp kann es notwendig sein, die Berechtigungen explizit anzupassen:

#!/bin/bash
# Temporäre Datei erstellen
temp_file=$(mktemp)

# Berechtigungen auf "nur Eigentümer" einschränken
chmod 600 "$temp_file"

# Umask temporär auf restriktive Einstellung setzen
old_umask=$(umask)
umask 077  # Nur Eigentümer hat Zugriff

# Weitere temporäre Dateien werden mit restriktiven Berechtigungen erstellt
another_temp_file=$(mktemp)

# Umask zurücksetzen
umask "$old_umask"

# Bereinigen am Ende
rm -f "$temp_file" "$another_temp_file"

17.5.5 Alternative Methoden für temporäre Dateien

Neben mktemp gibt es weitere Methoden, temporäre Dateien zu handhaben.

17.5.5.1 Verwendung des /proc/self/fd-Verzeichnisses

Auf Linux-Systemen können Sie File-Deskriptoren verwenden, um temporäre Daten zu speichern, ohne eine Datei auf der Festplatte zu erstellen:

#!/bin/bash
# Öffnen eines File-Deskriptors für temporäre Daten
exec 3> >(cat > /dev/null)  # File-Deskriptor 3 öffnen

# In den File-Deskriptor schreiben
echo "Temporäre Daten" >&3

# Verarbeitung...

# File-Deskriptor schließen
exec 3>&-

Diese Methode ist nützlich für kleine Datenmengen und vermeidet das Schreiben auf die Festplatte.

17.5.5.2 Verwendung von process substitution für temporäre Daten

Process Substitution kann verwendet werden, um temporäre Daten zu verarbeiten, ohne sie in eine Datei zu schreiben:

#!/bin/bash
# Daten direkt in einen Prozess leiten, ohne temporäre Datei
grep "pattern" <(echo "Daten für die Suche")

# Oder für komplexere Verarbeitung
diff <(sort file1.txt) <(sort file2.txt)

17.5.5.3 RAM-basierte temporäre Dateien mit /dev/shm

Auf Linux-Systemen können Sie /dev/shm für temporäre Dateien im RAM verwenden:

#!/bin/bash
# Temporäre Datei im RAM erstellen
temp_file="/dev/shm/myapp_temp_$(date +%s)_$$"
touch "$temp_file"
chmod 600 "$temp_file"

# Datei verwenden...
echo "RAM-basierte temporäre Datei" > "$temp_file"

# Bereinigen
rm -f "$temp_file"

Dateien in /dev/shm werden im RAM gespeichert, was zusätzliche Sicherheit bietet, da sie bei einem Neustart automatisch gelöscht werden und nicht auf der Festplatte landen.

17.5.6 Sicherheitsprobleme in temporären Verzeichnissen

Das standardmäßige temporäre Verzeichnis /tmp ist auf vielen Systemen für alle Benutzer zugänglich, was Sicherheitsrisiken mit sich bringen kann.

17.5.6.1 Verwendung von privaten temporären Verzeichnissen

Es ist oft sicherer, ein benutzerspezifisches temporäres Verzeichnis zu verwenden:

#!/bin/bash
# Privates temporäres Verzeichnis erstellen
private_tmp="$HOME/.tmp"
mkdir -p "$private_tmp"
chmod 700 "$private_tmp"

# Temporäre Datei im privaten Verzeichnis erstellen
temp_file=$(mktemp "$private_tmp/myapp.XXXXXXXX")

# Datei verwenden...

# Bereinigen
rm -f "$temp_file"

17.5.6.2 Überprüfung der Sicherheit von /tmp

Vor der Verwendung des Standard-Temp-Verzeichnisses sollten Sie seine Konfiguration überprüfen:

#!/bin/bash
# Überprüfen, ob /tmp das Sticky Bit gesetzt hat
if [[ "$(stat -c '%a' /tmp)" != *"t"* && "$(stat -c '%a' /tmp)" != *"1"* ]]; then
    echo "Warnung: /tmp hat kein Sticky Bit gesetzt. Verwende alternatives Verzeichnis." >&2
    private_tmp="$HOME/.tmp"
    mkdir -p "$private_tmp"
    chmod 700 "$private_tmp"
    TMPDIR="$private_tmp"
fi

# Temporäre Datei erstellen (verwendet TMPDIR, wenn gesetzt)
temp_file=$(mktemp)

# Bereinigen
rm -f "$temp_file"

17.5.7 Vermeidung von Informationslecks in temporären Dateien

Temporäre Dateien können sensible Informationen enthalten, die unbeabsichtigt offengelegt werden könnten.

17.5.7.1 Verwendung von In-Memory-Dateisystemen

Wenn möglich, sollten Sie ein RAM-basiertes Dateisystem verwenden:

#!/bin/bash
# Überprüfen, ob /dev/shm verfügbar ist
if [[ -d "/dev/shm" && -w "/dev/shm" ]]; then
    TMPDIR="/dev/shm"
fi

# Temporäre Datei erstellen
temp_file=$(mktemp)

# Nach Verwendung sofort bereinigen
shred -u "$temp_file"  # Überschreiben und löschen

17.5.7.2 Sicheres Löschen sensibler temporärer Dateien

Für hochsensible Daten sollten Sie erwägen, die Dateien sicher zu überschreiben:

#!/bin/bash
temp_file=$(mktemp)

# Sensible Daten in die Datei schreiben
echo "Geheime Informationen" > "$temp_file"

# Verarbeitung...

# Sicheres Löschen mit shred
shred -u -z -n 3 "$temp_file"

Der shred-Befehl überschreibt die Datei mehrmals, bevor sie gelöscht wird, was die Wiederherstellung erschwert.

17.5.8 Praktische Beispiele

17.5.8.1 Beispiel 1: Sicheres Datenverarbeitungsskript

Dieses Beispiel zeigt ein Skript, das sicher temporäre Dateien für die Datenverarbeitung verwendet:

#!/bin/bash
set -euo pipefail

# Funktion zur Bereinigung temporärer Ressourcen
cleanup() {
    echo "Bereinige temporäre Ressourcen..." >&2
    if [[ -n "${temp_dir:-}" && -d "$temp_dir" ]]; then
        find "$temp_dir" -type f -exec shred -u {} \;
        rm -rf "$temp_dir"
    fi
}

# Trap für Bereinigung einrichten
trap cleanup EXIT INT TERM

# Temporäres Verzeichnis erstellen
temp_dir=$(mktemp -d)
chmod 700 "$temp_dir"
echo "Temporäres Verzeichnis: $temp_dir"

# Quelldatei
source_file="$1"
if [[ ! -r "$source_file" ]]; then
    echo "Fehler: Quelldatei '$source_file' ist nicht lesbar" >&2
    exit 1
fi

# Originaldaten in temporäres Verzeichnis kopieren
temp_source="$temp_dir/source.dat"
cp "$source_file" "$temp_source"
chmod 600 "$temp_source"

# Verarbeitete Ausgabedatei
temp_output="$temp_dir/processed.dat"

# Datenverarbeitung
echo "Verarbeite Daten..."
# Beispiel-Verarbeitung: Zeilen zählen und Wortstatistik
wc -l "$temp_source" > "$temp_output"
echo "---" >> "$temp_output"
tr -s '[:space:]' '\n' < "$temp_source" | sort | uniq -c | sort -nr >> "$temp_output"

# Ergebnisse anzeigen
echo "Ergebnisse:"
cat "$temp_output"

# Ergebnisse in Ausgabedatei speichern, wenn angegeben
if [[ $# -ge 2 ]]; then
    output_file="$2"
    cp "$temp_output" "$output_file"
    echo "Ergebnisse wurden in '$output_file' gespeichert."
fi

# Die Bereinigung erfolgt automatisch durch den trap

17.5.8.2 Beispiel 2: Sicheres Batch-Verarbeitungsskript

Dieses Beispiel zeigt ein Skript, das mehrere temporäre Dateien für die Batch-Verarbeitung verwendet:

#!/bin/bash
set -euo pipefail

# Funktion zur Bereinigung
cleanup() {
    echo "Bereinige temporäre Ressourcen..." >&2
    for file in "${temp_files[@]:-}"; do
        if [[ -f "$file" ]]; then
            rm -f "$file"
        fi
    done
    
    if [[ -n "${temp_dir:-}" && -d "$temp_dir" ]]; then
        rm -rf "$temp_dir"
    fi
}

# Array für temporäre Dateien initialisieren
declare -a temp_files=()

# Trap für Bereinigung einrichten
trap cleanup EXIT INT TERM

# Temporäres Verzeichnis erstellen
if [[ -d "/dev/shm" && -w "/dev/shm" ]]; then
    # RAM-basiertes Verzeichnis, wenn verfügbar
    base_temp="/dev/shm"
else
    # Ansonsten Standard-Temp-Verzeichnis
    base_temp="${TMPDIR:-/tmp}"
fi

# Eindeutigen Unterordner erstellen
temp_dir=$(mktemp -d "$base_temp/batch_XXXXXXXX")
chmod 700 "$temp_dir"
echo "Temporäres Verzeichnis: $temp_dir"

# Einrichtung des Arbeitsbereichs
input_dir="$1"
output_dir="$2"

if [[ ! -d "$input_dir" || ! -r "$input_dir" ]]; then
    echo "Fehler: Eingabeverzeichnis '$input_dir' existiert nicht oder ist nicht lesbar" >&2
    exit 1
fi

if [[ ! -d "$output_dir" ]]; then
    mkdir -p "$output_dir"
fi

# Für jede Datei im Eingabeverzeichnis
for input_file in "$input_dir"/*.txt; do
    if [[ ! -f "$input_file" ]]; then
        continue
    fi
    
    filename=$(basename "$input_file")
    echo "Verarbeite $filename..."
    
    # Temporäre Dateien für diesen Durchlauf
    temp_input="$temp_dir/$filename"
    temp_processed="$temp_dir/${filename%.txt}_processed.txt"
    temp_files+=("$temp_input" "$temp_processed")
    
    # Daten in temporäre Datei kopieren
    cp "$input_file" "$temp_input"
    chmod 600 "$temp_input"
    
    # Verarbeitung (Beispiel: Text in Großbuchstaben konvertieren)
    tr '[:lower:]' '[:upper:]' < "$temp_input" > "$temp_processed"
    
    # Ergebnis in Ausgabeverzeichnis speichern
    cp "$temp_processed" "$output_dir/${filename%.txt}_processed.txt"
    
    # Temporäre Dateien sofort löschen
    rm -f "$temp_input" "$temp_processed"
    temp_files=("${temp_files[@]/"$temp_input"/}")
    temp_files=("${temp_files[@]/"$temp_processed"/}")
done

echo "Verarbeitung abgeschlossen. Ergebnisse wurden in '$output_dir' gespeichert."

17.5.9 Prinzipien und Alternativen

Die sichere Handhabung temporärer Dateien und Verzeichnisse ist ein wichtiger Aspekt der Shell-Skript-Sicherheit. Die wichtigsten Prinzipien sind:

  1. Verwenden Sie mktemp: Der mktemp-Befehl ist die sicherste Methode, um temporäre Dateien und Verzeichnisse zu erstellen.
  2. Stellen Sie die Bereinigung sicher: Verwenden Sie trap, um temporäre Ressourcen auch bei vorzeitiger Beendigung des Skripts zu bereinigen.
  3. Setzen Sie restriktive Berechtigungen: Beschränken Sie den Zugriff auf temporäre Dateien auf den Eigentümer.
  4. Erwägen Sie alternative Speicherorte: Bei sensiblen Daten können RAM-basierte Dateisysteme wie /dev/shm sicherer sein.
  5. Vermeiden Sie vorhersehbare Dateinamen: Generieren Sie zufällige, nicht vorhersehbare Namen für temporäre Dateien.
  6. Berücksichtigen Sie Race Conditions: Verwenden Sie atomare Operationen, um Race Conditions zu vermeiden.
  7. Behandeln Sie sensible Daten mit besonderer Sorgfalt: Verwenden Sie sicheres Löschen für sensible temporäre Dateien.

17.6 Code-Injection-Prävention

Code-Injection-Angriffe gehören zu den gefährlichsten Bedrohungen für Shell-Skripte. Bei diesen Angriffen wird bösartiger Code in ein Skript eingeschleust und mit den Berechtigungen des Skripts ausgeführt. Dieser Abschnitt behandelt verschiedene Techniken und Best Practices, um Code-Injection-Angriffe in Shell-Skripten zu verhindern.

17.6.1 Verständnis von Code-Injection-Angriffen

Code-Injection in Shell-Skripten tritt auf, wenn nicht vertrauenswürdige Eingaben (von Benutzern, Dateien oder Netzwerkquellen) als Shellcode interpretiert und ausgeführt werden.

17.6.1.1 Typische Einfallstore für Code-Injection

Die häufigsten Stellen, an denen Code-Injection auftreten kann, sind:

  1. Direkte Ausführung von Benutzereingaben

    # UNSICHER: Direkte Ausführung einer Benutzereingabe
    echo "Geben Sie einen Befehl ein:"
    read command
    eval "$command"  # Extrem gefährlich!
  2. Unsichere Verwendung von eval

    # UNSICHER: Dynamische Befehlskonstruktion mit eval
    param="$(cat user_input.txt)"
    eval "grep $param /etc/passwd"  # Angreifer kann Befehle einschleusen
  3. Command Substitution mit ungefilterter Eingabe

    # UNSICHER: Befehlssubstitution mit Benutzereingabe
    user_arg="$(cat user_input.txt)"
    result=$(find . -name "$user_arg")  # Angreifer kann Befehle einschleusen
  4. Unsichere dynamische Pfadkonstruktion

    # UNSICHER: Dynamische Pfadkonstruktion ohne Validierung
    username="$(cat user_input.txt)"
    cat "/home/$username/data.txt"  # Angreifer kann Pfadtraversierung durchführen

17.6.2 Grundlegende Schutzmaßnahmen gegen Code-Injection

17.6.2.1 Vermeidung gefährlicher Konstrukte

Der effektivste Schutz ist die Vermeidung von gefährlichen Konstrukten wie eval, Backticks und ungefilterter Befehlssubstitution:

# SICHER: Vermeidung von eval
case "$user_input" in
    option1) do_option1 ;;
    option2) do_option2 ;;
    *) echo "Ungültige Option" >&2; exit 1 ;;
esac

17.6.2.2 Validierung aller Eingaben

Alle Eingaben sollten vor der Verwendung validiert werden:

#!/bin/bash
# Funktion zur Validierung von alphanumerischen Eingaben
validate_alphanumeric() {
    local input="$1"
    local pattern="^[a-zA-Z0-9]+$"
    
    if [[ ! "$input" =~ $pattern ]]; then
        echo "Fehler: Nur alphanumerische Zeichen sind erlaubt." >&2
        return 1
    fi
    return 0
}

# Benutzereingabe holen
read -p "Geben Sie einen Dateinamen ein: " user_input

# Eingabe validieren
if ! validate_alphanumeric "$user_input"; then
    exit 1
fi

# Jetzt kann die validierte Eingabe sicher verwendet werden
touch "$user_input.txt"

17.6.2.3 Korrekte Verwendung von Anführungszeichen

Die konsequente Verwendung von doppelten Anführungszeichen um Variablen ist entscheidend:

# UNSICHER: Fehlende Anführungszeichen
file=$1
rm -rf $file  # Gefährlich, wenn $file Leerzeichen oder Wildcards enthält

# SICHER: Mit Anführungszeichen
file="$1"
rm -rf "$file"  # Verhindert unbeabsichtigte Expansion

17.6.3 Fortgeschrittene Techniken zur Code-Injection-Prävention

17.6.3.1 Array-Verwendung statt Stringkonkatenation

Arrays bieten eine sichere Alternative zur String-Konkatenation für Befehlsargumente:

#!/bin/bash
# UNSICHER: Stringkonkatenation für Befehlsargumente
file="$1"
options="-la"
ls $options $file  # Problematisch, wenn $file oder $options Metazeichen enthalten

# SICHER: Array-Verwendung für Befehlsargumente
file="$1"
options=(-la)
ls "${options[@]}" "$file"  # Korrekte Behandlung der Argumente

17.6.3.2 Funktion zur sicheren Befehlsausführung

Eine spezielle Funktion kann verwendet werden, um Befehle sicher auszuführen:

#!/bin/bash
# Funktion zur sicheren Befehlsausführung
safe_execute() {
    local cmd="$1"
    shift
    local -a args=("$@")
    
    # Überprüfen, ob der Befehl in der Liste erlaubter Befehle ist
    case "$cmd" in
        ls|grep|find|cat|echo)
            "$cmd" "${args[@]}"
            return $?
            ;;
        *)
            echo "Fehler: Unerlaubter Befehl '$cmd'" >&2
            return 1
            ;;
    esac
}

# Beispielverwendung
read -p "Befehl eingeben (ls, grep, find, cat, echo): " user_cmd
read -p "Argumente eingeben: " -a user_args

# Sicher ausführen
safe_execute "$user_cmd" "${user_args[@]}"

17.6.3.3 Strikte Pfadvalidierung

Für die sichere Handhabung von Dateipfaden ist eine strikte Validierung erforderlich:

#!/bin/bash
# Funktion zur sicheren Pfadvalidierung
validate_path() {
    local path="$1"
    local base_dir="$2"
    
    # Normalisierter absoluter Pfad
    local abs_path
    abs_path="$(cd "$(dirname "$path")" && pwd)/$(basename "$path")"
    
    # Überprüfen, ob der normalisierte Pfad innerhalb des Basisverzeichnisses liegt
    if [[ "$abs_path" != "$base_dir"/* ]]; then
        echo "Fehler: Pfad '$path' liegt außerhalb des erlaubten Verzeichnisses" >&2
        return 1
    fi
    
    return 0
}

# Beispielverwendung
read -p "Dateiname eingeben: " user_file
BASE_DIR="/var/data"

if validate_path "$BASE_DIR/$user_file" "$BASE_DIR"; then
    cat "$BASE_DIR/$user_file"
else
    exit 1
fi

17.6.4 Spezifische Schutzmaßnahmen für häufige Angriffsvektoren

17.6.4.1 Schutz vor Globbing-Angriffen

Globbing (Wildcards) kann für Angriffe missbraucht werden, wenn es unsachgemäß mit Benutzereingaben verwendet wird:

#!/bin/bash
# UNSICHER: Direktes Globbing mit Benutzereingabe
find /tmp -name "*$user_input*"  # Gefährlich

# SICHER: Escaping für Globbing
find /tmp -name "*$(printf '%q' "$user_input")*"

Noch besser ist die Deaktivierung von Globbing für kritische Operationen:

#!/bin/bash
# Globbing temporär deaktivieren
set -f
find /tmp -name "*$user_input*"
# Globbing wieder aktivieren
set +f

17.6.4.2 Schutz vor IFS-Manipulation

Die IFS-Variable (Internal Field Separator) kann für Angriffe missbraucht werden:

#!/bin/bash
# IFS-Variable sichern und neu setzen
OLD_IFS="$IFS"
IFS=$'\n\t'  # Nur Zeilenumbruch und Tab als Trennzeichen verwenden

# Kritische Operation durchführen...

# IFS zurücksetzen
IFS="$OLD_IFS"

17.6.4.3 Schutz vor Pfadmanipulation

Die PATH-Variable kann manipuliert werden, um bösartige Versionen von Standardbefehlen auszuführen:

#!/bin/bash
# PATH sichern und auf sichere Standardwerte setzen
OLD_PATH="$PATH"
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

# Kritische Operation durchführen...

# PATH zurücksetzen
PATH="$OLD_PATH"

Noch besser ist die Verwendung absoluter Pfade für kritische Befehle:

#!/bin/bash
# Absolute Pfade für kritische Befehle verwenden
/bin/rm -f "$temp_file"
/usr/bin/find /var/log -type f -name "*.log"

17.6.5 Behandlung von Externdata und Dateiinhalten

Nicht nur Benutzereingaben, sondern auch Dateiinhalte können für Code-Injection missbraucht werden:

#!/bin/bash
# UNSICHER: Dateiinhalt direkt in Befehl einbauen
config_value=$(grep "^parameter=" config.txt | cut -d= -f2)
eval "result=$config_value"  # Gefährlich!

# SICHER: Validierung des Dateiinhalts
config_value=$(grep "^parameter=" config.txt | cut -d= -f2)
if [[ ! "$config_value" =~ ^[0-9]+$ ]]; then
    echo "Fehler: Ungültiger Konfigurationswert" >&2
    exit 1
fi
result=$((config_value))  # Arithmetische Expansion ist sicherer

17.6.5.1 Sichere Verarbeitung von CSV-Dateien

CSV-Dateien können besonders tückisch sein, da sie Zeilenumbrüche, Anführungszeichen und Trennzeichen enthalten können:

#!/bin/bash
# UNSICHER: Einfache Verarbeitung von CSV-Zeilen
while IFS=, read -r name value; do
    eval "$name=$value"  # Hochgradig unsicher!
done < data.csv

# SICHER: Robuste CSV-Verarbeitung
while IFS=, read -r name value; do
    # Strenge Validierung der Feldnamen und -werte
    if [[ ! "$name" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
        echo "Ungültiger Feldname: $name" >&2
        continue
    fi
    
    # Wert in einer Variable speichern, nicht eval verwenden
    declare "$name=$value"
done < data.csv

17.6.6 Umgang mit dynamisch generiertem Code

In einigen Fällen kann die Generierung und Ausführung von Code erforderlich sein. Dies sollte äußerst vorsichtig gehandhabt werden:

#!/bin/bash
# Funktion zur sicheren Generierung und Ausführung von Code
generate_safe_code() {
    local template="$1"
    local value="$2"
    
    # Strikte Validierung des Wertes
    if [[ ! "$value" =~ ^[a-zA-Z0-9_]+$ ]]; then
        echo "Fehler: Ungültiger Wert für Codegenerierung" >&2
        return 1
    fi
    
    # Code mit validiertem Wert generieren
    local code="${template//PLACEHOLDER/$value}"
    
    # Generierten Code anzeigen (für Debugging)
    echo "Ausführe folgenden Code:" >&2
    echo "$code" >&2
    
    # Code ausführen
    eval "$code"
}

# Beispielverwendung
template='
for i in {1..3}; do
    echo "Verarbeite PLACEHOLDER $i"
done
'

generate_safe_code "$template" "item"

17.6.7 Praktische Beispiele

17.6.7.1 Beispiel 1: Sicheres Dateiverarbeitungsskript

#!/bin/bash
set -euo pipefail

# Konfiguration
ALLOWED_EXTENSIONS=("txt" "csv" "log")
BASE_DIR="/var/data"

# Funktion zur Validierung des Dateinamens
validate_filename() {
    local filename="$1"
    
    # Prüfen auf leeren Dateinamen
    if [[ -z "$filename" ]]; then
        echo "Fehler: Dateiname darf nicht leer sein" >&2
        return 1
    fi
    
    # Prüfen auf erlaubte Zeichen im Dateinamen
    if [[ ! "$filename" =~ ^[a-zA-Z0-9._-]+$ ]]; then
        echo "Fehler: Dateiname enthält ungültige Zeichen" >&2
        return 1
    fi
    
    # Prüfen auf Directory Traversal
    if [[ "$filename" == *"/"* || "$filename" == *".."* ]]; then
        echo "Fehler: Directory Traversal nicht erlaubt" >&2
        return 1
    fi
    
    # Prüfen auf erlaubte Dateiendung
    local extension="${filename##*.}"
    local valid_ext=0
    
    for ext in "${ALLOWED_EXTENSIONS[@]}"; do
        if [[ "$extension" == "$ext" ]]; then
            valid_ext=1
            break
        fi
    done
    
    if [[ $valid_ext -eq 0 ]]; then
        echo "Fehler: Unerlaubte Dateiendung '$extension'" >&2
        return 1
    fi
    
    return 0
}

# Funktion zur sicheren Verarbeitung der Datei
process_file() {
    local filename="$1"
    local full_path="$BASE_DIR/$filename"
    
    # Prüfen, ob die Datei existiert und lesbar ist
    if [[ ! -f "$full_path" || ! -r "$full_path" ]]; then
        echo "Fehler: Datei existiert nicht oder ist nicht lesbar" >&2
        return 1
    fi
    
    # Dateistatistik ausgeben (sicherer Befehl)
    echo "Dateistatistik für '$filename':"
    wc -l "$full_path"
    
    # Inhalt anzeigen (sicherer Befehl)
    echo "Inhalt der Datei:"
    cat "$full_path"
    
    return 0
}

# Hauptfunktion
main() {
    # Benutzereingabe holen
    read -p "Geben Sie einen Dateinamen ein: " user_filename
    
    # Eingabe validieren
    if ! validate_filename "$user_filename"; then
        exit 1
    fi
    
    # Datei verarbeiten
    if ! process_file "$user_filename"; then
        exit 1
    fi
    
    echo "Verarbeitung erfolgreich abgeschlossen."
}

# Skript ausführen
main

17.6.7.2 Beispiel 2: Sicheres Befehlsausführungsskript

#!/bin/bash
set -euo pipefail

# Konfiguration
declare -A ALLOWED_COMMANDS=(
    ["list"]="/bin/ls -la"
    ["find"]="/usr/bin/find . -type f -name"
    ["count"]="/usr/bin/wc -l"
    ["search"]="/bin/grep -i"
)

# Funktion zur Validierung des Befehls
validate_command() {
    local command="$1"
    
    # Prüfen, ob der Befehl in der Liste erlaubter Befehle ist
    if [[ ! -v ALLOWED_COMMANDS["$command"] ]]; then
        echo "Fehler: Unbekannter oder nicht erlaubter Befehl '$command'" >&2
        echo "Erlaubte Befehle: ${!ALLOWED_COMMANDS[*]}" >&2
        return 1
    fi
    
    return 0
}

# Funktion zur Validierung des Arguments
validate_argument() {
    local arg="$1"
    local command="$2"
    
    # Allgemeine Validierung für alle Argumente
    if [[ "$arg" == *";"* || "$arg" == *"|"* || "$arg" == *"&"* || 
          "$arg" == *">"* || "$arg" == *"<"* || "$arg" == *"\`"* || 
          "$arg" == *"$("* || "$arg" == *")"* ]]; then
        echo "Fehler: Argument enthält ungültige Zeichen" >&2
        return 1
    fi
    
    # Spezifische Validierung je nach Befehl
    case "$command" in
        "find")
            # Für 'find' nur alphanumerische Zeichen, Unterstrich und Sternchen erlauben
            if [[ ! "$arg" =~ ^[a-zA-Z0-9_*]+$ ]]; then
                echo "Fehler: Ungültiges Suchmuster für 'find'" >&2
                return 1
            fi
            ;;
        "search")
            # Für 'search' nur einfache Textmuster erlauben
            if [[ ! "$arg" =~ ^[a-zA-Z0-9_[:space:]]+$ ]]; then
                echo "Fehler: Ungültiges Suchmuster für 'search'" >&2
                return 1
            fi
            ;;
    esac
    
    return 0
}

# Funktion zur sicheren Befehlsausführung
execute_command() {
    local command="$1"
    local argument="$2"
    
    # Befehlsvorlage holen
    local cmd_template="${ALLOWED_COMMANDS["$command"]}"
    
    # Befehl mit Argument kombinieren und ausführen
    if [[ -n "$argument" ]]; then
        # Befehl mit Argument
        echo "Führe aus: $cmd_template \"$argument\""
        eval "$cmd_template \"$argument\""
    else
        # Befehl ohne Argument
        echo "Führe aus: $cmd_template"
        eval "$cmd_template"
    fi
    
    return $?
}

# Hauptfunktion
main() {
    # Befehle anzeigen
    echo "Verfügbare Befehle:"
    for cmd in "${!ALLOWED_COMMANDS[@]}"; do
        echo "- $cmd"
    done
    
    # Benutzereingabe holen
    read -p "Wählen Sie einen Befehl: " user_command
    
    # Befehl validieren
    if ! validate_command "$user_command"; then
        exit 1
    fi
    
    # Argument holen, falls erforderlich
    argument=""
    if [[ "$user_command" == "find" || "$user_command" == "search" ]]; then
        read -p "Geben Sie ein Argument ein: " argument
        
        # Argument validieren
        if ! validate_argument "$argument" "$user_command"; then
            exit 1
        fi
    fi
    
    # Befehl ausführen
    if ! execute_command "$user_command" "$argument"; then
        echo "Fehler bei der Befehlsausführung" >&2
        exit 1
    fi
    
    echo "Befehl erfolgreich ausgeführt."
}

# Skript ausführen
main

17.6.8 Prinzipien

Die Prävention von Code-Injection-Angriffen ist ein entscheidender Aspekt der Shell-Skript-Sicherheit. Die wichtigsten Prinzipien sind:

  1. Vermeiden Sie gefährliche Konstrukte: Seien Sie besonders vorsichtig mit eval, Backticks und Befehlssubstitution.
  2. Validieren Sie alle Eingaben: Überprüfen Sie alle externen Eingaben auf gültige Formate und Zeichen.
  3. Verwenden Sie Anführungszeichen konsequent: Setzen Sie alle Variablen in doppelte Anführungszeichen, um unerwünschte Expansion zu verhindern.
  4. Nutzen Sie Arrays für Befehle mit Argumenten: Arrays bieten eine sichere Methode für die Handhabung von Befehlsargumenten.
  5. Implementieren Sie strikte Pfadvalidierung: Stellen Sie sicher, dass Pfade keine Directory Traversal ermöglichen.
  6. Schützen Sie sich vor Umgebungsmanipulation: Sichern und kontrollieren Sie wichtige Umgebungsvariablen wie PATH und IFS.
  7. Behandeln Sie Dateiinhalte als nicht vertrauenswürdig: Validieren Sie Dateiinhalte, bevor Sie sie verwenden.
  8. Beschränken Sie die Ausführung von dynamisch generiertem Code: Wenn dynamischer Code unvermeidbar ist, validieren Sie ihn streng.