von HISTCONTROL bis zur Prozessliste
Ein Passwort als Kommandozeilenargument übergeben, landet dauerhaft in der Bash History und ist für jeden sichtbar, der später dieselbe Shell nutzt. Dieser Artikel zeigt, wie Secrets in der Bash History, der Prozessliste und in Logs entstehen, wie HISTCONTROL und sichere Übergabewege das verhindern, und wie man bestehende History-Dateien nach vergessenen Zugangsdaten durchsucht.
Inhaltsverzeichnis
- 1. Wie Secrets unbeabsichtigt in der Bash History landen
- 2. HISTCONTROL, HISTIGNORE und das führende Leerzeichen
- 3. Secrets aus Umgebungsvariablen und der Prozessliste fernhalten
- 4. Sichere Übergabe von Zugangsdaten: stdin, Dateien mit 600, Named Pipes
- 5. History-Dateien absichern: Speicherort, Berechtigungen, Löschen
- 6. Secrets in Logs und Debugging-Ausgaben vermeiden
- 7. CI/CD-Variablen und Secrets-Manager statt Klartext im Skript
- 8. Auditing: bestehende History und Logs nach Secrets durchsuchen
- 9. Vergleich: unsichere vs. sichere Umgangsformen mit Secrets
- 10. Zusammenfassung
- 11. FAQ
1. Wie Secrets unbeabsichtigt in der Bash History landen
Die Bash History speichert standardmäßig jeden interaktiv eingegebenen Befehl, einschließlich sämtlicher Argumente. Wer ein Passwort, einen API-Schlüssel oder ein Token direkt als Kommandozeilenargument übergibt, etwa mysql -u root -pGeheimesPasswort, hinterlässt dieses Secret dauerhaft in der Datei ~/.bash_history, oft für Monate oder Jahre unbemerkt. Jeder, der später Zugriff auf diese Datei erhält, sei es durch physischen Zugriff, ein kompromittiertes Backup oder einen anderen Nutzer auf demselben System, kann das Secret im Klartext auslesen.
Das Problem verschärft sich in geteilten Umgebungen, in denen mehrere Administratoren dieselbe Shell nutzen oder Bastion Hosts als zentraler Zugangspunkt dienen. Eine einmal in der Bash History gespeicherte Zugangsdaten bleibt dort, bis sie explizit gelöscht wird, und viele Teams merken erst bei einem Sicherheits-Audit, wie viele Secrets sich über Jahre in History-Dateien angesammelt haben.
Auch Bash-Skripte selbst können Secrets in der History hinterlassen, wenn sie interaktiv per source ausgeführt oder in einer Shell zusammengebaut und getestet werden, bevor sie in eine Datei geschrieben werden. Die Grundregel lautet deshalb: Kein Secret darf jemals als sichtbares Kommandozeilenargument in einer interaktiven Shell erscheinen, weder beim manuellen Testen noch in produktiven Bash-Skripten.
2. HISTCONTROL, HISTIGNORE und das führende Leerzeichen
Bash bietet mit HISTCONTROL einen eingebauten, aber oft ungenutzten Mechanismus, um bestimmte Befehle von der History auszuschließen. Der Wert ignorespace sorgt dafür, dass jeder Befehl, der mit einem Leerzeichen beginnt, nicht in die History aufgenommen wird. Das ist besonders nützlich für einmalige, sensible Befehle, die man interaktiv ausführen muss, etwa das Setzen einer Umgebungsvariablen mit einem Secret. Der Wert ignoredups verhindert zusätzlich, dass unmittelbar wiederholte Befehle mehrfach gespeichert werden, was die History kompakter, aber nicht per se sicherer macht.
HISTIGNORE erlaubt es, ganze Befehlsmuster über Glob-Ausdrücke von der History auszuschließen, etwa alle Befehle, die mit mysql -p beginnen. Diese Konfiguration sollte in jeder Shell gesetzt sein, die auf produktiven Systemen mit Zugangsdaten arbeitet, idealerweise zentral über /etc/bash.bashrc oder ein verpflichtendes Profil, statt sich auf die individuelle Konfiguration jedes einzelnen Administrators zu verlassen.
# ~/.bashrc or /etc/bash.bashrc — central history hardening
# Ignore commands starting with a space, and immediate duplicates
export HISTCONTROL=ignorespace:ignoredups
# Additionally exclude specific sensitive command patterns
export HISTIGNORE="mysql -p*:*PASSWORD=*:*TOKEN=*:*SECRET=*"
# Write history immediately instead of only on shell exit
export PROMPT_COMMAND="history -a; ${PROMPT_COMMAND:-}"
# Usage: a leading space keeps this command out of history
mysql -u root -p"$DB_ROOT_PASSWORD" -e "SHOW DATABASES;"
Wichtig ist, dass ignorespace nur wirkt, wenn tatsächlich ein Leerzeichen vor dem Befehl steht, und dass viele Shells oder Terminal-Emulatoren dieses Leerzeichen beim Copy-Paste automatisch entfernen. Sich ausschließlich auf dieses Feature zu verlassen, ist deshalb riskant. Die zuverlässigere Lösung bleibt, Secrets erst gar nicht als Kommandozeilenargument zu übergeben, wie in den folgenden Abschnitten gezeigt.
3. Secrets aus Umgebungsvariablen und der Prozessliste fernhalten
Selbst wenn ein Secret nicht in der Bash History landet, kann es über die Prozessliste sichtbar werden. Ein Befehl wie curl -u user:GeheimesPasswort https://api.example.com zeigt das Passwort für die Dauer der Ausführung in der Ausgabe von ps aux für jeden Nutzer auf demselben System an, der Leserechte auf /proc hat. Das gilt für jedes Kommandozeilenargument, unabhängig davon, ob es in der History landet oder nicht, weil ps die tatsächlichen Startargumente des laufenden Prozesses ausliest.
Umgebungsvariablen sind etwas besser geschützt, weil ps standardmäßig keine Umgebungsvariablen anzeigt, aber unter /proc/PID/environ sind sie für Root und den Prozessbesitzer trotzdem einsehbar. Für maximale Sicherheit sollten Secrets deshalb weder als Kommandozeilenargument noch dauerhaft als Umgebungsvariable existieren, sondern nur für die minimal nötige Zeit im Speicher eines einzelnen Prozesses vorhanden sein.
#!/usr/bin/env bash
set -euo pipefail
# UNSAFE: password visible in `ps aux` for the entire process runtime
curl -u "user:$DB_PASSWORD" https://api.example.com/status
# BETTER: password passed via stdin, never appears as an argument
curl --user-agent "deploy-script" \
--data-urlencode "password@-" \
https://api.example.com/login <<< "$DB_PASSWORD"
# BEST for many CLIs: dedicated --password-stdin style flags
echo "$REGISTRY_TOKEN" | docker login registry.example.com \
--username deploy --password-stdin
4. Sichere Übergabe von Zugangsdaten: stdin, Dateien mit 600, Named Pipes
Die sicherste Art, ein Secret an einen Befehl zu übergeben, ist über Standard Input, weil stdin weder in der Prozessliste noch in der Bash History sichtbar ist. Viele moderne CLI-Tools bieten dafür explizite Flags wie --password-stdin bei Docker oder -p- Varianten bei anderen Werkzeugen. Wo ein Tool das nicht unterstützt, ist eine temporäre Datei mit Berechtigung 600, die unmittelbar nach Gebrauch gelöscht wird, die zweitbeste Option.
Named Pipes, erzeugt mit mkfifo, bieten eine dritte Alternative für Fälle, in denen ein Secret zwischen zwei Prozessen übergeben werden muss, ohne dass es je auf der Festplatte landet. Ein Secret, das durch eine Named Pipe fließt, existiert nur im Kernel-Puffer und verschwindet, sobald beide Enden der Pipe geschlossen werden, ohne jede Spur im Dateisystem zu hinterlassen.
#!/usr/bin/env bash
set -euo pipefail
# SAFE: temp file with restrictive permissions, cleaned up via trap
secret_file=$(mktemp)
chmod 600 "$secret_file"
trap 'rm -f "$secret_file"' EXIT
echo "$API_TOKEN" > "$secret_file"
some-cli --token-file "$secret_file" deploy
# SAFE: named pipe, secret never touches disk
pipe=$(mktemp -u)
mkfifo -m 600 "$pipe"
trap 'rm -f "$pipe"' EXIT
( echo "$API_TOKEN" > "$pipe" & )
some-cli --token-file "$pipe" deploy
5. History-Dateien absichern: Speicherort, Berechtigungen, Löschen
Die Datei ~/.bash_history sollte immer die Berechtigung 600 haben, sodass nur der Besitzer sie lesen kann. Auf gemeinsam genutzten Systemen oder Bastion Hosts empfiehlt sich zusätzlich, HISTSIZE und HISTFILESIZE bewusst klein zu halten, damit alte Einträge, einschließlich versehentlich gespeicherter Secrets, schneller aus der Datei verschwinden. Für hochsensible Sitzungen, etwa auf einem Bastion Host mit direktem Zugriff auf Produktionssysteme, ist unset HISTFILE am Sitzungsanfang eine robuste Option, um die History für diese Sitzung vollständig zu deaktivieren.
Wichtig ist, dass ein einfaches history -c, das die History im Speicher löscht, die bereits auf die Festplatte geschriebene Datei nicht automatisch bereinigt. Erst history -c kombiniert mit history -w, das die geleerte History explizit zurückschreibt, entfernt die Einträge auch dauerhaft aus der Datei. Ohne diesen zweiten Schritt bleibt jedes vorher gespeicherte Secret weiterhin auf der Festplatte vorhanden.
# Ensure restrictive permissions on the history file
chmod 600 ~/.bash_history
# Keep history size limited on shared / bastion systems
export HISTSIZE=500
export HISTFILESIZE=500
# Fully disable history for one highly sensitive session
unset HISTFILE
# Clear history in memory AND on disk (both steps required)
history -c
history -w
6. Secrets in Logs und Debugging-Ausgaben vermeiden
set -x ist ein wertvolles Debugging-Werkzeug, gibt aber jeden ausgeführten Befehl mit allen expandierten Variablenwerten aus, einschließlich Secrets. Ein Skript, das set -x in Produktion aktiviert hat und dessen Ausgabe in eine Log-Datei oder ein zentrales Logging-System geschrieben wird, kann Secrets dadurch dauerhaft und durchsuchbar in Logs hinterlassen. Debugging mit set -x sollte deshalb ausschließlich lokal und temporär genutzt werden, niemals dauerhaft in Skripten, die in Produktion mit echten Zugangsdaten laufen.
Für Fälle, in denen selektives Debugging in Produktion unvermeidbar ist, sollte die Ausgabe von set -x über BASH_XTRACEFD in einen separaten Deskriptor umgeleitet werden, der nicht in das reguläre Log-System einfließt, und Secrets sollten in Variablennamen enthalten sein, die man bewusst aus dem Trace ausschließt, etwa durch temporäres Deaktivieren von set -x unmittelbar vor und Reaktivieren unmittelbar nach der Verwendung eines Secrets.
7. CI/CD-Variablen und Secrets-Manager statt Klartext im Skript
Bash-Skripte, die in CI/CD-Pipelines laufen, sollten Secrets niemals im Klartext im Skript oder im Repository speichern, sondern über die Secrets-Verwaltung des jeweiligen CI-Systems beziehen, etwa maskierte Variablen in GitLab CI oder Secrets in GitHub Actions. Diese Systeme maskieren Secrets automatisch in der Build-Ausgabe, sodass sie nicht versehentlich in Logs erscheinen, allerdings nur, wenn das Secret als exakter String in der Ausgabe auftaucht. Wird das Secret vor der Ausgabe transformiert, etwa Base64 kodiert, greift die Maskierung nicht mehr.
Für produktive Umgebungen mit vielen Secrets ist ein dedizierter Secrets Manager wie Vault, AWS Secrets Manager oder ein vergleichbares System die robustere Lösung. Ein Bash-Skript ruft das benötigte Secret zur Laufzeit ab, hält es nur für die Dauer der Nutzung im Speicher, und schreibt es niemals in eine Konfigurationsdatei oder ein Environment-File, das versehentlich ins Repository gelangen könnte.
8. Auditing: bestehende History und Logs nach Secrets durchsuchen
Nach der Einführung neuer Sicherheitsmaßnahmen bleibt oft die Frage offen, ob bereits Secrets in bestehenden History-Dateien oder Logs gespeichert sind. Ein systematisches Grep nach typischen Mustern wie -p gefolgt von einem Nicht-Leerzeichen, PASSWORD=, TOKEN= oder API_KEY= über alle History-Dateien und Log-Verzeichnisse deckt einen Großteil bereits gespeicherter Secrets auf. Spezialisierte Werkzeuge wie gitleaks oder trufflehog erweitern dieses Prinzip auf Git-Repositories und erkennen zusätzlich bekannte Secret-Formate wie AWS-Zugriffsschlüssel oder JWT-Tokens anhand ihrer charakteristischen Struktur.
Jedes gefundene Secret sollte nicht nur aus der History oder dem Log entfernt, sondern als kompromittiert betrachtet und rotiert werden, weil nicht mit Sicherheit feststellbar ist, wer in der Vergangenheit bereits Zugriff auf diese Datei hatte. Ein regelmäßiger, automatisierter Audit-Lauf, der neue Secrets in Logs und Historys erkennt, ist die nachhaltigere Lösung als eine einmalige manuelle Bereinigung.
#!/usr/bin/env bash
set -euo pipefail
# Simple audit: search history and logs for common secret patterns
declare -a PATTERNS=(
'-p[^[:space:]]'
'PASSWORD='
'TOKEN='
'API_KEY='
'SECRET='
)
for pattern in "${PATTERNS[@]}"; do
grep -rInE "$pattern" \
~/.bash_history /var/log/deploy 2>/dev/null \
| sed 's/^/[FOUND] /' || true
done
9. Vergleich: unsichere vs. sichere Umgangsformen mit Secrets
Die folgende Tabelle fasst die häufigsten unsicheren Muster im Umgang mit Secrets in Bash gegen ihre sicheren Alternativen zusammen.
| Situation | Unsicher | Sicher | Vorteil |
|---|---|---|---|
| Datenbank-Login | mysql -u root -pGeheim |
MYSQL_PWD via Datei mit 600, nicht als Argument | Kein Klartext in History oder ps aux |
| Docker Registry Login | docker login -u u -p Geheim |
--password-stdin |
Passwort nie als Argument sichtbar |
| Debugging in Produktion | set -x dauerhaft aktiv |
Temporär, lokal, mit BASH_XTRACEFD isoliert | Secrets landen nicht in Logs |
| Secrets in CI | Klartext im Repository oder Skript | Secrets Manager oder maskierte CI-Variablen | Zentrale Rotation, kein Repository-Leck |
| History bereinigen | history -c allein |
history -c && history -w |
Datei auf Festplatte wird tatsächlich bereinigt |
Mironsoft
Secrets Audits und Härtung von Bash-Automatisierung
Secrets versehentlich in History, Logs oder Prozessliste?
Wir durchsuchen bestehende History-Dateien und Logs systematisch nach gespeicherten Zugangsdaten und richten sichere Übergabewege für Secrets in euren Skripten und Pipelines ein.
Secrets-Audit
History, Logs und Repositories systematisch durchsuchen
Sichere Übergabe
stdin, temporäre Dateien mit 600 und Named Pipes einführen
Secrets-Manager
Vault oder Cloud Secrets Manager in CI/CD integrieren
10. Zusammenfassung
Secrets landen in der Bash History fast immer über denselben Weg: als sichtbares Kommandozeilenargument, das die Shell automatisch protokolliert. HISTCONTROL mit ignorespace und HISTIGNORE reduzieren das Risiko, ersetzen aber keine strukturelle Lösung, weil Copy-Paste das schützende Leerzeichen oft entfernt. Die zuverlässigste Absicherung ist, Secrets grundsätzlich über stdin, temporäre Dateien mit Berechtigung 600 oder Named Pipes zu übergeben, statt sie als Argument oder dauerhafte Umgebungsvariable zu exponieren.
Auch die Prozessliste, Debugging-Ausgaben mit set -x und CI Logs sind häufige, oft übersehene Quellen für unbeabsichtigt gespeicherte Secrets. Ein Secrets Manager statt Klartext im Skript, kombiniert mit regelmäßigem Auditing bestehender History-Dateien und Logs, schließt die Lücke zwischen einmaliger Härtung und dauerhafter Sicherheit im Umgang mit Zugangsdaten in Bash Automatisierung.
Secrets in der Bash History vermeiden — Das Wichtigste auf einen Blick
HISTCONTROL
ignorespace plus HISTIGNORE reduzieren das Risiko, ersetzen aber keine strukturelle Absicherung.
Sichere Übergabe
stdin, temporäre Dateien mit 600 oder Named Pipes statt Secrets als Kommandozeilenargument.
Prozessliste & Logs
ps aux zeigt Argumente jedem Nutzer. set -x niemals dauerhaft mit echten Secrets in Produktion.
Auditing
Regelmäßiges Grep nach typischen Secret-Mustern in History und Logs, jeden Fund rotieren.