본문 바로가기
코딩취미/Python

exec()에서 사용자 입력(악성코드) 동작 방지방

by 브링블링 2024. 9. 9.
반응형

exec()에서 사용자 입력(악성코드) 동작 방지방법

exec()는 문자열로 작성된 파이썬 코드 블록을 실행하는 매우 강력한 함수입니다. 그러나 외부 입력을 검증 없이 exec()로 처리하면 악의적인 코드를 실행할 수 있는 위험이 있습니다. 특히, import os; os.system('rm -rf /')와 같은 명령어가 실행되면 시스템에 심각한 피해를 줄 수 있습니다. 따라서 exec()를 사용할 때는 반드시 안전한 방법으로 사용자 입력을 처리해야 합니다.

 

 

1. 보안 문제 이해

exec() 함수는 문자열로 전달된 모든 파이썬 구문을 실행할 수 있습니다. 예를 들어, 사용자로부터 입력받은 문자열이 다음과 같은 악의적인 코드를 포함할 수 있습니다. 예시에서 exec() 함수는 사용자 입력을 그대로 실행하고, 그 결과 시스템이 손상될 수 있습니다.

user_input = "import os; os.system('rm -rf /')"
exec(user_input)  # 시스템 파일을 삭제하는 매우 위험한 코드 실행

2. exec() 사용 시 보안 강화 방법

2.1. 사용자 입력 검증 및 화이트리스트 적용

가장 중요한 방어 방법 중 하나는 입력 검증입니다. 입력값을 사전에 허용된 패턴이나 명령으로 제한하면 위험한 코드가 실행되는 것을 방지할 수 있습니다. 이 방법은 입력값을 특정 패턴이나 키워드만 허용하는 화이트리스트 방식으로 필터링합니다.

 

설명:

  • 정규식을 사용하여 입력값이 오직 안전한 연산자와 변수 이름으로만 구성되었는지 확인합니다.
  • exec()에 전달된 네임스페이스를 제한하여 빌트인 함수에 대한 접근을 차단합니다(__builtins__ = None).
  • 안전하지 않은 코드는 필터링되며, 허용된 범위 내에서만 코드를 실행할 수 있습니다.

장단점:

  • 장점: 간단한 수식이나 변수 선언 같은 코드는 안전하게 실행할 수 있습니다.
  • 단점: 정규식으로 패턴을 정의해야 하므로, 입력이 복잡할수록 검증이 까다로울 수 있습니다.
import re

def safe_exec(user_input):
    # 안전한 패턴으로 제한 (예: 숫자, 간단한 수식, 변수 선언)
    if re.match(r'^[\w\s=\+\-\*/\(\)]+$', user_input):
        try:
            exec(user_input, {"__builtins__": None}, {})
        except Exception as e:
            print(f"Error executing input: {e}")
    else:
        print("Invalid input")

# 예시 사용
user_input = "a = 5 + 3; print(a)"
safe_exec(user_input)  # 출력: 8

user_input = "import os; os.system('rm -rf /')"
safe_exec(user_input)  # 출력: Invalid input

2.2. 제한된 전역 및 지역 네임스페이스 사용

exec() 함수에 전달되는 전역(globals)지역(locals) 네임스페이스를 제한하여, 특정 함수나 변수에만 접근할 수 있도록 제어할 수 있습니다. 이를 통해 사용자가 임의로 시스템 명령어를 실행하는 것을 방지할 수 있습니다.

 

설명:

  • globals와 locals를 사용하여 허용된 함수(print, range)만 네임스페이스에 노출시킵니다.
  • __builtins__을 None으로 설정하여 기본적인 파이썬 빌트인 함수 및 모듈 접근을 차단합니다.
  • exec() 실행 시 사용자가 import나 os.system과 같은 위험한 함수에 접근하는 것을 방지합니다.

장단점:

  • 장점: 필요한 기능만 제공하면서 불필요한 함수나 모듈에 대한 접근을 차단할 수 있습니다.
  • 단점: 필요한 함수나 변수를 사전에 정의해야 하며, 기능 확장이 제한될 수 있습니다.
def safe_exec_with_namespace(user_input):
    # 허용된 함수 및 변수를 정의한 네임스페이스
    allowed_functions = {
        'print': print,
        'range': range
    }

    try:
        # 제한된 네임스페이스 내에서만 실행
        exec(user_input, {"__builtins__": None}, allowed_functions)
    except Exception as e:
        print(f"Error executing input: {e}")

