경로 조작 및 자원삽입
정의
- 검증되지 않은 외부 입력값을 통해 파일 및 서버 등 시스템 자원(파일, 소켓의 포트 등)에 대한 접근 혹은 식별을 허용할 경우, 입력값 조작으로 시스템이 보호하는 자원에 임의로 접근할 수 있는 보안약점을 의미한다.
- subprocess.popen()과 같이 프로세스를 여는 함수, os.pipe()처럼 파이프를 여는 함수, socket 연결 등에서 외부 입력값을 검증 없이 사용할 경우 취약점이 발생할 수 있다.
안전한 코딩기법
입력값을 자원(파일, 소켓의 포트 등)의 식별자로 사용하는 경우 적절한 검증을 거치도록 하거나, 사전에 정의된 리스트에 포함된 식별자만 사용하도록 해야한다. 특히 입력값이 파일명인 경우 필터를 적용해 공격의 위험이 있는 문자(/, \, .. 등)을 제거해야한다.
코드예제
경로조작
입력값으로 파일 경로 등을 입력받아 파일을 여는 예시로 안전하지 않은 코드 예제입니다.
#경로 조작 및 자원 삽입(안전X)
def get_info_bad(request):
request_file = request.GET.get('request_file')
(filename, file_ext) = os.path.splitext(request_file)
file_ext = file_ext.lower()
if file_ext not in ['.txt', '.csv']:
return render(request, 'error.html', {'msg': '파일을 열 수 없습니다.'})
with open(request_file, encoding='UTF8') as f:
data = f.read()
return render(request, 'success.html', {'data': data})
request_file=./../hello.txt
request_file=hello.py
다음은 외부입력값에서 경로 조작 문자열 ( /, \, .. 등)을 제거한 안전한 코드입니다.
#경로 조작 및 자원 삽입(안전O)
def get_info_good(request):
request_file = request.GET.get('request_file')
(filename, file_ext) = os.path.splitext(request_file)
file_ext = file_ext.lower()
if file_ext not in ['.txt', '.csv']:
return render(request, 'error.html', {'msg': '파일을 열 수 없습니다.'})
filename = filename.replace('.', '')
filename = filename.replace('/', '')
filename = filename.replace('\\', '')
try:
with open(filename + file_ext, encoding='UTF8') as f:
data = f.read()
except:
return render(request, 'error.html', {'msg': '파일이 존재하지 않거나 열 수 없는 파일입니다.'})
return render(request, 'success.html', {'data': data})
request_file=./../hello.txt
자원삽입
안전하지 않은 코드 예제로, 외부입력을 소켓 포트번호로 사용하고 있습니다. 기존 자원과의 충돌로 의도치 않은 에러가 발생할 수 있습니다.
#경로 조작 및 자원 삽입(안전X)
def get_info_bad2(request):
port = int(request.GET.get('port',))
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('127.0.0.1', port))
return render(request, 'success.html')
return render(request, 'error.html', {'msg':'소켓연결 실패'})
다음은 안전한 코드예제로 허용 가능한 목록을 설정한 후 목록 내에 포함된 포트만 할당되도록 작성한 것입니다.
#경로 조작 및 자원 삽입(안전O)
ALLOW_PORT = [400, 500, 800]
def get_info_good2(request):
port = int(request.GET.get('port',))
if port not in ALLOW_PORT:
return render(request, 'error.html', {'msg': '소켓연결 실패'})
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('127.0.0.1', port))
return render(request, 'success.html')
port = 1122
port = 800
입력데이터 검증 및 표현/경로 조작 및 자원 삽입
[참고문헌] Python 시큐어코딩 가이드(2022) / KISA(한국인터넷진흥원)