1) 파일 다운로드 취약점이란 무엇인가?
File Download Vulnerability
- 사용자가 파일을 다운로드 받을 때 발생되는 취약점
- 서버에 있는 파일을 다운로드 받는 기능에 대해서 정상적인 파일이 아닌 비정상적인 파일을 다운로드 받는 취약점
- 공격 대상: 파일 다운로드 기능
일반 사용자는 파일 다운로드 기능을 사용하여 지정된 경로에서 정상 파일을 다운로드 받는다.
공격자는 파일 다운로드 기능을 사용하여 지정되지 않은 경로에서 비정상 파일을 다운로드 받는다.
→ 파일 다운로드 할 수 있는 경로를 변조
→ 경로 이동 문자(../) 사용하여 지정된 경로가 아닌 상위 디렉터리에서 소스코드, 서버 설정 파일 등을 다운로드
정상 파일: 지정된 경로에서 다운로드 받는 파일
비정상 파일: 지정된 경로가 아닌 경로에서 다운로드 받는 파일
결론
- 파일 다운로드 기능에 사용하여 비정상적인 파일을 다운로드 받는 취약점
- 경로 이동 문자를 삽입하여 지정된 경로가 아닌 경로에서 비정상적인 파일을 다운로드 받음
- 파일 다운로드 시 지정된 경로를 벗어날 수 있다면 파일 다운로드 취약점이 존재한다고 볼 수 있다.
2) 공격 원리 분석
어플리케이션 관점에서 공격자가 파일 다운로드 기능에 대해서 파일 다운로드 취약점 공격을 할 때 어떻게 동작하는지 확인
step 1) 공격자로부터 입력값을 받음
- /webroot/upload/ 경로에서 파일 다운로드를 받는 기능이 존재
- 사용자로부터 파일명을 입력 받음, ex) 운동화.png, 북한산풍경.png, 배경1.png
- 공격자는 리눅스 시스템에 등록된 사용자 정보가 담긴 /etc/passwd 파일을 다운로드 하기 위해 ../ 를 사용함
- 경로 이동 문자 ../ 를 사용하여 상위 디렉터리로 이동하게 되며, /etc/passwd 경로의 위치에 상관없이 ../ 를 많이 사용하여 최상단 경로로 도달하면 됨 → 최상단 디렉터리로 이동하기 위해 ../../etc/passwd 를 사용해도 되지만, ../../../etc/passwd 를 사용해도된다.
- 클라이언트 관점에서는 서버 사이드에서 웹 서버 파일의 경로가 어떻게 구성되어 있는지 모르기 때문에, 경로 이동 문자 ../ 를 최상단에 도달할 때까지 사용해도 된다.
step 2) 사용자 입력값과 기존의 경로가 조합됨
- 경로 이동 문자 ../ 가 존재함으로써 상위 경로로 이동하게 됨
step 3) 사용자 입력값 + 기존 경로를 조합한 경로의 파일 입력하여 파일을 호출
- 서버 내 ../../etc/passwd 경로의 파일을 불러옴
step 4) HTTP 응답 메시지 제작 및 전송
- 공격자가 요청한 ../../etc/passwd 파일을 받은 후, HTTP 응답 메시지를 제작하여 공격자에게 전송
파일 다운로드 취약점의 핵심 포인트는 기존 경로의 강제 변조이다.
3) 공격 방법
CASE 1 : 단순히 파일명만 받는 경우
- 소/중규모 웹 서비스일 가능성이 높다
- 리눅스/유닉스의 경우에는 ../../etc/passwd 를 주로 사용하지만, 윈도우는 최상위 디렉터리에 받을것이 없다. (윈도우는 파티션을 분리해놓기 때문에 최상위 디렉터리에 디폴트로 된 것이 없다. - 전체 경로를 받지 않는 이상 파티션 이동이 불가)
- 윈도우는 파일 다운로드 시 지정된 경로가 웹 디렉터리인 경우 index 파일을 받음
- 경로 이동 문자 ../ 를 삽입하여 지정된 경로를 벗어날 수 있음
CASE 2 : 사용자로부터 일부의 경로(path), 파일명(filename)을 입력 받는 경우
- 중/대규모 웹 서비스일 가능성이 높다.
- 파일 경로 세분화, 게시판 분류별로 디렉터리를 분류(notice, image, event 등등) → 다수의 사용자인 경우 용이하게 관리하기 위해 사용
- 일부 경로를 받는 부분에 ../ 문자를 삽입해도 되고, 파일명에 ../ 문자를 삽입해도 된다.
CASE 3 : 전체 경로(Full path)가 나온 경우, 잘못된 예시
- 전체 경로를 표시하는것은 불필요하다. → 공격자에게 큰 힌트가 됨
- 일부 경로만 공개하는 것이 바람직한 설계
- 공격자는 /etc 경로만 입력해도 공격이 가능, ../ 입력하지 않아도 됨
운영 체제별 경로 구분 기호
OS | 구분 기호(Separator) |
Unix, Linux | / |
Windows | / \ (역 슬래시) |
Windows 는 / 와 \ 둘다 가능하므로, 두 가지다 염두에 두고 공격과 방어를 해야 한다.
실습7-1 파일 다운로드 취약점 공격 실습
파일 다운로드 공격 실습을 위해서 파일 다운로드 기능을 먼저 찾아야 한다.
업로드된 게시글을 참조해서 파일 다운로드 기능을 확인해야 한다.
먼저 게시글을 작성하며 임의의 txt 파일을 업로드 한다.
게시글 작성 후, 게시글을 클릭하여 버프스위트에서 Intercept On 클릭 후 파일 다운로드를 시도한다.
파일을 클릭하면 다음과 같이 요청 메시지가 전송된다.
해당 요청을 리피터로 보낸다.( Ctrl + R )
리피터로 요청을 보낸 후 해당 페이지에 파일 다운로드 취약점이 있는지 확인해본다.
무작정 ../ 를 삽입하는것이 아닌, 입력값에 대한 검증이 있는지 먼저 확인해본다.
/ 는 경로를 구분할 수 있는 구분 기호가 된다.
/ 를 먼저 입력 후 / 가 통하는지 요청을 보내본다. ( ctrl + space 또는 상단 좌측의 send 버튼 클릭 )
text_file.txt 내 문자열이 test 인 것이 확인된다. 이를 통해 성공적으로 다운로드 되는 것을 알 수 있다.
다음은 \ 역슬래시를 입력하여 통하는지 확인해본다.
\ 역슬래시를 입력해도 파일이 다운로드 되는 것을 보아 웹 서버가 윈도우라는 것을 알 수 있다.
./ 에 대한 입력감 검증 절차가 있는지 확인해보기 위해 ./ 를 입력하여 요청을 보낸다.
./ 는 현재 경로를 의미한다.
./ 입력을 해도 다운로드가 되는 것을 보아 ./ 에 대한 입력값 검증 절차는 없는 것으로 판단된다.
./ 가 삭제되는지 확인하기위해 파일명 중간에 넣어본다.
./ 가 삭제되지 않았다.
../ 를 입력하여 요청을 보낸다.
../ 는 상위 디렉터리로 이동하는 경로 이동 문자이다.
../ 를 입력하여 요청을 보내니 파일 다운로드가 되지 않았다. (txt 파일 내 test 문자열 확인되지 않음)
./ 를 입력하면 파일 다운로드가 되지만 ../ 를 입력하면 파일 다운로드가 되지 않았다.
현재 웹 사이트는 소규모 웹사이트로 upload 폴더 1개에 test_file.txt 파일이 업로드되어 있다. (업로드 경로 1개)
그러므로 정상적으로 파일을 다운로드 받는 경로는 upload 폴더 외 다른 폴더는 없다고 볼 수 있다.
../ 를 입력하게 되면 upload 폴더가 아닌 더 상위 폴더로 올라가게 되는데, 해당 폴더 내에는 test_file.txt 파일이 존재하지 않으므로 파일이 다운로드 되지 않았던 것이다. 만약 해당 상위 폴더 내 test_file.txt 파일이 존재한다면 다운로드가 가능할 것이다.
여기서 중요한 것은 ./test_file.txt 파일과 ../test_file.txt 파일은 엄연히 다른 파일이다.
(upload/test_file.txt 와 상위 폴더/upload/test_file.txt 차이)
상위 폴더에 올라갔다는 가정하에 특정 파일을 다운로드 받아야하는데 이때 주로 시도해보는 것은 index.php 파일이다.
웹 디렉터리에는 보통 index.php 파일이 존재하므로 상위 디렉터리가 웹 디렉터리임을 가정하고 index.php 파일 다운로드를 시도해본다.
../index.php 를 입력하여 send 요청을 보낸다.
응답값을 통해 index.php 파일을 성공적으로 다운로드 받는 것을 확인할 수 있다.
이를 통해 업로드 디렉터리(upload)의 상위 디렉터리가 웹 디렉터리임을 알 수 있다.
다운로드 관점에서는 소스코드를 다운로드 받을 수 있고, 업로드 관점에서는 웹쉘 업로드 여부를 파악할 수 있기 때문에 웹 디렉터리를 파악하는 것이 중요하다.
웹 아키텍처 구조상 클라이언트 - 서버로 이루어져 있는데, 클라이언트에서는 서버 사이드 스크립트를 확인할 수 없지만 파일 다운로드 취약점을 통해서 index.php 를 다운로드 받아 서버 사이드 스크립트로 작성된 코드를 확인할 수 있게 되었다.
common.php 도 다운로드를 시도해본다.
../common.php 를 입력하여 send 요청을 보낸다.
위와 같이 common.php 도 정상적으로 다운로드되는 것을 확인할 수 있다.
이를 통해 데이터베이스 접속 정보, ID와 PW, DB 정보도 획득할 수 있게 되었다.
이렇게 파일 다운로드 취약점이 존재하는 경우에는 공격자는 어플리케이션에 있는 코드를 모두 확인할 수 있어 추가 취약점을 찾기에도 용이하다. (소스코드 기반 진단 가능)
또한 파일 업로드 취약점과 연계가 되기 때문에 파일 업로드할때 어떤 로직을 통해 업로드 되는지 확인이 가능하여 웹쉘을 업로드 할 수 있게 된다.
실무에서 취약점 진단 시 파일 다운로드 취약점이 존재하는 경우 파급력이 굉장히 크다.
(서버 내 여러 설정 파일 및 소스코드 확인이 가능)
common.php 파일을 확인해보니 웹 서버의 업로드 경로를 확인할 수 있다.
upload 폴더가 해당 웹 사이트의 파일 업로드 경로라는 것을 알 수 있다.
웹 서버의 업로드 디렉터리를 확인했으니 ../upload/test_file.txt 를 입력하여 파일 다운로드가 되는지 확인해본다.
text_file.txt 파일 다운로드가 확인되며, 파일 내 test 문자열이 확인되었다.
이를 통해 upload 폴더가 웹 서버의 업로드 디렉터리임을 확실히 알 수 있게되었다.
실습7-2 개발자분들이 자주 실수하는 잘못된 대응 방안 적용
1) ../ 제거
2) ./ , ../ 제거
3) 윈도우 환경에서 / 문자 제거
4) 윈도우 환경에서 / 문자 필터링, ./ ../
../ 문자열 입력값 검증에 대한 치환(제거)에 대한 실수
→ ../ 문자열을 제거하게되면 우회가 발생할 수 있다.
에러 발생시켜서 경고창 출력 및 로직 중단
→ 이상적인 대응 방안
파일 다운로드 기능이 있는 코드 download.php 를 확인해본다.
위치: C:\APM_Setup\htdocs\insecure_website
<?
include_once("./common.php");
header("Content-Type: text/html; charset=UTF-8");
$file = $_GET["file"];
if(empty($file)) {
echo "<script>alert('값이 입력되지 않았습니다.');history.back(-1);</script>";
exit();
}
if(empty($file)) {
echo "<script>alert('파일 다운로드에 실패하였습니다.');history.back(-1);</script>";
exit();
}
$filepath = "{$upload_path}/{$file}";
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename={$file}");
@readfile($filepath);
?>
보통 파일명을 받을 때, original file name 과 real file name 2개를 받도록 로직을 설계 한다.
original file 은 header 에 위치하게 되고, real file 은 실제 서버에 업로드된 파일이다.
그러므로 실제 경로조작을 할 때는 real file 을 건드려야 한다.
이제 ../ 와 ..\ 문자열에 대한 치환(제거) 코드를 추가해본다.
<?
include_once("./common.php");
header("Content-Type: text/html; charset=UTF-8");
$file = $_GET["file"];
if(empty($file)) {
echo "<script>alert('값이 입력되지 않았습니다.');history.back(-1);</script>";
exit();
}
$file = str_replace("../", "", $file);
$file = str_replace("..\\", "", $file);
if(empty($file)) {
echo "<script>alert('파일 다운로드에 실패하였습니다.');history.back(-1);</script>";
exit();
}
$filepath = "{$upload_path}/{$file}";
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename={$file}");
@readfile($filepath);
?>
insecure_website 에서 파일 다운로드를 시도한다.
버프스위트를 켜고 Intercept On 후 text_file.txt 파일을 클릭한다.
이후 패킷이 잡히면 리피터로 전송한다. ( Ctrl + R )
Send 를 클릭 또는 Ctrl + space 를 입력하여 test_file.txt 다운로드에 대한 요청을 전송한다.
기본적인 test_file.txt 는 정상적으로 다운로드 된다. ( test 문자열 확인 )
../ 문자열을 추가하여 다운로드 요청을 보낸다.
../ 문자열은 제거가 되고 test_file.txt 파일이 다운로드 되는 것을 알 수 있다.
원래는 다운로드가 되면 안되지만, ../ 문자열을 제거하니까 test_file.txt 원본 파일이름과 동일하게 되어 다운로드 되는 것을 알 수 있다.
이번에는 파일 중간에 ../ 를 입력하여 파일 다운로드 요청을 전송한다.
../ 문자열이 제거되어 test_file.txt 파일이 다운로드 되었다.
..\ 문자열을 파일 중간에 입력하여 파일 다운로드 요청을 전송한다.
..\ 역시 제거 되어 test_file.txt 파일이 다운로드 되는 것을 확인할 수 있다.
이를 통해 ../ 와 ..\ 문자열 모두 제거되는 것을 알 수 있다.
그렇다면 어떻게 우회하여 상위 디렉터리로 접근해서 index.php 파일을 다운로드 할 수 있을까?
../ 문자열은 제거가 되니까, ../ 이 제거되도 ../ 이 남도록 입력해본다.
..././index.php 를 입력
→ ..././ 에서 ../ 이 제거되도, . ./ 문자열이 합쳐져서 ../ 이 되므로 상위 디렉터리로 이동하는 경로 이동 문자가 된다.
또는 ....// 를 입력해도 우회가 가능하다.
....//index.php 를 입력
→ ....// 에서 ../ 이 제거되도, .. / 문자열이 합쳐져서 ../ 이 되므로 상위 디렉터리로 이동하는 경로 이동 문자가 된다.
위와 동일하게 ..\ 문자열도 적용할 수 있다.
...\.\index.php 를 입력
→ ...\.\ 에서 ..\ 이 제거되도, . .\ 문자열이 합쳐져서 ..\ 이 되므로 상위 디렉터리로 이동하는 경로 이동 문자가 된다.
....\\index.php 를 입력
→ ....\\ 에서 ..\ 이 제거되도, .. \ 문자열이 합쳐져서 ..\ 이 되므로 상위 디렉터리로 이동하는 경로 이동 문자가 된다.
이번에는 download.php 에 ./ 와 .\ 를 제거하는 코드를 추가해본다.
<?
include_once("./common.php");
header("Content-Type: text/html; charset=UTF-8");
$file = $_GET["file"];
if(empty($file)) {
echo "<script>alert('값이 입력되지 않았습니다.');history.back(-1);</script>";
exit();
}
$file = str_replace("./", "", $file);
$file = str_replace(".\\", "", $file);
$file = str_replace("../", "", $file);
$file = str_replace("..\\", "", $file);
if(empty($file)) {
echo "<script>alert('파일 다운로드에 실패하였습니다.');history.back(-1);</script>";
exit();
}
$filepath = "{$upload_path}/{$file}";
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename={$file}");
@readfile($filepath);
?>
이후 다시 기존 방법으로 다운로드를 시도하면 파일 다운로드가 되지 않는다.
./ 와 ../ 모두 제거가 되기 때문에
.....///index.php 를 입력하면 우회가 가능하다.
→ ...../// 에서 ./ 가 제거되어 ....// 가 되고, ../ 가 제거 되어 마지막에 ../ 가 된다.
/ 를 제거하는 코드를 추가한다.
<?
include_once("./common.php");
header("Content-Type: text/html; charset=UTF-8");
$file = $_GET["file"];
if(empty($file)) {
echo "<script>alert('값이 입력되지 않았습니다.');history.back(-1);</script>";
exit();
}
$file = str_replace("/", "", $file);
if(empty($file)) {
echo "<script>alert('파일 다운로드에 실패하였습니다.');history.back(-1);</script>";
exit();
}
$filepath = "{$upload_path}/{$file}";
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename={$file}");
@readfile($filepath);
?>
/ 를 제거하는 경우, 리눅스에서는 ../ 상위 문자열 이동이 어렵지만, 윈도우에서는 \ 역슬래시가 있어 가능하다.
..\index.php 를 입력하여 우회가 가능하다.
4) 대응 방안
전체 경로 + 파일명 → 사용자 입력 값을 토대로 어플리케이션에서 직접 파일시스템에 접근
일부 경로 + 파일명 → 일반적인 경우(자주 사용하는 방법), 어플리케이션에서 직접 파일시스템에 접근
파일명 → 소규모 웹 사이트에 적합, 어플리케이션에서 직접 파일시스템에 접근
키(Key) 값 → 간접적인 다운로드 방법, 사용자 입력값 받은 후 DB에 질의하여 idx=192 에 대한 파일명 반환받음
전체 경로 + 파일명
예전에는 많이 있었으나, 최근에는 거의 없음
→ 시스템 전체 경로가 사용자에게 노출되어 보안상 취약한 설계
일부 경로 + 파일명
오늘날 웹에서 가장 많이 사용하는 파일 다운로드 기능의 설계 방식
→ 경로와 파일명 2개의 파라미터에 대해서 올바른 검증 절차가 구현되어야 함
검증 방법
→ 2개의 파라미터에 경로 이동 문자 ../ , ..\ 등 이 존재하는 경우 에러 메시지 출력 및 로직 종료
\ 역슬러시를 / 슬러시 문자로 치환하는 이유
→ 윈도우는 \ 역슬래시도 사용하기 때문에, 검증할때 굳이 \ 를 추가로 넣지않고 / 로 통일하기 위해
파일명
파일명 파라미터에 대해서만 / 와 .. 문자에 대해서만 검증한다.
윈도우 서버인 경우에는 \ 역슬래시를 / 슬래시로 치환 후 / 슬래시로만 통일하여 검증
키(Key) 값
키(Key) 값을 받는다고 해서 무조건 안전한건 아니다.
PrepareStatement 가 아닌 Statement 인 경우에는 SQL Injection 에 취약하다.
SQL Injection 을 통해서 파일 다운로드 취약점으로 연계시킬 수 있는 공격이 가능하다.
실습7-4 취약 환경 시큐어 코딩 적용 실습
파일 다운로드 취약점에 대한 시큐어 코딩을 적용하기 위해 download.php 파일을 연다.
위치: C:\APM_Setup\htdocs\insecure_website
strpos 함수
- 지정된 변수에 지정된 문자가 있는지 위치값을 반환해준다.
- 대상 문자열을 앞에서 부터 검색하여 찾고자 하는 문자열이 몇번째 위치에 있는지를 리턴하는 함수
- 문자가 없으면 False 가 출력됨
< download.php 에 시큐어 코딩 적용 >
<?
include_once("./common.php");
header("Content-Type: text/html; charset=UTF-8");
$file = $_GET["file"];
if(empty($file)) {
echo "<script>alert('값이 입력되지 않았습니다.');history.back(-1);</script>";
exit();
}
$file = str_replace("\\", "/", $file);
if(strpos($file, "/") !== false || strpos($file, "..") !== false ) {
echo "<script>alert('허용되지 않은 문자가 입력되었습니다.');history.back(-1);</script>";
exit();
}
if(empty($file)) {
echo "<script>alert('파일 다운로드에 실패하였습니다.');history.back(-1);</script>";
exit();
}
$filepath = "{$upload_path}/{$file}";
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename={$file}");
@readfile($filepath);
?>
\ 역슬래시는 / 슬래시로 치환하여 / 슬래시로 통일하여 검증한다.
여기서는 파일명만 노출되어 있기 때문에 / 문자열만 필터링해도 되지만, 일반적인 파일 다운로드 기능에는 일부 경로 + 파일명이 되어 있는 경우가 있으므로 .. 문자열도 같이 필터링해준다.
위치값 반환 시 0이 출력되는 경우가 있는데 0은 첫번째 인덱스라는 의미가 있지만, php 에서는 0은 False 의 의미를 가지고 있으므로, != 가 아닌 !== 를 사용하여 0인 위치값이 반환되도 False 로 인식하지 않게 해준다.
시큐어 코딩 적용 후 파일 다운로드 기능에 대해 다시 공격을 시도해본다.
..\index.php 를 입력하여 상위 디렉터리의 index.php 다운로드를 시도한다.
/ , \ , .. 를 입력해본다.
원래 다운로드 가능한 file_test.txt 파일은 다운로드가 가능하다. (파일 다운로드 기능 정상 동작)
이를 통해 파일 다운로드 기능은 정상적으로 동작하면서 파일 다운로드 취약점에 대한 시큐어 코딩이 완료된 것을 확인할 수 있다.
참고
'웹 해킹 > 웹 해킹 및 시큐어 코딩 기초' 카테고리의 다른 글
파라미터 변조 취약점 (0) | 2025.02.26 |
---|---|
파일 업로드 취약점 (0) | 2025.02.25 |
CSRF(Cross-Site Request Forgery) (0) | 2025.02.08 |
XSS (Cross-Site Scripting) (0) | 2025.01.24 |
XXE Injection (2) | 2025.01.04 |