소프트웨어 개발보안 가이드 분석(2021) : 운영체제 명령어 삽입
운영체제 명령어 삽입
https://owasp.org/www-community/attacks/Command_Injection
운영체제 명령어 삽입의 경우 적절한 검증 절차를 거치지 않아 공격자가 악의적인 명령어를 애플리케이션의 입력 폼에 삽입하여 실행하도록 유도하는 공격 기법입니다. 이러한 공격은 주로 웹 애플리케이션에서 발생하며, 사용자가 입력한 데이터를 통해 명령어가 동적으로 생성되어 실행되는 경우에 발생할 수 있습니다. 이를 통해 공격자는 시스템에 대한 완전한 제어권을 획득하거나 기밀 정보를 탈취할 수 있는 치명적인 보안약점입니다.
※ 웹 애플리케이션에서 system(), exec()와 같은 시스템 명령어를 실행할 수 있는 함수를 제공하며 사용자 입력값에 대한 필터링이 제대로 이루어지지 않을 경우 공격자가 시스템 명령어를 호출할 수 있습니다.
- 명령어 삽입: 공격자는 웹 애플리케이션의 입력 폼이나 매개 변수를 통해 악성 명령어를 삽입합니다.
- 명령어 실행: 삽입된 악성 명령어는 애플리케이션에서 동적으로 실행됩니다.
- 시스템 침투: 공격자가 악성 명령어를 통해 시스템에 대한 접근 권한을 획득하거나 기밀 정보를 탈취할 수 있습니다.
취약한 웹 어플리케이션의 예
1 BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
2 String InputData = reader.readLine();
3 Process process = Runtime.getRuntime().exec(InputData);
- 1행 : reader 변수에 표준 입력(System.in)에서 데이터를 읽어올 수 있게 합니다.
- 2행 : 입력 폼에서 입력한 데이터를 InputData로 가져옵니다. 공격자는 이곳에 악의적인 명령어를 입력합니다.
- 3행 : 외부에서 입력한 명령어 문자열을 실행하기 위해 'Runtime.getRuntime().exec()'를 사용하여 실행합니다.
- 입력 폼에서 입력한 데이터(InputData)에 대한 검증을 하지 않기 때문에 공격자들이 삽입한 명령어가 실행 가능합니다.
시큐어 코딩 적용 방법
- 입력값 검증
1 public static void main(String[] args) {
2 Scanner scanner = new Scanner(System.in);
3 String InputData = scanner.nextLine();
4 if (CommandSafe(InputData)) { // 입력값 허용 }
5 else { // 입력값 불허용 }
6 }
7
8 private static boolean CommandSafe(String InputData)
9 { return !InputData.contains("|") && !InputData.contains(";")
&& !InputData.contains("&") && !InputData.contains(":")
&& !InputData.contains(">") && !InputData.contains(">>"); }
- 2, 3행 : InputData 변수를 통해 입력값을 받습니다.
- 4행 : 입력값에 운영체제 명령을 실행할 수 있는 문자열이 없을 경우 동작합니다.
- 5행 : 입력값에 운영체제 명령을 실행할 수 있는 문자열이 있을 경우 적절히 통제해야 합니다.
- 8행 : 입력값에 대해 운영체제 명령을 실행할 수 있는 문자열(;, &, |, >, >>, :)을 필터링합니다.
- 입력값에 대해 유효성 검사를 수행하여 운영체제 명령을 실행할 수 있는 문자열을 필터링하여 다른 일반 문자로 교체하거나 위의 코드처럼 허용 혹은 불허용의 경우로 나누어 운영체제 명령어 삽입에 대응할 수 있습니다.
- 화이트리스트 기반 명령어 필터링
1 public static void main(String[] args) {
2 String[] allowedCommands = {"ls", "cd", "mkdir", "rm", "pwd"};
3 Scanner scanner = new Scanner(System.in);
4 String userInput = scanner.nextLine().trim();
5 if (CommandAllowed(userInput, allowedCommands)) {
6 //허용 된 명령어
7 } else {
8 //허용되지 않은 명령어
9 }
10 }
11 private static boolean CommandAllowed(String userInput, String[] allowedCommands) {
12 for (String allowedCommand : allowedCommands) {
13 if (userInput.equals(allowedCommand)) { return true; }
14 }
15 return false;
16 }
17 }
- 2행 : 허용 가능한 운영체제 명령어를 리스트로 정의해 놓습니다.
- 5~10행 : 입력값에 허용되는 명령어는 실행하고, 허용되지 않은 명령어는 통제합니다.
- 11~16행 : 입력값에 허용되는 명령어가 존재하는지 비교하여 판별합니다.
- 부득이하게 명령어가 입력값을 통해 실행되는 경우에 사용할 수 있는 대응방법입니다.
- 허용 가능한 명령어를 미리 리스트로 지정하여 해당 명령어만 실행이 가능하도록 하여 운영체제 명령어 삽입에 대응할 수 있습니다.