Resource injection

Database resource injection in C# (CWE-99)

Description

Resource injection occurs when untrusted values select a resource identifier or access descriptor. In .NET data-access code, connection strings and their components can determine:

  • SQL Server, PostgreSQL or MySQL servers and network addresses
  • Database or catalog names
  • SQLite file paths
  • ODBC DSNs or drivers, and OLE DB providers
  • Usernames, passwords and authentication methods

An attacker can direct the application to an unintended server, database or local file without changing SQL syntax. This differs from SQL injection. Effects occur when the configured connection or data source is opened or used.

Builders such as SqlConnectionStringBuilder and NpgsqlConnectionStringBuilder serialize quotes and separators correctly, but do not authorize the selected host, database, file or credentials. For example, DataSource = userInput can prevent delimiter injection while still allowing an attacker to select the server.

Potential impact

  • Queries, data or credentials may be sent to an attacker-controlled server.
  • Selecting a more privileged database or another tenant's store may bypass access controls.
  • SQLite paths or DSN/driver choices may select unintended local files or providers.
  • Repeated failures, long waits or connection-pool exhaustion can reduce availability.

Remediation

Manage connection settings on the server

Read complete connection strings from server-controlled configuration or secret storage. Do not put passwords or tokens in source code, URLs or logs. If deployment environment variables are used, restrict who can modify them and apply secret-management policy.

Do not use user input directly as a configuration key. configuration[userInput] can expose an unintended choice of key even when its value is stored securely.

Authorize and map logical identifiers

If users must choose a resource, accept narrowly defined identifiers such as primary or reporting. Authorize the caller for that identifier, then use a code-owned fixed map to select a trusted configuration name or connection descriptor. Control the map's contents and modification permissions too.

If individual components must vary, validate each against business rules before assigning it through the provider's ConnectionStringBuilder. The builder serializes syntax after validation; it does not replace validation or authorization. Do not assume helpers called Sanitize, Encrypt or Hash are sufficient without checking their actual contract.

Use supported providers and secure authentication

  • Use Microsoft.Data.SqlClient for new SQL Server code instead of deprecated System.Data.SqlClient.
  • Prefer managed identities, integrated authentication or short-lived access tokens over passwords where supported.
  • Grant database accounts least privilege and enforce tenant and business boundaries separately.
  • Enable transport encryption and server-certificate validation. A connection-string builder alone does not guarantee TLS.
  • For SQLite, map approved filenames and separately check that normalized paths remain inside a dedicated base directory.

Examples

Before

csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.SqlClient;

[ApiController]
[Route("database")]
public sealed class VulnerableDatabaseController : ControllerBase
{
    [HttpPost("connect")]
    public IActionResult Connect([FromQuery] string connectionString)
    {
        using var connection = new SqlConnection(connectionString);
        connection.Open();
        return Ok();
    }
}

The caller provides the complete connection string and can change the server, database and authentication context together.

After

csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;

[ApiController]
[Route("database")]
public sealed class SafeDatabaseController : ControllerBase
{
    private readonly IConfiguration _configuration;

    public SafeDatabaseController(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    [HttpPost("connect")]
    public IActionResult Connect([FromQuery] string databaseId)
    {
        string? connectionName = databaseId switch
        {
            "primary" => "PrimaryDatabase",
            "reporting" when User.IsInRole("ReportReader") => "ReportingDatabase",
            _ => null
        };
        if (connectionName is null)
        {
            return Forbid();
        }

        string? connectionString =
            _configuration.GetConnectionString(connectionName);
        if (string.IsNullOrWhiteSpace(connectionString))
        {
            return Problem("Database configuration is unavailable.");
        }

        using var connection = new SqlConnection(connectionString);
        connection.Open();
        return Ok();
    }
}

The revised example accepts fixed logical identifiers and adds a role check for reporting. Input is not used directly as a configuration key; the connection string comes only from server configuration. Do not expose a connection-testing endpoint in production. Apply the same selection and authorization policy to actual business operations.

References