설명
.NET 복합 형식 문자열은 {index[,alignment][:formatString]} 형식의 항목을 해석합니다. 이 문자열은 호출에 전달된 값 중 어떤 인수를 출력할지, 같은 인수를 몇 번 사용할지, 정렬과 값별 서식을 어떻게 적용할지를 결정합니다. 외부 입력을 String.Format, StringBuilder.AppendFormat, TextWriter.Write·WriteLine, Console.Write·WriteLine의 복합 형식 인자로 사용하면 공격자가 이 출력 구조를 제어할 수 있습니다.
이 문제는 C의 printf 형식 문자열 취약점과 영향이 같지 않습니다. 관리형 .NET 복합 형식 지정에는 %n과 같은 메모리 쓰기 지시문이 없고 호출 스택에서 임의의 값을 읽지도 않습니다. 따라서 C/C++에 해당하는 임의 메모리 읽기·쓰기나 직접적인 코드 실행 영향을 이 문제에 그대로 적용해서는 안 됩니다.
잠재적 영향
- 공격자는 인덱스를 바꾸거나 반복하여 애플리케이션이 같은 호출에 전달한 다른 값의 문자열 표현을 출력하게 할 수 있습니다. 민감한 값이 형식 인수로 함께 전달되었다면 정보 노출로 이어질 수 있습니다.
- 닫히지 않은 중괄호, 범위를 벗어난 인수 인덱스, 잘못된 형식 항목은
FormatException을 발생시킬 수 있습니다. 예외를 처리하지 않으면 요청 실패나 반복적인 서비스 장애로 이어질 수 있습니다. - 매우 큰 정렬 폭은 큰 출력과 추가 메모리 사용을 유발할 수 있습니다. 애플리케이션이 사용자 지정
IFormatProvider또는ICustomFormatter를 전달한다면 공격자가 애플리케이션별 형식 처리 경로를 선택할 수도 있습니다. - 로그, 응답, 알림처럼 형식 결과가 보안 판단이나 운영 절차에 사용되면 출력 변조가 후속 파서나 운영자를 오도할 수 있습니다.
해결 방법
- 복합 형식의 구조는 코드에서 관리하는 리터럴로 고정하고 신뢰할 수 없는 텍스트는 값 인수로만 전달합니다. 단순 표시라면 개발자가 작성한 보간 문자열도 형식 구조를 외부 입력과 분리합니다.
- 사용자가 표시 형식을 선택해야 한다면 원시 형식 문자열 대신
compact,labelled같은 식별자를 받고, 각 식별자를 변경할 수 없는 리터럴 형식에 매핑합니다. - 원시 형식 입력을 반드시 지원해야 한다면 완전히 고정된 소수의 리터럴과 정확히 일치하는 값만 허용하고, 형식에 전달할 값의 종류와 출력 크기도 제한합니다. 부분 문자열 검사나 접두사 검사는 형식 전체를 제한하지 않습니다.
CompositeFormat.Parse는 형식 문법을 검증하고 파싱 결과를 캐시하지만, 유효한 형식 안에서 공격자가 인수 선택과 서식을 제어하는 문제는 제거하지 않습니다. 이 API를 신뢰할 수 없는 형식의 안전성을 보장하는 수단으로 취급하지 않습니다.Sanitize같은 도우미 이름이나 중괄호 한쪽만 제거하는 변환은 안전성의 증거가 아닙니다. 여는 중괄호와 닫는 중괄호를 모두 제거하면 복합 형식 제어 문법은 사라지지만 원문을 훼손하므로 일반적인 해결책으로 권장하지 않습니다.
예시
변경 전
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("format")]
public sealed class FormatController : ControllerBase
{
[HttpGet]
public string Render(
[FromQuery] string format,
[FromQuery] string displayName)
{
string internalReference = HttpContext.TraceIdentifier;
return string.Format(format, displayName, internalReference);
}
}
format에 {1}을 전달하면 원래 표시할 의도가 없던 internalReference의 문자열 표현이 결과에 포함될 수 있습니다. 존재하지 않는 인덱스나 잘못된 중괄호를 전달하면 FormatException이 발생합니다.
변경 후
리터럴 형식
using System.Globalization;
return string.Format(
CultureInfo.InvariantCulture,
"사용자: {0}",
displayName);
형식 구조와 문화권 제공자는 애플리케이션이 결정하고, 외부 입력은 값 인수로만 사용합니다. 값을 그대로 출력하는 Console.WriteLine(message) 및 TextWriter.Write(message)의 단일 문자열 오버로드는 문자열을 복합 형식으로 해석하지 않습니다.
사전 정의 형식 선택
using System.Globalization;
static string SelectFormat(string style) => style switch
{
"compact" => "{0}",
"labelled" => "사용자: {0}",
_ => "사용자: {0}"
};
string format = SelectFormat(style);
return string.Format(CultureInfo.InvariantCulture, format, displayName);
사용자는 형식 문자열 자체가 아니라 제한된 식별자만 선택합니다. 모든 분기는 개발자가 작성한 리터럴을 반환합니다.
고정 형식도 인수 개수·타입과 일치하는지 확인하고, 사용자 지정 포매터의 동작을 검토하세요. .NET 분석기 규칙 CA2241과 테스트를 함께 사용할 수 있습니다.
참조
- Microsoft: .NET 복합 형식 지정
- Microsoft:
String.Format(.NET 10) - Microsoft:
StringBuilder.AppendFormat(.NET 10) - Microsoft:
TextWriter(.NET 10) - Microsoft:
Console.WriteLine(.NET 10) - Microsoft:
CompositeFormat(.NET 10) - Microsoft:
CompositeFormat.Parse(.NET 10) - Microsoft 코드 분석 규칙 CA2241
- CWE-134: Use of Externally-Controlled Format String