변경 파일 목록
총 1개
Component (1)
Validator.phpValidator/변경 파일 코드
추가삭제
변경 전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; | |