설명
명령어 주입(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대상 예시&|;$><`\!
참조
- OWASP: Command Injection
- CWE: CWE-77: Improper Neutralization of Special Elements used in a Command ('Command Injection')
- Injection Filter for UNIX Shells: shlex.quote