Informationsoffenlegung durch Shell-Fehlermeldung

Beschreibung

Informationsoffenlegung durch Shell-Fehlermeldung ist eine Schwachstelle, bei der eine Kommando-Shell-Fehlermeldung anzeigt, dass eine unbehandelte Ausnahme im Webanwendungscode existiert und potenziell sensible Systeminformationen offenlegt. Wenn Anwendungen Shell-Befehle aufrufen und Fehler nicht ordnungsgemäß behandeln, können die rohen Shell-Fehlermeldungen an Benutzer zurückgegeben werden. Diese Meldungen können Dateipfade, Systemkonfiguration, Benutzernamen, Befehlssyntax und andere interne Details offenlegen, die Angreifern helfen, die Systemarchitektur zu verstehen und potenzielle Angriffsvektoren zu identifizieren.

Risiko

Shell-Fehlermeldungen können kritische Systeminformationen an Angreifer offenlegen. Dateipfade enthüllen Verzeichnisstrukturen und Installationsorte. Befehlssyntaxfehler zeigen, welche Programme aufgerufen werden und wie. Berechtigungsfehler zeigen Sicherheitskonfigurationen an. Fehlende-Datei-Fehler enthüllen den erwarteten Systemzustand. Angreifer können diese Informationen nutzen, um gezielte Angriffe zu entwickeln, bestimmte Softwareversionen auszunutzen oder die Ausführungsumgebung zu verstehen. In vielen Fällen können Fehlerbedingungen, die diese Meldungen auslösen, absichtlich durch fehlerhafte Eingaben herbeigeführt werden, was aktive Aufklärung ermöglicht.

Lösung

Implementieren Sie umfassende Fehlerbehandlung für alle Shell-Befehlsausführungen. Fangen Sie alle Ausnahmen ab und geben Sie generische, benutzerfreundliche Fehlermeldungen zurück, die keine Systeminterna offenlegen. Loggen Sie detaillierte Fehlerinformationen serverseitig für Debugging, während Sie bereinigte Meldungen an Benutzer präsentieren. Verwenden Sie Whitelisting für Shell-Befehlseingaben und validieren Sie alle Parameter. Erwägen Sie die Verwendung nativer Bibliotheken anstelle von Shell-Befehlen wenn möglich. Implementieren Sie ordnungsgemäße Ausnahmebehandlungs-Wrapper um Befehlsausführungscode. Testen Sie Fehlerbehandlung mit verschiedenen Fehlerszenarien, um sicherzustellen, dass keine Informationen durchsickern.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Vertraulichkeit

Anwendungsdaten lesen - Shell-Fehlermeldungen können sensible Anwendungs- und Systeminformationen offenlegen, einschließlich Dateipfade, Benutzernamen, Softwareversionen und Konfigurationsdetails.

Beispielcode

Verwundbarer Code

<?php
// Verwundbar: Shell-Befehl mit offengelegten Fehlern
function vulnerableBackup($filename) {
    // Verwundbar: Direkte Shell-Ausführung
    $output = shell_exec("tar czf backup.tar.gz " . $filename);

    // Verwundbar: Shell-Fehler direkt an Benutzer zurückgegeben
    if ($output === null) {
        // Fehlermeldung von Shell wird angezeigt
        echo "Fehler: Befehl fehlgeschlagen";
        echo shell_exec("tar czf backup.tar.gz " . $filename . " 2>&1");
    }

    return $output;
}

// Bei Aufruf mit ungültiger Eingabe:
// tar: nonexistent_file: Cannot stat: No such file or directory
// tar: Error is not recoverable: exiting now
// Enthüllt: Dateisystempfade, tar-Befehlsverwendung

// Verwundbar: exec() mit Fehlerausgabe
function vulnerableSearch($query) {
    $cmd = "grep -r '" . $query . "' /var/www/data/";
    exec($cmd, $output, $return_var);

    if ($return_var !== 0) {
        // Verwundbar: Zeigt Shell-Fehler an Benutzer
        echo "Suche fehlgeschlagen: ";
        system($cmd . " 2>&1");
        // Enthüllt: Verzeichnisstruktur, grep-Befehlssyntax
    }

    return $output;
}
?>
# Verwundbar: Python-Subprocess mit offengelegten Fehlern
import subprocess

def vulnerable_convert(input_file, output_file):
    try:
        # Verwundbar: shell=True erlaubt Command-Injection
        cmd = f"convert {input_file} {output_file}"
        result = subprocess.run(cmd, shell=True, capture_output=True, text=True)

        if result.returncode != 0:
            # Verwundbar: Rohes stderr an Benutzer zurückgegeben
            return f"Konvertierung fehlgeschlagen: {result.stderr}"
            # Könnte enthüllen:
            # convert: unable to open image '/var/www/uploads/test.jpg': No such file or directory
            # convert: no images defined 'output.png'

    except Exception as e:
        # Verwundbar: Vollständige Ausnahmedetails offengelegt
        return f"Fehler: {str(e)}"

