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).'이 존재하지 않습니다.
'; } -include_once('./qatail.php'); \ No newline at end of file +include_once('./qatail.php'); diff --git a/bbs/qaview.php b/bbs/qaview.php index de008d1c9..fc93f9f7e 100644 --- a/bbs/qaview.php +++ b/bbs/qaview.php @@ -2,6 +2,8 @@ include_once('./_common.php'); include_once(G5_EDITOR_LIB); +$is_admin = get_super_admin_type($is_admin); + $qa_id = isset($_REQUEST['qa_id']) ? (int) $_REQUEST['qa_id'] : 0; if($is_guest) @@ -115,7 +117,7 @@ if(is_file($skin_file)) { } */ - if(($view['qa_type'] && $is_admin) || (!$view['qa_type'] && $view['qa_status'] == 0)) { + if(($view['qa_type'] && $is_admin === 'super') || (!$view['qa_type'] && $view['qa_status'] == 0)) { $update_href = G5_BBS_URL.'/qawrite.php?w=u&qa_id='.$view['qa_id'].$qstr; $delete_href = G5_BBS_URL.'/qadelete.php?qa_id='.$view['qa_id'].'&token='.$token.$qstr; } @@ -131,7 +133,7 @@ if(is_file($skin_file)) { and qa_parent = '{$view['qa_id']}' "; $answer = sql_fetch($sql); - if($is_admin) { + if($is_admin === 'super') { $answer_update_href = G5_BBS_URL.'/qawrite.php?w=u&qa_id='.$answer['qa_id'].$qstr; $answer_delete_href = G5_BBS_URL.'/qadelete.php?qa_id='.$answer['qa_id'].'&token='.$token.$qstr; } @@ -216,4 +218,4 @@ if(is_file($skin_file)) { echo '
'.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.php b/bbs/qawrite.php index 8904f574e..a42dc111b 100644 --- a/bbs/qawrite.php +++ b/bbs/qawrite.php @@ -2,6 +2,8 @@ include_once('./_common.php'); include_once(G5_EDITOR_LIB); +$is_admin = get_super_admin_type($is_admin); + if($w != '' && $w != 'u' && $w != 'r') { alert('올바른 방법으로 이용해 주십시오.'); } @@ -30,7 +32,7 @@ if(is_file($skin_file)) { if($w == 'u' || $w == 'r') { $sql = " select * from {$g5['qa_content_table']} where qa_id = '$qa_id' "; - if(!$is_admin) { + if($is_admin !== 'super') { $sql .= " and mb_id = '{$member['mb_id']}' "; } @@ -40,7 +42,7 @@ if(is_file($skin_file)) { if(!$write['qa_id']) alert('게시글이 존재하지 않습니다.\\n삭제되었거나 자신의 글이 아닌 경우입니다.'); - if(!$is_admin) { + if($is_admin !== 'super') { if($write['qa_type'] == 0 && $write['qa_status'] == 1) alert('답변이 등록된 문의글은 수정할 수 없습니다.'); @@ -117,7 +119,7 @@ if(is_file($skin_file)) { if($w == '' || $w == 'r') $write['qa_email'] = $member['mb_email']; - if($w == 'u' && $is_admin && $write['qa_type']) + if($w == 'u' && $is_admin === 'super' && $write['qa_type']) $is_email = false; } @@ -132,7 +134,7 @@ if(is_file($skin_file)) { if($w == '' || $w == 'r') $write['qa_hp'] = $member['mb_hp']; - if($w == 'u' && $is_admin && $write['qa_type']) + if($w == 'u' && $is_admin === 'super' && $write['qa_type']) $is_hp = false; } @@ -145,4 +147,4 @@ if(is_file($skin_file)) { echo '
'.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/mobile/skin/shop/basic/itemqa.skin.php b/mobile/skin/shop/basic/itemqa.skin.php index 9e80154c4..693d56550 100644 --- a/mobile/skin/shop/basic/itemqa.skin.php +++ b/mobile/skin/shop/basic/itemqa.skin.php @@ -27,7 +27,7 @@ add_stylesheet('', if($row['iq_secret']) { $iq_subject .= ' 비밀글'; - if($is_admin || $member['mb_id' ] == $row['mb_id']) { + if(is_shop_resource_owner_or_super_admin($row['mb_id'], $member['mb_id'], $is_admin)) { $iq_question = get_view_thumbnail(conv_content($row['iq_question'], 1), $thumbnail_width); } else { $iq_question = '비밀글로 보호된 문의입니다.'; @@ -87,7 +87,7 @@ add_stylesheet('',
- +
" class="itemqa_form btn01" onclick="return false;">수정 " class="itemqa_delete btn01">삭제 @@ -148,4 +148,4 @@ $(function(){ }); }); - \ No newline at end of file + diff --git a/mobile/skin/shop/basic/itemqalist.skin.php b/mobile/skin/shop/basic/itemqalist.skin.php index dbc384332..20d00cab7 100644 --- a/mobile/skin/shop/basic/itemqalist.skin.php +++ b/mobile/skin/shop/basic/itemqalist.skin.php @@ -46,7 +46,7 @@ add_stylesheet('', if($row['iq_secret']) { $iq_subject .= ' '; - if($is_admin || $member['mb_id' ] == $row['mb_id']) { + if(is_shop_resource_owner_or_super_admin($row['mb_id'], $member['mb_id'], $is_admin)) { $iq_question = get_view_thumbnail(conv_content($row['iq_question'], 1), $thumbnail_width); } else { $iq_question = '비밀글로 보호된 문의입니다.'; @@ -141,4 +141,4 @@ $(function(){ }); }); - \ No newline at end of file + diff --git a/mobile/skin/shop/basic/itemuse.skin.php b/mobile/skin/shop/basic/itemuse.skin.php index 8f6d5e3da..a012a046c 100644 --- a/mobile/skin/shop/basic/itemuse.skin.php +++ b/mobile/skin/shop/basic/itemuse.skin.php @@ -52,7 +52,7 @@ add_stylesheet('',
- +
" class="itemuse_form btn01" onclick="return false;">수정 " class="itemuse_delete btn01">삭제 @@ -130,4 +130,4 @@ $(function(){ }); }); - \ No newline at end of file + diff --git a/mobile/skin/shop/basic/list.10.skin.php b/mobile/skin/shop/basic/list.10.skin.php index 4d40fbfcc..5177a7abe 100644 --- a/mobile/skin/shop/basic/list.10.skin.php +++ b/mobile/skin/shop/basic/list.10.skin.php @@ -13,7 +13,7 @@ add_javascript('', 10); - + diff --git a/mobile/skin/shop/basic/main.10.skin.php b/mobile/skin/shop/basic/main.10.skin.php index d9195e172..4c2e2728d 100644 --- a/mobile/skin/shop/basic/main.10.skin.php +++ b/mobile/skin/shop/basic/main.10.skin.php @@ -13,7 +13,7 @@ add_javascript('', 10); - + diff --git a/mobile/skin/shop/basic/main.20.skin.php b/mobile/skin/shop/basic/main.20.skin.php index fc034d32d..a81e64f63 100644 --- a/mobile/skin/shop/basic/main.20.skin.php +++ b/mobile/skin/shop/basic/main.20.skin.php @@ -14,7 +14,7 @@ add_javascript('', 10); - + diff --git a/mobile/skin/shop/basic/main.30.skin.php b/mobile/skin/shop/basic/main.30.skin.php index 12f010d4d..69a273f19 100644 --- a/mobile/skin/shop/basic/main.30.skin.php +++ b/mobile/skin/shop/basic/main.30.skin.php @@ -14,7 +14,7 @@ add_javascript('', 10); - +
diff --git a/mobile/skin/shop/basic/relation.10.skin.php b/mobile/skin/shop/basic/relation.10.skin.php index 0a293d723..1d7e7f71c 100644 --- a/mobile/skin/shop/basic/relation.10.skin.php +++ b/mobile/skin/shop/basic/relation.10.skin.php @@ -11,7 +11,7 @@ add_stylesheet('', - + diff --git a/mobile/skin/social/social_login.skin.php b/mobile/skin/social/social_login.skin.php index e52419688..8044ead44 100644 --- a/mobile/skin/social/social_login.skin.php +++ b/mobile/skin/social/social_login.skin.php @@ -14,7 +14,7 @@ if( G5_SOCIAL_USE_POPUP ) { $self_url = G5_SOCIAL_LOGIN_URL.'/popup.php'; } -add_stylesheet('', 10); +add_stylesheet('', 10); ?>