Incomplete Filtering of Special Elements

Description

Incomplete Filtering of Special Elements is an input validation vulnerability where software receives data from an upstream component but does not completely filter special elements before sending it to a downstream component. While some filtering may be implemented, it fails to address all special characters, all instances of special characters, or all variations of special character encoding. This partial filtering provides a false sense of security while still allowing malicious input to reach the downstream component, where it may be interpreted as commands, markup, or control sequences.

Risk

Incomplete filtering is particularly dangerous because it suggests that developers were aware of the security concern but implemented an inadequate solution. Attackers can bypass incomplete filters through various techniques: using multiple instances of filtered sequences, encoding special characters differently, exploiting case sensitivity, or combining filtered elements in ways that survive partial removal. The vulnerability enables injection attacks including path traversal, command injection, SQL injection, and cross-site scripting. Because some filtering exists, the vulnerability may escape detection during basic security testing.

Solution

Implement comprehensive filtering that addresses all instances and variations of dangerous elements. Use global matching flags (like /g in regular expressions) to replace all occurrences. Loop filtering operations until no more matches are found to handle nested patterns. Consider using allowlist validation instead of denylist filtering—accept only known-safe characters rather than trying to block known-bad ones. Use well-tested input validation libraries rather than custom implementations. Prefer output encoding appropriate for the target context over input filtering. Test filtering implementations with various bypass techniques including double encoding, case variations, and recursive patterns.

Common Consequences

ImpactDetails
IntegrityScope: Integrity

Unexpected State - Unfiltered special elements cause application to reach unintended states or execute unintended operations.
ConfidentialityScope: Confidentiality

Read Application Data - Path traversal through incomplete filtering exposes sensitive files.
IntegrityScope: Integrity

Execute Unauthorized Code - Injection attacks become possible when filtering fails to neutralize all dangerous elements.

Example Code

Vulnerable Code

# Vulnerable: Single replacement instead of global
my $Username = GetUntrustedInput();

# Vulnerable: Missing /g modifier - only removes FIRST "../"
$Username =~ s/\.\.\///;

my $filename = "/home/user/" . $Username;
ReadAndSendFile($filename);

# Attack: Input "../../../etc/passwd"
# After single replacement: "../../etc/passwd"
# Full path: "/home/user/../../etc/passwd" -> "/etc/passwd"
// Vulnerable: Only filters specific tag, not all variations
<?php
function vulnerable_filter_xss($input) {
    // Vulnerable: Only removes lowercase <script> tags
    $filtered = str_replace("<script>", "", $input);
    $filtered = str_replace("</script>", "", $filtered);
    return $filtered;
}

// Bypasses:
// <SCRIPT>alert(1)</SCRIPT> - case variation
// <scr<script>ipt>alert(1)</scr</script>ipt> - nested
// <img onerror=alert(1) src=x> - different tag
# Vulnerable: Only filters one type of path separator
def vulnerable_filter_path(path):
    # Vulnerable: Only filters forward slash traversal
    filtered = path.replace('../', '')
    return filtered

# Bypass: "..\\..\\etc\\passwd" on Windows
# Or mixed: "..\\/etc/passwd"
// Vulnerable: Filters some SQL metacharacters but not all
public String vulnerableFilterSQL(String input) {
    // Vulnerable: Only escapes single quote
    String filtered = input.replace("'", "''");
    return filtered;
}

// Bypasses:
// Using double quotes: "value" OR "1"="1
// Using backslash: \'; DROP TABLE users; --
// Using encoded characters in some databases
// Vulnerable: Only checks for one null byte encoding
char* vulnerable_filter_null(char* input) {
    char* result = malloc(strlen(input) + 1);
    int j = 0;

    for (int i = 0; input[i] != '\0'; i++) {
        // Vulnerable: Doesn't filter %00 or \\0 encodings
        if (input[i] != 0x00) {
            result[j++] = input[i];
        }
    }
    result[j] = '\0';
    return result;
}

// Bypass: "valid.txt%00.exe" - %00 not filtered

Fixed Code

# Fixed: Global replacement with recursive filtering
my $Username = GetUntrustedInput();

# Fixed: Use /g for global replacement
# And loop until stable to handle nested sequences
my $prev;
do {
    $prev = $Username;
    $Username =~ s/\.\.[\\/]//g;  # Handle both / and \
    $Username =~ s/%2e%2e[%2f%5c]//gi;  # Handle URL-encoded
} while ($Username ne $prev);

# Better: Allowlist validation
if ($Username !~ /^[a-zA-Z0-9_.-]+$/) {
    die "Invalid username";
}

my $filename = "/home/user/" . $Username;
// Fixed: Comprehensive HTML filtering
<?php
function fixed_filter_xss($input) {
    // Fixed: Use proper HTML encoding
    return htmlspecialchars($input, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}

// Or use allowlist for specific allowed tags
function fixed_filter_allowed_tags($input) {
    // Strip all tags except allowed ones
    return strip_tags($input, '<b><i><u><p><br>');
}

// Best: Use a proper HTML sanitizer library
use HTMLPurifier;
function fixed_sanitize_html($input) {
    $config = HTMLPurifier_Config::createDefault();
    $purifier = new HTMLPurifier($config);
    return $purifier->purify($input);
}
# Fixed: Filter all path separator variations
import os
import re

def fixed_filter_path(path):
    # Fixed: Filter all variations recursively
    prev = None
    while path != prev:
        prev = path
        # Filter Unix and Windows separators
        path = re.sub(r'\.\.[\\/]', '', path)
        # Filter URL-encoded versions
        path = re.sub(r'%2e%2e[%2f%5c]', '', path, flags=re.IGNORECASE)
        path = re.sub(r'%252e%252e[%252f%255c]', '', path, flags=re.IGNORECASE)

    return path

# Best: Validate canonical path
def fixed_validate_path(base_dir, user_path):
    full_path = os.path.realpath(os.path.join(base_dir, user_path))
    if not full_path.startswith(os.path.realpath(base_dir) + os.sep):
        raise ValueError("Path traversal detected")
    return full_path
// Fixed: Use parameterized queries instead of filtering
public void fixedQuery(String username) {
    // Fixed: No need to filter - use prepared statement
    String sql = "SELECT * FROM users WHERE name = ?";
    PreparedStatement stmt = connection.prepareStatement(sql);
    stmt.setString(1, username);
    ResultSet rs = stmt.executeQuery();
}

// If filtering is needed, use comprehensive approach
public String fixedFilterSQL(String input) {
    // Use database-specific escape function
    // Or implement comprehensive escaping
    StringBuilder result = new StringBuilder();
    for (char c : input.toCharArray()) {
        switch (c) {
            case '\'': result.append("''"); break;
            case '"': result.append("\\\""); break;
            case '\\': result.append("\\\\"); break;
            case '\0': break;  // Remove null bytes
            default: result.append(c);
        }
    }
    return result.toString();
}

  • CWE-790: Improper Filtering of Special Elements (parent)
  • CWE-792: Incomplete Filtering of One or More Instances of Special Elements (child)
  • CWE-795: Only Filtering Special Elements at a Specified Location (child)

References

  1. MITRE Corporation. "CWE-791: Incomplete Filtering of Special Elements." https://cwe.mitre.org/data/definitions/791.html
  2. OWASP. "Input Validation Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html
  3. OWASP. "Testing for Filter Bypass." OWASP Testing Guide.