Einschluss sensibler Informationen in Quellcode
Beschreibung
Einschluss sensibler Informationen in Quellcode ist eine Schwachstelle, bei der Quellcode sensible Informationen enthält, die für Benutzer nicht zugänglich sein sollten. Quellcode auf Webservern, in Repositories oder in verteilten Anwendungen enthält oft Anmeldedaten, API-Schlüssel, interne Pfade, Konfigurationsdetails und andere sensible Daten. Wenn dieser Quellcode durch falsch konfigurierte Server, Repository-Offenlegung, Client-seitige Code-Inspektion oder Fehlerbedingungen für Benutzer zugänglich wird, können Angreifer diese sensiblen Informationen extrahieren, um Systeme zu kompromittieren oder Anwendungsinterna für weitere Angriffe zu verstehen.
Risiko
Quellcode-Offenlegung erzeugt erhebliche Sicherheitsrisiken, da Angreifer, die Zugriff erlangen, Anwendungslogik verstehen und sensible Daten extrahieren können. Hartcodierte Anmeldedaten bieten direkten Systemzugriff. API-Schlüssel ermöglichen unbefugte Service-Nutzung. Interne Pfade offenbaren Systemarchitektur. Code-Kommentare können Schwachstellen oder Sicherheits-Workarounds beschreiben. Datenbankverbindungsstrings legen Backend-Systeme offen. Konfigurationswerte offenbaren Sicherheitseinstellungen. Selbst Metadaten wie Versionsnummern und Hostnamen helfen Angreifern, verwundbare Komponenten zu identifizieren. Das Risiko wird verstärkt, da Quellcode sich selten ändert und offengelegte Geheimnisse lange nach der Entdeckung gültig bleiben können.
Lösung
Entfernen Sie alle sensiblen Informationen aus Quellcode vor dem Deployment. Verwenden Sie Umgebungsvariablen oder sichere Konfigurationsverwaltung für Anmeldedaten. Implementieren Sie Code-Review-Prozesse zur Erkennung eingebetteter Geheimnisse. Verwenden Sie statische Analysetools, die nach gängigen Geheimnismustern suchen (API-Schlüssel, Passwörter, Tokens). Konfigurieren Sie Webserver, um Quellcode-Download zu verhindern. Halten Sie Konfigurationsdateien außerhalb web-zugänglicher Verzeichnisse. Entfernen Sie HTML-Kommentare mit technischen Details. Implementieren Sie Geheimnis-Scanning in CI/CD-Pipelines, um Commits mit sensiblen Daten zu verhindern. Verwenden Sie dedizierte Geheimisverwaltungslösungen anstelle von hartcodierten Anmeldedaten.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Vertraulichkeit Anwendungsdaten lesen - Angreifer können auf Anmeldedaten, API-Schlüssel, interne Pfade und andere sensible Informationen zugreifen, die in zugänglichem Quellcode eingebettet sind. |
Beispielcode + Lösungscode
Verwundbarer Code
<?php
// Verwundbar: Datenbank-Anmeldedaten im Quellcode
// /var/www/html/includes/db_config.inc
// (kann als Klartext bereitgestellt werden, wenn .inc-Erweiterung nicht behandelt wird)
$db_host = 'prod-db.company.internal';
$db_user = 'app_admin';
$db_password = 'Pr0duction_P@ssw0rd_2024!'; // Offengelegt!
$db_name = 'customer_data';
// Verwundbar: API-Schlüssel im Quellcode
$stripe_secret_key = 'sk_live_51ABC123XYZ789'; // Live-Schlüssel offengelegt!
$stripe_publishable_key = 'pk_live_51ABC123';
$aws_access_key = 'AKIAIOSFODNN7EXAMPLE';
$aws_secret_key = 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY';
// Verwundbar: Interne Endpunkte im Code
$internal_api_url = 'http://10.0.0.50:8080/internal/api';
$admin_panel_url = 'https://admin.internal.company.com';
?>
<!-- Verwundbar: Sensible Informationen in HTML-Kommentaren -->
<!DOCTYPE html>
<html>
<head>
<title>Firmenportal</title>
<!-- TODO: Vor Produktion entfernen -->
<!-- Datenbank: prod-db.company.com:3306/customers -->
<!-- Admin-Passwort: AdminPass123! -->
<!-- API-Endpunkt: /api/v2/internal/users -->
<!--
FIXME: SQL-Injection-Schwachstelle in getUserById()
Temporärer Workaround: Eingabe manuell bereinigen
Bug-Ticket: JIRA-1234
-->
</head>
<body>
<!-- Debug-Info - später entfernen -->
<!-- Server: web-prod-03.company.internal -->
<!-- Build: 2024.01.15-abc123 -->
<!-- Config: /etc/app/production.conf -->
</body>
</html>
// Verwundbar: Geheimnisse in Client-seitigem JavaScript
// /var/www/html/js/app.js (für alle Benutzer zugänglich!)
const config = {
// Verwundbar: API-Schlüssel in Client-seitigem Code
apiKey: 'sk_live_51ABC123XYZ789',
apiSecret: 'shpss_secret_token_12345',
// Verwundbar: Interne URLs offengelegt
apiEndpoint: 'https://api.company.com',
adminEndpoint: 'https://admin.internal.company.com',
// Verwundbar: OAuth-Anmeldedaten
oauthClientId: 'client_id_production',
oauthClientSecret: 'client_secret_should_be_private',
// Verwundbar: Verschlüsselungsschlüssel im Code
encryptionKey: 'AES256-key-for-production-data'
};
// Verwundbar: Debugging-Code in Produktion belassen
function debugLogin(username) {
console.log('Login-Versuch für:', username);
console.log('Verwende API-Schlüssel:', config.apiKey);
// Verwundbar: Backdoor für Tests
if (username === 'debug_admin') {
return { authenticated: true, role: 'admin' };
}
}
// Verwundbar: Java-Klasse mit eingebetteten Anmeldedaten
public class VerwundbareConfig {
// Verwundbar: Hartcodierte Datenbank-Anmeldedaten
private static final String DB_URL = "jdbc:mysql://prod-db:3306/customers";
private static final String DB_USER = "db_admin";
private static final String DB_PASSWORD = "ProductionPassword123!";
// Verwundbar: API-Schlüssel als Konstanten
public static final String PAYMENT_API_KEY = "sk_live_payment_key";
public static final String ANALYTICS_KEY = "UA-12345678-1";
// Verwundbar: Interne Server-IPs
private static final String[] INTERNAL_SERVERS = {
"10.0.0.10", // Datenbankserver
"10.0.0.20", // Cache-Server
"10.0.0.30", // Datei-Server
};
// Verwundbar: Kommentar mit Sicherheitsinfo
// Standard-Admin-Anmeldedaten: admin/admin123
// LDAP-Server: ldap.company.internal:389
// SSH-Schlüssel-Speicherort: /home/deploy/.ssh/id_rsa
}
# Verwundbar: Python-Konfiguration mit Geheimnissen
# config.py (im Repository)
# Verwundbar: Hartcodierte Anmeldedaten
DATABASE_URL = "postgresql://admin:[email protected]:5432/production"
# Verwundbar: API-Tokens
GITHUB_TOKEN = "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
SLACK_WEBHOOK = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
SENDGRID_API_KEY = "SG.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# Verwundbar: Private Schlüssel
JWT_SECRET = "super-secret-jwt-key-for-production"
ENCRYPTION_KEY = b'Sixteen byte key'
# Verwundbar: Interne URLs
INTERNAL_API = "http://internal-api.company.local:8080"
ADMIN_DASHBOARD = "https://admin.company.internal"
Lösungscode
<?php
// Behoben: Konfiguration aus Umgebung geladen
// /var/www/html/includes/db_config.php
// Behoben: Aus Umgebungsvariablen laden
$db_host = getenv('DB_HOST');
$db_user = getenv('DB_USER');
$db_password = getenv('DB_PASSWORD');
$db_name = getenv('DB_NAME');
// Behoben: Validieren, dass Konfiguration geladen ist
if (empty($db_host) || empty($db_user) || empty($db_password)) {
error_log("Datenbankkonfiguration nicht ordnungsgemäß gesetzt");
die("Konfigurationsfehler"); // Keine Details offenbaren
}
// Behoben: API-Schlüssel aus sicherer Konfiguration
$stripe_secret_key = getenv('STRIPE_SECRET_KEY');
$aws_access_key = getenv('AWS_ACCESS_KEY_ID');
$aws_secret_key = getenv('AWS_SECRET_ACCESS_KEY');
// Behoben: Keine internen URLs im Code
// Service-Discovery oder Konfigurationsverwaltung verwenden
?>
<!-- Behoben: Keine sensiblen Informationen in HTML -->
<!DOCTYPE html>
<html>
<head>
<title>Firmenportal</title>
<!-- Behoben: Keine technischen Kommentare in Produktions-HTML -->
<!-- Gesamte Konfiguration serverseitig geladen -->
</head>
<body>
<!-- Behoben: Build-Version aus Config, nicht hartcodiert -->
<meta name="version" content="<?php echo htmlspecialchars(getenv('BUILD_VERSION') ?: 'dev'); ?>">
</body>
</html>
// Behoben: Client-seitiger Code ohne Geheimnisse
// /var/www/html/js/app.js
const config = {
// Behoben: Nur öffentliche Konfiguration in Client-seitigem Code
apiEndpoint: '/api/v1', // Relativer Pfad, keine internen URLs
// Behoben: Client-ID ist öffentlich, Geheimnis bleibt serverseitig
oauthClientId: 'public_client_id',
// Kein Client-Geheimnis im Frontend!
// Behoben: Feature-Flags, keine sensiblen Daten
features: {
darkMode: true,
newDashboard: false
}
};
// Behoben: Kein Debug-Code in Produktion
// Build-Prozess zum Entfernen von Debug-Code verwenden:
// if (process.env.NODE_ENV !== 'production') { ... }
// Behoben: Authentifizierung über sicheren Server-Endpunkt
async function login(username, password) {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
return response.json();
}
// Behoben: Java-Konfiguration aus Umgebung/Geheimnismanager
public class SichereConfig {
// Behoben: Aus Umgebung laden
private static final String DB_URL;
private static final String DB_USER;
private static final String DB_PASSWORD;
static {
DB_URL = System.getenv("DATABASE_URL");
DB_USER = System.getenv("DATABASE_USER");
DB_PASSWORD = System.getenv("DATABASE_PASSWORD");
// Behoben: Konfiguration validieren
if (DB_URL == null || DB_USER == null || DB_PASSWORD == null) {
throw new IllegalStateException("Datenbankkonfiguration nicht gesetzt");
}
}
// Behoben: Geheimnismanager für sensible Daten verwenden
public static String getApiKey(String keyName) {
return SecretsManager.getSecret(keyName);
}
// Behoben: Keine hartcodierten Server
public static String[] getInternalServers() {
return ServiceDiscovery.getServers("internal");
}
// Behoben: Keine sensiblen Kommentare
// Gesamte Dokumentation in separaten, nicht deploybaren Docs
}
# Behoben: Python-Konfiguration aus Umgebung
# config.py
import os
from functools import lru_cache
# Behoben: Aus Umgebung mit Validierung laden
def get_required_env(key):
"""Erforderliche Umgebungsvariable abrufen oder Fehler werfen."""
value = os.environ.get(key)
if not value:
raise ValueError(f"Erforderliche Umgebungsvariable {key} nicht gesetzt")
return value
# Behoben: Lazy Loading aus Umgebung
@lru_cache()
def get_database_url():
return get_required_env('DATABASE_URL')
@lru_cache()
def get_api_key(service):
return get_required_env(f'{service.upper()}_API_KEY')
# Behoben: Geheimnismanager für sensible Daten verwenden
from secrets_manager import SecretsClient
secrets = SecretsClient()
def get_jwt_secret():
return secrets.get('jwt_secret')
def get_encryption_key():
return secrets.get('encryption_key')
# Behoben: Keine hartcodierten URLs oder internen Pfade
# Service-Discovery oder Konfigurationsverwaltung verwenden
# Behoben: CI/CD-Pipeline mit Geheimnis-Scanning
name: Build
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# Behoben: Nach Geheimnissen im Code scannen
- name: Nach Geheimnissen scannen
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: main
# Behoben: Auf hartcodierte Anmeldedaten-Muster prüfen
- name: Auf sensible Muster prüfen
run: |
if grep -rn "password\s*=\s*['\"]" --include="*.py" --include="*.js" --include="*.php" --include="*.java" .; then
echo "FEHLER: Potenziell hartcodiertes Passwort gefunden!"
exit 1
fi
# Behoben: Sicherstellen, dass keine API-Schlüssel im Code
- name: Auf API-Schlüssel prüfen
run: |
if grep -rn "sk_live_\|AKIA" --include="*.py" --include="*.js" --include="*.php" --include="*.java" .; then
echo "FEHLER: API-Schlüssel im Quellcode gefunden!"
exit 1
fi
CVE-Beispiele
- CVE-2022-25512: Team Awareness Kit enthielt sensible Tokens in JavaScript-Quellcode, der für Benutzer zugänglich war.
- CVE-2022-24867: IT Asset Management-Tool legte LDAP-Passwörter in gerenderter HTML-Ausgabe offen.
- CVE-2007-6197: Anwendung leakte Versionsnummern und Hostnamen in HTML-Kommentaren.
Referenzen
- MITRE Corporation. "CWE-540: Inclusion of Sensitive Information in Source Code." https://cwe.mitre.org/data/definitions/540.html
- OWASP. "Secrets Management Cheat Sheet."
- GitHub. "About Secret Scanning."