[시큐어코딩 가이드] 운영체제 명령어 삽입
- 정보보안/시큐어코딩 가이드
- 2022. 8. 9.
■ 입력데이터 검증 및 표현
프로그램 입력 값에 대한 검증 누락 또는 부적절한 검증, 데이터의 잘못된 형식지정, 일관되지 않은 언어셋 사용 등으로 인해 발생되는 보안약점으로 SQL 삽입, 크로스사이트 스크립트(XSS) 등의 공격을 유발할 수 있다.
운영체제 명령어 삽입
■ 개요
적절한 검증절차를 거치지 않은 사용자 입력값이 운영체제 명령어의 일부 또는 전부로 구성되어 실행되는 경우, 의도하지 않은 시스템 명령어가 실행되어 부적절하게 권한이 변경되거나 시스템 동작 및 운영에 악영향을 미칠 수 있다.
일반적으로 명령어 라인의 파라미터나 스트림 입력 등 외부 입력을 사용하여 시스템 명령어를 생성 하는 프로그램이 많이 있다. 하지만 이러한 경우 외부 입력 문자열은 신뢰할 수 없기 때문에 적절한 처리를 해주지 않으면, 공격자가 원하는 명령어 실행이 가능하게 된다.
파이썬에서 eval() 함수와 exec() 함수는 내부에서 문자열을 실행하기에 편리하지만, String 형식의 표현된 식을 인수로 받아 반환하는 eval() 함수와 인수로 받은 문자열을 실행하는 exec()를 같이 사용하면 여러 변수들에 동적으로 값을 할당하여 사용될 수 있어 Command Injection 공격에 취약하다.
■ 안전한 코딩 기법
외부 입력값이 시스템 명령에 포함되는 경우 |, ;, &, :, >, <, `(backtick), \, ! 과 같이 멀티라인 파이프 리다이렉트 문자 등을 필터링 하고 명령을 수행할 파일명, 옵션을 제한하여 인자로만 사용될 수 있도록 한다. 외부 입력에 따라 명령어를 생성하거나 선택이 필요한 경우에는 명령어 생성에 필요한 값 들을 미리 지정해 놓고 외부 입력에 따라 선택하여 사용한다.
코드예제
다음 예제는 os.system을 이용하여 외부 입력 받은 값을 통해 프로그램을 실행하며, 외부에서 전달되는 인자 값은 명령어의 생성에 사용된다. 그러나 해당 프로그램에서 실행할 프로그램을 제한하지 않고 있기 때문에 외부의 공격자는 가능한 모든 프로그램을 실행시킬 수 있다.
안전하지 않은 코드의 예 |
import os from django.shortcuts import render def execute_command(request): app_name_string = request.POST.get('app_name','') # 입력받은 파라미터를 제한하지 않아 외부 입력값으로 전달된 # 모든 프로그램이 실행될 수 있음 os.system(app_name_string) return render(request, '/success.html') |
외부에서 입력 받은 값이 명령어의 인자로 사용되지 않고 명령어로 사용될 경우에는 미리 화이트리스트의 정의된 파라미터 배열을 만들어 놓고, 외부의 입력에 따라 적절한 파라미터를 선택하도록 하여, 외부의 부적절한 입력이 명령어로 사용될 가능성을 배제하여야 한다.
안전한 코드의 예 |
import os from django.shortcuts import render ALLOW_PROGRAM = ['notepad', 'calc'] def execute_command(request): app_name_string = request.POST.get('app_name','') # 입력받은 파라미터를 사용가능한 시스템 명령어 일부로 제한하여 사용 if app_name_string not in ALLOW_PROGRAM: return render(request, '/error.html', {'ERROR':'허용되지 않은 프로그램입니다.'}) os.system(app_name_string) return render(request, '/success.html') |
다음은 subprocess()함수를 사용하여 별도의 프로세스로 응용프로그램을 실행하는 경우의 안전하지 않은 예제이다. 외부 입력값으로 받은 파라미터를 별도의 검증 없이 subprocess의 인자 값으로 사용하고 있다.
안전하지 않은 코드의 예 |
import subprocess from django.shortcuts import render def execute_command(request): date = request.POST.get('date','') # 입력받은 파라미터를 제한하지 않아 전달된 모든 프로그램이 # 실행될 수 있음 cmd_str = “cmd /c backuplog.bat ” + date subprocess.run(cmd_str, shell=True) return render(request, '/success.html') |
운영체제 명령어 실행 시에는 아래와 같이 외부에서 들어오는 값에 의하여 멀티라인을 지원하는 특수문자(|, ;, &, :, `, \, !)나 파일 리다이렉트 특수문자( >, >> )등을 제거하여 원하지 않는 운영체제 명령어가 실행 될 수 없도록 필터링을 수행한다.
Command lines을 구문 분석하고 escape하는 기능을 제공하는 모듈인 shlex 모듈을 사용하여 필터링을 수행한다. subprocess의 옵션 값 중 shell=True 일 경우 중간 프로세스에 의해 명령이 실행되고 파일 이름, 와일드카드(*), 환경변수 확장 등의 쉘 기능을 검증 없이 실행하게 되므로 shell의 옵션은 삭제한다. (default 값은 False)
안전한 코드의 예 |
import subprocess from django.shortcuts import render def execute_command(request): date = request.POST.get('date','') # 명령어를 추가로 실행 또는 다른 명령이 실행될 수 있는 키워드에 # 대한 예외처리 for word in ['|', ';', '&', ':', '>', '<', '`', '\\', '!']: date = date.replace(word, “”) # shell=True 옵션은 제거 하고 명령과 인자를 배열로 입력 subprocess.run(["cmd", "/c", "backuplog.bat", date]) return render(request, '/success.html') |
re.sub함수를 사용하여 다음과 같이 특수문자를 제거할 수도 있다.
※ date = re.sub('[|;&:><`\\\!]', '', date)