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)를 사용하여 안전한 환경에서 코드를 실행하는 것도 좋은 방법입니다.
- 입력 파싱을 직접 구현하여, 허용된 연산만을 실행하도록 제어하는 방법도 안전한 대안입니다.
- 항상 예외 처리를 포함하여 예기치 않은 오류나 공격을 방어해야 합니다.
'코딩취미 > Python' 카테고리의 다른 글
리스트 컴프리헨션 구조와 사용방법: 초보자를 위한 간단한 설명과 예제 (0) | 2024.09.10 |
---|---|
QTreeView 트리구조 생성 및 사용방법(+이벤트 처리) (0) | 2024.09.10 |
eval() 에서 악의적인 코드 실행 방지하기 : 보안 위험 방지 (0) | 2024.09.05 |
파이썬 리스트 객체, 원소 추가하기 : append와 extend 의 기능과 차이점 (0) | 2024.09.04 |
문자열을 파이썬 코드로 해석해서 실행하는 함수 : eval(), exec() 비교 (0) | 2024.09.04 |