5. 운영체제 명령어 삽입 (OS Command Injection)
개요
운영체제 명령어 삽입은 웹 애플리케이션이 외부 입력값(예: 사용자 입력, 파일 이름, 환경 변수)을 사용하여 운영체제 명령어(쉘 명령어)를 생성하고 실행할 때 발생하는 보안 약점입니다. 공격자는 입력값에 악의적인 명령어 구문(예: `;`, `|`, `&&`, `||`, `\`` 등)을 삽입하여, 웹 서버(또는 애플리케이션 실행 환경)에서 의도하지 않은 임의의 운영체제 명령어를 실행시킬 수 있습니다.
이 공격이 성공하면 공격자는 시스템 파일 접근, 데이터베이스 정보 유출, 악성 코드 실행, 시스템 종료 등 심각한 피해를 입힐 수 있으며, 웹 서버의 완전한 제어권 장악으로 이어질 수도 있습니다.
보안 대책
- - 명령어 실행 함수 사용 최소화: 가능하면 외부 입력값을 사용하여 운영체제 명령어를 직접 생성하고 실행하는 기능을 구현하지 않는 것이 가장 안전합니다.
- - 입력값 검증 및 필터링: 운영체제 명령어에 사용될 외부 입력값에 대해 허용된 문자 집합(Whitelist)을 엄격하게 정의하고, 그 외의 모든 문자, 특히 쉘 메타 문자(`;`, `|`, `&`, `$`, `<`, `>`, `\``, `\n` 등)를 필터링하거나 차단합니다.
- - 이스케이프 처리: 입력값을 명령어 문자열의 일부로 사용해야 하는 경우, 각 운영체제와 쉘 환경에 맞는 이스케이프 함수를 사용하여 메타 문자가 명령어로 해석되지 않도록 처리합니다.
- - 안전한 API 사용: 운영체제 명령어 실행이 불가피한 경우, 문자열 결합 방식 대신 명령어와 인자를 분리하여 안전하게 실행하는 API(예: Java의 `ProcessBuilder`, Python의 `subprocess` 모듈에서 리스트 형태 인자 전달)를 사용합니다.
- - 최소 권한 원칙: 웹 서버 또는 애플리케이션 실행 계정에 필요한 최소한의 권한만 부여하여, 명령어 삽입 공격이 성공하더라도 피해 범위를 제한합니다.
코드 예시 (Java)
취약한 코드 (Runtime.exec):
// 사용자 입력(filename)을 검증 없이 명령어 문자열에 포함
String command = "ls -l " + filename;
Process process = Runtime.getRuntime().exec(command); // 명령어 삽입 공격에 취약
// 예: filename = "file.txt; rm -rf /"
// ProcessBuilder를 사용하여 명령어와 인자를 분리
List commandList = new ArrayList<>();
commandList.add("ls");
commandList.add("-l");
// 입력값 검증 (예: 파일명에 허용된 문자만 있는지 확인)
// ... (검증 로직) ...
if (isValidFilename(filename)) {
commandList.add(filename); // 검증된 인자만 추가
ProcessBuilder pb = new ProcessBuilder(commandList);
Process process = pb.start(); // 명령어 삽입 방어됨
} else {
// 유효하지 않은 입력 처리
}
코드 예시 (Python)
취약한 코드 (os.system):
import os
# 사용자 입력을 직접 명령어 문자열에 포매팅
command = f"grep '{search_term}' /var/log/app.log"
os.system(command) # 명령어 삽입 공격에 취약
# 예: search_term = "'; cat /etc/passwd #"
import subprocess
import shlex
# 입력값 검증 또는 이스케이프 (shlex.quote 사용)
safe_search_term = shlex.quote(search_term)
# 명령어와 인자를 리스트로 분리하여 전달 (shell=False가 기본값)
command_list = ["grep", safe_search_term, "/var/log/app.log"]
try:
# shell=True를 사용하지 않으면 메타 문자가 해석되지 않음
result = subprocess.run(command_list, check=True, capture_output=True, text=True)
print(result.stdout)
except subprocess.CalledProcessError as e:
print(f"Error executing command: {e}")
JavaScript(Node.js의 `child_process`), C#(System.Diagnostics.Process), C(exec 계열 함수) 등에서도 명령어와 인자를 분리하여 전달하는 방식이 일반적으로 더 안전합니다.