getimagesize 함수는 GD 확장의 일부가 아닙니다. 이 함수는 표준 설치된 PHP에서 사용할 수 있습니다. 먼저 이 함수에 대한 문서 설명을 살펴보세요: http://php.net/manual/zh/function.getimagesize.php
지정된 파일이 유효한 이미지가 아닌 경우 false가 됩니다. 반환되고 데이터가 반환됩니다. 문서 유형을 나타내는 필드도 있습니다. 파일의 크기를 알아내는 데 사용하지 않고 업로드된 파일이 이미지 파일인지 확인하는 데 사용한다면 매우 좋은 해결책인 것 같습니다. 물론 이는 가능한 경고를 차단해야 합니다. 코드는 다음과 같이 작성됩니다.
<?php $filesize = @getimagesize('/path/to/image.png'); if ($filesize) { do_upload(); } # 另外需要注意的是,你不可以像下面这样写: # if ($filesize[2] == 0) # 因为 $filesize[2] 可能是 1 到 16 之间的整数,但却绝对不对是0。
그런데 그런 검증만 한다면 불행하게도 코드에 웹셸 취약점을 성공적으로 심은 것입니다.
이 문제를 분석하기 위해 먼저 이 함수의 프로토타입을 살펴보겠습니다.
static void php_getimagesize_from_stream(php_stream *stream, zval **info, INTERNAL_FUNCTION_PARAMETERS) { ... itype = php_getimagetype(stream, NULL TSRMLS_CC); switch( itype) { ... } ... } static void php_getimagesize_from_any(INTERNAL_FUNCTION_PARAMETERS, int mode) { ... php_getimagesize_from_stream(stream, info, INTERNAL_FUNCTION_PARAM_PASSTHRU); php_stream_close(stream); } PHP_FUNCTION(getimagesize) { php_getimagesize_from_any(INTERNAL_FUNCTION_PARAM_PASSTHRU, FROM_PATH); }
이제 위 코드에서 두 가지 세부 사항이 숨겨져 있음을 알 수 있습니다. :
최종 처리 함수는 php_getimagesize_from_stream
파일 유형을 결정하는 함수는 php_getimagetype입니다
php_getimagetype 구현을 살펴보겠습니다.
PHPAPI int php_getimagetype(php_stream * stream, char *filetype TSRMLS_DC) { ... if (!memcmp(filetype, php_sig_gif, 3)) { return IMAGE_FILETYPE_GIF; } else if (!memcmp(filetype, php_sig_jpg, 3)) { return IMAGE_FILETYPE_JPEG; } else if (!memcmp(filetype, php_sig_png, 3)) { ... } }
일부 세부 사항이 제거되었습니다. php_sig_gif php_sig_png 등은 파일 헤더에 정의되어 있습니다.
PHPAPI const char php_sig_gif[3] = {'G', 'I', 'F'}; ... PHPAPI const char php_sig_png[8] = {(char) 0x89, (char) 0x50, (char) 0x4e, (char) 0x47, (char) 0x0d, (char) 0x0a, (char) 0x1a, (char) 0x0a};
파일의 처음 몇 바이트를 기준으로 이미지 유형이 판단되는 것을 볼 수 있습니다. 스트림(파일 헤더). 그렇다면 이 경우 이러한 판단을 우회하기 위해 특별한 PHP 파일을 구성할 수 있습니까? 한번 시도해 보세요.
PHP 문을 작성하려면 16진수 편집기를 찾으세요. 예:
<?php phpinfo(); ?>
이러한 문자의 16진수 인코딩(UTF-8)은 다음과 같습니다. :
3C3F 7068 7020 7068 7069 6E66 6F28 293B 203F 3E
다음과 같이 구성하고 앞에 PNG 파일의 헤더 바이트를 추가해 보겠습니다.
8950 4E47 0D0A 1A0A 3C3F 7068 7020 7068 7069 6E66 6F28 293B 203F 3E
마지막으로 .php 접미사를 붙인 파일로 저장합니다(위의 내용은 16진수 값이라는 점에 유의하세요). test.php와 같은 파일). php test.php를 실행하면 성공적으로 실행되는 것을 확인할 수 있습니다. 그렇다면 getimagesize를 사용하여 파일 정보를 읽을 수 있습니까? 새 파일을 생성하고 코드를 작성하여 시도해 보세요:
<?php print_r(getimagesize('test.php'));
실행 결과:
Array ( [0] => 1885957734 [1] => 1864902971 [2] => 3 [3] => width="1885957734" height="1864902971" [bits] => 32 [mime] => image/png )
을 성공적으로 읽었으며, 너비가 작아도 PNG 파일로 정상적으로 인식되었습니다. 그리고 높이 값이 좀 터무니없네요.
이제 위에서 webshell의 숨겨진 위험이 있다고 말한 이유를 이해해야 합니다. 여기에 그러한 업로드 판단만 있고 업로드된 파일에 접근이 가능하다면 이 입구를 통해 임의의 코드가 주입되어 실행될 수 있습니다.
그러면 왜 위 파일이 PHP에서 정상적으로 실행될 수 있을까요? token_get_all 함수를 사용하여 이 파일을 살펴보세요.
<?phpprint_r(token_get_all(file_get_contents('test.php')));
표시가 정상이라면 출력 배열의 첫 번째 요소의 구문 분석기 코드가 312이고 token_name을 통해 얻은 이름이 T_INLINE_HTML이어야 합니다. 즉, 파일 헤더의 정보는 일반적인 임베디드 HTML 코드로 간주되어 무시됩니다.
왜 폭과 높이가 터무니없이 큰지는 php_handle_png 함수의 구현을 보면 알 수 있습니다. 이 정보는 특정 파일 헤더 비트를 읽어도 알 수 있습니다.
따라서 일반 이미지 파일의 경우 getimagesize가 완전히 가능하지만 의도적으로 구성된 일부 파일 구조의 경우에는 불가능합니다.
사용자가 업로드한 파일을 처리할 때, PHP 파일이 아닌 이상 서버에서 직접 실행되지 않도록 파일 확장자를 간단하고 대략적으로 파악하고 파일명을 처리하는 것도 효과적인 방법입니다. 그런 다음 getimagesize를 사용하여 일부 보조 처리를 수행할 수 있습니다.
위 내용은 getimagesize 함수가 완전히 신뢰할 수 없다는 내용입니다. 더 많은 관련 내용은 PHP 중국어 홈페이지(m.sbmmt.com)를 참고해주세요!