Incomplete Filtering of One or More Instances of Special Elements
Description
Incomplete Filtering of One or More Instances of Special Elements is an input validation vulnerability where software filters some but not all instances of special elements from input data. This manifests in two primary ways: filtering only the first (or last) occurrence of a special element while additional instances remain, or failing to filter all types of special elements when multiple different dangerous elements are present. The unfiltered instances can then be used to execute injection attacks against downstream components.
Risk
When filtering only targets individual instances, attackers can include multiple copies of dangerous sequences to bypass protection. For example, if only the first "../" is removed, input containing "../../" will still have one traversal sequence after filtering. Similarly, if only single quotes are filtered but not double quotes, SQL injection remains possible through double-quoted strings. This vulnerability is common when developers use non-global string replacement functions or regex patterns without global flags. The resulting partial protection may pass basic security tests while remaining exploitable.
Solution
Always use global replacement mechanisms when filtering dangerous elements. In regular expressions, use the /g flag (or equivalent) for global matching. In string replacement functions, loop until no more replacements occur to handle nested patterns. Better yet, use allowlist validation that accepts only known-safe characters. Test filters with inputs containing multiple instances of dangerous sequences in various positions. Consider using purpose-built sanitization libraries that have been designed and tested to handle all variations. For context-specific output (SQL, HTML, shell), prefer parameterized APIs or encoding over input filtering.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Unexpected State - Unfiltered instances of special elements cause unintended application behavior. |
| Confidentiality | Scope: Confidentiality Read Application Data - Multiple traversal sequences allow file access after partial filtering. |
| Integrity | Scope: Integrity Execute Unauthorized Code - Remaining special elements enable injection attacks. |
Example Code
Vulnerable Code
# Vulnerable: Only removes first occurrence
my $Username = GetUntrustedInput();
# Vulnerable: No /g modifier - removes only first "../"
$Username =~ s/\.\.\///;
my $filename = "/home/user/" . $Username;
ReadAndSendFile($filename);
# Attack: "../../../etc/passwd"
# After filter: "../../etc/passwd"
# Result: Traverses to /etc/passwd
// Vulnerable: str_replace without loop
<?php
function vulnerable_filter($input) {
// Vulnerable: Single replacement - doesn't handle nested/multiple
$filtered = str_replace("../", "", $input);
return $filtered;
}
// Attack: "....//file" -> becomes "../file" after one replacement
// Attack: "..././..././etc/passwd" -> "..//..//etc/passwd" -> "../etc/passwd"
// Vulnerable: Non-global regex replacement
function vulnerableFilter(input) {
// Vulnerable: No 'g' flag - only first match replaced
return input.replace(/\.\.\//, '');
}
// Attack: "../../../etc/passwd" becomes "../../etc/passwd"
# Vulnerable: Filters only some special SQL characters
def vulnerable_sql_filter(value):
# Vulnerable: Only handles single quote, not other metacharacters
filtered = value.replace("'", "''")
return filtered
# Bypass via double quotes (MySQL):
# value: " OR "1"="1
# Bypass via backslash (some databases):
# value: \'; DROP TABLE users;--
// Vulnerable: Only filters specific HTML tags
public String vulnerableXSSFilter(String input) {
// Vulnerable: Only removes specific tags, not all dangerous content
String filtered = input;
filtered = filtered.replaceAll("<script>", "");
filtered = filtered.replaceAll("</script>", "");
return filtered;
}
// Bypasses:
// <SCRIPT> (case sensitivity)
// <scr<script>ipt> (nested - produces <script> after filtering)
// <img onerror=alert(1)> (different tag/attribute)
Fixed Code
# Fixed: Global replacement with loop for nested patterns
my $Username = GetUntrustedInput();
# Fixed: Loop until no changes (handles nested patterns)
my $prev;
do {
$prev = $Username;
# /g for global replacement
$Username =~ s/\.\.\///g;
# Also handle backslash variant
$Username =~ s/\.\.\\//g;
} while ($Username ne $prev);
# Better: Validate with allowlist
unless ($Username =~ /^[a-zA-Z0-9_-]+$/) {
die "Invalid username format";
}
my $filename = "/home/user/" . $Username;
// Fixed: Loop until no more replacements
<?php
function fixed_filter($input) {
// Fixed: Loop until stable
do {
$prev = $input;
$input = str_replace("../", "", $input);
$input = str_replace("..\\", "", $input);
} while ($input !== $prev);
return $input;
}
// Better: Use realpath and verify
function fixed_path_validation($base, $userPath) {
$fullPath = realpath($base . '/' . $userPath);
if ($fullPath === false ||
strpos($fullPath, realpath($base)) !== 0) {
throw new Exception("Invalid path");
}
return $fullPath;
}
// Fixed: Use global flag
function fixedFilter(input) {
// Fixed: 'g' flag for global replacement
let result = input;
let prev;
do {
prev = result;
result = result.replace(/\.\.[\\/]/g, '');
} while (result !== prev);
return result;
}
// Or use comprehensive path validation
function fixedPathValidation(basePath, userPath) {
const path = require('path');
const fullPath = path.resolve(basePath, userPath);
if (!fullPath.startsWith(path.resolve(basePath) + path.sep)) {
throw new Error('Path traversal detected');
}
return fullPath;
}
# Fixed: Comprehensive SQL escaping or parameterization
import re
def fixed_sql_parameterized(cursor, value):
# Fixed: Use parameterized query - no escaping needed
cursor.execute("SELECT * FROM users WHERE name = %s", (value,))
# If escaping is required, be comprehensive
def fixed_sql_escape(value, connection):
# Use database-specific escape function
return connection.escape_string(value)
// Fixed: Comprehensive HTML encoding
import org.apache.commons.text.StringEscapeUtils;
import org.owasp.encoder.Encode;
public String fixedXSSFilter(String input) {
// Fixed: Encode all HTML special characters
return Encode.forHtml(input);
}
// Or for specific contexts
public String fixedAttributeFilter(String input) {
return Encode.forHtmlAttribute(input);
}
public String fixedJSFilter(String input) {
return Encode.forJavaScript(input);
}
Related CWEs
- CWE-791: Incomplete Filtering of Special Elements (parent)
- CWE-793: Only Filtering One Instance of a Special Element (child)
- CWE-794: Incomplete Filtering of Multiple Instances of Special Elements (child)
References
- MITRE Corporation. "CWE-792: Incomplete Filtering of One or More Instances of Special Elements." https://cwe.mitre.org/data/definitions/792.html
- OWASP. "Input Validation Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html
- CERT. "IDS11-J. Perform any string modifications before validation."