Incomplete Filtering of Multiple Instances of Special Elements

Description

Incomplete Filtering of Multiple Instances of Special Elements is an input validation vulnerability where software fails to filter all instances of special elements when they appear multiple times in the input. This applies to both sequential elements (dangerous sequences appearing adjacent to each other, like "....//") and non-sequential elements (the same dangerous sequence appearing in multiple locations throughout the input). When filtering doesn't address all instances, attackers can construct inputs where removal of some instances produces or reveals additional dangerous sequences.

Risk

This vulnerability enables sophisticated bypass techniques. With sequential elements, attackers can craft inputs like "....//file" where removing the middle ".." and "/" leaves behind a new "../" sequence. With non-sequential elements, inputs like "..%2f..%2f../file" may have some traversal sequences filtered while encoded or differently formatted ones survive. The risk is compounded when filtering is applied only once without checking if the result is now dangerous. These bypasses are particularly effective against naive filter implementations that assume a single pass is sufficient.

Solution

Implement recursive or iterative filtering that continues until no more dangerous sequences are found. Apply filtering in a loop, comparing the result to the previous iteration until they match (indicating no more changes). Handle all variations of dangerous elements including different encodings, case variations, and separator styles. Consider multiple filtering passes for different categories of dangerous elements. Use canonical representation before validation—for paths, resolve to absolute canonical form before checking. Prefer allowlist validation that only permits known-safe characters. Test with complex nested and encoded inputs.

Common Consequences

ImpactDetails
IntegrityScope: Integrity

Unexpected State - Unfiltered element instances cause unintended application behavior.
ConfidentialityScope: Confidentiality

Read Application Data - Sequential traversal patterns that survive filtering enable file access.
IntegrityScope: Integrity

Execute Unauthorized Code - Multiple encoding variations of dangerous elements may enable injection.

Example Code

Vulnerable Code

# Vulnerable: Single pass doesn't handle sequential patterns
my $Username = GetUntrustedInput();

# Vulnerable: Only removes literal "../" sequences
$Username =~ s/\.\.\///g;

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

# Attack: "....//....//etc/passwd"
# After filter: each ".." followed by "." followed by "/" becomes different
# Actually: "...." becomes ".." and "//" becomes "/" after removing middle
# More precisely, "..../" contains "../" which is removed leaving "../"
# Result may still allow traversal depending on exact pattern
// Vulnerable: Single replacement pass
<?php
function vulnerable_filter($input) {
    // Vulnerable: Doesn't loop until stable
    $filtered = str_replace("../", "", $input);
    return $filtered;
}

// Attack: "..././..././etc/passwd"
// After ONE pass: "..//..//etc/passwd"
// The "..././" contains "../" which is removed, leaving "./"
// But "./../" then becomes "../" - still traversal!
# Vulnerable: Doesn't handle double-encoded sequences
import urllib.parse

def vulnerable_filter(path):
    # First pass: remove path traversal
    filtered = path.replace('../', '')

    # Vulnerable: Doesn't handle encoded versions
    return filtered