def vulnerable_ping(host):
    import os
    # Verwundbar: os.popen mit Fehleroffenlegung
    result = os.popen(f"ping -c 1 {host} 2>&1").read()
    return result
    # Könnte enthüllen:
    # ping: unknown host malicious-input; cat /etc/passwd
    # sh: syntax error near unexpected token
// Verwundbar: Java Runtime.exec mit Fehleroffenlegung
public class VulnerableCommandExecutor {

    public String executeCommand(String filename) {
        try {
            // Verwundbar: Benutzereingabe im Befehl
            String command = "ls -la " + filename;
            Process process = Runtime.getRuntime().exec(command);

            BufferedReader errorReader = new BufferedReader(
                new InputStreamReader(process.getErrorStream())
            );

            StringBuilder errors = new StringBuilder();
            String line;
            while ((line = errorReader.readLine()) != null) {
                errors.append(line).append("\n");
            }

            if (process.waitFor() != 0) {
                // Verwundbar: Shell-Fehler an Aufrufer zurückgegeben
                return "Fehler bei Befehlsausführung: " + errors.toString();
                // Könnte enthüllen:
                // ls: cannot access '/etc/shadow': Permission denied
                // ls: cannot access '/nonexistent': No such file or directory
            }

            return "Erfolg";
        } catch (Exception e) {
            // Verwundbar: Ausnahmedetails offengelegt
            return "Befehlsausführung fehlgeschlagen: " + e.getMessage();
        }
    }
}
// Verwundbar: Node.js child_process mit Fehleroffenlegung
const { exec, execSync } = require('child_process');

function vulnerableCommand(userInput) {
    return new Promise((resolve, reject) => {
        // Verwundbar: Benutzereingabe im Shell-Befehl
        exec(`cat ${userInput}`, (error, stdout, stderr) => {
            if (error) {
                // Verwundbar: Shell-Fehler an Benutzer zurückgegeben
                resolve({
                    success: false,
                    error: stderr,
                    // Könnte enthüllen:
                    // cat: /etc/shadow: Permission denied
                    // cat: /nonexistent: No such file or directory
                    code: error.code
                });
                return;
            }
            resolve({ success: true, data: stdout });
        });
    });
}

function vulnerableSyncCommand(filename) {
    try {
        // Verwundbar: Sync exec mit Fehler in Ausnahme
        const result = execSync(`file ${filename}`, { encoding: 'utf8' });
        return result;
    } catch (error) {
        // Verwundbar: Vollständige Fehlerdetails offengelegt
        return `Befehl fehlgeschlagen: ${error.stderr}`;
    }
}

Lösungscode

<?php
// Behoben: Sichere Befehlsausführung mit Fehlerbehandlung
function secureBackup($filename) {
    // Behoben: Dateinamen validieren
    if (!preg_match('/^[a-zA-Z0-9_.-]+$/', $filename)) {
        return ['success' => false, 'error' => 'Ungültiger Dateiname'];
    }

    // Behoben: escapeshellarg verwenden
    $safeFilename = escapeshellarg($filename);

    // Behoben: Fehler intern erfassen
    $descriptors = [
        0 => ['pipe', 'r'],
        1 => ['pipe', 'w'],
        2 => ['pipe', 'w']  // stderr
    ];

    $process = proc_open(
        "tar czf backup.tar.gz {$safeFilename}",
        $descriptors,
        $pipes
    );

    if (is_resource($process)) {
        $stdout = stream_get_contents($pipes[1]);
        $stderr = stream_get_contents($pipes[2]);
        $returnCode = proc_close($process);

        if ($returnCode !== 0) {
            // Behoben: Fehler intern loggen
            error_log("Backup-Befehl fehlgeschlagen: " . $stderr);

            // Behoben: Generische Meldung an Benutzer zurückgeben
            return [
                'success' => false,
                'error' => 'Backup-Operation fehlgeschlagen. Bitte kontaktieren Sie den Support.'
            ];
        }

        return ['success' => true, 'message' => 'Backup erfolgreich erstellt'];
    }

    return ['success' => false, 'error' => 'Backup-Prozess könnte nicht gestartet werden'];
}

