설명
리소스 인젝션은 신뢰할 수 없는 값이 애플리케이션이 사용할 리소스의 식별자나 접근 설명자를 결정할 때 발생합니다. .NET 데이터 접근 코드에서는 연결 문자열이나 그 구성요소가 다음과 같은 보안 결정을 포함할 수 있습니다.
- SQL Server·PostgreSQL·MySQL 서버 또는 네트워크 주소
- 데이터베이스나 카탈로그 이름
- SQLite 파일 경로
- ODBC DSN·드라이버 또는 OLE DB 공급자
- 사용자 이름, 비밀번호, 인증 방식과 같은 인증 컨텍스트
공격자가 이 값을 제어하면 SQL 문을 변경하지 않더라도 애플리케이션을 의도하지 않은 서버, 데이터베이스 또는 로컬 파일로 연결할 수 있습니다. 따라서 이 문제는 쿼리 구문을 조작하는 SQL 인젝션과 구분됩니다. 실제 피해는 구성된 연결이나 데이터 소스가 열리거나 사용될 때 발생합니다.
SqlConnectionStringBuilder, NpgsqlConnectionStringBuilder 같은 연결 문자열 빌더는 구성요소 값의 따옴표와 구분자 구문을 안전하게 직렬화합니다. 그러나 빌더는 사용자가 지정한 호스트, 데이터베이스, 파일 또는 자격 증명에 대한 권한을 확인하지 않습니다. 예를 들어 DataSource = userInput은 구분자 인젝션은 막더라도 공격자가 선택한 서버에 연결하는 문제를 해결하지 못합니다.
잠재적 영향
- 공격자가 운영 데이터베이스 대신 자신이 제어하는 서버로 연결을 유도해 쿼리, 데이터 또는 자격 증명을 노출할 수 있습니다.
- 더 높은 권한의 데이터베이스나 다른 테넌트의 저장소를 선택해 접근 통제를 우회할 수 있습니다.
- SQLite 파일 경로나 DSN·드라이버를 조작해 의도하지 않은 로컬 파일 또는 공급자를 사용하게 할 수 있습니다.
- 연결 실패, 장시간 대기 또는 연결 풀 고갈을 반복시켜 서비스 가용성을 저하시킬 수 있습니다.
해결 방법
연결 정보를 서버 측에서 관리
완전한 연결 문자열은 서버가 제어하는 구성이나 비밀 저장소에서 읽습니다. 비밀번호와 토큰을 소스 코드, URL, 로그에 넣지 마세요. 배포 환경 변수를 사용하는 경우 해당 값을 변경할 수 있는 주체를 배포 권한으로 제한하고 비밀 관리 정책을 적용해야 합니다.
사용자 입력을 그대로 구성 키로 사용하지 마세요. 예를 들어 configuration[userInput]은 값 자체가 비밀 저장소에 있더라도 사용자가 예상하지 않은 키를 선택하게 할 수 있습니다.
논리 식별자를 권한 검사 후 매핑
사용자가 여러 리소스 중 하나를 선택해야 한다면 primary, reporting처럼 의미가 제한된 논리 식별자만 받습니다. 요청 주체가 해당 식별자를 사용할 권한이 있는지 확인하고, 코드가 소유한 고정 매핑을 통해 신뢰할 수 있는 구성 이름이나 연결 설명자로 변환합니다. 매핑 내용과 변경 권한도 애플리케이션이 통제해야 합니다.
개별 연결 구성요소를 동적으로 지정해야 한다면 각 값을 비즈니스 규칙에 따라 별도로 검증한 후 공급자별 ConnectionStringBuilder에 할당합니다. 빌더는 검증 뒤에 구문을 안전하게 구성하는 용도이며, 검증이나 권한 부여를 대신하지 않습니다. 이름이 Sanitize, Encrypt, Hash인 헬퍼도 실제 계약을 확인하지 않고 안전하다고 간주하면 안 됩니다.
현재 공급자와 안전한 인증 사용
- SQL Server 신규 코드에는 사용 중단된
System.Data.SqlClient대신Microsoft.Data.SqlClient를 사용합니다. - 지원되는 환경에서는 비밀번호보다 관리 ID, 통합 인증 또는 단기 액세스 토큰을 우선합니다.
- 데이터베이스 계정에 최소 권한을 부여하고 테넌트·업무별 접근 경계를 별도로 적용합니다.
- 전송 암호화와 서버 인증서 검증을 활성화합니다. 연결 문자열 빌더를 사용했다는 사실만으로 TLS가 보장되지는 않습니다.
- SQLite 파일을 선택해야 한다면 파일 이름을 고정 매핑하고 정규화된 경로가 전용 기준 디렉터리 안에 있는지 별도로 검증합니다.
예시
변경 전
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();
}
}
요청자가 전체 연결 문자열을 지정하므로 서버, 데이터베이스, 인증 컨텍스트를 함께 바꿀 수 있습니다.
변경 후
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();
}
}
안전한 예시는 요청값을 고정된 논리 식별자로 제한하고 reporting 선택에는 역할 검사를 추가합니다. 요청값으로 구성 키를 직접 조회하지 않으며, 실제 연결 문자열은 서버 측 구성에서만 가져옵니다. 프로덕션에서는 연결을 시험하는 엔드포인트를 공개하지 말고 실제 업무 작업에 같은 선택·권한 정책을 적용하세요.
참조
- 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 연결 문자열
- ADO.NET 연결 문자열 빌더
- 연결 정보 보호
- .NET 10
DbProviderFactory.CreateDataSource Microsoft.Data.SqlClient- 사용 중단된
System.Data.SqlClient Microsoft.Data.Sqlite연결 문자열- Npgsql 기본 사용법과
NpgsqlDataSource - Npgsql 보안 및 TLS