본문으로 건너뛰기

파일 업로드 보안 강화

PHP 8 · GODO25
변경 파일 수: 1개
변경 파일 목록
1
변경 파일 코드
추가삭제
Component/Validator/Validator.php+70-17
변경 전Validator.php
}
 
/**
* 웹쉘이 추가된 이미지 업로드시 eval 포함여부 검증
*
* @param string $tmpName
* @return bool
*/
public static function validateIncludeEval($tmpName) {
try {
$fp = fopen($tmpName, 'rb');
$text = fread($fp, filesize($tmpName));
fclose($fp);
 
 
/*
* 1차 : preg_math_all 을 통해 php 구문이 존재할 경우 구문안의 문자열 추출
* 2차 : 구문안의 문자열 중 <? 존재할 경우 explode 배열 만든 후 마지막 <? 데이터만 추출 (예 : <? 문자1 <? 문자2 <? 문자3 ?>)
* 3차 : 최종 추출한 문자열에 불가 문자 여부를 검출 (대소문자 구분 x)
*/
$checkContent = nl2br(str_replace(["\r\n", "\r", "\n"], "", $text));
if (preg_match_all('/(?<=\<\?)(.*?)(?=\?>)/', $checkContent, $matches)) {
for ($i = 0; $i < gd_count($matches[0]); $i++) {
$checkText = $matches[1][$i];
$checkTextArr = explode('<?', $checkText);
$checkVal = $checkTextArr[gd_array_last_key($checkTextArr)];
 
// 추출 문자내 불가 문자 검증
if (preg_match('/eval|\<\?php|\$_POST|\$_GET|\$_REQUEST/i', $checkVal)) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
return false;
}
}
 
변경 후Validator.php
}
 
/**
* 웹쉘이 추가된 이미지 업로드시 PHP 코드 포함여부 검증
*
* @param string $tmpName
* @return bool true=안전, false=차단
*/
public static function validateIncludeEval($tmpName) {
try {
$text = file_get_contents($tmpName);
if ($text === false) {
return false;
}
 
/*
* PHP 열림 태그(<?php / <?=)부터 닫힘 태그(또는 파일 끝)까지 추출 — 닫힘 태그 우회 차단 + 바이너리 false positive 회피.
* 열림 태그를 <?php / <?= 로 한정: 바이너리에 우연히 나온 <? 가 거대한 가짜 PHP 블록을 만들어 정상 이미지를 오탐하던 문제 차단.
* /s 모디파이어로 . 가 newline 도 매칭하여 nl2br 트릭 불필요. /i 로 <?PHP 대소문자 무시.
*/
if (preg_match_all('/<\?(?:php|=)(.*?)(?:\?>|$)/si', $text, $matches)) {
$dangerPatterns = [
'variable_access' => [
'description' => '슈퍼글로벌 변수 및 변수 변수 접근',
'patterns' => [
'\$_(?:GET|POST|REQUEST|COOKIE|SERVER|FILES|ENV|SESSION)',
'\$\{\s*[\'"]?_(?:GET|POST|REQUEST|COOKIE|SERVER|FILES|ENV|SESSION)',
'\$\$\w+',
]
],
'code_execution' => [
'description' => '코드 실행 함수',
'patterns' => [
'\beval\s*\(',
'\bassert\s*\(',
'\bcreate_function\s*\(',
]
],
'system_commands' => [
'description' => '시스템 명령 실행',
'patterns' => [
'\b(?:system|exec|passthru|shell_exec|popen|proc_open|pcntl_exec)\s*\(',
]
],
'file_operations' => [
'description' => '파일 포함 및 읽기/쓰기',
'patterns' => [
'\b(?:include|require)(?:_once)?\b',
'\b(?:file_get_contents|file_put_contents|fopen|readfile|file)\s*\(',
]
],
'dynamic_calls' => [
'description' => '동적 함수 호출',
'patterns' => [
'\bcall_user_func(?:_array)?\s*\(',
]
],
'encoding_functions' => [
'description' => '인코딩/디코딩 함수 (코드 실행 가능)',
'patterns' => [
'\b(?:base64_decode|gzinflate|gzuncompress|str_rot13|hex2bin|convert_uudecode)\s*\(',
]
],
'regex_functions' => [
'description' => '정규식 함수 (코드 실행 가능)',
'patterns' => [
'\bpreg_replace\s*\(',
]
],
'stream_wrappers' => [
'description' => 'PHP 스트림 래퍼',
'patterns' => [
'php:\/\/',
]
],
];
 
$combinedPattern = '/' . implode('|', array_merge(...array_column($dangerPatterns, 'patterns'))) . '/i';
 
foreach ($matches[1] as $phpContent) {
if (preg_match($combinedPattern, $phpContent)) {
return false;
}
}