Fehlende benutzerdefinierte Fehlerseite

Beschreibung

Fehlende benutzerdefinierte Fehlerseite ist eine Konfigurationsschwachstelle, bei der eine Webanwendung keine benutzerdefinierten Fehlerseiten an Benutzer zurückgibt, sondern stattdessen Standard-Server-Fehlerseiten anzeigt, die sensible Informationen preisgeben können. Wenn unbehandelte Ausnahmen auftreten oder Fehler ausgelöst werden, enthalten Standard-Fehlerseiten oft detaillierte Stack-Traces, Framework-Versionen, Datenbankinformationen, Dateipfade und andere technische Details, die Angreifern helfen, die interne Architektur der Anwendung zu verstehen. Diese Informationsoffenlegung ermöglicht gezieltere Angriffe gegen die spezifischen verwendeten Technologien und Konfigurationen.

Risiko

Standard-Fehlerseiten schaffen erhebliche Aufklärungsmöglichkeiten für Angreifer. Stack-Traces offenbaren Klassennamen, Methodennamen und Codepfade, die die Struktur der Anwendung anzeigen. Framework- und Versionsinformationen legen bekannte Schwachstellen in diesen spezifischen Versionen offen. Datenbankfehlermeldungen können Tabellennamen, Spaltennamen und Abfragestrukturen offenlegen, die für SQL-Injection nützlich sind. Dateipfade legen die Verzeichnisstruktur des Servers offen. Serversoftware-Identifikation ermöglicht versionsspezifische Angriffe. Selbst scheinbar harmlose Informationen helfen Angreifern, ein umfassendes Bild des Zielsystems aufzubauen, was nachfolgende Angriffe effizienter und effektiver macht.

Lösung

Konfigurieren Sie benutzerdefinierte Fehlerseiten für alle HTTP-Fehlercodes (400, 401, 403, 404, 500, etc.). Stellen Sie sicher, dass Fehlerseiten benutzerfreundliche Meldungen ohne technische Details anzeigen. Protokollieren Sie detaillierte Fehlerinformationen serverseitig für Debugging, während Sie Benutzern generische Meldungen zeigen. In ASP.NET verwenden Sie <customErrors mode="On"> mit einem defaultRedirect. In Java/J2EE konfigurieren Sie error-page-Elemente in web.xml. In PHP deaktivieren Sie display_errors in der Produktion. Testen Sie die Fehlerbehandlung durch Auslösen verschiedener Fehlerbedingungen und verifizieren Sie, dass keine sensiblen Informationen preisgegeben werden. Nehmen Sie die Fehlerseiten-Konfiguration in Sicherheitshärtungs-Checklisten auf.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Vertraulichkeit

Anwendungsdaten lesen - Fehlermeldungen legen interne Architektur, Pfade und Konfigurationsdetails offen.
ZugriffskontrolleBereich: Zugriffskontrolle

Privilegien erlangen - Offengelegte Informationen ermöglichen gezieltere Angriffe gegen bekannte Schwachstellen.
SonstigesBereich: Sonstiges

Qualitätsverschlechterung - Unprofessionelle Fehlerseiten reduzieren das Benutzervertrauen in die Anwendung.

Beispielcode + Lösungscode

Anfälliger Code

<!-- Anfällig: ASP.NET web.config mit customErrors Off -->
<configuration>
    <system.web>
        <!-- Anfällig: Zeigt vollständige Stack-Traces allen Benutzern -->
        <customErrors mode="Off" />
    </system.web>
</configuration>

<!-- Standard ASP.NET-Fehlerseite legt offen:
- Vollständiger Exception-Stack-Trace
- Quellcode-Zeilennummern
- Dateipfade
- .NET Framework-Version
- Assembly-Informationen
-->
<!-- Anfällig: Java web.xml ohne Fehlerseiten -->
<web-app>
    <!-- Keine error-page-Elemente definiert -->
    <!-- Container zeigt Standard-Fehlerseiten mit Stack-Traces -->
</web-app>

<!-- Tomcat Standard-Fehlerseite legt offen:
- Java-Exception-Klasse und -Meldung
- Vollständiger Stack-Trace
- Servlet-Container-Version
- JVM-Version
-->
<?php
// Anfällig: PHP mit display_errors aktiviert in Produktion
ini_set('display_errors', 1);
error_reporting(E_ALL);

