본문으로 건너뛰기

상품 상세페이지 이미지 업로드 안정성 개선

PHP 7 · GODO25
변경 파일 수: 1개
변경 파일 목록
총 1개
변경 파일 코드
추가삭제
Component/Validator/Validator.php+34-8
변경 전Validator.php
const TEXT_USERID_INVALID = '%s은(는) 아이디형식에 맞지 않습니다.';
const TEXT_YN_INVALID = '%s은(는) y 또는 n 이어야 합니다.';
const TEXT_BOOLEAN_INVALID = '%s은(는) true 또는 false 이어야 합니다.';
 
 
 
 
 
/**
* act 함수 실행 시 데이터가 없는경우에도 체크하도록 하기 위한 플래그
* @var bool
}
 
/*
* 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' => [
 
$combinedPattern = '/' . implode('|', array_merge(...array_column($dangerPatterns, 'patterns'))) . '/i';
 
foreach ($matches[1] as $phpContent) {
if (preg_match($combinedPattern, $phpContent)) {
 
 
 
 
 
 
 
 
 
return false;
}
 
 
 
 
 
}
}
return true;
 
변경 후Validator.php
const TEXT_USERID_INVALID = '%s은(는) 아이디형식에 맞지 않습니다.';
const TEXT_YN_INVALID = '%s은(는) y 또는 n 이어야 합니다.';
const TEXT_BOOLEAN_INVALID = '%s은(는) true 또는 false 이어야 합니다.';
/**
* 웹쉘 검사 시 PHP 열림 태그 뒤로 훑을 최대 바이트 수
* 실제 페이로드는 태그 뒤 100바이트 안쪽에 나오고 범위를 넓힐수록 바이너리 오탐이 늘어, 4096 에서 절충
*/
const INCLUDE_EVAL_SCAN_WINDOW = 4096;
/**
* act 함수 실행 시 데이터가 없는경우에도 체크하도록 하기 위한 플래그
* @var bool
}
 
/*
* PHP 열림 태그 위치만 찾아, 각 태그 뒤 INCLUDE_EVAL_SCAN_WINDOW 바이트만 검사한다.
* 열림 태그부터 파일 끝까지를 한 블록으로 잡으면 바이너리에 우연히 섞인 3바이트 열림 태그
* 하나가 뒤쪽 전체를 검사 대상으로 끌고 와 정상 이미지를 오탐한다 (용량이 클수록 확률 증가).
* 정규식이 블록을 소비하지 않으므로, 이미지 뒤에 이어붙인 웹쉘의 열림 태그도 개별로 검사된다.
*
* 태그를 한 번에 모으지 않고 앞에서부터 하나씩 찾아 나가며, 겹치는 윈도우는 하나로 합쳐
* 합쳐진 구간 단위로 검사한다. 태그를 전부 모으면 3바이트 태그만 반복한 파일에서 오프셋
* 배열이 파일 크기의 수백 배로 불어나고, 윈도우를 태그마다 따로 검사하면 검사량이
* 태그 개수 × 윈도우로 증폭돼 파일 하나로 CPU 와 메모리를 오래 점유할 수 있다.
*/
$openTagPattern = '/<\?(?:php|=)/i';
if (preg_match($openTagPattern, $text, $firstTag, PREG_OFFSET_CAPTURE)) {
$dangerPatterns = [
'variable_access' => [
'description' => '슈퍼글로벌 변수 및 변수 변수 함수 호출',
'patterns' => [
'\$_(?:GET|POST|REQUEST|COOKIE|SERVER|FILES|ENV|SESSION)',
'\$\{\s*[\'"]?_(?:GET|POST|REQUEST|COOKIE|SERVER|FILES|ENV|SESSION)',
'\$\$\{?\w+\}?(?:\[[^\]\n]{0,64}\])?\s*\(',
]
],
'code_execution' => [
 
$combinedPattern = '/' . implode('|', array_merge(...array_column($dangerPatterns, 'patterns'))) . '/i';
 
$scanStart = $firstTag[0][1];
$scanEnd = $scanStart + static::INCLUDE_EVAL_SCAN_WINDOW;
$offset = $scanStart + 1;
while (preg_match($openTagPattern, $text, $openTag, PREG_OFFSET_CAPTURE, $offset)) {
$start = $openTag[0][1];
$offset = $start + 1;
if ($start <= $scanEnd) {
$scanEnd = $start + static::INCLUDE_EVAL_SCAN_WINDOW;
continue;
}
if (preg_match($combinedPattern, substr($text, $scanStart, $scanEnd - $scanStart))) {
return false;
}
$scanStart = $start;
$scanEnd = $start + static::INCLUDE_EVAL_SCAN_WINDOW;
}
if (preg_match($combinedPattern, substr($text, $scanStart, $scanEnd - $scanStart))) {
return false;
}
}
return true;