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

AuswirkungDetails
VertraulichkeitBereich: 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

  1. MITRE. "CWE-531: Inclusion of Sensitive Information in Test Code." https://cwe.mitre.org/data/definitions/531.html

  2. OWASP. "Testing Guide - Test Code Review."

  3. GitHub. "Secret Scanning Documentation."