Weakness reference
CWE-755

Improper Handling of Exceptional Conditions

This weakness occurs when software fails to properly catch, log, or respond to errors and exceptional conditions. When exceptions go unhandled or are handled…

01Summary

This weakness occurs when software fails to properly catch, log, or respond to errors and exceptional conditions. When exceptions go unhandled or are handled incorrectly, the application may crash unexpectedly, leak sensitive information in error messages, or enter an inconsistent state that attackers can exploit. Proper exception handling is foundational to both security and reliability.

02How It Happens

Exceptional conditions—database connection failures, file I/O errors, network timeouts, invalid user input—are inevitable in real applications. When code does not anticipate these conditions or catches them but fails to respond appropriately, several problems emerge: the application may expose stack traces or internal paths in error output, leave resources (database connections, file handles) open, skip critical security checks, or allow execution to continue in an unsafe state. The root cause is usually either missing try-catch blocks, overly broad exception handlers that mask real problems, or handlers that log/display too much detail without sanitization.

03Real-World Impact

Unhandled exceptions can lead to denial of service (the application crashes), information disclosure (stack traces reveal database schemas, file paths, or library versions), authentication bypass (security checks skipped during error recovery), or data corruption (transactions left incomplete). In web applications, verbose error pages shown to users become reconnaissance tools for attackers. In backend services, silent failures can cause cascading outages or leave the system in a state where subsequent requests behave unpredictably.

04Vulnerable & Fixed Patterns

Vulnerable pattern
import sqlite3

def fetch_user_data(user_id):
    conn = sqlite3.connect('app.db')
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
    result = cursor.fetchone()
    conn.close()
    return result

# Called from a web handler without exception handling
user_data = fetch_user_data(request.args.get('id'))

Why it's vulnerable:
If the database is unavailable, the connection fails, or the query times out, the exception propagates uncaught. The connection may not close, and the raw error is exposed to the caller or logged without context. Security checks or cleanup code after this call never execute.

Fixed pattern
import sqlite3
import logging

def fetch_user_data(user_id):
    conn = None
    try:
        conn = sqlite3.connect('app.db', timeout=5)
        cursor = conn.cursor()
        cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
        result = cursor.fetchone()
        return result
    except sqlite3.OperationalError as e:
        logging.error("Database error (not user-facing): %s", str(e))
        return None
    except Exception as e:
        logging.error("Unexpected error: %s", type(e).__name__)
        return None
    finally:
        if conn:
            conn.close()
Vulnerable pattern
<?php
function get_user_email($user_id) {
    $mysqli = new mysqli("localhost", "user", "pass", "mydb");
    $result = $mysqli->query("SELECT email FROM users WHERE id = " . $user_id);
    $row = $result->fetch_assoc();
    return $row['email'];
}

$email = get_user_email($_GET['id']);
echo "Email: " . $email;
?>

Why it's vulnerable:
No error checking on the connection or query. If the database is down or the query fails, $result is false, fetch_assoc() returns null, and the code attempts to access $row['email'], triggering a warning or fatal error. The raw error is exposed to the user.

Fixed pattern
<?php
function get_user_email($user_id) {
    $mysqli = new mysqli("localhost", "user", "pass", "mydb");
    
    if ($mysqli->connect_error) {
        error_log("DB connection failed: " . $mysqli->connect_error);
        return null;
    }
    
    $result = $mysqli->query("SELECT email FROM users WHERE id = " . intval($user_id));
    
    if (!$result) {
        error_log("Query failed: " . $mysqli->error);
        return null;
    }
    
    $row = $result->fetch_assoc();
    return $row ? $row['email'] : null;
}

$email = get_user_email($_GET['id']);
if ($email) {
    echo "Email: " . esc_html($email);
} else {
    echo "User not found or error occurred.";
}
?>

05Prevention Checklist

Wrap risky operations in try-catch blocks.
Database queries, file I/O, network calls, and external API calls should always be caught and handled explicitly.
Log errors securely.
Record full details (stack traces, query strings, system state) to server-side logs only; never expose them to end users.
Use generic error messages for users.
Display "An error occurred" or "Please try again later" instead of technical details, file paths, or database schema hints.
Ensure cleanup in all paths.
Use finally blocks (Python) or ensure statements to close connections, release locks, and free resources even when exceptions occur.
Fail securely.
If a security check throws an exception, deny access by default; do not skip the check or allow the request to proceed.
Test error paths.
Deliberately trigger exceptions (network failures, invalid input, resource exhaustion) and verify the application recovers safely.

06Signs You May Already Be Affected

- Verbose error pages displayed to users
containing file paths, database table names, or library versions. - Application crashes or hangs
when external services (database, API) become temporarily unavailable. - Orphaned database connections or file handles
visible in system monitoring, indicating cleanup code was never reached. - Inconsistent data or incomplete transactions
in logs, suggesting error recovery left the system in a partial state.

07Related Recent Vulnerabilities