// Behoben: Sichere Suchfunktion
function secureSearch($query) {
    // Behoben: Whitelist-Validierung
    if (!preg_match('/^[a-zA-Z0-9\s]+$/', $query)) {
        return ['error' => 'Ungültige Suchanfrage'];
    }

    $safeQuery = escapeshellarg($query);
    $safeDir = '/var/www/data/';

    $cmd = "grep -r {$safeQuery} " . escapeshellarg($safeDir);
    exec($cmd . " 2>/dev/null", $output, $returnCode);

    if ($returnCode !== 0) {
        // Behoben: Generischer Fehler, detailliertes Log
        error_log("Suche fehlgeschlagen mit Code $returnCode");
        return ['results' => [], 'message' => 'Keine Ergebnisse gefunden'];
    }

    return ['results' => $output];
}
?>
# Behoben: Python-Subprocess mit sicherer Fehlerbehandlung
import subprocess
import logging
import re

logger = logging.getLogger(__name__)

def secure_convert(input_file, output_file):
    # Behoben: Dateinamen validieren
    if not re.match(r'^[\w\-./]+

```java
// Behoben: Java sichere Befehlsausführung
import java.util.logging.Logger;
import java.util.regex.Pattern;

public class SecureCommandExecutor {
    private static final Logger logger = Logger.getLogger(
        SecureCommandExecutor.class.getName()
    );
    private static final Pattern SAFE_FILENAME = Pattern.compile("^[\\w\\-./]+$");

    public CommandResult executeCommand(String filename) {
        // Behoben: Eingabe validieren
        if (!SAFE_FILENAME.matcher(filename).matches()) {
            return new CommandResult(false, "Ungültiges Dateinamenformat");
        }

        try {
            // Behoben: ProcessBuilder mit Listen-Argumenten verwenden
            ProcessBuilder pb = new ProcessBuilder("ls", "-la", filename);
            pb.redirectErrorStream(false);

            Process process = pb.start();

            // Behoben: stderr lesen aber nicht offenlegen
            String stderr = readStream(process.getErrorStream());
            String stdout = readStream(process.getInputStream());

            int exitCode = process.waitFor();

            if (exitCode != 0) {
                // Behoben: Intern loggen, generische Meldung zurückgeben
                logger.warning("Befehl fehlgeschlagen: " + stderr);
                return new CommandResult(false, "Datei-Operation fehlgeschlagen");
            }

            return new CommandResult(true, "Operation erfolgreich abgeschlossen");

        } catch (IOException e) {
            logger.log(Level.SEVERE, "IO-Fehler bei Befehlsausführung", e);
            return new CommandResult(false, "Systemfehler aufgetreten");

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            logger.warning("Befehl unterbrochen");
            return new CommandResult(false, "Operation abgebrochen");
        }
    }

    private String readStream(InputStream stream) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line).append("\n");
        }
        return sb.toString();
    }
}
// Behoben: Node.js sichere Befehlsausführung
const { execFile } = require('child_process');
const path = require('path');

function secureCommand(userInput) {
    return new Promise((resolve) => {
        // Behoben: Eingabe validieren
        if (!/^[\w\-./]+$/.test(userInput)) {
            resolve({
                success: false,
                error: 'Ungültiges Eingabeformat'
            });
            return;
        }

        // Behoben: execFile mit Argument-Array verwenden
        execFile('cat', [userInput], { timeout: 5000 }, (error, stdout, stderr) => {
            if (error) {
                // Behoben: Fehler loggen, generische Meldung zurückgeben
                console.error('Befehlsfehler:', stderr);

                resolve({
                    success: false,
                    error: 'Datei könnte nicht gelesen werden'
                });
                return;
            }

            resolve({ success: true, data: stdout });
        });
    });
}

// Behoben: Fehlerbehandlungs-Wrapper
function safeExec(command, args, options = {}) {
    return new Promise((resolve, reject) => {
        execFile(command, args, {
            timeout: options.timeout || 10000,
            maxBuffer: options.maxBuffer || 1024 * 1024
        }, (error, stdout, stderr) => {
            if (error) {
                // Behoben: Strukturierter Fehler ohne Shell-Details
                console.error(`${command} fehlgeschlagen:`, stderr);
                resolve({
                    success: false,
                    code: 'COMMAND_FAILED',
                    message: 'Operation könnte nicht abgeschlossen werden'
                });
                return;
            }
            resolve({ success: true, output: stdout });
        });
    });
}

CVE-Beispiele

Keine spezifischen CVEs sind in der MITRE-Datenbank für dieses CWE gelistet. Das Schwachstellenmuster ist jedoch dokumentiert in:

  • Allgemeine Fehlerbehandlungs-Best-Practices
  • Web-Anwendungs-Sicherheitstestrichtlinien

Referenzen

  1. MITRE. "CWE-535: Exposure of Information Through Shell Error Message." https://cwe.mitre.org/data/definitions/535.html

  2. OWASP. "Error Handling Cheat Sheet."

  3. OWASP. "OS Command Injection Prevention."