Only Filtering Special Elements Relative to a Marker

Description

Only Filtering Special Elements Relative to a Marker is an input validation vulnerability where software filters dangerous elements only when they appear at specific positions relative to string markers, such as the beginning (^) or end ($) of a string. This approach uses anchored pattern matching that only checks at boundary positions, allowing identical dangerous sequences to pass through when they appear elsewhere in the input. The filter assumes attackers will place malicious content at predictable locations, which is easily circumvented.

Risk

Attackers can trivially bypass marker-relative filtering by positioning dangerous sequences away from the checked marker. If a filter removes "../" only at the string beginning, inputs like "x/../../../etc/passwd" or "foo/../../../etc/passwd" pass through completely unchanged. This is particularly dangerous in path handling where any traversal sequence, regardless of position, can navigate the directory tree. The vulnerability is common when developers use regex anchors (^, $) without realizing they limit matching to specific positions. Even a single unfiltered instance of a dangerous sequence can fully compromise the intended security control.

Solution

Remove position anchors from security-critical pattern matching unless the entire string must match a specific format. Use global, unanchored patterns that find dangerous sequences anywhere in the input. For path validation, resolve inputs to canonical paths and verify they remain within allowed directories. Implement iterative filtering that continues until no more dangerous patterns are found. Prefer allowlist validation that only accepts known-safe characters. When anchors are needed for format validation, combine them with comprehensive content validation that examines the entire input.

Common Consequences

ImpactDetails
IntegrityScope: Integrity

Unexpected State - Dangerous elements positioned away from markers pass through filters unchanged.
ConfidentialityScope: Confidentiality

Read Application Data - Unfiltered path traversal sequences enable access to sensitive files.
IntegrityScope: Integrity

Execute Unauthorized Code - Injection sequences avoid filtering by appearing after checked markers.

Example Code

Vulnerable Code

# Vulnerable: ^ anchor only matches at string start
my $Username = GetUntrustedInput();

# Vulnerable: Only removes "../" at the very beginning
$Username =~ s/^\.\.\///;

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

# Attack: "../../../etc/passwd" -> removes first, leaves "../../etc/passwd"
# Attack: "x/../../../etc/passwd" -> "../" not at start, unchanged
# Attack: "subdir/../../../etc/passwd" -> prefix allows bypass
# Vulnerable: Only checks if string starts with dangerous pattern
def vulnerable_filter(path):
    # Vulnerable: Only removes leading traversal
    if path.startswith('../'):
        path = path[3:]
    return path

