명령어 주입 (Command Injection)

Command Injection

설명

명령어 주입(Command Injection)은 애플리케이션이 사용자 입력을 운영 체제 명령어로 실행할 때 발생하는 취약성입니다. 공격자는 조작된 입력을 통해 애플리케이션 프로세스의 권한으로 명령을 실행하여 데이터 유출이나 시스템 손상을 일으킬 수 있습니다.

잠재적 영향

  • 프로세스 권한 악용: 실행 프로세스가 접근할 수 있는 파일과 서비스가 악용될 수 있습니다. 관리자 권한이 자동으로 부여되는 것은 아닙니다.
  • 데이터 유출: 민감한 데이터가 공격자에게 노출될 수 있습니다.
  • 서비스 거부(DoS): 시스템 자원을 소모시키거나 중요한 프로세스를 중단시켜 서비스가 중단될 수 있습니다.

해결 방법

  • 사용자 입력 검증: 명령어로 사용되는 모든 사용자 입력을 철저히 검증합니다.
  • 명령어와 인자 분리: 가능하면 subprocess.run([...], shell=False)처럼 명령어와 인자를 리스트로 분리하고 셸을 사용하지 않습니다.
  • 셸 사용 최소화: shell=True가 꼭 필요한 경우에만 사용하고, POSIX 셸 문자열은 shlex.quote() 또는 shlex.join()으로 명시적으로 이스케이프합니다.
  • 플랫폼 차이 고려: shlex는 Unix/POSIX 셸을 대상으로 하므로 Windows 셸까지 안전하다고 가정하지 않습니다. shell=True에서 사용자 입력이 리스트/튜플의 뒤쪽 인자로 들어가는 패턴도 플랫폼별 동작 차이가 있어 안전한 예외로 보지 않습니다.

예시

OS `system`

변경 전

python
# Unsafe os.system code
import os
from flask import request

@app.route('/command')
def unsafe_command():
    command = request.args.get('command')
    os.system(command)
    return "Command executed"

변경 후

python
# Safe subprocess code
import subprocess
from flask import request, abort

@app.route('/command')
def safe_command():
    command = request.args.get('command')
    if command not in ['ls', 'pwd']:  # 허용된 명령어만 실행
        return abort(400, description="Invalid command")
    result = subprocess.run([command], shell=False, capture_output=True, text=True)
    return result.stdout

설명:

  • 변경 전: 사용자 입력값을 검증하지 않고 운영 체제 명령어로 실행하여, 공격자가 임의의 명령어를 실행할 수 있습니다.
  • 변경 후: 허용된 명령어만 실행하도록 검증하고, shell=False 상태에서 인자 배열로 전달하여 셸 특수문자 해석을 피합니다.

Paramiko `exec_command`

다음은 Flask 애플리케이션과 SSH 자격 증명 구성이 생략된 발췌 예시입니다. SSH 서버의 호스트 키를 검증하고, 원격 셸이 POSIX 셸인지 확인하세요. shlex.quote()는 셸 구문을 이스케이프할 뿐 접속 대상을 승인하지 않으므로 host도 허용된 대상으로 검증해야 합니다.

변경 전

python
# Unsafe paramiko code
from flask import request
from paramiko.client import SSHClient

@app.route('/paramiko')
def paramiko():
    client = SSHClient()
    client.load_system_host_keys()
    client.connect("example.com", username=USER, password=PASS)
    client.exec_command(request.args.get("cmd")) # Noncompliant
    return "Command sent"

변경 후

python
# Safe paramiko code
import subprocess
import shlex
from flask import request, abort
from paramiko.client import SSHClient

@app.route('/paramiko')
def paramiko():
    client = SSHClient()
    client.load_system_host_keys()
    client.connect("xeize.com", username=USER, password=PASS)
    
    health_cmd= "/bin/ping -c 3 -- %s"
    cmd = health_cmd % shlex.quote(request.args.get('host'))
    client.exec_command(cmd)
    return "Command sent"

설명:

  • 변경 전: 사용자 입력값을 검증하지 않고 운영 체제 명령어로 실행하여, 공격자가 임의의 명령어를 실행할 수 있습니다.
  • 변경 후: shlex.quote를 사용하여 POSIX 셸에서 인자를 하나의 안전한 토큰으로 전달하거나, 가능할 경우 허용된 명령어만 실행하도록 검증하여 공격자가 임의의 명령어를 실행할 수 없도록 합니다.
  • 참고: shlex.quote는 Unix shells 대상으로만 만들어졌기에 Windows와 같은 non-POSIX compliant shells 대상으로는 안전하지 않음
    • shlex.quote 대상 예시
      • &
      • |
      • ;
      • $
      • >
      • <
      • `
      • \
      • !

참조