# Attack: "..%2f..%2fetc%2fpasswd"
# After filter: unchanged because %2f is not /
# When URL-decoded by web server: "../../../etc/passwd"
// Vulnerable: Doesn't handle all variations
function vulnerableFilter(input) {
    // Vulnerable: Only handles forward slash variant
    return input.replace(/\.\.\//g, '');
}

// Bypass 1 (Windows): "..\\..\\etc\\passwd"
// Bypass 2 (Mixed): "..\\/etc/passwd"
// Bypass 3 (Encoded): "..%2Fetc/passwd"
// Vulnerable: Filters each type separately, doesn't loop
public String vulnerableFilter(String input) {
    String result = input;

    // Remove forward slash traversal
    result = result.replaceAll("\\.\\./", "");

    // Remove backslash traversal
    result = result.replaceAll("\\.\\.\\\\", "");

    return result;
}

// Attack: "..../\\" -> after removing "../" leaves "..\\"
// Attack: "..\\../file" -> after removing "..\" leaves "../file"

Fixed Code

# Fixed: Loop until no more changes
my $Username = GetUntrustedInput();

# Fixed: Recursive filtering
my $prev;
do {
    $prev = $Username;
    # Remove all variants
    $Username =~ s/\.\.[\/\\]//g;           # Literal traversal
    $Username =~ s/%2e%2e[%2f%5c]//gi;      # URL-encoded
    $Username =~ s/%252e%252e[%252f%255c]//gi;  # Double-encoded
} while ($Username ne $prev);

# Better: Validate against allowlist
unless ($Username =~ /^[a-zA-Z0-9_.-]+$/) {
    die "Invalid username";
}

my $filename = "/home/user/" . $Username;
// Fixed: Multi-pass filtering with all variants
<?php
function fixed_filter($input) {
    // Decode URL encoding first
    $decoded = rawurldecode(rawurldecode($input));  // Double decode

    // Loop until stable
    do {
        $prev = $decoded;
        $decoded = str_replace("../", "", $decoded);
        $decoded = str_replace("..\\", "", $decoded);
        $decoded = str_replace(".." . DIRECTORY_SEPARATOR, "", $decoded);
    } while ($decoded !== $prev);

    return $decoded;
}

// Better: Canonical path validation
function fixed_validate_path($base, $userInput) {
    // Decode first
    $decoded = rawurldecode(rawurldecode($userInput));

    // Resolve to canonical path
    $realBase = realpath($base);
    $fullPath = realpath($base . DIRECTORY_SEPARATOR . $decoded);

    if ($fullPath === false ||
        strpos($fullPath, $realBase . DIRECTORY_SEPARATOR) !== 0) {
        throw new Exception("Path traversal detected");
    }

    return $fullPath;
}
# Fixed: Handle all encoding variants and loop
import urllib.parse
import os.path

def fixed_filter(path):
    # Decode multiple times to handle double/triple encoding
    decoded = path
    for _ in range(3):  # Handle up to triple encoding
        prev = decoded
        decoded = urllib.parse.unquote(decoded)
        if decoded == prev:
            break

    # Loop until stable
    prev = None
    while decoded != prev:
        prev = decoded
        # Remove all traversal variants
        decoded = decoded.replace('../', '')
        decoded = decoded.replace('..\\', '')

    return decoded

# Better: Use canonical path validation
def fixed_validate_path(base_dir, user_path):
    # Decode the user input
    decoded = user_path
    for _ in range(3):
        prev = decoded
        decoded = urllib.parse.unquote(decoded)
        if decoded == prev:
            break

    # Resolve to canonical path
    full_path = os.path.realpath(os.path.join(base_dir, decoded))
    base_path = os.path.realpath(base_dir)

    # Verify it's under base directory
    if not full_path.startswith(base_path + os.sep):
        raise ValueError("Path traversal detected")

    return full_path
// Fixed: Comprehensive filtering with loop
import java.net.URLDecoder;
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathValidator {

    public String fixedFilter(String input) {
        // Decode URL encoding (multiple passes for double encoding)
        String decoded = input;
        for (int i = 0; i < 3; i++) {
            try {
                String prev = decoded;
                decoded = URLDecoder.decode(decoded, "UTF-8");
                if (decoded.equals(prev)) break;
            } catch (Exception e) {
                break;
            }
        }

        // Loop until stable
        String result = decoded;
        String prev;
        do {
            prev = result;
            result = result.replaceAll("\\.\\.[/\\\\]", "");
        } while (!result.equals(prev));

        return result;
    }

    // Better: Canonical path validation
    public Path fixedValidatePath(Path base, String userInput) throws Exception {
        // Decode
        String decoded = userInput;
        for (int i = 0; i < 3; i++) {
            String prev = decoded;
            decoded = URLDecoder.decode(decoded, "UTF-8");
            if (decoded.equals(prev)) break;
        }

        // Resolve and validate
        Path resolved = base.resolve(decoded).normalize().toRealPath();
        Path realBase = base.toRealPath();

        if (!resolved.startsWith(realBase)) {
            throw new SecurityException("Path traversal detected");
        }

        return resolved;
    }
}

  • CWE-792: Incomplete Filtering of One or More Instances of Special Elements (parent)
  • CWE-793: Only Filtering One Instance of a Special Element (sibling)
  • CWE-23: Relative Path Traversal (related exploitation)

References

  1. MITRE Corporation. "CWE-794: Incomplete Filtering of Multiple Instances of Special Elements." https://cwe.mitre.org/data/definitions/794.html
  2. OWASP. "Path Traversal." https://owasp.org/www-community/attacks/Path_Traversal
  3. OWASP. "Double Encoding." https://owasp.org/www-community/Double_Encoding