[시큐어코딩 가이드] 코드 삽입

 

 

 

■ 입력데이터 검증 및 표현

 

프로그램 입력 값에 대한 검증 누락 또는 부적절한 검증, 데이터의 잘못된 형식지정, 일관되지 않은 언어셋 사용 등으로 인해 발생되는 보안약점으로 SQL 삽입, 크로스사이트 스크립트(XSS) 등의 공격을 유발할 수 있다.

 

 

코드 삽입

 

■ 개요

 

공격자가 소프트웨어의 의도된 동작을 변경하도록 임의 코드를 삽입하여 소프트웨어가 비정상적으로 동작하도록 하는 보안 약점을 말한다. 코드 삽입은 프로그래밍 언어 자체의 기능에 한해 이뤄진다는 점에서 운영체제 명령어 삽입과 다르다.

 

취약한 프로그램에서 사용자의 입력 값에 코드가 포함되는 것을 허용할 경우, 공격자는 개발자가 의도하지 않은 코드를 실행하여 권한을 탈취하거나 인증 우회, 시스템 명령어 실행 등을 할 수 있다.

 

Python에서 코드 삽입 공격을 유발할 수 있는 함수로 eval(), exec()등의 함수가 있다. 해당 함수의 인자를 면밀히 검증하지 않는 경우 공격자가 전달한 코드를 그대로 실행할 수 있다.

 

 

■ 안전한 코딩 기법

 

동적코드를 실행할 수 있는 함수를 사용하지 않는다. 필요 시, 실행 가능한 동적코드를 입력 값으로 받지 않도록, 외부 입력값에 대하여 화이트리스트 방식으로 검증한다. 또는 유효한 문자만 포함하도록 동적 코드에 사용되는 사용자 입력 값을 필터링 한다.

 

 

코드예제

 

■ eval()함수 사용 예제

 

다음은 안전하지 않은 예제소스코드로 eval()을 사용하여 사용자로부터 입력받은 값을 실행하여 결과를 리턴하는 예제이다.


5라인에서 입력 받은 값을 아무런 검증 없이 10라인에서 eval()함수의 인자로 사용하고 있다. 외부 입력값을 검증 없이 사용할 경우 공격자는 Python 코드를 입력하여 라이브러리 로드 및 원격 대화형 쉘 등을 실행할 수도 있다. 만일 message에 공격자가 다음과 같은 코드를 입력 하면 20초 동안 응용프로그램이 sleep 상태에 빠질 수 있다.


예시) “compile(‘for x in range(1):\n import time\n time.sleep(20)’,’a’,’single’)”

안전하지 않은 코드의 예
from django.shortcuts import render

def route(request):
#외부로 입력받은 값을 검증 없이 사용하면 안전하지 않다.
message = request.POST.get('message', '')

# eval함수에 외부 입력값을 검증 없이 사용할 경우 Python 코드가
# 실행될 수 있어 위험하다.

ret = eval(message)
return render(request, '/success.html', {'data':ret})

 

다음은 안전한 코드로 변환한 예제이다. 외부 입력값에 Python 코드를 실행할 수 있는 특수 문자 등을 필터링 하여 사전에 검증하는 코드를 추가하면 코드삽입을 완화할 수 있다.

 

아래 코드는 4라인에서 입력 받은 외부 입력값을 10라인의 eval() 함수의 인자 값으로 사용하기 전에 9라인에서 입력 값이 영문과 숫자만으로 입력되었는지 검증 후 사용하도록 하고 있다.

 

안전한 코드의 예
from django.shortcuts import render

def route(request):
message = request.POST.get('message', '')

# 사용자 입력을 영문, 숫자로 제한하는 예로. 특수문자가 포함되어
# 있을 경우 에러 메시지를 리턴 한다.

if message.isalnum():
ret = eval(message)
return render(request, '/success.html', {'data':ret})
......

 

Python은 다양한 String Methods를 제공하고 있다. 외부 입력값에 대한 검증이 필요한 경우 적절한 메소드를 사용해야 한다. 아래는 Python에서 제공하는 String Methods중 일부 예시이다.
⦁str.isalpha() : 문자열 내의 모든 문자가 알파벳이고, 적어도 하나의 문자가 존재하는 경우 True를 반환.
⦁str.isdecimal() : 문자열 내의 모든 문자가 십진수 문자이고, 적어도 하나의 문자가 존재하는 경우 True를 반환.
⦁str.isdigit() : 문자열 내의 모든 문자가 숫자이고, 적어도 하나의 문자가 존재하는 경우 True를 반환. 십진수 문자와 호환되는 위 첨자 숫자와 같은 숫자도 포함. ex) ‘52’ 는 True를 반환.

 

입력 값 검증 시 외부 입력값이 특정 형식을 따라야 할 경우 정규식을 이용하여 검증을 할 수 있고, Python의 re 라이브러리를 사용하여 검증 가능하다.

 

예로 Email 형식의 입력만 받아야 한다면 다음과 같은 정규식으로 검증 가능하다.
ex) prog = re.compile(r'([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,})+')

 

 

■ exec()함수 사용 예제

 

다음은 exec()함수를 사용한 안전하지 않은 코드 예제이다. 4라인에서 입력받은 외부 입력값을 검증 없이 9라인의 exec 함수의 인자로 사용하고 있다. 이 경우 중요 데이터 탈취 및 서버의 권한 탈취, 액세스 거부, 심지어 완전한 호스트 탈취를 유발할 수 있다.

안전하지 않은 코드의 예
from django.shortcuts import render

def request_rest_api(request):
function_name = request.POST.get('function_name', '')

# 사용자에게 전달받은 함수명을 검증하지 않고 실행
# 입력 값으로 “import plaform \n platform.system()” 등을 입력 시
# 시스템정보 노출 위험이 있다.
ret = exec('{}()'.format(function_name))

return render(request, '/success', {'data':ret})

 

다음은 안전한 코드로 변환한 예제이다. 우선 외부에서 입력 받는 라이브러리 명을 미리 정의한 화이트리스트에 있는 지를 확인하고 리스트에 없는 경우엔 에러 페이지를 리턴한다.

 

안전한 코드의 예
from django.shortcuts import render

WHITE_LIST = ['get_friends_list', 'get_address', 'get_phone_number']

def request_rest_api(request):
function_name = request.POST.get('function_name', '')

# 함수 명을 화이트리스트로 제한
if function_name in WHITE_LIST:
ret = exec('{}()'.format(function_name))
return render(request, '/success', {'data':ret})

return render(request, '/error', {'error':'허용되지 않은 함수입니다.'})

 

 

댓글

Designed by JB FACTORY