# Attack: "foo/../../../etc/passwd" - doesn't start with "../"
# Attack: "./../../etc/passwd" - starts with "./" not "../"
// Vulnerable: Only matches at string boundaries
function vulnerableFilter(input) {
    // Vulnerable: ^ anchors match only at start
    return input.replace(/^\.\.\//g, '');
}

// Even with /g flag, ^ means "start of string"
// Attack: "x/../../../etc/passwd" - unchanged
// Attack: "dir/../../../etc/passwd" - unchanged
// Vulnerable: Only strips from beginning or end
<?php
function vulnerable_filter_start($input) {
    // Vulnerable: Only removes from beginning
    if (strpos($input, '../') === 0) {
        return substr($input, 3);
    }
    return $input;
}

function vulnerable_filter_end($input) {
    // Vulnerable: Only removes from end
    if (substr($input, -3) === '../') {
        return substr($input, 0, -3);
    }
    return $input;
}

// Attacks work when dangerous content is in the middle
// Vulnerable: Uses startsWith/endsWith checks
public String vulnerableFilter(String input) {
    // Vulnerable: Only checks at boundaries
    if (input.startsWith("../")) {
        return input.substring(3);
    }
    if (input.endsWith("../")) {
        return input.substring(0, input.length() - 3);
    }
    return input;
}

// Attack: "subdir/../../../etc/passwd"
// Attack: "../../../etc/passwd" only removes first "../"
// Vulnerable: Only checks first N characters
char* vulnerable_filter(char* input) {
    // Vulnerable: Only checks if "../" at position 0
    if (strncmp(input, "../", 3) == 0) {
        return input + 3;
    }
    return input;
}

// Attack: "x/../../../etc/passwd" - "../" not at position 0

Fixed Code

# Fixed: Remove anchors, use global matching with loop
my $Username = GetUntrustedInput();

# Fixed: No ^ anchor - matches anywhere
# Loop handles nested patterns
my $prev;
do {
    $prev = $Username;
    $Username =~ s/\.\.[\\/]//g;  # Match both separators
} while ($Username ne $prev);

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

my $filename = "/home/user/" . $Username;
# Fixed: Check entire string, use canonical path
import os.path

def fixed_filter(path):
    # Fixed: Remove from anywhere, loop until stable
    prev = None
    while path != prev:
        prev = path
        path = path.replace('../', '').replace('..\\', '')
    return path

# Better: Canonical path validation
def fixed_validate_path(base_dir, user_path):
    # Resolve to canonical form
    full_path = os.path.realpath(os.path.join(base_dir, user_path))
    base_path = os.path.realpath(base_dir)

    # Verify result is under base directory
    if not full_path.startswith(base_path + os.sep):
        raise ValueError("Path traversal detected")
    return full_path
// Fixed: No anchors, global search
function fixedFilter(input) {
    // Fixed: No anchors, matches anywhere
    let result = input;
    let prev;
    do {
        prev = result;
        // No ^ or $ - matches throughout string
        result = result.replace(/\.\.[\\/]/g, '');
    } while (result !== prev);

    return result;
}

// Better: Use path module for validation
const path = require('path');
function fixedValidatePath(basePath, userInput) {
    const fullPath = path.resolve(basePath, userInput);
    const realBase = path.resolve(basePath);

    if (!fullPath.startsWith(realBase + path.sep)) {
        throw new Error('Path traversal detected');
    }
    return fullPath;
}
// Fixed: Check and remove from entire string
<?php
function fixed_filter($input) {
    // Fixed: Loop until no more changes
    do {
        $prev = $input;
        // Remove from anywhere in string
        $input = str_replace('../', '', $input);
        $input = str_replace('..\\', '', $input);
    } while ($input !== $prev);

    return $input;
}

// Better: Use realpath validation
function fixed_validate_path($base, $userPath) {
    $basePath = realpath($base);
    $fullPath = realpath($base . DIRECTORY_SEPARATOR . $userPath);

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

    return $fullPath;
}
// Fixed: Search entire string
import java.nio.file.Path;
import java.nio.file.Paths;

public String fixedFilter(String input) {
    // Fixed: Remove from anywhere, loop until stable
    String result = input;
    String prev;
    do {
        prev = result;
        result = result.replace("../", "").replace("..\\", "");
    } while (!result.equals(prev));

    return result;
}

// Better: Canonical path validation
public Path fixedValidatePath(Path base, String userInput) throws Exception {
    Path resolved = base.resolve(userInput).normalize().toRealPath();
    Path realBase = base.toRealPath();

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

  • CWE-795: Only Filtering Special Elements at a Specified Location (parent)
  • CWE-797: Only Filtering Special Elements at an Absolute Position (sibling)
  • CWE-791: Incomplete Filtering of Special Elements (grandparent)
  • CWE-22: Improper Limitation of a Pathname to a Restricted Directory (related exploitation)

References

  1. MITRE Corporation. "CWE-796: Only Filtering Special Elements Relative to a Marker." https://cwe.mitre.org/data/definitions/796.html
  2. OWASP. "Path Traversal." https://owasp.org/www-community/attacks/Path_Traversal
  3. Regular-Expressions.info. "Anchors." https://www.regular-expressions.info/anchors.html