Acceptance of Extraneous Untrusted Data With Trusted Data
Description
Acceptance of Extraneous Untrusted Data With Trusted Data is a vulnerability that occurs when a product, while processing trusted data, accepts any untrusted data that is also included with the trusted data, treating the untrusted data as if it were trusted. This weakness represents a fundamental failure to distinguish between authenticated and unauthenticated information within a data package. Common manifestations include accepting additional fields in signed messages that were not part of the original signed content, trusting all records in a DNS response when only specific records were authenticated, accepting extra parameters in authenticated API responses, and including untrusted metadata alongside trusted content. The vulnerability exploits the assumption that if some data is trusted, all accompanying data must also be trusted.
Risk
Accepting untrusted data bundled with trusted data allows attackers to inject malicious content that inherits the trust of legitimate data. In certificate validation, attackers can forge certificates by including extra data in signatures that enables certificate chain manipulation. DNS responses can include additional records beyond what was queried, allowing cache poisoning attacks where attackers inject records for domains they don't control. API responses that are partially signed allow attackers to add unsigned fields that the application processes as trusted. Firmware updates with partial signature coverage allow malicious code injection in unsigned sections. The risk is amplified because applications often apply trust decisions at the container level (e.g., "this message is signed") rather than at the individual data element level, creating a trust escalation vulnerability where any data in the container is trusted regardless of what was actually authenticated.
Solution
Implement strict data validation that distinguishes between authenticated and unauthenticated portions of data. For signed data, only process fields that are explicitly covered by the signature and reject or ignore any additional fields. In DNS responses, only cache records that directly answer the query and validate that authority and additional sections are relevant to the queried domain. For API responses, define explicit schemas and reject responses with unexpected fields. Implement "sign-then-encrypt" rather than "encrypt-then-sign" to prevent manipulation of signed content boundaries. Use cryptographic techniques like canonical serialization that ensure signatures cover exactly the intended data. Apply the principle of least authority - only trust the minimum data necessary and treat all extraneous data as untrusted.
Common Consequences
| Impact | Details |
|---|---|
| Access Control | Scope: Access Control Attackers can bypass access controls by bundling unauthorized data with authorized data, gaining access to resources or functionality they should not have. |
| Integrity | Scope: Integrity Accepting extraneous untrusted data allows attackers to modify application behavior, inject malicious content, or corrupt data stores with unauthenticated information. |
| Authentication | Scope: Authentication Trust boundary violations enable attackers to forge certificates or credentials by manipulating signature coverage to include malicious data. |
Example Code
Vulnerable Code (Python/Java)
The following examples demonstrate acceptance of untrusted data with trusted data:
# Vulnerable: Accepting extraneous untrusted data
import json
import hmac
import hashlib
from typing import Dict, Any
# Vulnerable: Processing all fields from signed message
def vulnerable_process_signed_message(message_json: str, signature: str,
secret_key: bytes) -> Dict[str, Any]:
message = json.loads(message_json)
# Vulnerable: Only signature covers "signed_data" field
signed_portion = message.get('signed_data', {})
expected_sig = hmac.new(
secret_key,
json.dumps(signed_portion, sort_keys=True).encode(),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected_sig):
raise ValueError("Invalid signature")
# Vulnerable: Processing ALL fields, not just signed ones
# Attacker can add "admin": true to the message
return message # Includes unsigned fields!
# Vulnerable: DNS response with extra records
class VulnerableDNSCache:
def __init__(self):
self.cache = {}
def process_response(self, query_domain: str, response: Dict):
# Vulnerable: Caching all records from response
# even if they weren't part of the query
# Answer section - what we queried
for record in response.get('answers', []):
self.cache[record['name']] = record['address']
# Vulnerable: Also caching authority section
for record in response.get('authority', []):
self.cache[record['name']] = record['ns']
# Vulnerable: Caching additional section (can be injected)
for record in response.get('additional', []):
# Attacker can inject records for any domain here
self.cache[record['name']] = record['address']
# Vulnerable: API response with extra fields
def vulnerable_process_api_response(response: Dict, signature: str,
public_key) -> Dict:
# Response structure:
# {
# "user_id": "123",
# "timestamp": "2024-01-01T00:00:00Z",
# "signature_covers": ["user_id", "timestamp"]
# }
# Vulnerable: Only verifying specific fields
signed_data = {k: response[k] for k in response.get('signature_covers', [])}
if not verify_signature(signed_data, signature, public_key):
raise ValueError("Invalid signature")
# Vulnerable: Returning entire response including unsigned fields
# Attacker adds: "is_admin": true, "permissions": ["all"]
return response
# Vulnerable: Certificate with extra extensions
def vulnerable_validate_certificate(cert):
# Vulnerable: Only checking core fields, not all extensions
if not verify_ca_signature(cert):
raise ValueError("Invalid CA signature")
if cert.not_before > now() or cert.not_after < now():
raise ValueError("Certificate expired")
# Vulnerable: Processing all extensions, including unauthenticated ones
# X.509 extensions might include attacker-injected data
for extension in cert.extensions:
apply_extension(extension) # Includes untrusted extensions
return True
# Vulnerable: Firmware update with partial coverage
def vulnerable_apply_firmware(firmware_package: bytes, signature: bytes,
public_key):
# Package format: [header: 256 bytes][signed_code][extra_data]
header = firmware_package[:256]
code_length = int.from_bytes(header[0:4], 'big')
signed_code = firmware_package[256:256+code_length]
extra_data = firmware_package[256+code_length:] # Unsigned!
# Vulnerable: Only signed_code is verified
if not verify_signature(signed_code, signature, public_key):
raise ValueError("Invalid signature")
# Vulnerable: Applying both signed and unsigned data
apply_code(signed_code)
apply_config(extra_data) # Vulnerable: This data is NOT signed!
// Vulnerable: Accepting extraneous untrusted data in Java
import java.util.*;
import javax.crypto.*;
public class VulnerableExtraneousData {
// Vulnerable: Processing all JSON fields from signed message
public Map<String, Object> vulnerableProcessMessage(
String messageJson, String signature, SecretKey key)
throws Exception {
Map<String, Object> message = parseJson(messageJson);
Map<String, Object> signedData =
(Map<String, Object>) message.get("signed_data");
// Vulnerable: Only signed_data is authenticated
String expectedSig = computeHmac(signedData, key);
if (!MessageDigest.isEqual(
signature.getBytes(), expectedSig.getBytes())) {
throw new SecurityException("Invalid signature");
}
// Vulnerable: Returning entire message including unsigned fields
return message; // Contains attacker-injected fields
}
// Vulnerable: XML signature with extra elements
public Document vulnerableProcessSignedXml(Document doc) throws Exception {
// XML structure:
// <message>
// <SignedInfo>...</SignedInfo> <!-- Signed -->
// <data>...</data> <!-- Signed (referenced) -->
// <extra>...</extra> <!-- NOT signed! -->
// </message>
XMLSignature signature = new XMLSignature(doc);
// Vulnerable: Only validates SignedInfo references
if (!signature.validate()) {
throw new SecurityException("Invalid XML signature");
}
// Vulnerable: Processing entire document including unsigned elements
return doc; // Contains unsigned <extra> element
}
// Vulnerable: Token with extra claims
public Map<String, Object> vulnerableProcessToken(String token, Key key) {
// Token: header.payload.signature
String[] parts = token.split("\\.");
Map<String, Object> header = decodeBase64Json(parts[0]);
Map<String, Object> payload = decodeBase64Json(parts[1]);
// Vulnerable: Signature only covers original payload
// But attacker can modify the base64 to include extra claims
// due to JSON parsing quirks or whitespace injection
String signedPortion = parts[0] + "." + parts[1];
if (!verifySignature(signedPortion, parts[2], key)) {
throw new SecurityException("Invalid signature");
}
// Vulnerable: Parser might include extra data
return payload;
}
// Vulnerable: Config file with signed and unsigned sections
public void vulnerableLoadConfig(byte[] configData, byte[] signature,
PublicKey publicKey) throws Exception {
// Config format: [signed_section_length:4][signed][unsigned]
int signedLength = ByteBuffer.wrap(configData, 0, 4).getInt();
byte[] signedSection = Arrays.copyOfRange(configData, 4, 4 + signedLength);
byte[] unsignedSection = Arrays.copyOfRange(
configData, 4 + signedLength, configData.length);
// Vulnerable: Only verifying signed section
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(publicKey);
sig.update(signedSection);
if (!sig.verify(signature)) {
throw new SecurityException("Invalid config signature");
}
// Vulnerable: Applying both sections
applySignedConfig(signedSection);
applyUnsignedConfig(unsignedSection); // Attacker-controlled!
}
// Vulnerable: HTTP response with extra headers
public void vulnerableProcessResponse(HttpResponse response,
String expectedBodyHash) {
// Vulnerable: Only body is integrity-checked
String actualHash = sha256(response.getBody());
if (!actualHash.equals(expectedBodyHash)) {
throw new SecurityException("Body integrity check failed");
}
// Vulnerable: Processing headers that weren't integrity-checked
String redirectUrl = response.getHeader("X-Redirect-To");
if (redirectUrl != null) {
redirect(redirectUrl); // Attacker-injected header
}
}
}
Fixed Code (Python/Java)
# Fixed: Only processing authenticated data
import json
import hmac
import hashlib
from typing import Dict, Any, Set
# Fixed: Only return signed fields
def secure_process_signed_message(message_json: str, signature: str,
secret_key: bytes) -> Dict[str, Any]:
message = json.loads(message_json)
# Fixed: Extract only the signed portion
signed_portion = message.get('signed_data', {})
expected_sig = hmac.new(
secret_key,
json.dumps(signed_portion, sort_keys=True).encode(),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected_sig):
raise ValueError("Invalid signature")
# Fixed: Only return the signed data
return signed_portion # Only authenticated data
# Fixed: DNS cache with strict response validation
class SecureDNSCache:
def __init__(self):
self.cache = {}
def process_response(self, query_domain: str, response: Dict):
# Fixed: Only cache records that answer the query
for record in response.get('answers', []):
# Fixed: Verify record is for queried domain
if self._is_valid_answer(query_domain, record):
self.cache[record['name']] = record['address']
# Fixed: Authority section only for queried domain's zone
for record in response.get('authority', []):
if self._is_in_bailiwick(query_domain, record):
self.cache[record['name']] = record['ns']
# Fixed: Additional section only for names already trusted
allowed_names = {query_domain} | self._get_ns_names(response)
for record in response.get('additional', []):
# Fixed: Only cache if referenced by trusted records
if record['name'] in allowed_names:
self.cache[record['name']] = record['address']
def _is_valid_answer(self, query_domain: str, record: Dict) -> bool:
# Record must be for queried domain or valid CNAME chain
return record['name'] == query_domain or \
record['name'].endswith('.' + query_domain)
def _is_in_bailiwick(self, query_domain: str, record: Dict) -> bool:
# Authority must be for queried domain's parent zone
parts = query_domain.split('.')
for i in range(len(parts)):
zone = '.'.join(parts[i:])
if record['name'] == zone:
return True
return False
def _get_ns_names(self, response: Dict) -> Set[str]:
return {r['ns'] for r in response.get('authority', [])}
# Fixed: API response with strict schema
def secure_process_api_response(response: Dict, signature: str,
public_key, expected_schema: Set[str]) -> Dict:
# Fixed: Define exact expected fields
SIGNED_FIELDS = {'user_id', 'timestamp', 'action'}
# Fixed: Extract only expected signed fields
signed_data = {}
for field in SIGNED_FIELDS:
if field not in response:
raise ValueError(f"Missing required field: {field}")
signed_data[field] = response[field]
if not verify_signature(signed_data, signature, public_key):
raise ValueError("Invalid signature")
# Fixed: Only return signed, validated fields
return signed_data
# Fixed: Certificate with validated extensions
def secure_validate_certificate(cert):
if not verify_ca_signature(cert):
raise ValueError("Invalid CA signature")
if cert.not_before > now() or cert.not_after < now():
raise ValueError("Certificate expired")
# Fixed: Only process known, critical extensions
KNOWN_EXTENSIONS = {
'basic_constraints',
'key_usage',
'subject_alt_name'
}
for extension in cert.extensions:
if extension.oid in KNOWN_EXTENSIONS:
# Fixed: Only known extensions processed
apply_extension(extension)
elif extension.critical:
# Fixed: Reject unknown critical extensions
raise ValueError(f"Unknown critical extension: {extension.oid}")
# Fixed: Unknown non-critical extensions are ignored
return True
# Fixed: Firmware update with full coverage
def secure_apply_firmware(firmware_package: bytes, signature: bytes,
public_key):
# Fixed: Signature covers ENTIRE package
if not verify_signature(firmware_package, signature, public_key):
raise ValueError("Invalid signature")
# Fixed: Parse after verification
header = firmware_package[:256]
code_length = int.from_bytes(header[0:4], 'big')
code = firmware_package[256:256+code_length]
config = firmware_package[256+code_length:]
# Fixed: All data was verified
apply_code(code)
apply_config(config) # Safe: was part of signed package
// Fixed: Only processing authenticated data in Java
import java.util.*;
import javax.crypto.*;
public class SecureExtraneousData {
// Fixed: Only return signed fields
public Map<String, Object> secureProcessMessage(
String messageJson, String signature, SecretKey key)
throws Exception {
Map<String, Object> message = parseJson(messageJson);
Map<String, Object> signedData =
(Map<String, Object>) message.get("signed_data");
if (signedData == null) {
throw new SecurityException("Missing signed_data");
}
String expectedSig = computeHmac(signedData, key);
if (!MessageDigest.isEqual(
signature.getBytes(), expectedSig.getBytes())) {
throw new SecurityException("Invalid signature");
}
// Fixed: Only return the signed portion
return new HashMap<>(signedData); // Only authenticated data
}
// Fixed: XML signature with strict element validation
public Map<String, Object> secureProcessSignedXml(Document doc)
throws Exception {
XMLSignature signature = new XMLSignature(doc);
if (!signature.validate()) {
throw new SecurityException("Invalid XML signature");
}
// Fixed: Only extract signed references
Set<String> signedElements = signature.getSignedElementIds();
Map<String, Object> result = new HashMap<>();
for (String elementId : signedElements) {
Element element = doc.getElementById(elementId);
if (element != null) {
result.put(element.getTagName(), extractContent(element));
}
}
// Fixed: Only return elements covered by signature
return result;
}
// Fixed: Token with exact payload validation
public Map<String, Object> secureProcessToken(String token, Key key) {
String[] parts = token.split("\\.");
if (parts.length != 3) {
throw new SecurityException("Invalid token format");
}
// Fixed: Verify signature over exact input
String signedPortion = parts[0] + "." + parts[1];
if (!verifySignature(signedPortion, parts[2], key)) {
throw new SecurityException("Invalid signature");
}
// Fixed: Parse with strict JSON parser
Map<String, Object> payload = strictJsonParse(
new String(Base64.getUrlDecoder().decode(parts[1]))
);
// Fixed: Only allow expected claims
Set<String> allowedClaims = Set.of(
"sub", "iat", "exp", "iss", "aud"
);
Map<String, Object> filtered = new HashMap<>();
for (String claim : allowedClaims) {
if (payload.containsKey(claim)) {
filtered.put(claim, payload.get(claim));
}
}
// Fixed: Only return allowed, verified claims
return filtered;
}
// Fixed: Config file with signature covering everything
public void secureLoadConfig(byte[] configData, byte[] signature,
PublicKey publicKey) throws Exception {
// Fixed: Verify signature over ENTIRE config
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(publicKey);
sig.update(configData);
if (!sig.verify(signature)) {
throw new SecurityException("Invalid config signature");
}
// Fixed: Parse only after full verification
Config config = parseConfig(configData);
applyConfig(config); // Safe: all data was signed
}
// Fixed: HTTP response with authenticated headers
public void secureProcessResponse(HttpResponse response,
String expectedHash,
Set<String> signedHeaders) {
// Fixed: Hash covers body AND critical headers
StringBuilder toHash = new StringBuilder();
for (String header : signedHeaders) {
toHash.append(header).append(":")
.append(response.getHeader(header)).append("\n");
}
toHash.append(response.getBody());
String actualHash = sha256(toHash.toString());
if (!actualHash.equals(expectedHash)) {
throw new SecurityException("Response integrity check failed");
}
// Fixed: Only process headers that were integrity-checked
for (String header : signedHeaders) {
processHeader(header, response.getHeader(header));
}
}
private Map<String, Object> strictJsonParse(String json) {
// Use a strict JSON parser that doesn't allow duplicates
// or trailing data
return new StrictJsonParser().parse(json);
}
}
The fix ensures only explicitly authenticated data is processed and all extraneous data is rejected or ignored.
Exploited in the Wild
DNS Cache Poisoning (CVE-2002-0018)
DNS resolvers accepted additional records in responses beyond what was queried, allowing attackers to inject records for arbitrary domains.
Certificate Signature Forging (CVE-2006-5462)
Extra data in certificate signatures enabled forging of certificate chains by manipulating data outside the signed portion.
Tools to Test/Exploit
-
Burp Suite — Inject extra fields in API responses and signed messages.
-
DNS testing tools — Test DNS resolver behavior with extra records.
-
jwt_tool — Test JWT handling with extra claims.
CVE Examples
-
CVE-2002-0018 — DNS accepting records without authority.
-
CVE-2006-5462 — Certificate signature with extra data.
References
-
MITRE Corporation. "CWE-349: Acceptance of Extraneous Untrusted Data With Trusted Data." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/349.html
-
OWASP Foundation. "Injection Flaws." https://owasp.org/www-community/Injection_Flaws
-
RFC 5155. "DNS Security (DNSSEC) Hashed Authenticated Denial of Existence." https://tools.ietf.org/html/rfc5155