Only Filtering Special Elements at a Specified Location
Description
Only Filtering Special Elements at a Specified Location is an input validation vulnerability where software filters special elements only at specific positions within the input, leaving dangerous elements unfiltered elsewhere in the data. This includes filtering only relative to markers (like the beginning or end of a string) or at absolute positions (like specific byte offsets). The assumption that dangerous elements will only appear at certain locations is flawed, as attackers can position malicious sequences anywhere in the input to bypass the incomplete filter.
Risk
This vulnerability provides attackers with a predictable bypass mechanism. If a filter only removes "../" sequences at the start of a string, attackers simply prepend any character or construct inputs where the dangerous sequences appear after the checked position. For example, "foo/../../../etc/passwd" passes through a filter that only checks position 0. The vulnerability is especially dangerous because the filter gives a false sense of security—developers may believe path traversal or injection is handled when it clearly is not. Testing that only uses malicious input at the expected position will pass, making the vulnerability easy to miss during development.
Solution
Never assume dangerous elements will only appear at specific locations. Use global pattern matching that searches the entire input for all instances of dangerous sequences. For path validation, resolve the path to its canonical form and verify it falls within allowed boundaries. Implement allowlist validation that permits only known-safe characters regardless of position. Apply filters iteratively until no more dangerous patterns are found. For path-based operations, use realpath() or equivalent canonical path resolution, then verify the result is within the intended directory. Test with dangerous elements at various positions: beginning, middle, end, and multiple locations.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Unexpected State - Unfiltered elements at unexpected positions cause data to pass through in a dangerous state. |
| Confidentiality | Scope: Confidentiality Read Application Data - Path traversal sequences outside the checked position enable unauthorized file access. |
| Integrity | Scope: Integrity Execute Unauthorized Code - Injection sequences positioned outside filtered locations enable attacks. |
Example Code
Vulnerable Code
# Vulnerable: Only filters "../" at the beginning of the string
my $Username = GetUntrustedInput();
# Vulnerable: ^ anchor only matches at string start
$Username =~ s/^\.\.\///;
my $filename = "/home/user/" . $Username;
ReadAndSendFile($filename);
# Attack: "foo/../../../etc/passwd" - "../" not at start, passes filter
# Attack: "../../../etc/passwd" - only first "../" removed, leaves "../../etc/passwd"
# Vulnerable: Only checks substring at absolute position 0-2
my $Username = GetUntrustedInput();
# Vulnerable: Only checks first 3 characters
if (substr($Username, 0, 3) eq '../') {
$Username = substr($Username, 3);
}
my $filename = "/home/user/" . $Username;
ReadAndSendFile($filename);
# Attack: "../../../etc/passwd" becomes "../../etc/passwd"
# Attack: "x/../../../etc/passwd" - "../" not at position 0, unchanged
// Vulnerable: Only strips prefix
<?php
function vulnerable_filter($path) {
// Vulnerable: Only removes leading ../
if (strpos($path, '../') === 0) {
$path = substr($path, 3);
}
return $path;
}
// Attack: "foo/../../../etc/passwd" - not at start, unchanged
// Attack: "./../../etc/passwd" - "./" at start, not filtered
# Vulnerable: Only checks at specific offset
def vulnerable_filter(input_string):
# Vulnerable: Only removes <script> at very beginning
if input_string.startswith('<script>'):
input_string = input_string[8:]
return input_string
# Attack: " <script>alert(1)</script>" - space before tag
# Attack: "x<script>alert(1)</script>" - character before tag
// Vulnerable: Only filters at string end
function vulnerableFilter(input) {
// Vulnerable: Only removes trailing semicolon
if (input.endsWith(';')) {
return input.slice(0, -1);
}
return input;
}
// For SQL context, this is meaningless:
// Attack: "'; DROP TABLE users; --" - semicolon not at end
// Vulnerable: Only checks beginning for null bytes
public String vulnerableFilter(String input) {
// Vulnerable: Only removes leading null bytes
int start = 0;
while (start < input.length() && input.charAt(start) == '\0') {
start++;
}
return input.substring(start);
}
// Attack: "valid.txt\0.exe" - null byte in middle
Fixed Code
# Fixed: Filter all occurrences throughout string
my $Username = GetUntrustedInput();
# Fixed: No anchor - matches anywhere; /g for global replacement
# Loop until no changes for nested patterns
my $prev;
do {
$prev = $Username;
$Username =~ s/\.\.[\\/]//g; # All occurrences
} while ($Username ne $prev);
# Better: Validate with allowlist
unless ($Username =~ /^[a-zA-Z0-9_.-]+$/) {
die "Invalid username";
}
my $filename = "/home/user/" . $Username;
// Fixed: Use canonical path validation
<?php
function fixed_validate_path($base, $userPath) {
// Resolve to canonical path
$basePath = realpath($base);
$fullPath = realpath($base . DIRECTORY_SEPARATOR . $userPath);
// Check if resolved path is under base
if ($fullPath === false ||
strpos($fullPath, $basePath . DIRECTORY_SEPARATOR) !== 0) {
throw new Exception("Path traversal detected");
}
return $fullPath;
}
// Filter function that works anywhere in string
function fixed_filter_traversal($input) {
$prev = null;
while ($input !== $prev) {
$prev = $input;
$input = str_replace('../', '', $input);
$input = str_replace('..\\', '', $input);
}
return $input;
}
# Fixed: Check entire string, not just specific position
import re
import os.path
def fixed_filter_xss(input_string):
# Fixed: Remove <script> tags anywhere in string (case-insensitive)
pattern = re.compile(r'<script[^>]*>.*?</script>', re.IGNORECASE | re.DOTALL)
result = input_string
prev = None
while result != prev:
prev = result
result = pattern.sub('', result)
return result
# Better: Use proper HTML encoding
import html
def fixed_encode_html(input_string):
return html.escape(input_string)
// Fixed: Comprehensive validation regardless of position
function fixedFilter(input) {
// Remove dangerous characters from anywhere
let result = input;
let prev;
do {
prev = result;
result = result.replace(/\.\.[\\/]/g, '');
} while (result !== prev);
return result;
}
// Better: Use path resolution
const path = require('path');
function fixedValidatePath(basePath, userInput) {
const resolved = path.resolve(basePath, userInput);
const realBase = path.resolve(basePath);
if (!resolved.startsWith(realBase + path.sep)) {
throw new Error('Path traversal detected');
}
return resolved;
}
// Fixed: Remove null bytes from entire string
public String fixedFilter(String input) {
// Fixed: Remove all null bytes, not just leading ones
StringBuilder result = new StringBuilder();
for (char c : input.toCharArray()) {
if (c != '\0') {
result.append(c);
}
}
return result.toString();
}
// Better: Validate with allowlist
public String fixedValidateFilename(String filename) {
if (!filename.matches("^[a-zA-Z0-9_.-]+$")) {
throw new IllegalArgumentException("Invalid filename");
}
return filename;
}
Related CWEs
- CWE-791: Incomplete Filtering of Special Elements (parent)
- CWE-796: Only Filtering Special Elements Relative to a Marker (child)
- CWE-797: Only Filtering Special Elements at an Absolute Position (child)
- CWE-22: Improper Limitation of a Pathname to a Restricted Directory (related exploitation)
References
- MITRE Corporation. "CWE-795: Only Filtering Special Elements at a Specified Location." https://cwe.mitre.org/data/definitions/795.html
- OWASP. "Input Validation Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html
- OWASP. "Path Traversal." https://owasp.org/www-community/attacks/Path_Traversal