[python] eval() 과 literal_eval()

Suhyeon Lee·2025년 1월 14일
0

자기주도학습

목록 보기
80/83
post-thumbnail

eval() 함수 사용을 조심해야 하는 이유

출처

  • eval()
    • python의 built-in 함수 중 하나
    • 매우 강력하면서도 사용을 자제하도록 권고하는 양날의 검과 같은 기능
      • expression 인자에 string 값을 넣으면 해당 값을 그대로 실행하여 결과를 출력
eval("(5*10)/2")
>>> 25

→ 간단한 연산식을 string으로 저장 후 eval로 싱행하면 연산된 값을 얻을 수 있음

expr = "10+10"

type(expr)
>>> <type 'str'>

eval(expr)
>>> 20

→ 변수 expr은 string 타입이지만 eval로 실행하면 그대로 연산된 값을 얻을 수 있음

  • 단순 연산뿐 아니라 내장 함수나 객체도 실행 가능
eval("max([1, 2, 3, 4])")
>>> 4

list = ['a', 'b', 'c', 'd', 'e']
expr = "len(list)"

eval(expr)
>>> 5
  • eval은 이러한 자유로움으로 인해 발생할 수 있는 커다란 위험을 내포하고 있음
x = str(input(">> input some text for mathematical expression : ")

print eval(x)
>> input some text for mathematical expression: __import__('os').system('ls /')
Applications			Volumes				home				tmp
Developer			bin				installer.failurerequests	usr
Library				cores				macOS Install Data		var
Network				data				net
System				dev				private
Users				etc				sbin

🡆 서버의 root 디렉토리의 정보가 그대로 노출

__import__('os').system('rm -rf /') 같은 위험한 값을 사용자가 입력했을 때 그대로 실행할 수 있음!
__import__('os').system('rm -rf /') 코드는 매우 위험한 Python 명령어입니다.
절대로 실행해서는 안 됩니다.
이 코드는 Python의 내장 함수인 import()를 사용하여 os 모듈을 동적으로 가져오고, 그 모듈의 system() 함수를 호출하여 'rm -rf /' 명령을 실행하려고 합니다.
'rm -rf /' 명령은 Unix 계열 운영체제에서 루트 디렉토리(/)부터 시작해 모든 파일과 디렉토리를 강제로 재귀적으로 삭제하는 매우 위험한 명령입니다.
이 명령이 실행되면 시스템의 모든 데이터가 영구적으로 삭제될 수 있으며, 운영체제가 완전히 손상될 수 있습니다. 따라서 이런 코드는 절대 실행해서는 안 되며, 보안상의 이유로 대부분의 현대 운영체제에서는 이런 명령의 실행을 방지하는 안전장치가 있습니다.
이러한 코드는 악의적인 목적으로 사용될 수 있으므로, 출처가 불분명하거나 신뢰할 수 없는 코드는 실행하지 않도록 주의해야 합니다.
→ cf. Python Pickle에서 언피클을 신중하게 해야 하는 이유: 신뢰할 수 없는 데이터를 함부로 unpickle 하면, 매우 위험한 함수가 호출될 수 있습니다. 따라서, 신뢰할 수 없는 데이터는 절대로 unpickle하면 안 됩니다.

  • eval() 함수는 해당 표현식을 그대로 실행하기 때문에 Command Injection Flaws를 그대로 노출할 수 있으며 대형 참사로 이어질 수 있음

    • 이는 시스템 명령을 삽입할 수 있는 스크립트 언어(PHP, Javascript 등)은 모두가 가지고 있는 취약점이기도 함
  • 이뿐 아니라, eval() 명령은 코드의 가독성을 떨어뜨리고 디버깅을 어렵게 만들 수 있음

  • 또한 eval을 사용해 일부 로컬 환경에 의존하도록 구현할 경우 환경 의존성도 생길 수 있으므로 되도록 사용하지 않는 방향을 권장

eval과 literal_eval의 차이

literal_eval은 정말 안전한 eval인 것인가

  • literal_eval

    • eval과 달리 built-in 함수는 아니며, AST 모듈에서 제공하는 함수 중 하나임
      • AST(Abstract Syntax Trees) 모듈: 문법을 구조화시켜주는 모듈 정도로 이해하면 OK
    • 말 그대로 literal(=문자 그대로) evaluate를 실행하는 함수
      • 즉, python에서 제공하는 기본 type 정도만 변환해주는 용도로 사용 가능
  • dictionary 형태로 저장된 string값을 str_dict에 저장 후, 이를 literal_eval을 통해 실제 dictionary로 가공할 수 있음

import aststr_dict = "{'a': 3, 'b': 5}"print type(str_dict)           # <type 'str'>convert_dict = ast.literal_eval(str_dict)print type(convert_dict)   # <type 'dict'>print convert_dict['a']      #  3print convert_dict['b']      #  5
  • eval처럼 함수나 객체를 실행할 수 있을까?
import ast
str = '__import__("os").system("ls /")'
print type(str) # <type 'str'>
eval(str)
'''
Applications			Volumes				home				tmp
Developer			bin				installer.failurerequests	usr
Library				cores				macOS Install Data		var
Network				data				net
System				dev				private
Users				etc				sbin
'''
ast.literal_eval(str)
'''
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>  
  File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/ast.py", line 80, in literal_eval    
    return _convert(node_or_string)
  File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/ast.py", line 79, in _convert    
    raise ValueError('malformed string')
ValueError: malformed string
'''

→ literal_eval의 경우는 ValueError를 발생시킴킴

  • literal_eval은 python의 기본 자료형 정도만 evaluate가 가능하도록 지원
    • eval과 비교해 훨씬 엄격하기 때문에 결과적으로 안전을 보장한다고 설명한 것
    • 결과적으로 eval 대비 안전하겠지만,  그 사용 용도가 eval 대비 훨씬 제한적일 수 밖에 없음
  • 이쯤 되면, literal_eval 이 왜 AST 모듈에 존재하는지도 이해할 수 있음
    • AST 모듈은 Syntax 를 구조화하는 모듈이기 때문에, literal_eval의 용도인 string을 자료형으로 구조화 시키는 것과 그 목적을 같이 한 것으로 이해해도 될 것

ast.literral_eval()로 문자열(str) 속 표현식(expression) 인식하기

출처

  • 가만히 보면 Python 표현식(expression statement)도 문자열(str)

    • a = 1 + 2라는 구문(statement)이 있을 때 ‘1 더하기 2’를 나타낸 우변도 결국 '1 + 2' 형태의 문자열이라는 의미
  • Python 작업을 하다 보면 이렇게 문자열에 담겨 있는 표현식을 꺼내 써야 할 때가 종종 발생함

    • 메모리에 있는 중간 처리 결과를 문자열로 출력한 후 메모장 같은 곳에 저장해 두었다가 나중에 쓰는 경우
    • 리스트(list) 표현식을 통째로 문자열로 메모해 두었다가 쓰는 경우
  • 예시

    동창회 멤버 10명의 생년월일 데이터를 자주 사용하는 모임 총무가 있다고 칩시다. 길이(len)가 고작 10이고 각 요소(element)도 8자리 정수(int)인 자그마한 데이터를 굳이 pickle 같은 것을 사용해 보관할 필요는 없습니다.(오히려 번거롭지요.)

birth_day_list = ['19950415', '19950527', '19951103', '19950606', '19950717', '19950327', '19951030', '19950424', '19950815', '19950901']
print(len(birth_day_list)) # 10
print(len(birth_day_list[0])) # 8
  • 이 정도의 데이터는 Python 표현식 모양 그대로 메모장이나 클라우드 노트(Evernote, OneNote 따위)에 저장해 놓았다가 복사/붙여넣기로 다시 가져다 쓰는 경우가 많음

    • 그런데 이렇게 문자열로 메모해 놓은 표현식이 한두 개면 몰라도 개수가 많아지면 매번 복사/붙여넣기 식으로 하기 번거로움
      → 문자열로 기록된 표현식들을 일괄로 읽어 들여 와 Python 문자열 변수로 만들고 이를 이용해 원래의 Python 데이터를 얻어 내는 방법이 있을까요?
  • 표현식의 일괄 읽기는 그렇게 어렵지 않음

    • open(), read(), readlines()를 적절히 쓰면 됨
    • 관건은 ‘문자열 변수에 할당된 표현식으로부터 원래의 데이터를 얻는 방법’
list_in_str = '[1, 2, 3, 4, 5]'
list_data = list_in_str
print(type(list_data)) # str - wrong solution
  • 위 방법은 list_in_str을 그대로 재할당하는 작업일 뿐임
    • 어떻게 해야 list_in_str에 내포된 데이터를 복구해 낼 수 있을까?
      → 답은 ast.literal_eval()을 사용하는 것
import ast
list_in_str = '[1, 2, 3, 4, 5]'
list_data = ast.literal_eval(list_in_str)
print(type(list_data)) # list - correct solution
  • 딕셔너리(dict)도 동일한 방법으로 처리 가능
import ast
list_in_str = "{'a': 1, 'b': 2, 'c': 3}"
list_data = ast.literal_eval(list_in_str)
print(type(list_data)) # dict

사실 eval()이라는 내장(built-in) 함수가 있습니다. 그렇지만 이 함수는 악용될 소지가 커 가급적 사용을 자제해야 합니다.
한편, ast.literal_eval()은 위에서 우리가 하려고 했던 목적을 이루게 해 주면서도, 안전성을 보장합니다. 정확히 이야기하자면, 인자(parameter)로 주어진 표현식을 실행하기 전에 그것이 ‘수상한 표현식’인지 아닌지 먼저 검증합니다.

  • ast.listeral_eval()은 안전한 대신 인자로 제한된 리터럴(literal)로 구성된 표현식만 허용
    -즉, 다음과 같은 리터럴들만 표현식에 등장할 수 있음

eval()이 내장 함수이기도 하고 Python이 아닌 다른 언어에도 보통 등장하는 기능이라, 무심코 사용하는 경향이 있습니다. 물론 개인적인 용도로 사용할 프로그램이거나 믿을 수 있는 사용자들만 사용할 프로그램이라면 괜찮겠으나, 믿을 수 있는 그룹이 아닌 불특정 다수 그룹이 사용할 것이라면, eval()의 사용을 최소한으로 하고 ast.literal_eval()과 같은 대체 기능을 사용해야 합니다.

profile
2 B R 0 2 B

0개의 댓글

관련 채용 정보