설명
신뢰할 수 없는 입력이 Process.Start 또는 ProcessStartInfo에 전달되면 두 가지 위험이 생길 수 있습니다.
- 입력이
ProcessStartInfo.FileName이나Process.Start의 첫 번째 인자를 제어하면 공격자가 의도하지 않은 실행 파일을 선택할 수 있습니다. - 입력이
ProcessStartInfo.Arguments나Process.Start(string, string)의 두 번째 인자에 포함되면 원시 명령줄의 인자 경계 또는 옵션이 변경될 수 있습니다. 실행 파일이cmd.exe, PowerShell,/bin/sh,/bin/bash같은 명령 인터프리터라면 입력이 명령 구문으로 실행될 수 있습니다.
UseShellExecute = false는 운영 체제의 문서 연결 셸을 사용하지 않도록 설정할 뿐입니다. 이 값은 애플리케이션이 cmd.exe, PowerShell, sh 같은 명령 인터프리터를 직접 실행하는 것을 막지 않습니다.
ProcessStartInfo.ArgumentList와 .NET 10의 Process.Start(string, IEnumerable<string>) 오버로드는 각 인자의 경계를 보존하므로 원시 Arguments 문자열보다 안전합니다. 그러나 인자를 분리해도 대상 프로그램이 공격자 입력을 옵션으로 해석할 수 있으며, sh -c, cmd /c, PowerShell -Command 뒤의 값은 여전히 실행할 코드입니다.
잠재적 영향
- 애플리케이션 계정 권한으로 임의 명령 또는 프로그램 실행
- 파일 읽기·변조·삭제, 자격 증명 탈취, 내부 네트워크 접근
- 공격자가 선택한 옵션에 따른 데이터 유출, 권한 오용 또는 서비스 중단
해결 방법
- 가능하면 프로세스를 생성하지 말고 파일, 압축, 네트워크, 데이터 변환 등을 위한 관리형 라이브러리 API를 사용합니다.
- 프로세스 실행이 필요하면 외부 입력을 실행 파일 경로로 사용하지 않습니다. 검증된 작업 식별자를 개발자가 관리하는 고정된 절대 경로에 매핑하고, 실행 파일은 신뢰할 수 있으며 비권한 사용자가 쓸 수 없는 위치에 둡니다.
UseShellExecute를false로 설정하고 명령 인터프리터를 호출하지 않습니다. 특히 신뢰할 수 없는 값을-c,/c,-Command또는 유사한 코드 실행 옵션 뒤에 전달하지 않습니다.- 인자를
ProcessStartInfo.ArgumentList또는IEnumerable<string>오버로드로 하나씩 전달합니다. 각 외부 값에는 대상 프로그램의 문법과 의미에 맞는 허용 목록 기반 검증 및 길이 제한을 적용합니다. - 대상 프로그램이 지원하면 신뢰할 수 없는 피연산자 앞에
--같은 옵션 종료 표식을 사용합니다. 이 표식은 대상이 해당 규칙을 지원할 때만 유효합니다. - 프로세스를 최소 권한 계정으로 실행하고 필요한 파일 시스템 및 네트워크 접근만 허용합니다. 모든 프로그램에 통용되는 명령줄 이스케이프 함수가 있다고 가정하지 않습니다.
예시
변경 전
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
public sealed class RunController : Controller
{
public void Run([FromQuery] string command)
{
var processInfo = new ProcessStartInfo
{
FileName = "/bin/sh",
UseShellExecute = false
};
processInfo.ArgumentList.Add("-c");
processInfo.ArgumentList.Add(command);
Process.Start(processInfo);
}
}
ArgumentList가 인자 경계를 보존하더라도 -c 다음 값은 셸 프로그램이 해석할 코드입니다. UseShellExecute = false도 직접 실행한 /bin/sh의 해석을 비활성화하지 않습니다.
변경 후
using System.Diagnostics;
static class Printer
{
public static void PrintUserValue(string userInput)
{
var processInfo = new ProcessStartInfo
{
FileName = "/usr/bin/printf",
UseShellExecute = false
};
processInfo.ArgumentList.Add("%s\n");
processInfo.ArgumentList.Add(userInput);
Process.Start(processInfo);
}
}
안전한 예시는 명령 인터프리터를 사용하지 않고 고정된 실행 파일과 고정된 형식 문자열을 선택합니다. 사용자 값은 별도의 데이터 인자로 전달됩니다. 실제 애플리케이션에서는 /usr/bin/printf의 존재와 소유권을 배포 환경에서 보장해야 하며, 단순 출력만 필요하다면 Console.WriteLine처럼 프로세스를 생성하지 않는 API가 더 적절합니다.
참조
- Microsoft CA3006: Review code for process command injection vulnerabilities
- Microsoft: ProcessStartInfo.ArgumentList
- Microsoft: ProcessStartInfo.Arguments
- Microsoft: ProcessStartInfo.UseShellExecute
- OWASP OS Command Injection Defense Cheat Sheet
- OWASP Top 10:2025 A05 Injection
- CWE-78: Improper Neutralization of Special Elements used in an OS Command
- CWE-88: Improper Neutralization of Argument Delimiters in a Command