// Wenn Fehler auftritt, werden vollständige Details dem Benutzer gezeigt
$result = $db->query("SELECT * FROM users WHERE id = " . $_GET['id']);

// Fehlermeldung könnte zeigen:
// - Datenbankserver-Typ und -Version
// - SQL-Abfragestruktur
// - Tabellen- und Spaltennamen
// - Dateipfade
?>
// Anfällig: Java-Servlet ohne Fehlerbehandlung
public class VulnerableServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request,
                        HttpServletResponse response) {

        String userId = request.getParameter("id");

        // Anfällig: SQLException propagiert zum Container
        // Standard-Fehlerseite zeigt vollständigen Stack-Trace
        Connection conn = dataSource.getConnection();
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery(
            "SELECT * FROM users WHERE id = " + userId);

        // Exception offenbart:
        // - Datenbanktyp (MySQL, Oracle, etc.)
        // - Abfragestruktur
        // - JDBC-Treiberversion
        // - Anwendungsklassennamen
    }
}
# Anfällig: Flask mit Debug-Modus in Produktion
from flask import Flask

app = Flask(__name__)
app.debug = True  # Anfällig: Zeigt Debugger in Produktion

@app.route('/user/<id>')
def get_user(id):
    # Wenn Exception auftritt, zeigt Flask-Debug-Seite:
    # - Vollständiger Stack-Trace
    # - Lokale Variablen in jedem Frame
    # - Quellcode-Kontext
    # - Interaktiver Debugger (kann Code ausführen!)
    return db.query(f"SELECT * FROM users WHERE id = {id}")

if __name__ == '__main__':
    app.run()

Behobener Code

<!-- Behoben: ASP.NET mit benutzerdefinierten Fehlerseiten -->
<configuration>
    <system.web>
        <!-- Behoben: Benutzerdefinierte Fehler für alle Benutzer aktiviert -->
        <customErrors mode="On" defaultRedirect="~/Error/General">
            <error statusCode="404" redirect="~/Error/NotFound" />
            <error statusCode="500" redirect="~/Error/ServerError" />
            <error statusCode="403" redirect="~/Error/Forbidden" />
        </customErrors>
    </system.web>
</configuration>

<!-- Alternative: RemoteOnly zeigt Details nur auf localhost -->
<customErrors mode="RemoteOnly" defaultRedirect="~/Error/General">
    <!-- Entwickler auf Server sehen Details, entfernte Benutzer sehen benutzerdefinierte Seiten -->
</customErrors>
<!-- Behoben: Java web.xml mit Fehlerseiten -->
<web-app>
    <!-- Behoben: Benutzerdefinierte Fehlerseiten für Exceptions -->
    <error-page>
        <exception-type>java.lang.Exception</exception-type>
        <location>/WEB-INF/views/error/general.jsp</location>
    </error-page>

    <!-- Behoben: Benutzerdefinierte Seiten für HTTP-Statuscodes -->
    <error-page>
        <error-code>404</error-code>
        <location>/WEB-INF/views/error/notfound.jsp</location>
    </error-page>

    <error-page>
        <error-code>500</error-code>
        <location>/WEB-INF/views/error/servererror.jsp</location>
    </error-page>

    <error-page>
        <error-code>403</error-code>
        <location>/WEB-INF/views/error/forbidden.jsp</location>
    </error-page>
</web-app>
<?php
// Behoben: PHP mit ordentlicher Fehlerbehandlung für Produktion

// Behoben: Fehleranzeige in Produktion deaktivieren
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', '/var/log/php/error.log');
error_reporting(E_ALL);

// Behoben: Benutzerdefinierter Fehlerhandler
set_error_handler(function($errno, $errstr, $errfile, $errline) {
    // Vollständige Details protokollieren
    error_log("Fehler [$errno]: $errstr in $errfile in Zeile $errline");

    // Generische Meldung an Benutzer zeigen
    http_response_code(500);
    include('error_pages/500.html');
    exit();
});

