1.4 시스템 자원 접근 및 명령어 수행 입력값 검증

개요

소프트웨어가 파일, 디렉토리 등 시스템 자원에 접근하거나 운영체제 명령어를 수행할 때, 외부 입력값을 경로 지정이나 명령어 인자로 사용하는 경우가 많습니다. 이때 입력값을 적절히 검증하고 처리하지 않으면, 공격자가 의도하지 않은 시스템 자원에 접근(예: 경로 조작(Path Traversal))하거나 임의의 명령어를 실행(예: OS 명령어 삽입)하는 심각한 보안 문제가 발생할 수 있습니다.

예를 들어, 파일 다운로드 기능에서 파일명을 입력받을 때 `../` 와 같은 경로 조작 문자를 필터링하지 않으면, 웹 루트 상위 디렉토리의 민감한 파일(예: `/etc/passwd`)에 접근할 수 있습니다. 또한, 시스템 상태 확인 기능 등에서 사용자 입력을 받아 `ping` 명령어를 실행할 때, 입력값에 명령어 구분자(`;`, `|` 등)를 삽입하면 추가적인 명령어를 실행시킬 수 있습니다.

이 기준은 시스템 자원 접근 및 명령어 수행 시 사용되는 외부 입력값의 안전한 검증 및 처리에 대한 보안 요구사항을 정의합니다.

보안 대책

  • - 입력값 검증 및 필터링 (Whitelist): 파일 경로, 파일명, 명령어 인자 등에 사용될 입력값에 대해 허용된 문자 집합(영숫자, 특정 기호 등)을 명확히 정의하고, 이 외의 모든 문자, 특히 경로 조작 문자(`../`, `..\\`, `/`, `\`)나 쉘 메타 문자(`;`, `|`, `&`, `$`, `<`, `>`, `\``, `\n`)를 필터링하거나 차단합니다.
  • - 경로 정규화 및 검증: 파일 경로를 처리하기 전에 운영체제가 해석하는 최종 절대 경로(Canonical Path)로 변환하고, 이 경로가 사전에 정의된 안전한 디렉토리 범위 내에 있는지 확인합니다.
  • - 이스케이프 처리: 입력값을 명령어 문자열의 일부로 사용해야 하는 경우, 각 운영체제와 쉘 환경에 맞는 이스케이프 함수를 사용하여 메타 문자가 명령어로 해석되지 않도록 처리합니다.
  • - 안전한 API 사용: 운영체제 명령어 실행 시, 문자열 결합 방식 대신 명령어와 인자를 분리하여 안전하게 실행하는 API(예: Java `ProcessBuilder`, Python `subprocess` 리스트 인자)를 사용합니다. 파일 접근 시에도 경로 조작에 안전한 API를 사용합니다.
  • - 최소 권한 원칙: 웹 서버 또는 애플리케이션 실행 계정에 시스템 자원 접근 및 명령어 실행에 필요한 최소한의 권한만 부여합니다.
  • - 간접 참조 매핑: 사용자에게 실제 파일 경로나 자원 식별자를 직접 노출하는 대신, 안전한 식별자(예: 숫자 ID, UUID)를 사용하고 서버 내부에서 실제 경로로 매핑하여 사용합니다.

코드 예시 (Java - 경로 조작 방지)

취약한 코드:

// 사용자 입력(filePath)을 검증 없이 파일 경로에 사용
String userPath = request.getParameter("filePath");
String fullPath = "/var/www/data/" + userPath; // 경로 조작에 취약
// 예: userPath = "../../etc/passwd" -> /var/www/data/../../etc/passwd

File file = new File(fullPath);
// 파일을 읽거나 쓰는 로직...
                        
안전한 코드 (경로 정규화 및 검증):

import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;

String userPath = request.getParameter("filePath");

// 1. 입력값에서 경로 조작 문자 필터링 (기본적인 방어)
if (userPath.contains("..")) {
    throw new SecurityException("Invalid file path specified.");
}

// 2. 기본 디렉토리 설정 및 경로 결합
Path baseDir = Paths.get("/var/www/data/").toAbsolutePath();
Path requestedPath = baseDir.resolve(userPath).normalize(); // 경로 결합 및 정규화 (../ 등 제거 시도)

// 3. 최종 경로가 기본 디렉토리 내부에 있는지 확인 (가장 중요)
if (!requestedPath.startsWith(baseDir)) {
    throw new SecurityException("Access denied. Path is outside the allowed directory.");
}

File file = requestedPath.toFile();
// 안전하게 파일 접근
                        

코드 예시 (Python - 명령어 삽입 방지)

안전한 코드 (subprocess 모듈 사용):

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:
    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), C#, C 등 다른 언어에서도 파일 경로 처리 시 경로 정규화 및 검증, 명령어 실행 시 인자 분리 전달이 중요합니다.