Klartextspeicherung sensibler Informationen in Umgebungsvariablen
Beschreibung
Klartextspeicherung sensibler Informationen in Umgebungsvariablen ist eine Schwachstelle, bei der ein Produkt unverschlüsselte sensible Daten in Umgebungsvariablen speichert und sie damit unbefugtem Zugriff durch andere Prozesse innerhalb desselben Ausführungskontexts aussetzt. Umgebungsvariablen sind für Prozesse zugänglich, die den Ausführungskontext teilen, einschließlich Kindprozesse und Cloud-Serverless-Funktionen. Ihr Inhalt kann durch Systemnachrichten, Fehlerausgaben, Prozessauflistungen, Logdateien oder andere Ausgaben offengelegt werden. Drittanbieter-Abhängigkeiten erben oft Umgebungszugriff, ohne ihn zu benötigen, was die Angriffsfläche über beabsichtigte Grenzen hinaus erweitert.
Risiko
Das Speichern von Geheimnissen in Umgebungsvariablen ohne Verschlüsselung erzeugt mehrere Offenlegungsvektoren. Der ps-Befehl kann Umgebungsvariablen in Prozessauflistungen enthüllen. Kindprozesse erben Umgebungsvariablen des Elternprozesses, möglicherweise einschließlich Geheimnissen. Log-Aggregationssysteme können Umgebungsvariableninhalte erfassen. Fehlermeldungen und Stack-Traces geben oft Umgebungskontext aus. In containerisierten Umgebungen sind Umgebungsvariablen oft in Orchestrierungstools sichtbar. Debug-Modi können alle Umgebungsvariablen offenlegen. Cloud-Serverless-Plattformen können Umgebungsvariablen in Funktionsaufrufen protokollieren. Der kumulative Effekt ist, dass Geheimnisse, die in Klartext-Umgebungsvariablen gespeichert sind, wahrscheinlich durch mehrere Kanäle offengelegt werden.
Lösung
Verschlüsseln Sie sensible Informationen vor dem Speichern in Umgebungsvariablen, oder besser, vermeiden Sie das Speichern von Geheimnissen in Umgebungsvariablen vollständig. Verwenden Sie dedizierte Secrets-Management-Systeme wie HashiCorp Vault, AWS Secrets Manager oder Azure Key Vault. Implementieren Sie ordnungsgemäße Secret-Injection zur Laufzeit anstelle statischer Umgebungskonfiguration. Entfernen oder leeren Sie Umgebungsvariablen nach Gebrauch wenn möglich. Verwenden Sie separate, eingeschränkte Prozesse für die Verarbeitung sensibler Operationen. Prüfen Sie Log-Ausgaben um sicherzustellen, dass Umgebungsvariablen nicht erfasst werden. In Serverless-Umgebungen verwenden Sie plattformspezifisches Secrets-Management anstelle von Umgebungsvariablen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Vertraulichkeit Anwendungsdaten lesen - Unbefugte Offenlegung sensibler Informationen, die in Umgebungsvariablen gespeichert sind, einschließlich API-Schlüssel, Datenbank-Anmeldedaten, Verschlüsselungsschlüssel oder andere Geheimnisse. |
Beispielcode
Verwundbarer Code
# Verwundbar: Geheimnisse in Umgebungsvariablen (Shell-Skript)
#!/bin/bash
# Verwundbar: Datenbank-Anmeldedaten in Umgebung
export DB_PASSWORD="super_secret_password_123"
export DB_USER="admin"
# Verwundbar: API-Schlüssel in Umgebung
export API_KEY="sk_live_abcdef123456789"
export SECRET_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxx"
# Verwundbar: Verschlüsselungsschlüssel im Klartext
export ENCRYPTION_KEY="AES256-key-in-plain-text"
# Anwendung ausführen - erbt alle Umgebungsvariablen
./myapp
# Verwundbar: Kann mit ps-Befehl gesehen werden
# ps auxe | grep myapp
# Verwundbar: Python verwendet Klartext-Umgebungsvariablen
import os
import subprocess
# Verwundbar: Geheimnisse aus Umgebung lesen
DATABASE_URL = os.environ.get('DATABASE_URL')
# Könnte sein: "postgresql://admin:[email protected]:5432/mydb"
API_KEY = os.environ.get('API_KEY')
SECRET_KEY = os.environ.get('SECRET_KEY')
# Verwundbar: An Subprocess übergeben
def run_backup():
subprocess.run([
'backup_script.sh',
], env={
**os.environ, # Verwundbar: Alle Geheimnisse an Subprocess übergeben
'BACKUP_PASSWORD': 'backup_secret_123'
})
# Verwundbar: Fehlerbehandlung legt Umgebung offen
try:
connect_to_database()
except Exception as e:
# Stack-Trace könnte Umgebungskontext enthalten
print(f"Fehler: {e}")
print(f"Umgebung: {os.environ}") # Legt alle Geheimnisse offen!
# Verwundbar: Umgebung protokollieren
import logging
logging.info(f"Starte mit Konfiguration: {dict(os.environ)}")
// Verwundbar: Node.js mit Klartext-Umgebungsgeheimnissen
const express = require('express');
const app = express();
// Verwundbar: Geheimnisse aus Umgebung gelesen
const DB_PASSWORD = process.env.DB_PASSWORD;
const API_SECRET = process.env.API_SECRET;
const JWT_KEY = process.env.JWT_SECRET;
// Verwundbar: Debug-Endpunkt legt Umgebung offen
app.get('/debug', (req, res) => {
// Extrem gefährlich!
res.json({
env: process.env, // Legt alle Geheimnisse offen
config: {
dbPassword: DB_PASSWORD,
apiSecret: API_SECRET
}
});
});
// Verwundbar: Fehler-Handler leckt Umgebung
app.use((err, req, res, next) => {
console.error('Fehler:', err);
console.error('Umgebung:', process.env); // Protokolliert Geheimnisse!
res.status(500).json({ error: err.message });
});
// Verwundbar: Kindprozess spawnen
const { spawn } = require('child_process');
spawn('worker.js', [], {
env: process.env // Alle Geheimnisse an Kind übergeben
});
# Verwundbar: Dockerfile mit Umgebungsgeheimnissen
FROM node:16
# Verwundbar: Geheimnisse in Image eingebacken
ENV DATABASE_PASSWORD=production_password_123
ENV API_KEY=sk_live_xxxxxxxxxxxx
ENV ENCRYPTION_KEY=super_secret_key
# Diese sind sichtbar mit: docker inspect <image>
# Und bleiben in Image-Layern bestehen
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]
# Verwundbar: Kubernetes-Deployment mit Klartext-Geheimnissen
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
spec:
containers:
- name: myapp
image: myapp:latest
env:
# Verwundbar: Geheimnisse im Klartext
- name: DB_PASSWORD
value: "production_password_123"
- name: API_KEY
value: "sk_live_xxxxxxxxxxxx"
- name: JWT_SECRET
value: "jwt_secret_key_here"
Lösungscode
# Behoben: Secrets-Manager statt Umgebungsvariablen verwenden
#!/bin/bash
# Behoben: Geheimnisse zur Laufzeit aus Vault abrufen
DB_PASSWORD=$(vault kv get -field=password secret/database)
API_KEY=$(vault kv get -field=key secret/api)
# Behoben: Sensible Variablen nach Gebrauch löschen
export DB_PASSWORD
./myapp
unset DB_PASSWORD
# Behoben: Secret-Dateien statt Umgebung verwenden
./myapp --db-password-file=/run/secrets/db_password
# Alternative: Prozesssubstitution verwenden um Env-Vars zu vermeiden
./myapp --config <(vault kv get -format=json secret/myapp/config)
# Behoben: Sicheres Secrets-Management in Python
import os
from functools import lru_cache
from cryptography.fernet import Fernet
# Behoben: Secrets-Manager-Integration
class SecretsManager:
def __init__(self):
# Richtigen Secrets-Management-Service verwenden
self._client = self._init_secrets_client()
def _init_secrets_client(self):
# AWS Secrets Manager, HashiCorp Vault, etc.
import boto3
return boto3.client('secretsmanager')
@lru_cache(maxsize=1)
def get_secret(self, secret_name):
response = self._client.get_secret_value(SecretId=secret_name)
return response['SecretString']
secrets = SecretsManager()
# Behoben: Geheimnisse vom Manager holen, nicht aus Umgebung
DATABASE_PASSWORD = secrets.get_secret('myapp/database/password')
API_KEY = secrets.get_secret('myapp/api/key')
# Behoben: Wenn Umgebungsvariablen verwendet werden müssen, verschlüsseln
class EncryptedEnvVar:
def __init__(self, key):
self.fernet = Fernet(key)
def get(self, var_name):
encrypted = os.environ.get(var_name)
if encrypted:
return self.fernet.decrypt(encrypted.encode()).decode()
return None
def set(self, var_name, value):
encrypted = self.fernet.encrypt(value.encode()).decode()
os.environ[var_name] = encrypted
# Behoben: Niemals Umgebungsvariablen protokollieren
import logging
class SanitizedLogger(logging.Logger):
SENSITIVE_PATTERNS = ['password', 'secret', 'key', 'token', 'credential']
def _sanitize(self, msg):
# Potenzielle Geheimnisse aus Log-Nachrichten entfernen
for pattern in self.SENSITIVE_PATTERNS:
if pattern.lower() in str(msg).lower():
return '[GESCHWÄRZT - Sensible Daten erkannt]'
return msg
def info(self, msg, *args, **kwargs):
super().info(self._sanitize(msg), *args, **kwargs)
# Behoben: Sichere Subprocess-Behandlung
def run_backup_secure():
import subprocess
# Behoben: Nur erforderliche, nicht-sensible Umgebung übergeben
clean_env = {
'PATH': os.environ.get('PATH', ''),
'HOME': os.environ.get('HOME', ''),
}
# Passwort vom Secrets-Manager holen, über stdin übergeben
password = secrets.get_secret('backup/password')
proc = subprocess.Popen(
['backup_script.sh'],
env=clean_env, # Minimale, saubere Umgebung
stdin=subprocess.PIPE,
text=True
)
proc.communicate(input=password)
// Behoben: Sicheres Secrets-Management in Node.js
const express = require('express');
const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager');
const app = express();
const secretsClient = new SecretsManagerClient({});
// Behoben: Geheimnisse vom Manager, nicht aus Umgebung
async function getSecret(secretName) {
const command = new GetSecretValueCommand({ SecretId: secretName });
const response = await secretsClient.send(command);
return JSON.parse(response.SecretString);
}
// Behoben: Geheimnisse sicher initialisieren
let secrets = {};
async function initSecrets() {
secrets = await getSecret('myapp/secrets');
}
initSecrets();
// Behoben: Kein Debug-Endpunkt der Geheimnisse offenlegt
// /debug-Endpunkt komplett entfernt
// Behoben: Fehler-Handler leckt keine Geheimnisse
app.use((err, req, res, next) => {
// Nur Fehler-ID protokollieren, keine Details
const errorId = generateErrorId();
console.error(`Fehler ${errorId}: ${err.message}`);
// Niemals process.env oder Geheimnisse protokollieren
res.status(500).json({
error: 'Interner Serverfehler',
errorId: errorId
});
});
// Behoben: Kindprozesse bekommen minimale Umgebung
const { spawn } = require('child_process');
function spawnWorker() {
spawn('worker.js', [], {
env: {
// Behoben: Nur nicht-sensible Variablen
NODE_ENV: process.env.NODE_ENV,
PATH: process.env.PATH
}
});
// Geheimnisse über IPC übergeben, nicht Umgebung
worker.on('message', (msg) => {
if (msg.type === 'GET_SECRET') {
worker.send({ type: 'SECRET', value: secrets[msg.name] });
}
});
}
# Behoben: Dockerfile ohne eingebettete Geheimnisse
FROM node:16
# Behoben: Keine Geheimnisse in Umgebung
# Geheimnisse werden zur Laufzeit über Secrets-Manager bereitgestellt
WORKDIR /app
COPY . .
RUN npm install
# Behoben: Geheimnisse aus gemounteten Volumes oder externem Service verwenden
# Nicht aus Umgebungsvariablen, die ins Image eingebacken sind
CMD ["node", "server.js"]
# Behoben: Kubernetes mit ordnungsgemäßem Secrets-Management
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
spec:
containers:
- name: myapp
image: myapp:latest
env:
# Behoben: Kubernetes-Secrets referenzieren (im Ruhezustand verschlüsselt)
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: myapp-secrets
key: db-password
# Behoben: Oder External-Secrets-Operator verwenden
- name: API_KEY
valueFrom:
secretKeyRef:
name: external-secrets
key: api-key
volumeMounts:
# Behoben: Geheimnisse als Dateien mounten
- name: secrets-volume
mountPath: "/run/secrets"
readOnly: true
volumes:
- name: secrets-volume
secret:
secretName: myapp-file-secrets
---
# Behoben: Kubernetes Secret (verschlüsselt gespeichert)
apiVersion: v1
kind: Secret
metadata:
name: myapp-secrets
type: Opaque
data:
# Base64-kodiert (Kubernetes verschlüsselt im Ruhezustand)
db-password: cHJvZHVjdGlvbl9wYXNzd29yZF8xMjM=
CVE-Beispiele
-
CVE-2022-43691: CMS legt sensible serverseitige Informationen aus Umgebungsvariablen offen, wenn der Debug-Modus aktiviert ist.
-
CVE-2022-27195: Jenkins-Plugin fügt Umgebungsvariableninhalte einschließlich Geheimnissen in Build-XML-Dateien ein.
-
CVE-2022-25264: CI/CD-Tool protokolliert passwortbezogene Umgebungsvariablen in Build-Logs.
Referenzen
-
MITRE. "CWE-526: Cleartext Storage of Sensitive Information in an Environment Variable." https://cwe.mitre.org/data/definitions/526.html
-
OWASP. "Secrets Management Cheat Sheet."
-
CIS Controls. "Secure Configuration for Hardware and Software."