Skip to content

CWE-99: Improper Control of Resource Identifiers ('Resource Injection')

Overview

Resource Injection occurs when untrusted input is used to select or construct the identifier for a system resource - a file path, class name, port number, database name, connection string, or URL - without validation, letting an attacker redirect the application to a resource it was never meant to touch. Untrusted input can originate from HTTP requests, external APIs, databases, files, or any source outside the application's control.

Relationship to Other CWEs

CWE-99 is a Class-level weakness, ChildOf CWE-74, the general injection parent. Use this page when the finding is about an untrusted value choosing which resource the application operates on: a file, class, port, database, or URL. Untrusted data embedded in a command or query string sent to an interpreter belongs to CWE-74's other children, such as CWE-77, CWE-89, CWE-90, CWE-91, CWE-94 and CWE-943.

Almost every CWE-99 finding has a more specific page here, and which one depends on the sink. The list below is indexed by the kind of identifier named in the Overview.

  • A file path, including a template or include path - CWE-22 where the value walks out of the directory it was meant to stay in, CWE-73 where it names the file at all. MITRE lists CWE-73 as a CanAlsoBe of this entry, and a file sink is what a CWE-99 finding most often turns out to be, so check these first.
  • A class or method name resolved by reflection - CWE-470. This is the java.lang.Runtime line in the vulnerable example below. The control is not only an allowlist but binding each permitted key to the operation it invokes, and loading the class without running its static initializers, since those run before any type check can reject it.
  • A URL the server fetches - CWE-918. What the attacker gains is the server's network position: internal services, cloud metadata endpoints, anything reachable from the host and not from the client. The control is checking every address the hostname resolves to, and constraining the HTTP client so a redirect cannot walk back out of the allowlist after the check passed.
  • A URL the browser is sent to - CWE-601. What they gain is your domain's credibility in front of the user. The control is a server-chosen destination, or a path matched against an allowlist rather than a full URL.
  • A database name, a connection string, or any other configuration setting - CWE-15. MITRE's own demonstrative example for CWE-15 is conn.setCatalog(request.getParameter("catalog")), which is the information_schema attack in the vulnerable block below, and that page carries the worked version including the C# ChangeDatabase equivalent. The control is that the setting is derived server-side from the session, not that the supplied value is inspected.
  • A port number is the one kind with no page of its own. Where the port is a configuration setting a request can influence, CWE-15 covers it; where a request picks the port a socket binds or connects to directly, this page is the terminus.

MITRE's mapping guidance for CWE-99 is Allowed-with-Review, for the reason all of the above illustrates: it is a Class, and its Base-level children - CWE-641 (restricted names), CWE-694 (duplicate identifiers) and CWE-914 (dynamically-identified variables) - may fit a finding better than the class does. None of those three has a page here, so a finding in their territory lands on this one.

None of the pages listed above is a descendant of CWE-99 in any view, so these are pointers by sink, not relationships to this entry. Where each one does sit is worth knowing, because a finding may arrive carrying the parent rather than the page you need:

  • CWE-73 - ChildOf CWE-610 and CWE-642, both in Research Concepts. MITRE also lists it as CanPrecede CWE-22, CWE-41, CWE-59, CWE-98 and CWE-434, which is why it comes first in the file row above: controlling the name is the step that makes the traversal available.
  • CWE-15 - ChildOf CWE-610 and CWE-642 in Research Concepts, CWE-20 in Seven Pernicious Kingdoms.
  • CWE-470 - ChildOf CWE-913 in both the research and simplified-mapping views, and CWE-610 in Research Concepts.
  • CWE-601 - ChildOf CWE-610 in both views.
  • CWE-918 - ChildOf CWE-610 in Simplified Mapping, CWE-441 in Research Concepts.
  • CWE-22 - the one that sits elsewhere: ChildOf CWE-706 (Use of Incorrectly-Resolved Name or Reference) in both views. MITRE lists CWE-706 as a PeerOf CWE-99, so it is the nearest thing to a sibling this entry has, and a path finding that is about resolution rather than about who chose the name belongs on that side of the line.

CWE-610, CWE-913 and CWE-706 have no pages here; CWE-642 does.

OWASP Classification

A05:2025 - Injection

Risk

