security: KVE-2026-2329 XHTML 등 실행형 첨부를 저장 전에 차단

게시판·1:1문의·폼메일·쇼핑몰 로고의 PHP 업로드 검사에서 위험 확장자를 거부한다. 대소문자와 다중 확장자 등 파일명 변형을 검사하고 클라이언트 검사도 일치시킨다.

검증: 위험 파일 및 정상 이미지·문서 파일명 격리 검증, 저장 전 차단 검증, PHP·JavaScript 문법 검사 통과.
This commit is contained in:
whitedot
2026-09-09 07:58:16 +00:00
parent 65875b7f35
commit 873839add7
6 changed files with 33 additions and 13 deletions
+2 -2
View File
@@ -19,8 +19,8 @@ $logo_img_fields = array('logo_img', 'logo_img2', 'mobile_logo_img', 'mobile_log
foreach ($logo_img_fields as $logo_img_field) { foreach ($logo_img_fields as $logo_img_field) {
if (isset($_FILES[$logo_img_field]['name']) && $_FILES[$logo_img_field]['name']) { if (isset($_FILES[$logo_img_field]['name']) && $_FILES[$logo_img_field]['name']) {
$filename = get_safe_filename($_FILES[$logo_img_field]['name']); $filename = get_safe_filename($_FILES[$logo_img_field]['name']);
if (is_disallowed_svg_filename($filename)) { if (is_disallowed_active_filename($filename)) {
alert('허용되지 않는 파일 확장자입니다. (svg, svgz)'); alert('허용되지 않는 파일 확장자입니다.');
} }
} }
} }
+2 -2
View File
@@ -34,8 +34,8 @@ for ($i=1; $i<=$attach; $i++) {
$file_key = 'file'.$i; $file_key = 'file'.$i;
if (isset($_FILES[$file_key]['name']) && $_FILES[$file_key]['name']) { if (isset($_FILES[$file_key]['name']) && $_FILES[$file_key]['name']) {
$filename = get_safe_filename($_FILES[$file_key]['name']); $filename = get_safe_filename($_FILES[$file_key]['name']);
if (is_disallowed_svg_filename($filename)) { if (is_disallowed_active_filename($filename)) {
alert_close('허용되지 않는 파일 확장자입니다. (svg, svgz)'); alert_close('허용되지 않는 파일 확장자입니다.');
} }
} }
} }
+2 -2
View File
@@ -136,8 +136,8 @@ if($w == 'u' || $w == 'a' || $w == 'r') {
if (isset($_FILES['bf_file']['name']) && is_array($_FILES['bf_file']['name'])) { if (isset($_FILES['bf_file']['name']) && is_array($_FILES['bf_file']['name'])) {
foreach ($_FILES['bf_file']['name'] as $filename) { foreach ($_FILES['bf_file']['name'] as $filename) {
$filename = get_safe_filename($filename); $filename = get_safe_filename($filename);
if (is_disallowed_svg_filename($filename)) { if (is_disallowed_active_filename($filename)) {
alert('허용되지 않는 파일 확장자입니다. (svg, svgz)'); alert('허용되지 않는 파일 확장자입니다.');
} }
} }
} }
+2 -2
View File
@@ -173,8 +173,8 @@ for ($i=1; $i<=10; $i++) {
if (isset($_FILES['bf_file']['name']) && is_array($_FILES['bf_file']['name'])) { if (isset($_FILES['bf_file']['name']) && is_array($_FILES['bf_file']['name'])) {
foreach ($_FILES['bf_file']['name'] as $filename) { foreach ($_FILES['bf_file']['name'] as $filename) {
$filename = get_safe_filename($filename); $filename = get_safe_filename($filename);
if (is_disallowed_svg_filename($filename)) { if (is_disallowed_active_filename($filename)) {
alert('허용되지 않는 파일 확장자입니다. (svg, svgz)'); alert('허용되지 않는 파일 확장자입니다.');
} }
} }
} }
+13 -4
View File
@@ -70,6 +70,15 @@ function is_disallowed_svg_filename(filename)
return /\.(svg|svgz)$/i.test(filename); return /\.(svg|svgz)$/i.test(filename);
} }
function is_disallowed_active_filename(filename)
{
filename = filename || "";
if (/[\x00-\x1f\x7f]/.test(filename)) return true;
filename = filename.replace(/["'<>=#&!%\\()*+?]/g, "");
filename = filename.substring(filename.lastIndexOf("/") + 1).replace(/[ .]+$/, "");
return /\.(svgz?|xhtml|xht|xml|xsl|xslt|mht|mhtml|htc)([. :]|$)/i.test(filename);
}
function is_svg_upload_target(input) function is_svg_upload_target(input)
{ {
var name = input.name || ""; var name = input.name || "";
@@ -92,7 +101,7 @@ function has_disallowed_svg_file(input)
if (input.files && input.files.length) { if (input.files && input.files.length) {
for (i=0; i<input.files.length; i++) { for (i=0; i<input.files.length; i++) {
if (is_disallowed_svg_filename(input.files[i].name)) { if (is_disallowed_active_filename(input.files[i].name)) {
return true; return true;
} }
} }
@@ -100,7 +109,7 @@ function has_disallowed_svg_file(input)
return false; return false;
} }
return is_disallowed_svg_filename(input.value); return is_disallowed_active_filename(input.value);
} }
function check_disallowed_svg_upload(form) function check_disallowed_svg_upload(form)
@@ -118,7 +127,7 @@ function check_disallowed_svg_upload(form)
return true; return true;
} }
alert("허용되지 않는 파일 확장자입니다. (svg, svgz)"); alert("허용되지 않는 파일 확장자입니다.");
$(form).find("input:submit, button:submit, input:image").prop("disabled", false); $(form).find("input:submit, button:submit, input:image").prop("disabled", false);
invalid_file.focus(); invalid_file.focus();
@@ -799,7 +808,7 @@ $(function() {
return true; return true;
} }
alert("허용되지 않는 파일 확장자입니다. (svg, svgz)"); alert("허용되지 않는 파일 확장자입니다.");
this.value = ""; this.value = "";
return false; return false;
+12 -1
View File
@@ -4236,7 +4236,18 @@ function get_safe_filename($name)
return $name; return $name;
} }
// 업로드 파일명이 SVG 또는 SVGZ 확장자인지 확인 // 브라우저에서 실행될 수 있는 첨부 확장자를 저장 전에 거부한다.
function is_disallowed_active_filename($filename)
{
if (!is_string($filename)) return true;
if (preg_match('/[\x00-\x1f\x7f]/', $filename)) return true;
$filename = basename(str_replace('\\', '/', $filename));
$filename = rtrim($filename, ' .');
// 다중 확장자 및 Windows 대체 데이터 스트림 표기도 차단한다.
return (bool) preg_match('/\.(?:svgz?|xhtml|xht|xml|xsl|xslt|mht|mhtml|htc)(?:[. :]|$)/i', $filename);
}
// 기존 스킨 및 플러그인과의 호환을 위한 SVG 검사 함수
function is_disallowed_svg_filename($filename) function is_disallowed_svg_filename($filename)
{ {
if (!is_string($filename) || $filename === '') { if (!is_string($filename) || $filename === '') {