diff --git a/AGENTS.md b/AGENTS.md
index 8207863b1..24147eee0 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -35,9 +35,16 @@
- `chore`: 기능과 직접 관련 없는 유지보수 작업
- `revert`: 이전 변경 되돌리기
+### 보안 이슈 식별자
+
+- KVE, CVE 등 식별자가 부여된 보안 이슈를 조치하는 커밋은 제목에 관련 식별자를 반드시 병기한다.
+- 식별자는 `security:` 접두어 바로 뒤에 원문 그대로 작성한다.
+- 하나의 커밋이 여러 보안 이슈를 함께 조치하면 관련 식별자를 모두 병기한다.
+- 예: `security: KVE-2026-1909 모바일 KCP Windows 명령 인자 주입 차단`
+
### 작성 예시
- `docs: Git과 압축파일 설치 절차를 구분해 README에 안내`
- `fix: 품목 일부 취소 시 PG 취소금액 불일치 수정`
-- `security: 상품 정렬값 화이트리스트 검증으로 SQL 삽입 차단`
+- `security: KVE-2026-1899 상품 정렬값 화이트리스트 검증으로 SQL 삽입 차단`
- `refactor: 객체 캐시의 유형별 저장 키 생성 로직 분리`
diff --git a/adm/admin.tail.php b/adm/admin.tail.php
index a44a66acc..19c73d905 100644
--- a/adm/admin.tail.php
+++ b/adm/admin.tail.php
@@ -52,8 +52,8 @@ $print_version = ($is_admin == 'super') ? 'Version ' . G5_GNUBOARD_VER : '';
-
-
+
+
-
+
diff --git a/adm/css/admin.css b/adm/css/admin.css
index 697f1032e..9877286db 100644
--- a/adm/css/admin.css
+++ b/adm/css/admin.css
@@ -354,16 +354,7 @@ border-bottom: 5px solid black;}
.excel-download-progress .progress-download-box a:hover { border-color: #6f809a; box-shadow: 0 2px 4px rgba(111, 128, 154, 0.15); }
.excel-download-progress .progress-download-box a:active { box-shadow: inset 0 1px 2px rgba(0,0,0,0.1); }
-.field-select-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: 15px; gap: 0px 10px; padding: 10px; background-color: #f9fafb; border: 1px solid #e5e7eb; border-radius: 8px; color: #374151; }
-.field-select-form label { display: flex; align-items: center; cursor: pointer; padding: 6px 10px; border-radius: 4px; }
-.field-select-form label:hover { background-color: #f3f4f6; }
-.field-select-form input[type="checkbox"] { margin-right: 8px; transform: scale(1.2); }
-.field-separator { grid-column: 1 / -1; border-top: 1px solid #d1d5db; margin: 8px 0; }
-.selected-fields-preview { padding: 8px; background-color: #eef2f7; border: 1px solid #d1d5db; border-radius: 6px; margin: 10px 0px; color: #1f2937; display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
-.selected-fields-preview strong { padding: 4px 8px; }
-.selected-fields-preview .field-tag { background-color: #dbeafe; color: #1e40af; padding: 4px 8px; border-radius: 4px; }
-
-/* 페이지 내 실행 */
+/* 페이지 내 실행 */
.local_cmd {min-width:960px}
.local_cmd01 {margin:0 0 10px;padding:0 }
.local_cmd01 .cmd_tit {font-weight:bold}
@@ -1217,4 +1208,4 @@ input[type="text"]{max-width:200px}
@media only screen and (max-device-width : 480px) and (orientation : portrait){
/* Styles */
input[type="text"]{max-width:200px}
-}
\ No newline at end of file
+}
diff --git a/adm/member_list_exel.lib.php b/adm/member_list_exel.lib.php
index f9b017145..83a2d2276 100644
--- a/adm/member_list_exel.lib.php
+++ b/adm/member_list_exel.lib.php
@@ -59,18 +59,8 @@ function get_export_config($type = null)
*/
function get_member_export_params()
{
- // 친구톡 양식 - 엑셀 양식에 포함할 항목
- $fieldArray = array_map('trim', explode(',', isset($_GET['fields']) ? $_GET['fields'] : ''));
- $vars = array();
- foreach ($fieldArray as $index => $field) {
- if(!empty($field)){
- $vars['var' . ($index + 1)] = $field;
- }
- }
-
$params = array(
'page' => 1,
- 'formatType' => (int)(isset($_GET['formatType']) ? $_GET['formatType'] : 1),
'use_stx' => isset($_GET['use_stx']) ? $_GET['use_stx'] : 0,
'stx_cond' => clean_xss_tags(isset($_GET['stx_cond']) ? $_GET['stx_cond'] : 'like'),
'sfl' => clean_xss_tags(isset($_GET['sfl']) ? $_GET['sfl'] : ''),
@@ -93,7 +83,6 @@ function get_member_export_params()
'agree_date_end' => clean_xss_tags(isset($_GET['agree_date_end']) ? $_GET['agree_date_end'] : ''),
'use_intercept' => isset($_GET['use_intercept']) ? $_GET['use_intercept'] : 0,
'intercept' => clean_xss_tags(isset($_GET['intercept']) ? $_GET['intercept'] : 'exclude'),
- 'vars' => $vars,
);
// 레벨 범위 검증
diff --git a/adm/member_list_exel.php b/adm/member_list_exel.php
index 2295167fa..633b283c6 100644
--- a/adm/member_list_exel.php
+++ b/adm/member_list_exel.php
@@ -271,7 +271,7 @@ function closePreviousEventSource() {
}
// 2. FormData QueryString 변환
-function buildDownloadParams(selectedFields = []) {
+function buildDownloadParams() {
const formData = new FormData(document.getElementById('fsearch'));
const params = new URLSearchParams(formData);
@@ -281,10 +281,10 @@ function buildDownloadParams(selectedFields = []) {
}
// 3. 메인 함수
-function startExcelDownload(selectedFields = []) {
+function startExcelDownload() {
closePreviousEventSource();
- const query = buildDownloadParams(selectedFields);
+ const query = buildDownloadParams();
showDownloadPopup();
eventSource = new EventSource(`member_list_exel_export.php?${query}`);
@@ -340,32 +340,6 @@ function handlePopupCloseWithConfirm(e) {
PopupManager.close('popupOverlay');
}
-// 체크박스 선택 시 최대 3개 제한 및 선택된 항목 미리보기 표시
-function bindFieldSelectEvents() {
- const fieldSelectForm = document.getElementById('fieldSelectForm');
- if (!fieldSelectForm) return;
-
- fieldSelectForm.addEventListener('change', function (e) {
- if (e.target.name === 'fields') {
- const selected = fieldSelectForm.querySelectorAll('input[name="fields"]:checked');
- if (selected.length > 3) {
- alert("최대 3개까지 선택 가능합니다.");
- e.target.checked = false;
- return;
- }
-
- // 선택된 항목 표시
- const previewContainer = document.getElementById('selectedFieldsPreview');
- let spans = '선택된 항목:';
- selected.forEach(field => {
- const label = field.parentElement.textContent.trim();
- spans += `${label}`;
- });
- previewContainer.innerHTML = spans;
- }
- });
-}
-
// 엑셀 생성 및 다운로드 실행
function handleProgressUpdate() {
return function(e) {
diff --git a/adm/shop_admin/configformupdate.php b/adm/shop_admin/configformupdate.php
index 74f492807..f3f263cad 100644
--- a/adm/shop_admin/configformupdate.php
+++ b/adm/shop_admin/configformupdate.php
@@ -15,6 +15,16 @@ if(! (isset($_POST['de_admin_company_tel']) && check_vaild_callback($_POST['de_a
// 로그인을 바로 이 주소로 하는 경우 쇼핑몰설정값이 사라지는 현상을 방지
if (!$_POST['de_admin_company_owner']) goto_url("./configform.php");
+$logo_img_fields = array('logo_img', 'logo_img2', 'mobile_logo_img', 'mobile_logo_img2');
+foreach ($logo_img_fields as $logo_img_field) {
+ if (isset($_FILES[$logo_img_field]['name']) && $_FILES[$logo_img_field]['name']) {
+ $filename = get_safe_filename($_FILES[$logo_img_field]['name']);
+ if (is_disallowed_svg_filename($filename)) {
+ alert('허용되지 않는 파일 확장자입니다. (svg, svgz)');
+ }
+ }
+}
+
if (! empty($_POST['logo_img_del'])) @unlink(G5_DATA_PATH."/common/logo_img");
if (! empty($_POST['logo_img_del2'])) @unlink(G5_DATA_PATH."/common/logo_img2");
if (! empty($_POST['mobile_logo_img_del'])) @unlink(G5_DATA_PATH."/common/mobile_logo_img");
diff --git a/bbs/formmail_send.php b/bbs/formmail_send.php
index 3d43aebae..355524f35 100644
--- a/bbs/formmail_send.php
+++ b/bbs/formmail_send.php
@@ -28,9 +28,22 @@ if (!preg_match("/([0-9a-zA-Z_-]+)@([0-9a-zA-Z_-]+)\.([0-9a-zA-Z_-]+)/", $to)){
}
$file = array();
+
+// 첨부파일을 이동하기 전에 모든 파일명을 먼저 확인한다.
for ($i=1; $i<=$attach; $i++) {
- if ($_FILES['file'.$i]['name'])
- $file[] = attach_file($_FILES['file'.$i]['name'], $_FILES['file'.$i]['tmp_name']);
+ $file_key = 'file'.$i;
+ if (isset($_FILES[$file_key]['name']) && $_FILES[$file_key]['name']) {
+ $filename = get_safe_filename($_FILES[$file_key]['name']);
+ if (is_disallowed_svg_filename($filename)) {
+ alert_close('허용되지 않는 파일 확장자입니다. (svg, svgz)');
+ }
+ }
+}
+
+for ($i=1; $i<=$attach; $i++) {
+ $file_key = 'file'.$i;
+ if (isset($_FILES[$file_key]['name']) && $_FILES[$file_key]['name'])
+ $file[] = attach_file($_FILES[$file_key]['name'], $_FILES[$file_key]['tmp_name']);
}
$content = stripslashes($content);
@@ -78,4 +91,4 @@ include_once(G5_PATH.'/head.sub.php');
alert_close('메일을 정상적으로 발송하였습니다.');
-include_once(G5_PATH.'/tail.sub.php');
\ No newline at end of file
+include_once(G5_PATH.'/tail.sub.php');
diff --git a/bbs/qadelete.php b/bbs/qadelete.php
index 499745e9d..3fa54573c 100644
--- a/bbs/qadelete.php
+++ b/bbs/qadelete.php
@@ -1,6 +1,8 @@
'.str_replace(G5_PATH.'/', '', $skin_file).'이 존재하지 않습니다.
'.str_replace(G5_PATH.'/', '', $skin_file).'이 존재하지 않습니다.
';
}
-include_once('./qatail.php');
\ No newline at end of file
+include_once('./qatail.php');
diff --git a/bbs/qawrite_update.php b/bbs/qawrite_update.php
index 0f6252175..35d63cfef 100644
--- a/bbs/qawrite_update.php
+++ b/bbs/qawrite_update.php
@@ -1,6 +1,8 @@
$qaconfig['qa_upload_size']) {
+ // 최고관리자가 아니면서 설정한 업로드 사이즈보다 크다면 건너뜀
+ if ($is_admin !== 'super' && $filesize > $qaconfig['qa_upload_size']) {
$file_upload_msg .= '"'.$filename.'" 파일의 용량('.number_format($filesize).' 바이트)이 게시판에 설정('.number_format($qaconfig['qa_upload_size']).' 바이트)된 값보다 크므로 업로드 하지 않습니다.\\n';
continue;
}
diff --git a/bbs/write_update.php b/bbs/write_update.php
index be65214be..e851908b4 100644
--- a/bbs/write_update.php
+++ b/bbs/write_update.php
@@ -137,6 +137,15 @@ for ($i=1; $i<=10; $i++) {
}
}
+if (isset($_FILES['bf_file']['name']) && is_array($_FILES['bf_file']['name'])) {
+ foreach ($_FILES['bf_file']['name'] as $filename) {
+ $filename = get_safe_filename($filename);
+ if (is_disallowed_svg_filename($filename)) {
+ alert('허용되지 않는 파일 확장자입니다. (svg, svgz)');
+ }
+ }
+}
+
@include_once($board_skin_path.'/write_update.head.skin.php');
run_event('write_update_before', $board, $wr_id, $w, $qstr);
diff --git a/extend/version.extend.php b/extend/version.extend.php
deleted file mode 100644
index 086d5a9cc..000000000
--- a/extend/version.extend.php
+++ /dev/null
@@ -1,8 +0,0 @@
-'.PHP_EOL;
+ echo ''.PHP_EOL;
} else {
$shop_css = '';
if (defined('_SHOP_')) $shop_css = '_shop';
- echo ''.PHP_EOL;
+ echo ''.PHP_EOL;
}
?>
\n";
\ No newline at end of file
+echo "\n\n";
diff --git a/mobile/shop/listtype.php b/mobile/shop/listtype.php
index dee950022..e984fac87 100644
--- a/mobile/shop/listtype.php
+++ b/mobile/shop/listtype.php
@@ -1,9 +1,6 @@
', 0);
if ($config['cf_cert_use'] && ($config['cf_cert_simple'] || $config['cf_cert_ipin'] || $config['cf_cert_hp']))
- add_javascript('', 0);
+ add_javascript('', 0);
?>
diff --git a/mobile/skin/member/basic/password_lost.skin.php b/mobile/skin/member/basic/password_lost.skin.php
index 6278d8eba..ec9a50847 100644
--- a/mobile/skin/member/basic/password_lost.skin.php
+++ b/mobile/skin/member/basic/password_lost.skin.php
@@ -5,7 +5,7 @@ if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
add_stylesheet('', 0);
if($config['cf_cert_use'] && ($config['cf_cert_simple'] || $config['cf_cert_ipin'] || $config['cf_cert_hp'])) { ?>
-
+
diff --git a/mobile/skin/member/basic/register_form.skin.php b/mobile/skin/member/basic/register_form.skin.php
index 9972caabf..ab8961150 100644
--- a/mobile/skin/member/basic/register_form.skin.php
+++ b/mobile/skin/member/basic/register_form.skin.php
@@ -5,7 +5,7 @@ if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
add_stylesheet('', 0);
add_javascript('', 0);
if ($config['cf_cert_use'] && ($config['cf_cert_simple'] || $config['cf_cert_ipin'] || $config['cf_cert_hp']))
- add_javascript('', 0);
+ add_javascript('', 0);
?>
diff --git a/mobile/skin/qa/basic/view.answerform.skin.php b/mobile/skin/qa/basic/view.answerform.skin.php
index e89ec6c66..1aa9556ea 100644
--- a/mobile/skin/qa/basic/view.answerform.skin.php
+++ b/mobile/skin/qa/basic/view.answerform.skin.php
@@ -4,7 +4,7 @@ if (!defined("_GNUBOARD_")) exit; // 개별 페이지 접근 불가
답변등록
@@ -151,4 +151,4 @@ if (!defined("_GNUBOARD_")) exit; // 개별 페이지 접근 불가
-
\ No newline at end of file
+
diff --git a/mobile/skin/shop/basic/item.form.skin.php b/mobile/skin/shop/basic/item.form.skin.php
index 6db60f890..c8850c8e2 100644
--- a/mobile/skin/shop/basic/item.form.skin.php
+++ b/mobile/skin/shop/basic/item.form.skin.php
@@ -11,7 +11,7 @@ add_javascript('', 10);
-
+
diff --git a/theme/basic/mobile/skin/member/basic/password_lost.skin.php b/theme/basic/mobile/skin/member/basic/password_lost.skin.php
index 956894e0b..dbaf8774d 100644
--- a/theme/basic/mobile/skin/member/basic/password_lost.skin.php
+++ b/theme/basic/mobile/skin/member/basic/password_lost.skin.php
@@ -5,7 +5,7 @@ if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
add_stylesheet('', 0);
if($config['cf_cert_use'] && ($config['cf_cert_simple'] || $config['cf_cert_ipin'] || $config['cf_cert_hp'])) { ?>
-
+
diff --git a/theme/basic/mobile/skin/member/basic/register_form.skin.php b/theme/basic/mobile/skin/member/basic/register_form.skin.php
index 543adcdfa..fbe471ed9 100644
--- a/theme/basic/mobile/skin/member/basic/register_form.skin.php
+++ b/theme/basic/mobile/skin/member/basic/register_form.skin.php
@@ -5,7 +5,7 @@ if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
add_stylesheet('', 0);
add_javascript('', 0);
if ($config['cf_cert_use'] && ($config['cf_cert_simple'] || $config['cf_cert_ipin'] || $config['cf_cert_hp']))
- add_javascript('', 0);
+ add_javascript('', 0);
?>
diff --git a/theme/basic/mobile/skin/qa/basic/view.answerform.skin.php b/theme/basic/mobile/skin/qa/basic/view.answerform.skin.php
index e89ec6c66..1aa9556ea 100644
--- a/theme/basic/mobile/skin/qa/basic/view.answerform.skin.php
+++ b/theme/basic/mobile/skin/qa/basic/view.answerform.skin.php
@@ -4,7 +4,7 @@ if (!defined("_GNUBOARD_")) exit; // 개별 페이지 접근 불가
답변등록
@@ -151,4 +151,4 @@ if (!defined("_GNUBOARD_")) exit; // 개별 페이지 접근 불가
-
\ No newline at end of file
+
diff --git a/theme/basic/mobile/skin/shop/basic/item.form.skin.php b/theme/basic/mobile/skin/shop/basic/item.form.skin.php
index 79aa47569..1be1c4216 100644
--- a/theme/basic/mobile/skin/shop/basic/item.form.skin.php
+++ b/theme/basic/mobile/skin/shop/basic/item.form.skin.php
@@ -11,7 +11,7 @@ add_javascript('', 10);
-
+
" class="itemuse_form btn01" onclick="return false;">수정" class="itemuse_delete btn01">삭제
@@ -131,4 +131,4 @@ $(function(){
});
});
-
\ No newline at end of file
+
diff --git a/version.php b/version.php
index 6f057d671..678a3a8a0 100644
--- a/version.php
+++ b/version.php
@@ -2,7 +2,7 @@
if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
define('G5_VERSION', '그누보드5');
-define('G5_GNUBOARD_VER', '5.6.35');
+define('G5_GNUBOARD_VER', '5.6.36');
// 그누보드5.4.5.5 버전과 영카트5.4.5.5.1 버전을 합쳐서 그누보드5.4.6 버전에서 시작함 (kagla-210617)
// G5_YOUNGCART_VER 이 상수를 사용하는 곳이 있으므로 주석 처리 해제함
// 그누보드5.4.6 이상 버전 부터는 영카트를 그누보드에 포함하여 배포하므로 영카트5의 버전은 의미가 없습니다.