High: An attacker who controls the identifier chooses which resource the application opens, instantiates, binds, or connects to. How bad that is depends on the sink behind it: reading a file the application never meant to serve at one end, running attacker-chosen code through a class loader at the other.

Remediation Steps

Core Principle: Never let untrusted input select a resource by name or path directly; map identifiers to an allowlist of resources the application controls.

Trace the Data Path

  • Source: Where untrusted data enters (HTTP requests, external files, databases, network requests)
  • Resource Selection: How that data decides which resource is accessed - watch for it flowing into file opens, dynamic class loading/reflection, port/socket binds, template loading, database or connection-string selection, and redirect/URL construction
  • Sink: The resource access itself
  • Missing Validation: The absence of an allowlist or identifier mapping between source and sink

Validate Resource Identifiers Against an Allowlist (Primary Defense)

Never use untrusted data directly as a resource identifier, or to build one:

  • Keep an explicit, hardcoded list or map of permitted identifiers
  • Have the user supply a short key, such as an ID or an enum value, and let the application resolve that key to the actual file path, class, port, or connection string
  • Match keys exactly - no pattern, prefix, or partial matching
  • Never let internal file paths, class names, or connection strings appear in a request the client can modify

Add Input Validation (Defense in Depth)

Even with an allowlist in place, validate the raw identifier as a second layer:

  • Reject anything that is not a plausible key: unexpected length, path separators, or protocol prefixes like http:// or php://
  • Check that port numbers, IDs, and array indexes fall within the expected range
  • Treat validation as a supplement to the allowlist, not a replacement for it

Apply Least Privilege

Limit the blast radius if a resource identifier is still misused:

  • Make only the resources the application needs available to it: restrict filesystem paths, bind only the ports required, and grant the database account access to its own schema only
  • Keep configuration, template, and plugin directories separate from application code and secrets

Test with Malicious Resource Identifiers

Verify the fix with realistic attack input:

  • Path traversal in a file/template identifier: ../../../etc/passwd, ../../.env
  • Unauthorized resource values: privileged port numbers (22, 443), internal database names (information_schema, mysql), other tenants' identifiers
  • Dynamic-class/reflection payloads: a fully-qualified class name for a built-in or unexpected type
  • Sequential or enumerable IDs to check for resource enumeration
  • Confirm legitimate resource access still works, then re-scan to check the finding is closed

Common Vulnerable Patterns

// VULNERABLE - pseudo-code
resource_id = untrusted_input   // from request parameter, body, or header
resource = open_resource(resource_id)
// Attack: resource_id = "../../../etc/passwd" (file/template loader)
// Attack: resource_id = "java.lang.Runtime" (class loader / reflection)
// Attack: resource_id = 22 (port binder targeting SSH)
// Attack: resource_id = "information_schema" (database selector)
// Result: the application opens, instantiates, binds, or connects to a
// resource the attacker chose, not one the application intended to expose

Why this is vulnerable: the input is not describing a resource, it is naming one, so validating its shape does not constrain what it reaches. A well-formed path, a well-formed class name and a well-formed port number are all exactly what the attacker sends. The question the check has to answer is not "is this a valid identifier" but "is this one of the identifiers this operation is allowed to touch", and only an allowlist of permitted values answers that.

The four attacks in the example are the same line with four different sinks behind it, which is what makes triage of a finding here awkward: the severity is decided by open_resource, not by anything visible at the call site. The same code is a file disclosure against a template loader, remote code execution against a class loader, a service hijack against a port binder, and a cross-database read against a connection factory. Trace what the identifier is handed to before deciding how serious the finding is - and note that the resolution usually happens somewhere else, so the reported line and the dangerous line are frequently in different files.

Secure Patterns

// SECURE - pseudo-code
allowed_resources = { "home": home_resource, "about": about_resource, ... }
resource_key = untrusted_input
resource = allowed_resources.get(resource_key)
if resource is None:
    reject_request()
else:
    open_resource(resource)   // resource is a value the application defined, never user-supplied

Why this works: The untrusted input only ever selects a key into a fixed, application-defined map - it never becomes (or is concatenated into) the actual file path, class name, port, or connection string. Even if an attacker sends an unexpected key, the lookup fails closed and the request is rejected before any resource is touched.

Additional Resources