Einschluss sensibler Informationen in Testcode
Beschreibung
Einschluss sensibler Informationen in Testcode ist eine Schwachstelle, bei der Testanwendungen oder Testcode sensible Informationen enthält, die für unbefugte Parteien zugänglich werden. Entwickler schließen oft echte Anmeldedaten, API-Schlüssel, Konfigurationsdetails und andere sensible Daten in Testcode ein, ohne zu antizipieren, dass dieser Code in Produktion deployed, in Repositories aufgenommen oder anderweitig zugänglich gemacht werden könnte. Testcode enthält häufig administrative Funktionen, hartcodierte Benutzernamen und Passwörter, Sitzungsidentifikatoren, Datenbankverbindungsstrings und detaillierte Systemkonfigurationsinformationen, die ausgenutzt werden können, wenn sie offengelegt werden.
Risiko
Testcode mit sensiblen Informationen erzeugt mehrere Sicherheitsrisiken. Echte Anmeldedaten oder API-Schlüssel in Testdateien können in Versionskontrolle committed und durch Repository-Lecks offengelegt werden. Versehentlich in Produktion deployter Testcode bietet Angreifern administrativen Zugriff oder Debugging-Fähigkeiten. Unit-Tests können gültige Datenbank-Anmeldedaten enthalten, die unbefugten Datenzugriff ermöglichen. Integrationstests könnten interne API-Endpunkte oder Authentifizierungs-Bypass-Mechanismen offenlegen. Test-Fixtures und Beispieldaten können echte persönliche Informationen enthalten. Die informelle Natur von Testcode führt oft zu schwächeren Sicherheitspraktiken, was ihn zu einer reichhaltigen Quelle sensibler Informationen für Angreifer macht.
Lösung
Entfernen Sie allen Testcode, Testseiten und Debugging-Funktionalität vor dem Deployment von Anwendungen in Produktion. Verwenden Sie niemals echte Anmeldedaten oder Produktionsgeheimnisse in Testcode - verwenden Sie Mock-Daten, Test-Anmeldedaten oder umgebungsspezifische Konfiguration. Implementieren Sie automatisierte Prüfungen in CI/CD-Pipelines zur Erkennung von Testcode oder Test-Artefakten in Produktions-Deployments. Verwenden Sie separate Testdatenbanken und Test-API-Schlüssel, die keinen Zugriff auf Produktionsressourcen haben. Speichern Sie Testkonfiguration getrennt von Produktionskonfiguration. Überprüfen Sie Testcode auf sensible Informationen vor dem Commit in Repositories. Implementieren Sie .gitignore-Regeln zum Ausschluss von Test-Anmeldedaten.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Vertraulichkeit Anwendungsdaten lesen - Angreifer können auf sensible Informationen zugreifen, die in Testcode eingebettet sind, einschließlich Anmeldedaten, API-Schlüssel, persönliche Daten und Systemkonfigurationsdetails. |
Beispielcode
Verwundbarer Code
// Verwundbar: Testklasse mit hartcodierten Anmeldedaten
package com.example.tests;
import org.junit.Test;
import static org.junit.Assert.*;
public class VulnerableDatabaseTest {
// Verwundbar: Echte Produktions-Anmeldedaten im Test!
private static final String DB_HOST = "prod-db.company.com";
private static final String DB_USER = "admin";
private static final String DB_PASSWORD = "Production_P@ssw0rd_2024!";
// Verwundbar: Echte API-Schlüssel
private static final String API_KEY = "sk_live_51ABC123XYZ";
private static final String SECRET_KEY = "whsec_secret123456";
@Test
public void testDatabaseConnection() {
// Test verwendet echte Produktions-Anmeldedaten
Connection conn = DriverManager.getConnection(
"jdbc:mysql://" + DB_HOST + "/production",
DB_USER,
DB_PASSWORD
);
assertTrue(conn.isValid(5));
}
@Test
public void testApiIntegration() {
// Test verwendet echten Produktions-API-Schlüssel
ApiClient client = new ApiClient(API_KEY, SECRET_KEY);
Response response = client.makeRequest("/live/endpoint");
assertEquals(200, response.getStatus());
}
}
# Verwundbar: Testdatei mit sensiblen Informationen
# tests/test_integration.py
import unittest
import requests
# Verwundbar: Echte AWS-Anmeldedaten in Testdatei
AWS_ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE"
AWS_SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
AWS_BUCKET = "production-user-data"
# Verwundbar: Echte Datenbank-Anmeldedaten
DATABASE_URL = "postgresql://admin:[email protected]:5432/users"
# Verwundbar: Admin-Testkonten mit echten Passwörtern
TEST_ADMIN = {
"username": "[email protected]",
"password": "AdminPass123!", # Echtes Admin-Passwort!
"role": "superuser"
}
class IntegrationTests(unittest.TestCase):
def test_admin_login(self):
# Verwundbar: Verwendet echte Admin-Anmeldedaten
response = requests.post(
"https://app.company.com/login",
json=TEST_ADMIN
)
self.assertEqual(response.status_code, 200)
def test_s3_access(self):
# Verwundbar: Verwendet echte AWS-Anmeldedaten
import boto3
s3 = boto3.client('s3',
aws_access_key_id=AWS_ACCESS_KEY,
aws_secret_access_key=AWS_SECRET_KEY
)
# Greift auf echten Produktions-Bucket zu!
objects = s3.list_objects(Bucket=AWS_BUCKET)
// Verwundbar: Testkonfiguration mit Geheimnissen
// tests/config.test.js
const testConfig = {
// Verwundbar: Produktions-API-Schlüssel
stripe: {
publishableKey: 'pk_live_51ABC123',
secretKey: 'sk_live_51ABC123XYZ789',
webhookSecret: 'whsec_realwebhooksecret'
},
// Verwundbar: Echte OAuth-Anmeldedaten
oauth: {
clientId: 'real-production-client-id',
clientSecret: 'real-production-client-secret',
redirectUri: 'https://app.company.com/callback'
},
// Verwundbar: Echte Admin-Anmeldedaten
adminUser: {
email: '[email protected]',
password: 'Admin123!',
totpSecret: 'JBSWY3DPEHPK3PXP' // TOTP-Seed offengelegt!
},
// Verwundbar: Interne API-Endpunkte
internalApis: {
userService: 'http://internal-user-api:8080',
paymentService: 'http://internal-payment-api:8080',
adminPanel: 'https://admin.internal.company.com'
}
};
module.exports = testConfig;
<?php
// Verwundbar: Testseite in Produktion deployed
// /var/www/html/test.php (sollte nicht in Produktion existieren!)
// Verwundbar: Debug-Modus aktiviert
error_reporting(E_ALL);
ini_set('display_errors', 1);
// Verwundbar: Hartcodierte Test-Anmeldedaten
$test_admin_user = 'admin';
$test_admin_pass = 'admin123'; // Schwaches Test-Passwort das funktioniert!
// Verwundbar: Datenbank-Test-Anmeldedaten
$test_db_config = [
'host' => 'localhost',
'user' => 'root',
'pass' => 'root_password_123',
'database' => 'production'
];
// Verwundbar: Testfunktion legt Systeminformationen offen
function debug_system_info() {
phpinfo(); // Legt gesamte PHP-Konfiguration offen
echo "<pre>";
print_r($_SERVER); // Server-Informationen
print_r($_ENV); // Umgebungsvariablen
echo "</pre>";
}
// Verwundbar: Authentifizierungs-Bypass für Tests
if ($_GET['test_mode'] === 'enabled') {
$_SESSION['authenticated'] = true;
$_SESSION['role'] = 'admin';
}
?>
Lösungscode
// Behoben: Testklasse verwendet Mock-Daten und Test-Anmeldedaten
package com.example.tests;
import org.junit.Test;
import org.junit.BeforeClass;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
public class SecureDatabaseTest {
// Behoben: Umgebungsvariablen für Test-Anmeldedaten verwenden
private static String testDbHost;
private static String testDbUser;
private static String testDbPassword;
@BeforeClass
public static void setup() {
// Behoben: Aus testspezifischer Umgebung laden
testDbHost = System.getenv("TEST_DB_HOST");
testDbUser = System.getenv("TEST_DB_USER");
testDbPassword = System.getenv("TEST_DB_PASSWORD");
// Behoben: Validieren, dass wir nicht Produktion verwenden
if (testDbHost != null && testDbHost.contains("prod")) {
throw new IllegalStateException(
"Tests dürfen nicht gegen Produktion laufen!"
);
}
}
@Test
public void testDatabaseConnection() {
// Behoben: Testdatenbank verwenden, nicht Produktion
// Oder besser, In-Memory-Datenbank für Unit-Tests
Connection conn = DriverManager.getConnection(
"jdbc:h2:mem:testdb",
"sa",
""
);
assertTrue(conn.isValid(5));
}
@Test
public void testApiIntegration() {
// Behoben: API-Client für Unit-Tests mocken
ApiClient mockClient = mock(ApiClient.class);
when(mockClient.makeRequest("/endpoint"))
.thenReturn(new Response(200, "OK"));
Response response = mockClient.makeRequest("/endpoint");
assertEquals(200, response.getStatus());
}
}
# Behoben: Testdatei verwendet ordnungsgemäße Testkonfiguration
# tests/test_integration.py
import unittest
import os
from unittest.mock import patch, MagicMock
# Behoben: Anmeldedaten aus Umgebung laden, niemals hartcodieren
def get_test_config():
"""Testkonfiguration aus Umgebung holen."""
env = os.environ.get('TEST_ENV', 'test')
# Behoben: Sicherstellen, dass wir nicht in Produktion sind
if env == 'production':
raise RuntimeError("Tests können nicht in Produktion laufen!")
return {
'db_url': os.environ.get('TEST_DATABASE_URL'),
'api_key': os.environ.get('TEST_API_KEY'),
}
# Behoben: Test-Fixtures mit Fake-Daten verwenden
TEST_USER = {
"username": "[email protected]", # Fake-E-Mail
"password": "TestPassword123", # Nur-Test-Passwort
"role": "user"
}
class IntegrationTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
# Behoben: Testumgebung verifizieren
if os.environ.get('ENVIRONMENT') == 'production':
raise RuntimeError("Integrationstests können nicht in Produktion laufen!")
cls.config = get_test_config()
@patch('requests.post')
def test_user_login(self, mock_post):
# Behoben: Externe Aufrufe mocken
mock_post.return_value = MagicMock(status_code=200)
response = requests.post(
"https://test.example.com/login",
json=TEST_USER
)
self.assertEqual(response.status_code, 200)
@patch('boto3.client')
def test_s3_access(self, mock_boto):
# Behoben: AWS-Client mocken
mock_s3 = MagicMock()
mock_boto.return_value = mock_s3
mock_s3.list_objects.return_value = {'Contents': []}
import boto3
s3 = boto3.client('s3')
objects = s3.list_objects(Bucket='test-bucket')
# Berührt niemals echtes AWS
self.assertIsNotNone(objects)
// Behoben: Testkonfiguration verwendet Umgebungsvariablen
// tests/config.test.js
// Behoben: Aus Umgebung laden, mit testspezifischen Defaults
const testConfig = {
stripe: {
// Behoben: Nur Test-Modus-Schlüssel
publishableKey: process.env.TEST_STRIPE_PK || 'pk_test_placeholder',
secretKey: process.env.TEST_STRIPE_SK || 'sk_test_placeholder',
webhookSecret: process.env.TEST_STRIPE_WEBHOOK || 'whsec_test'
},
// Behoben: Mock-OAuth für Tests
oauth: {
clientId: 'test-client-id',
clientSecret: 'test-client-secret',
redirectUri: 'http://localhost:3000/callback'
},
// Behoben: Testbenutzer mit Fake-Daten
testUser: {
email: '[email protected]', // Fake-Domain
password: 'TestOnly123', // Nur-Test-Passwort
// Keine TOTP-Geheimnisse im Code
},
// Behoben: Localhost oder Mock-URLs verwenden
apis: {
userService: 'http://localhost:8080',
paymentService: 'http://localhost:8081',
}
};
// Behoben: Validierung zur Verhinderung der Produktionsnutzung
if (process.env.NODE_ENV === 'production') {
throw new Error('Testkonfiguration darf nicht in Produktion verwendet werden!');
}
module.exports = testConfig;
# Behoben: CI/CD-Pipeline verhindert Testcode-Deployment
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# Behoben: Testdateien vor Deployment entfernen
- name: Testcode entfernen
run: |
rm -rf tests/
rm -rf test/
rm -rf spec/
rm -rf __tests__/
rm -f **/test_*.py
rm -f **/*_test.py
rm -f **/*.test.js
rm -f **/*.spec.js
rm -f **/Test*.java
rm -f **/*Test.java
# Behoben: Nach Testmustern im verbleibenden Code scannen
- name: Auf Testcode prüfen
run: |
if grep -rn "test_password\|TEST_API_KEY\|pk_test_\|sk_test_" --include="*.py" --include="*.js" --include="*.php" --include="*.java" .; then
echo "FEHLER: Test-Anmeldedaten im Deployment gefunden!"
exit 1
fi
- name: Deploy
run: ./deploy.sh
CVE-Beispiele
Keine spezifischen CVEs sind in der MITRE-Datenbank für dieses CWE gelistet. Das Schwachstellenmuster ist jedoch extrem verbreitet:
- Offengelegte API-Schlüssel in GitHub-Repositories
- Testseiten in Produktions-Deployments belassen
- Anmeldedaten in Testdateien in öffentliche Repos committed
Referenzen
-
MITRE. "CWE-531: Inclusion of Sensitive Information in Test Code." https://cwe.mitre.org/data/definitions/531.html
-
OWASP. "Testing Guide - Test Code Review."
-
GitHub. "Secret Scanning Documentation."