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.SqlClientfor new SQL Server code instead of deprecatedSystem.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
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
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
- CWE-99: Improper Control of Resource Identifiers
- OWASP Top 10:2025 A05 - Injection
- OWASP Top 10:2021 A03 - Injection
- OWASP ASVS 5.0.0 V2.2.1
- ADO.NET connection strings
- ADO.NET connection string builders
- Protecting connection information
- .NET 10
DbProviderFactory.CreateDataSource Microsoft.Data.SqlClient- Deprecated
System.Data.SqlClient Microsoft.Data.Sqliteconnection strings- Npgsql basic usage and
NpgsqlDataSource - Npgsql security and TLS