FlawOpen

flawopen.com/Command Injection/Java Runtime.exec()

Java Runtime.exec()은 Command Injection을 막을까?

CWE-78: OS Command Injection참고 페이지
요약

Runtime.exec(String)은 shell을 암묵적으로 실행하지 않으므로 shell 연산자가 자동으로 shell 구문으로 해석되지는 않습니다. 하지만 문자열은 프로그램과 인수로 나뉘며 신뢰할 수 없는 텍스트가 대상 프로그램에 전달되는 인수를 바꿀 수 있습니다. ProcessBuilder는 경계를 명시하지만 각 값 검증과 실행 파일 및 허용 옵션 제한은 여전히 필요합니다.

위험
// 사용자 입력을 명령 문자열에 연결
Runtime.getRuntime().exec(
  "approved-tool --input " + filename
);
더 안전
// 실행 파일 고정 및 인수 분리; filename을 먼저 검증
String safeName = validateFilename(filename);
new ProcessBuilder(
  "/opt/app/bin/approved-tool",
  "--input", safeName
).start();

Shell injection과 argument injection은 다르다

Java 18부터 Oracle은 오류가 발생하기 쉽다는 이유로 Runtime.exec(String)을 deprecated 처리하고 exec(String[]) 또는 ProcessBuilder 사용을 권장합니다. 문자열 오버로드는 공백 문자로 토큰을 나누므로 공백이 포함된 경로 같은 인수가 분리될 수 있습니다. Runtime.exec(String)은 sh나 다른 shell을 암묵적으로 시작하지 않습니다. Oracle 문서에 따르면 단일 문자열 오버로드는 명령 문자열을 인수 배열로 변환합니다. 따라서 일반적인 shell 메타문자는 shell 구문으로 처리되지 않지만, 토큰화로 추가 인수가 생기거나 대상 프로그램이 인수를 옵션으로 해석할 수 있습니다. ProcessBuilder는 실행 파일과 인수의 문자열 목록을 받으며 일반적인 플랫폼에서 각 요소는 별도 인수 경계입니다. 실행 파일을 고정하고 대상 프로그램의 문법에 맞게 값을 검증하며 옵션처럼 보이는 값은 거부하거나 안전하게 처리하세요. --는 대상 문서가 동작을 보장할 때만 사용하세요. 신뢰할 수 없는 데이터를 sh -c 같은 shell에 명시적으로 전달하지 마세요.

자주 묻는 질문

Runtime.exec(String)이 shell을 실행하지 않으니 안전한가요?

아니요. 해당 오버로드는 shell injection 위험을 낮추지만 argument injection은 여전히 가능합니다. 대상 프로그램이 공격자 입력을 옵션이나 파일명으로 해석할 수 있습니다. 고정된 실행 파일과 분리된 인수를 ProcessBuilder로 전달하고 각 값을 대상 프로그램 기준으로 검증하세요.

ProcessBuilder가 인수를 검증해 주나요?

아니요. 프로그램과 인수 목록을 만들고 프로세스를 시작할 뿐 안전성이나 유효성을 검사하지 않습니다. 애플리케이션에서 허용 목록과 검증을 적용하고 대상 프로그램의 문서화된 인수 규칙을 따르세요.

참고 자료

함께 보기 OS 명령어 인젝션(CWE-78): 동작 원리와 모든 언어에서의 예방법 — flawopen.com Node.js exec()의 명령 주입 위험 Node.js exec() vs execFile(): 차이와 보안