본문 바로가기

개발/php

PHP에서 업로드한 파일이 이미지인가 아닌가 체크

제시된 문제는 썸네일 이미지를 업로드하는 부분에서 이미지 이외의 이상한 파일 업로드를 막아 달라는 것이었다. PHP 매뉴얼 사이트에서도 경고 했던 내용이고 하니, 많은 사람들이 알고 있겠지만, 정리 차원에서 기록해 둠.

보통 HTML에서 업로드를 하면 아래와 같은 정보가 함께 전달된다.

array (
    'name' => '235_thumb.jpg',
    'type' => 'application/octet-stream',
    'tmp_name' => '/tmp/phpthc4Yi',
    'error' => 0,
    'size' => 3187,
  ),


업로드시 사용된 파일이름, 파일의 mime타입, 크기 그리고 서버에 임시로 저장하기 위해 사용된 파일이름 등이 있다.
type 정보를 이용하면 이미지인지 아닌지 구별을 할 수 있지 않을까라고 생각 할 수 있겠지만, 여기에 헛점이 존재한다.

테스트 삼아 워드 문서 파일의 확장자를 jpg등으로 설정한 후 실행시켜 보면, 파일은 워드 문서인데데 type이 "image/jpeg"등으로 설정 되어 있을거다. 아니면 위에 있는 것처럼 "application/octet-stream"으로 설정해 버릴 수도 있다. 따라서 업로드 시에 제공된 정보로는 파일의 타입을 신뢰 할 수 없다. 이 정보를 신뢰 했다가는 업로드 된 파일에 의해 안좋은 일이 벌어질 수도 있다.

그러면 방법은 임시로 저장된 파일을 직접 읽어 들여서 판단하는 것이 가장 안전한 방법일 것이다. PHP에 그런 작업을 해 주는 함수가 하나 존재한다. getimagesize라는 넘인데, 파일의 크기와 세로/가로 크기를 읽어서 알려준다. 덤으로 이미지 타입도 반환해 주는 함수다. 영문 사이트

함수의 결과는 배열로 반환되는데, 아래와 같이 반환된다.

array
(
    [0] => 60
    [1] => 60
    [2] => 2
    [3] => width="60" height="60"
    [bits] => 8
    [channels] => 3
    [mime] => image/jpeg
)



배열 값에서 [0],[1]은 이미지 파일의 가로/세로 값이다. [2]의 값이 어떤 종류의 이미지인지 알려준다. 매뉴얼 페이지를 아래로 내리다 보면 이미지 종류에 대한 정수값이 정의된 샘플 소스를 볼 수 있다. 위의 결과를 보면 "jpeg"임을 알 수 있다. 물론 [mime]을 이용해도 된다. 

$types = array(
        1 => 'GIF',
        2 => 'JPG',
        3 => 'PNG',
        4 => 'SWF',
        5 => 'PSD',
        6 => 'BMP',
        7 => 'TIFF(intel byte order)',
        8 => 'TIFF(motorola byte order)',
        9 => 'JPC',
        10 => 'JP2',
        11 => 'JPX',
        12 => 'JB2',
        13 => 'SWC',
        14 => 'IFF',
        15 => 'WBMP',
        16 => 'XBM'
    );



오늘 작성한 코드의 일부다.


$uploaded_file = $_FILES["thumbnail"]["tmp_name"];
$this->logger->debug("check image type : ".$uploaded_file);
$ret = getimagesize($uploaded_file, $extinfo);
list($image_width, $image_height, $image_type, $image_attr, $image_mime) = $ret;

if($image_type == 2) {
    // 원하는 작업을 했다.
}else {
    // 오류다.
}


그런데, getimagesize를 어떻게 믿냐고 한다면...음...방법이 없을까? 
다른 방법은 없는지 찾아 봤다. 모 딱히 없었다. 다만, 다른 언어에서도 사용할 수 있는 아주 쉬운 방법이 있어 소개를 할 까 한다.(난 사용하지 않았다.) 어떤 방법이냐 하면, 실제 파일의 앞부분을 읽어 분석 하는 것이다. 이건 개발 언어와는 상관 없는 방법이다. 문제는 원하는 타입을 구별하기 위해선 해당 파일의 구별자를 모두 알아서 넣어야 한다는 정도 있겠다.

PHP에서 jpg/gif/png를 구별하기 위해 적용한 코드는 아래와 같다. 
jpg는 0xffd8로 시작되고, PNG는 0x89PNG라는 문자열로 시작한다.


$fp = fopen($_FILES["thumbnail"]["tmp_name"], "r");
$image_stream = fread($fp, 64);
if ( preg_match( '/^\x89PNG\x0d\x0a\x1a\x0a/', $image_stream) )  {
    $type = "png";
} elseif ( preg_match( '/^GIF8[79]a/', $image_stream) )  {
    $type = "gif";
} elseif ( preg_match( '/^\xff\xd8/', $image_stream) )  {
    $type = "jpg";
}
fclose($fp);


각 파일의 구별자를 알아 내는 것이야 구글링 하면 쉽게 찾을 수 있다. 
참고로 OS에 따라서는 헤더 정보를 파일로 갖고 있는 경우도 있다. 
윈도우는 레지스트리에 정보가 있을 듯 하지만, 확인을 안해 봐서 모르겠고, 리눅스나 맥OSX등은 시스템에 정보를 magic이라는 파일에 보관하고 있다. 맥OSX는 "/usr/share/file/magic"을 참고하면 된다.