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.
Eine der größten Gefahren im Shell-Scripting ist die unzureichende Validierung und Verarbeitung von Benutzereingaben. Dies kann zu verschiedenen Angriffsszenarien führen:
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 $dateinameBei 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.
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.
Shell-Skripte verwenden häufig temporäre Dateien zur Zwischenspeicherung von Daten. Unsichere Implementierungen können zu Sicherheitslücken führen:
Die Verwendung vorhersehbarer Namen für temporäre Dateien ist problematisch:
#!/bin/bash
TEMP_FILE="/tmp/myscript_temp.txt"
# Daten in TEMP_FILE schreibenDiese 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).
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!Der sorglose Umgang mit sensiblen Daten ist ein weiteres wesentliches Sicherheitsrisiko:
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"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.
Shell-Skripte nutzen häufig Umgebungsvariablen, die eigene Risiken mit sich bringen:
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_FORMATJe nach Wert von $LANG oder anderen Umgebungsvariablen
könnten Befehle unterschiedliche Ausgaben erzeugen.
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 /tmpWenn ein Angreifer einen bösartigen ls-Befehl in einem
vorrangigen Verzeichnis im PATH platziert, wird dieser statt des echten
Befehls ausgeführt.
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 warWenn der Löschbefehl fehlschlägt, könnten nachfolgende Operationen zu unerwarteten Ergebnissen führen oder Sicherheitslücken schaffen.
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.
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"
fiZwischen der Überprüfung und dem Schreiben könnte ein Angreifer eine symbolische Verknüpfung erstellen, die auf eine kritische Systemdatei zeigt.
Das unkontrollierte Entpacken von Archiven kann zu Sicherheitsproblemen führen:
#!/bin/bash
# FALSCH: Unsicheres Entpacken ohne Pfadvalidierung
tar -xf archiv.tarArchive können absolute Pfade oder ../-Referenzen
enthalten, die es ermöglichen, Dateien an beliebigen Stellen im
Dateisystem zu erstellen oder zu überschreiben.
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.
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.
Die erste Verteidigungslinie ist eine gründliche Validierung aller Eingabedaten:
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
fiBei 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
fiDie Ü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
fiKorrektes Escaping und Quoting sind entscheidend, um zu verhindern, dass Benutzereingaben als Befehle interpretiert werden:
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 $fileprintfDie 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"Die Bash bietet spezielle Funktionen, die bei der sicheren Handhabung von Eingaben helfen können:
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}"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
Manchmal ist es besser, Alternativen zur direkten Befehlsausführung mit Benutzereingaben zu finden:
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
;;
esacArrays 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
fiEinige Bash-Konstrukte sollten bei der Verarbeitung von Benutzereingaben generell vermieden werden:
evalDie 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.
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#!/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#!/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
fiDie sichere Handhabung von Benutzereingaben ist eine wesentliche Voraussetzung für sichere Shell-Skripte. Die wichtigsten Prinzipien sind:
eval, Backticks und
Befehlssubstitution.set -euo pipefail, um das Skriptverhalten sicherer zu
machen.case-Anweisungen oder Mapping-Arrays anstelle direkter
Befehlsausführung mit Benutzereingaben.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.
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
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.shOder 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.shWichtig: Stellen Sie sicher, dass die Datei mit den Umgebungsvariablen restriktive Berechtigungen hat:
chmod 600 .env # Nur der Eigentümer kann lesen und schreibenEine 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"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"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)Wenn die interaktive Eingabe von Passwörtern notwendig ist, sollte dies sicher erfolgen:
read -sDie 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"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.
Für besonders schutzbedürftige Daten sollte eine Verschlüsselung in Betracht gezogen werden:
gpg zur VerschlüsselungGNU 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"openssl für einfache VerschlüsselungAls 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"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=""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.logDie Bash speichert standardmäßig ausgeführte Befehle in der History-Datei. Bei der Eingabe sensibler Daten sollte dies verhindert werden:
#!/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 historyBefehle, 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"#!/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=""#!/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=""Der sichere Umgang mit sensiblen Daten in Shell-Skripten erfordert besondere Aufmerksamkeit und spezielle Techniken:
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.
Bevor wir in die Details des Berechtigungsmanagements in Skripten einsteigen, ist es wichtig, die Grundlagen der Unix-Berechtigungen zu verstehen:
In Unix-basierten Systemen gibt es drei Arten von Berechtigungen:
r (read): Leserechtw (write): Schreibrechtx (execute): AusführungsrechtDiese Berechtigungen werden für drei Kategorien von Benutzern definiert:
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.shIn 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 ErgebnisDie Wahl der richtigen Berechtigungen für Shell-Skripte ist entscheidend für die Sicherheit:
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-xWenn das Skript vertrauliche Informationen enthält (z.B. Passwörter), sollte der Zugriff noch restriktiver sein:
chmod 700 mein_skript.sh # rwx------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.
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 erstelltEin 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.
Viele Aufgaben erfordern keine root-Rechte. Skripte sollten daher:
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."sudo mit EinschränkungenDer 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
Shell-Skripte sollten die Berechtigungen überprüfen und durchsetzen, um sicherzustellen, dass sie in einer sicheren Umgebung ausgeführt werden:
#!/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...#!/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
fiDie 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_programmWichtig: 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.
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...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."
fiFü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."
fiBestimmte Dateien und Verzeichnisse erfordern besondere Aufmerksamkeit:
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"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#!/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#!/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
fiDas Berechtigungsmanagement ist ein wesentlicher Aspekt der Sicherheit von Shell-Skripten. Die wichtigsten Prinzipien sind:
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.
Die Verwendung temporärer Dateien birgt verschiedene Sicherheitsrisiken:
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"
fiEin 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 überschriebenDie 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"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!mktempDer 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
mktempSie 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.a1b2c3d4mktempÄ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"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.
trap für die BereinigungMit 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 BereinigungWenn 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 BereinigungSelbst 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"Neben mktemp gibt es weitere Methoden, temporäre Dateien
zu handhaben.
/proc/self/fd-VerzeichnissesAuf 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.
process substitution für temporäre DatenProcess 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)/dev/shmAuf 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.
Das standardmäßige temporäre Verzeichnis /tmp ist auf
vielen Systemen für alle Benutzer zugänglich, was Sicherheitsrisiken mit
sich bringen kann.
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"/tmpVor 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"Temporäre Dateien können sensible Informationen enthalten, die unbeabsichtigt offengelegt werden könnten.
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öschenFü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.
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 trapDieses 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."Die sichere Handhabung temporärer Dateien und Verzeichnisse ist ein wichtiger Aspekt der Shell-Skript-Sicherheit. Die wichtigsten Prinzipien sind:
mktemp: Der
mktemp-Befehl ist die sicherste Methode, um temporäre
Dateien und Verzeichnisse zu erstellen.trap, um temporäre Ressourcen auch bei vorzeitiger
Beendigung des Skripts zu bereinigen./dev/shm
sicherer sein.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.
Code-Injection in Shell-Skripten tritt auf, wenn nicht vertrauenswürdige Eingaben (von Benutzern, Dateien oder Netzwerkquellen) als Shellcode interpretiert und ausgeführt werden.
Die häufigsten Stellen, an denen Code-Injection auftreten kann, sind:
Direkte Ausführung von Benutzereingaben
# UNSICHER: Direkte Ausführung einer Benutzereingabe
echo "Geben Sie einen Befehl ein:"
read command
eval "$command" # Extrem gefährlich!Unsichere Verwendung von eval
# UNSICHER: Dynamische Befehlskonstruktion mit eval
param="$(cat user_input.txt)"
eval "grep $param /etc/passwd" # Angreifer kann Befehle einschleusenCommand Substitution mit ungefilterter Eingabe
# UNSICHER: Befehlssubstitution mit Benutzereingabe
user_arg="$(cat user_input.txt)"
result=$(find . -name "$user_arg") # Angreifer kann Befehle einschleusenUnsichere dynamische Pfadkonstruktion
# UNSICHER: Dynamische Pfadkonstruktion ohne Validierung
username="$(cat user_input.txt)"
cat "/home/$username/data.txt" # Angreifer kann Pfadtraversierung durchführenDer 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 ;;
esacAlle 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"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 ExpansionArrays 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 ArgumenteEine 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[@]}"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
fiGlobbing (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 +fDie 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"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"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 sichererCSV-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.csvIn 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"#!/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#!/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
mainDie Prävention von Code-Injection-Angriffen ist ein entscheidender Aspekt der Shell-Skript-Sicherheit. Die wichtigsten Prinzipien sind:
eval, Backticks und
Befehlssubstitution.