# 예시 사용
user_input = "print('Hello World')"
safe_exec_with_namespace(user_input)  # 출력: Hello World

user_input = "import os; os.system('rm -rf /')"
safe_exec_with_namespace(user_input)  # 출력: Error: name 'os' is not defined

2.3. 사용자 입력을 파싱하여 제한적으로 처리

사용자 입력을 직접 파싱하고 안전한 작업만을 실행하도록 제어하는 방법입니다. 이 방법을 사용하면 예상치 못한 명령어가 실행되는 것을 방지할 수 있습니다.

 

설명:

  • 허용된 명령어 리스트(allowed_commands)를 정의하고, 입력값이 해당 리스트 내에 있는 명령어만 포함하는지 확인합니다.
  • 사용자가 허용된 명령어 외의 명령을 입력하면 이를 실행하지 않고, 에러 메시지를 반환합니다.

장단점:

  • 장점: 매우 안전하게 제한된 작업만 실행할 수 있습니다.
  • 단점: 허용된 명령어가 많아질수록 관리가 복잡해질 수 있습니다.
def safe_parse_and_exec(user_input):
    allowed_commands = ['print', 'a =', 'b =', '+', '-', '*', '/']
    
    # 입력값을 확인하여 허용된 명령만 포함하는지 확인
    if any(command in user_input for command in allowed_commands):
        try:
            exec(user_input, {"__builtins__": None}, {})
        except Exception as e:
            print(f"Error executing input: {e}")
    else:
        print("Invalid command")

# 예시 사용
user_input = "a = 5 + 3; print(a)"
safe_parse_and_exec(user_input)  # 출력: 8

user_input = "import os; os.system('rm -rf /')"
safe_parse_and_exec(user_input)  # 출력: Invalid command
반응형

2.5. 서드파티 라이브러리 사용

서드파티 라이브러리를 사용하여 안전하게 코드를 파싱하고 평가하는 방법도 있습니다. 예를 들어, restrictedpython 같은 라이브러리는 파이썬 코드 실행을 제한하는 환경을 제공합니다.

 

설명:

  • RestrictedPython은 파이썬 코드를 안전한 환경에서 실행할 수 있도록 도와주는 라이브러리입니다.
  • 임의의 코드 실행을 제한하고, 특정 기능만을 허용할 수 있습니다.

장단점:

  • 장점: 복잡한 코드를 안전하게 실행할 수 있습니다.
  • 단점: 라이브러리를 설치해야 하며, 일부 파이썬 기능이 제한될 수 있습니다.
pip install RestrictedPython
from RestrictedPython import compile_restricted, safe_globals

def safe_exec_restricted(user_input):
    try:
        code = compile_restricted(user_input, '<string>', 'exec')
        exec(code, safe_globals)
    except Exception as e:
        print(f"Error executing input: {e}")

# 예시 사용
user_input = "print('Hello World')"
safe_exec_restricted(user_input)  # 출력: Hello World

user_input = "import os; os.system('rm -rf /')"
safe_exec_restricted(user_input)  # 출력: Error: RestrictedPython 실행 제한

3. 결론 및 정리

exec() 함수는 매우 강력하지만, 잘못된 사용은 보안상의 심각한 문제를 일으킬 수 있습니다. 특히 외부 입력을 처리할 때는 항상 신중하게 검증하고, 가능한 안전한 대체 방법을 사용하는 것이 중요합니다. 위에서 설명한 여러 가지 방법을 사용하여 exec()의 보안 위험을 최소화하고, 안전한 소프트웨어를 개발할 수 있습니다.

  • 가능하면 exec()를 사용하지 않고, 대체 방법을 사용하는 것이 가장 안전합니다.
  • exec()를 반드시 사용해야 한다면, 입력 검증을 철저히 하고, 전역 및 지역 네임스페이스를 제한하여 위험한 함수에 접근할 수 없도록 해야 합니다.
  • 정규식과 화이트리스트 방식으로 입력값을 필터링하고, 허용된 패턴만 처리합니다.
  • 서드파티 라이브러리(예: RestrictedPython)를 사용하여 안전한 환경에서 코드를 실행하는 것도 좋은 방법입니다.
  • 입력 파싱을 직접 구현하여, 허용된 연산만을 실행하도록 제어하는 방법도 안전한 대안입니다.
  • 항상 예외 처리를 포함하여 예기치 않은 오류나 공격을 방어해야 합니다.
반응형