// Behoben: Benutzerdefinierter Exception-Handler
set_exception_handler(function($exception) {
    // Vollständige Details intern protokollieren
    error_log("Exception: " . $exception->getMessage() .
              "\n" . $exception->getTraceAsString());

    // Generische Meldung an Benutzer zeigen
    http_response_code(500);
    include('error_pages/500.html');
    exit();
});

// Behoben: Datenbankfehler spezifisch abfangen
try {
    $stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
    $stmt->execute([$_GET['id']]);
    $result = $stmt->fetch();
} catch (PDOException $e) {
    error_log("Datenbankfehler: " . $e->getMessage());
    http_response_code(500);
    include('error_pages/database_error.html');
    exit();
}
?>
// Behoben: Java-Servlet mit ordentlicher Fehlerbehandlung
public class SecureServlet extends HttpServlet {

    private static final Logger logger =
        LoggerFactory.getLogger(SecureServlet.class);

    @Override
    protected void doGet(HttpServletRequest request,
                        HttpServletResponse response)
            throws ServletException, IOException {

        try {
            String userId = request.getParameter("id");

            // Validieren und verarbeiten
            if (!isValidId(userId)) {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST);
                return;
            }

            User user = userService.findById(userId);

            if (user == null) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND);
                return;
            }

            // Benutzer verarbeiten...

        } catch (SQLException e) {
            // Behoben: Vollständige Details intern protokollieren
            logger.error("Datenbankfehler bei Anfrageverarbeitung", e);

            // Behoben: Generische Fehlerantwort
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

        } catch (Exception e) {
            logger.error("Unerwarteter Fehler", e);
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }
}
# Behoben: Flask mit ordentlicher Fehlerbehandlung
from flask import Flask, render_template
import logging

app = Flask(__name__)
app.debug = False  # Behoben: Debug in Produktion deaktiviert

# Logging konfigurieren
logging.basicConfig(
    filename='/var/log/app/error.log',
    level=logging.ERROR
)

# Behoben: Benutzerdefinierte Fehlerhandler
@app.errorhandler(404)
def not_found_error(error):
    return render_template('errors/404.html'), 404

@app.errorhandler(500)
def internal_error(error):
    # Vollständige Details protokollieren
    app.logger.error(f'Server-Fehler: {error}')
    return render_template('errors/500.html'), 500

@app.errorhandler(Exception)
def handle_exception(e):
    # Vollständige Exception-Details protokollieren
    app.logger.exception('Unbehandelte Ausnahme')
    return render_template('errors/500.html'), 500

@app.route('/user/<id>')
def get_user(id):
    try:
        # Parametrisierte Abfrage verwenden
        user = db.execute(
            "SELECT * FROM users WHERE id = ?", [id]
        ).fetchone()

        if user is None:
            abort(404)

        return render_template('user.html', user=user)

    except Exception as e:
        app.logger.error(f'Fehler beim Abrufen von Benutzer {id}: {e}')
        abort(500)

if __name__ == '__main__':
    app.run()
<!-- Beispiel benutzerdefinierte Fehlerseite (500.html) -->
<!DOCTYPE html>
<html>
<head>
    <title>Server-Fehler</title>
</head>
<body>
    <h1>Etwas ist schiefgelaufen</h1>
    <p>Es tut uns leid, aber bei der Verarbeitung Ihrer Anfrage ist ein Fehler aufgetreten.</p>
    <p>Bitte versuchen Sie es später erneut oder kontaktieren Sie den Support, wenn das Problem weiterhin besteht.</p>
    <p>Fehlerreferenz: ERR-<!-- Eindeutige Fehler-ID für Support-Suche einfügen --></p>
    <!-- Keine Stack-Traces, Pfade oder technische Details -->
</body>
</html>

CVE-Beispiele

  • CVE-2017-5638: Apache Struts Fehlermeldungen offenbarten interne Details, die Angriffsverfeinerung ermöglichten.
  • CVE-2019-1003000: Jenkins Standard-Fehlerseiten legten sensible Systeminformationen offen.

Referenzen

  1. MITRE Corporation. "CWE-756: Missing Custom Error Page." https://cwe.mitre.org/data/definitions/756.html
  2. OWASP Top Ten 2021: A05:2021 Security Misconfiguration.
  3. OWASP. "Error Handling Cheat Sheet."