1. Upload 취약점
파일 업로드 기능이 제공되는 경우, 파일의 크기와 개수, 종류들 제한하지 않고, 외부에서 접근할 수 있는 경로에 업로드 파일을 저장하는 경우 발생
1) 가이드
- 파일 크기와 개수를 제한하지 않는 경우
- 서버의 연결 및 디스크 자원을 고갈시켜 정상적인 서비스를 방해
- 종류를 제한하지 않는 경우
- 바이러스와 같은 파일을 업로드해서 해당 서버를 악성코드 유포지로 활용
- 서버에서 실행할 수 있는 파일을 업로드해서 실행
- 웹쉘(WebShell)
- 외부에서 접근할 수 있는 경로에 업로드 파일을 저장하는 경우
- URL을 통해서 접근할 수 있는 경로에 위치
- Web Document Root 아래에 위치
2) 파일 업로드 기능 확인 방법
form 태그 설명
<form enctype="multipart/form-data" method="post" ... >
<input type="file" ... >
</form>
위 코드가 파일 업로드에 사용되는 코드다.
input type에 파일이 들어가면 enctype에는 multipart/form-data가 들어가게 되어 있다.
3) 방어 기법
- 업로드 파일의 크기와 개수를 제한
- 설계 시 제공하는 서비스에 맞는 적절한 크기와 개수를 정의하는 것이 필요
- 파일의 종류를 제한
- 업로드 가능한 파일의 종류를 미리 정의하고 정의된 범위 내에서만 업로드 허용
- 화이트 리스트(허용 목록) 방식으로 제한
- 파일 종류를 비교하는 방법
- 확장자 - 파일명이 어떻게 끝나는가로 판단
- Content--Type
- File Signature - Magic Number
- 업로드 파일을 외부에서 접근할 수 없는 경로에 저장
- Web Root(document) 밖
- 업로드 파일의 이름을 외부에서 알 수 없는 형태로 변경해서 알 수 없는 경로에 저장
- 업로드 파일의 실행 속성을 제거하고 저장
2. 이미지 업로드 실습
1) 이미지 업로드
here에 마우스를 올리면 위와 같은 주소가 나온다. 이 주소는 외부에서 접근할 수 있는 경로에 업로드 파일명과 동일한 이름으로 저장하고 있는 것을 볼 수 있다.
이 페이지는 php로 만들어진 페이지로 동작하고 있는데 이때 서버에서 실행할 수 있는 파일을 업로드 해본다.
2) 실행할 수 있는 파일 업로드
PHP로 만들어진 웹쉘 업로드
PHP로 만들어진 웹쉘은 kali에서 제공하는데 /usr/share/webshells/php 경로에 simple-backdoor.php 파일이다.
이 파일을 업로드 페이지에 업로드 하게되면 위와 같이 경로 주소가 나오게 된다.
위에 나온 주소로 이동하게 되면 주소 뒤에 ?cmd=cat /cat/passwd를 넣으라는 사용법이 나온다.
사용법에 따라 주소를 추가로 넣게 되면 passwd의 내용이 나오는 것을 볼 수 있다.
3) 취약한 소스 코드 확인
sudo gedit /var/www/bWAPP/unrestricted_file_upload.php
코드가 길어 내용이 길어지기 때문에 코드를 간략하게 본다. 위 파일에 들어가면 코드 원본이 있으므로 참고한다.
switch($_COOKIE["security_level"])
{
case "0" :
move_uploaded_file($_FILES["file"]["tmp_name"], "images/" . $_FILES["file"]["name"]);
break;
}
이 코드는 업로드 파일을 현재 디렉터리의 images 아래에 업로드 파일명으로 저장하는 코드다.
그렇기 때문에 위 실습에서 외부에서 접근할 수 있는 주소로 나오게 된 것이다.
(1) 보안 등급이 중간인 경우
file_upload_check_1 함수
function file_upload_check_1($file, $file_extensions = array("asp", "aspx", "dll", "exe", "jsp", "php"), $directory = "images")
{
$file_array = explode(".", $file["name"]);
// Puts the last part of the array (= the file extension) in a new variabele
// Converts the characters to lower case
$file_extension = strtolower($file_array[count($file_array) - 1]);
// Searches if the file extension exists in the 'allowed' file extensions array
if(in_array($file_extension, $file_extensions))
{
$file_error = "Sorry, the file extension is not allowed. The following extensions are blocked: <b>" . join(", ", $file_extensions) . "</b>";
return $file_error;
}
// Checks if the file already exists in the directory
if(is_file("$directory/" . $file["name"]))
{
$file_error = "Sorry, the file already exists. Please rename the file...";
}
return $file_error;
}
file_upload_check_1 함수 코드인데, 중간에 많은 내용이 생략되었고 필요한 부분만 잘라서 가져온 것이기 때문에, 파일에 있는 함수를 참고하기를 바란다.
file_extensions로 허용되지 않는(블랙 리스트) 파일 확장자를 정의하고 있다.
- file_array = explode
- 확장자 검증을 위해서 .을 기준으로 분리
- file_extension = ...
- 확장자를 소문자로 변환
- if(in_array(...))
- 제한 목록에 포함된 경우 오류 처리
- if(is_file(...))
- 동일한 파일이 존재하는지 검증
(2) 확장자 변경
위 코드를 확인했을 때 블랙 리스트로 정의가 되어 있었다. 블랙 리스트에는 PHP3가 정의되어 있지 않기 때문에 PHP3로 확장자를 변경해서 업로드 해볼 수 있다.
보안 등급을 중간으로 올린 후 업로드 페이지로 들어간다.
보안 등급이 중간단계인 경우 php 파일을 업로드 하려고 하면 블랙 리스트에 정의 되어 있어 오류가 발생한다.
그러나 위에서 말했듯이 확장자를 php3으로 바꿔 업로드 하면 업로드가 되는 것을 볼 수 있다.
블랙 리스트로 정의했을 때 발생할 수 있는 보안 사고를 보여주는 예제이다.
(3) 보안 등급이 가장 높은 경우
function file_upload_check_2($file, $file_extensions = array("jpeg", "jpg", "png", "gif"), $directory = "images")
{
// Searches if the file extension exists in the 'allowed' file extensions array
if(!in_array($file_extension, $file_extensions))
{
$file_error = "Sorry, the file extension is not allowed. Only the following extensions are allowed: <b>" . join(", ", $file_extensions) . "</b>";
return $file_error;
}
이 코드도 많은 생략이 되었으므로 파일에서 코드를 찾아보기 바란다.
- file_extensions
- 화이트 리스트 방식으로 확장자 체한
- if(!in_array(...))
- 화이트 리스트에 포함되지 않은 확장자인 경우 오류 처리
4) 웹 루트 밖 저장
업로드 파일이 /data/images/myimage.gif 형태로 저장되어 있다고 가정한다.
이때 data 앞에 있는 / 이 디렉터리는 해당 서버의 루트 디렉터리를 의미한다.
그렇다면 아래와 같이 접근이 불가다.
<img src="/data/images/myimage.gif">
<a href="/data/images/myimage.gif" >
왜냐하면 위 코드의 data 앞에 있는 / 이 디렉터리는 웹 루트 디렉터리를 의미하기 때문이다.
업로드 파일에 접근하려면 다운로드 기능을 추가해야 한다.
<img src="download?file=/data/images/myimage.gif">
<a href="download?file=/data/images/myimage.gif">
위 코드처럼 두 가지의 방법을 사용할 수 있다. download는 요청 파라미터로 전달된 경로의 파일을 읽어서 응답으로 반환한다. 이때 되도록 업로드 파일이 저장된 경로는 외부에 노출되지 않는 것이 안전하다.
<img src="download?file=myimage.gif">
<a href="download?file=myimage.gif">
여기서 download는 요청 파라미터로 전달된 파일을 해당 서버의 /data/images 디렉터리에서 읽어서 응답으로 반환한다.
경로는 서버 내부에 숨겨 놓고 파일명만 받는 것이 안전하다.
(1) 다운로드 기능 유의 사항
- 지정된 경로를 벗어나서 파일을 읽지 못하도록 제한
- 경로 조작 취약점(path traversal)에 노출
download?file=myimage.gif 로 만들면 좋다고 했다. 하지만 서버 내부에서 file의 경로를 검증하지 않고 그냥 사용하면 지정된 경로를 조작할 수 있는 문자열을 넣을 수 있게 된다.
download?file=../../../../etc/passwd 로 상위 디렉터리로 이동할 수 있는 명령어를 사용해서 루트 디렉터리로 이동해 시스템 파일에 접근할 수 있게 된다.
(2) 취약한 코드 수정
case "2" :
$file_error = file_upload_check_2($_FILES["file"], array("jpg","png"));
if(!$file_error)
{
move_uploaded_file($_FILES["file"]["tmp_name"], "/data/images/" . $_FILES["file"]["name"]);
// 외부에서 접근할 수 없는 경로에 파일을 저장하도록 수정
}
break;
if(isset($_POST["form"]))
{
if(!$file_error)
{
echo "The image has been uploaded <a href=\"/data/images/" . $_FILES["file"]["name"] . "\" target=\"_blank\">here</a>.";
echo "<img src=\"/data/images/" . $_FILES["file"]["name"] . "\">";
} // 업로드된 파일에 대한 링크(<a>)와 출력(<img>)을 저장된 경로로 수정
// 주소를 통해서 접근할 수 없으므로 링크도 출력도 되지 않음
(3) Bee box 가상머신에 파일 저장을 위한 디렉터리 생성
sudo mkdir -p /data/images
ls /data/images
sudo chown root:www-data /data/images
sudo chmod 777 /data/images
(4) Kali에서 이미지 업로드
웹 루트 디렉터리 아래에 존재하지 않는 디렉터리와 파일을 참조하므로 링크도 동작하지 않고 이미지도 나타나지 않는다.
(5) 다운로드 기능 추가
sudo gedit /var/www/bWAPP/download.php
<?php
$target_Dir = "/data/images/";
$file = $_GET['file'];
$down = $target_Dir.$file;
$filesize = filesize($down);
if(file_exists($down)){
header("Content-Type:application/octet-stream");
header("Content-Disposition:attachment;filename=$file");
header("Content-Transfer-Encoding:binary");
header("Content-Length:".filesize($target_Dir.$file));
header("Cache-Control:cache,must-revalidate");
header("Pragma:no-cache");
header("Expires:0");
if(is_file($down)){
$fp = fopen($down,"r");
while(!feof($fp)){
$buf = fread($fp,8096);
$read = strlen($buf);
print($buf);
flush();
}
fclose($fp);
}
} else{
?><script>alert("존재하지 않는 파일입니다.")</script><?
}
?>
(6) 링크와 이미지 출력 반영
if(isset($_POST["form"]))
{
if(!$file_error)
{
/*
echo "The image has been uploaded <a href=\"/data/images/" . $_FILES["file"]["name"] . "\" target=\"_blank\">here</a>.";
echo "<img src=\"/data/images/" . $_FILES["file"]["name"] . "\">";
*/
echo "The image has been uploaded <a href=\"download.php?file=" . $_FILES["file"]["name"] . "\" target=\"_blank\">here</a>.";
echo "<img src=\"download.php?file=" . $_FILES["file"]["name"] . "\">";
}
else
{
echo "<font color=\"red\">" . $file_error . "</font>";
}
}
이미지와 링크가 출력되는 것을 볼 수 있다.
댓글