PHP - 파일 업로드 보안 : 다중 확장자 취약점
항상 이슈가 되는 건 파일 업로드 취약점을 이용하는 공격입니다.
다중 파일 확장자 취약점
아파치 문서에 나와 있는 내용으로 지시어 extension 부분을 보면 아래와 같이 설명하고 있습니다. (출처: https://httpd.apache.org/docs/2.4/ko/mod/directive-dict.html)
일반적으로 filename에서 마지막 마침표 뒤에 나오는 부분이다. 그러나 아파치는 여러 확장자를 인식할 수 있기 때문에, filename에 마침표가 여러 개 포함된 경우 마침표로 구분된 모든 부분을 확장자(extension)로 처리한다. 예를 들어, 파일명 file.html.en은 .html과 .en이라는 두 가지 확장자를 가진다. 아파치 지시어에서 extension에 지정한 값 앞에 마침표가 있어도 되고 없어도 된다. 또, extension은 대소문자를 가리지 않는다.
일반적으로 habony.txt.php.shtml.en.html 인 파일은 html 로 인식해야 합니다. 하지만 이건 착각입니다. 아파치는 여러 확장자를 지원하므로 위 설명대로라면 마침표를 분리해 보면 .txt, .php, .shtml, .en, .html 확장자가 나오고 실행 가능한 마임 타입(mime-type)만 4개가 나옵니다.
확장자가 어디에 위치하든 .php 확장자를 포함하고 있으면 php 핸들러가 php로 해석하고 스크립트를 실행한다는 설명입니다. 아래 코드를 작성한 다음 파일 이름을 index.php.ko.kr 로 저장해서 실행해 봅시다.
<?php
phpinfo();
?>
<?php
$filename = 'habony.pHp.HelloWorld.shtml';
$arr = array('php', 'phps', 'php3', 'php4', 'php5', 'php7', 'pht', 'phtml');
$exts = explode('.', strtolower($filename));
foreach($arr as $val)
{
if(in_array($val, $exts))
{
echo '<b>' . $val . '</b> 는 업로드 할 수 없습니다.';
exit;
}
}
if(move_uploaded_file($_FILES['fname']['tmp_name'], $filename))
{
echo "파일 업로드 성공!";
}
?>
확장자를 변경하지 않고 저장하는 코드이면 .php만 막을게 아니라 .htaccess, .html, .htm 파일도 실행되지 않게 막아야 합니다.
<?php
$arr = array('php', 'phps', 'php3', 'php4', 'php5', 'php7', 'pht', 'phtml', ‘htaccess’, ‘html’, ‘htm’, 'inc');
?>
이미지 업로드 변조
<?php
$imagesizedata = getimagesize($file);
if ($imagesizedata ) {
// do something
}
?>
PHP 문서는 주어진 파일이 유효한 이미지인지 확인하기 위해 getimagesize() 를 사용하지 말라고 권고합니다. 이뿐만이 아니라 info_file(), mime_content_type(), exif_imagetype() 함수도 올바로 검증하지 못합니다.
다음 내용을 myimage.gif 로 저장한 다음 getimagesize() 로 유효성을 검증하면 image/gif 를 반환할 것입니다.
GIF89a<
<?php
echo "Hello Habony";
?>
결국 이미지 내용의 앞머리만 검증하는 함수로는 우리를 계속 속일 것입니다. 가장 안전한 방법은 imagecreate() 함수를 이용하는 것입니다. 이 함수를 이용해 이미지 생성에 실패하면 false 를 반환합니다.
<?php
function chkImageFile($filename)
{
$isimage = null;
$chk = 'true';
$imginfo = @getimagesize($filename);
switch( $imginfo['mime'] )
{
case 'image/gif':
if(!$isimage = @imagecreatefromgif($filename))
$chk = 'Not an gif';
break;
case 'image/jpeg':
if(!$isimage = @imagecreatefromjpeg($filename))
$chk = 'Not an jpg';
break;
case 'image/png':
if(!$isimage = @imagecreatefrompng($filename))
$chk = 'Not an png';
break;
case 'image/bmp':
if(!$isimage = @imagecreatefromwbmp($filename))
$chk = 'Not an bmp';
break;
default:
$chk = "no Image";
}
return $chk;
}
$filename = "pacman.php.png";
if(chkImageFile($filename) === "true")
{
if(move_uploaded_file($_FILES['fname']['tmp_name'], $filename))
{
echo "파일 업로드 성공!";
}
}
?>
웹 공격이 가능하려면 ‘<’와 ‘>’ 같은 문자가 있어야만 작성할 수 있는 스크립트 언어이기 때문에 파일을 이동하기 전에 파일 이름을 한 번 더 검증하는 것이 좋습니다.
<?php
$filename = "my<image>.gif";
// ex.1)
move_uploaded_file($_FILES['fname']['tmp_name'], htmlentities($filename));
// ex.2)
if(strlen($filename) === strcspn($filename, "\0\/:;*?\"'<>|"))
{
move_uploaded_file($_FILES['fname']['tmp_name'], $filename);
}
?>
약간 응용해서 이미지 파일이면 확장자 변경 없이 그대로 저장하고, 나머지 파일은 모두 md5() 로 저장하는 것입니다.
<?php
if(strlen($file) === strcspn($file, "\0\/:;*?\"'<>|"))
{
if(chkImageFile($file) === "true")
{
// 이미지는 그대로 저장합니다.
move_uploaded_file($_FILES['fname']['tmp_name'], 'data/' . $file);
echo "이미지를 data 에 저장하였습니다.";
}else{
// 나머지 파일은 md5 로 저장합니다.
move_uploaded_file($_FILES['fname']['tmp_name'], 'upload/' . md5($file));
echo "파일을 upload 에 저장하였습니다.";
}
}else{
echo "파일 이름에 문제가 있습니다.";
}
?>
0 댓글