게시판·1:1문의·쇼핑몰분류 상단/하단 파일 경로 처리 보강

콘텐츠와 동일하게, 게시판(bo_include)·1:1문의(qa_include)·쇼핑몰 분류
(ca_include)의 상단/하단 파일 경로도 저장 전에 먼저 정규화한 뒤 확장자와
위치를 검증하도록 순서를 조정한다. 정규화 과정에서 확장자가 사라져 검증을
우회하던 경로를 차단하고, data 디렉터리로 귀결되는 경로를 거부한다.

실행 직전 board_head/board_tail, qahead/qatail, shop 목록·상품 페이지에서도
승인 확장자와 data 디렉터리 여부를 다시 확인하는 is_content_include_allowed()
를 적용한다. 이미 저장된 경로도 실행 시 재확인된다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 0c6bca704725ebea2a584d4c06366282e9bc36d6)
This commit is contained in:
thisgun
2026-08-24 07:44:22 +00:00
parent 6571119db6
commit 96df43480b
9 changed files with 53 additions and 19 deletions
+12 -3
View File
@@ -62,6 +62,12 @@ if ($check_captcha) {
}
}
// 저장·검증 전에 경로를 먼저 정규화하여, 검증 이후 경로가 달라지지 않도록 한다.
if (function_exists('filter_input_include_path')) {
$bo_include_head = filter_input_include_path($bo_include_head);
$bo_include_tail = filter_input_include_path($bo_include_tail);
}
if ($file = $bo_include_head) {
$file_ext = pathinfo($file, PATHINFO_EXTENSION);
@@ -86,9 +92,12 @@ if (!is_include_path_check($bo_include_tail, 1)) {
alert('하단 파일 경로에 포함시킬수 없는 문자열이 있습니다.');
}
if (function_exists('filter_input_include_path')) {
$bo_include_head = filter_input_include_path($bo_include_head);
$bo_include_tail = filter_input_include_path($bo_include_tail);
if ($bo_include_head && function_exists('is_content_include_allowed') && !is_content_include_allowed($bo_include_head)) {
alert('상단 파일 경로로 사용할 수 없는 위치입니다.');
}
if ($bo_include_tail && function_exists('is_content_include_allowed') && !is_content_include_allowed($bo_include_tail)) {
alert('하단 파일 경로로 사용할 수 없는 위치입니다.');
}
$board_path = G5_DATA_PATH . '/file/' . $bo_table;
+12 -3
View File
@@ -36,6 +36,12 @@ if ($board && ($qaconfig['qa_include_head'] !== $qa_include_head || $qaconfig['q
}
}
// 저장·검증 전에 경로를 먼저 정규화하여, 검증 이후 경로가 달라지지 않도록 한다.
if (function_exists('filter_input_include_path')) {
$qa_include_head = filter_input_include_path($qa_include_head);
$qa_include_tail = filter_input_include_path($qa_include_tail);
}
if ($qa_include_head) {
$file_ext = pathinfo($qa_include_head, PATHINFO_EXTENSION);
@@ -62,9 +68,12 @@ if ($qa_include_tail && !is_include_path_check($qa_include_tail, 1)) {
$error_msg = '/data/file/ 또는 /data/editor/ 포함된 문자를 하단 파일 경로에 포함시킬수 없습니다.';
}
if (function_exists('filter_input_include_path')) {
$qa_include_head = filter_input_include_path($qa_include_head);
$qa_include_tail = filter_input_include_path($qa_include_tail);
if ($qa_include_head && function_exists('is_content_include_allowed') && !is_content_include_allowed($qa_include_head)) {
alert('상단 파일 경로로 사용할 수 없는 위치입니다.');
}
if ($qa_include_tail && function_exists('is_content_include_allowed') && !is_content_include_allowed($qa_include_tail)) {
alert('하단 파일 경로로 사용할 수 없는 위치입니다.');
}
// 분류에 & 나 = 는 사용이 불가하므로 2바이트로 바꾼다.
+13 -5
View File
@@ -6,6 +6,11 @@ auth_check_menu($auth, $sub_menu, "w");
$ca_include_head = isset($_POST['ca_include_head']) ? trim($_POST['ca_include_head']) : '';
$ca_include_tail = isset($_POST['ca_include_tail']) ? trim($_POST['ca_include_tail']) : '';
// 저장·검증 전에 경로를 먼저 정규화하여, 검증 이후 경로가 달라지지 않도록 한다.
if( function_exists('filter_input_include_path') ){
$ca_include_head = filter_input_include_path($ca_include_head);
$ca_include_tail = filter_input_include_path($ca_include_tail);
}
$ca_id = isset($_REQUEST['ca_id']) ? preg_replace('/[^0-9a-z]/i', '', $_REQUEST['ca_id']) : '';
if( ! $ca_id ){
@@ -93,6 +98,14 @@ if(!is_include_path_check($ca_include_tail, 1)) {
alert('하단 파일 경로에 포함시킬수 없는 문자열이 있습니다.');
}
if($ca_include_head && function_exists('is_content_include_allowed') && !is_content_include_allowed($ca_include_head)) {
alert('상단 파일 경로로 사용할 수 없는 위치입니다.');
}
if($ca_include_tail && function_exists('is_content_include_allowed') && !is_content_include_allowed($ca_include_tail)) {
alert('하단 파일 경로로 사용할 수 없는 위치입니다.');
}
$check_keys = array('ca_skin_dir', 'ca_mobile_skin_dir', 'ca_skin', 'ca_mobile_skin');
foreach( $check_keys as $key ){
@@ -101,11 +114,6 @@ foreach( $check_keys as $key ){
}
}
if( function_exists('filter_input_include_path') ){
$ca_include_head = filter_input_include_path($ca_include_head);
$ca_include_tail = filter_input_include_path($ca_include_tail);
}
if ($w == "u" || $w == "d")
check_demo();
+2 -1
View File
@@ -9,7 +9,8 @@ if (G5_IS_MOBILE) {
} else {
// 상단 파일 경로를 입력하지 않았다면 기본 상단 파일도 include 하지 않음
if (trim($board['bo_include_head'])) {
if (is_include_path_check($board['bo_include_head'])) { //파일경로 체크
if (is_include_path_check($board['bo_include_head'])
&& (!function_exists('is_content_include_allowed') || is_content_include_allowed($board['bo_include_head']))) { //파일경로 체크
@include ($board['bo_include_head']);
} else { //파일경로가 올바르지 않으면 기본파일을 가져옴
include_once(G5_BBS_PATH.'/_head.php');
+2 -1
View File
@@ -10,7 +10,8 @@ if (G5_IS_MOBILE) {
echo run_replace('board_content_tail', html_purifier(stripslashes($board['bo_content_tail'])), $board);
// 하단 파일 경로를 입력하지 않았다면 기본 하단 파일도 include 하지 않음
if (trim($board['bo_include_tail'])) {
if (is_include_path_check($board['bo_include_tail'])) { //파일경로 체크
if (is_include_path_check($board['bo_include_tail'])
&& (!function_exists('is_content_include_allowed') || is_content_include_allowed($board['bo_include_tail']))) { //파일경로 체크
@include ($board['bo_include_tail']);
} else { //파일경로가 올바르지 않으면 기본파일을 가져옴
include_once(G5_BBS_PATH.'/_tail.php');
+2 -1
View File
@@ -9,7 +9,8 @@ if (G5_IS_MOBILE) {
include_once('./_head.php');
echo run_replace('qa_mobile_content_head', conv_content($qaconfig['qa_mobile_content_head'], 1), $qaconfig);
} else {
if($qaconfig['qa_include_head'] && is_include_path_check($qaconfig['qa_include_head']))
if($qaconfig['qa_include_head'] && is_include_path_check($qaconfig['qa_include_head'])
&& (!function_exists('is_content_include_allowed') || is_content_include_allowed($qaconfig['qa_include_head'])))
@include ($qaconfig['qa_include_head']);
else
include ('./_head.php');
+2 -1
View File
@@ -7,7 +7,8 @@ if (G5_IS_MOBILE) {
include_once('./_tail.php');
} else {
echo run_replace('qa_content_tail', conv_content($qaconfig['qa_content_tail'], 1), $qaconfig);
if($qaconfig['qa_include_tail'] && is_include_path_check($qaconfig['qa_include_tail']))
if($qaconfig['qa_include_tail'] && is_include_path_check($qaconfig['qa_include_tail'])
&& (!function_exists('is_content_include_allowed') || is_content_include_allowed($qaconfig['qa_include_tail'])))
@include ($qaconfig['qa_include_tail']);
else
include ('./_tail.php');
+4 -2
View File
@@ -107,7 +107,8 @@ define('G5_SHOP_CSS_URL', str_replace(G5_PATH, G5_URL, $skin_dir));
$g5['title'] = $it['it_name'].' &gt; '.$it['ca_name'];
// 분류 상단 코드가 있으면 출력하고 없으면 기본 상단 코드 출력
if ($ca['ca_include_head'] && is_include_path_check($ca['ca_include_head']))
if ($ca['ca_include_head'] && is_include_path_check($ca['ca_include_head'])
&& (!function_exists('is_content_include_allowed') || is_content_include_allowed($ca['ca_include_head'])))
@include_once($ca['ca_include_head']);
else
include_once(G5_SHOP_PATH.'/_head.php');
@@ -280,7 +281,8 @@ echo run_replace('shop_it_tail_html', conv_content($it['it_tail_html'], 1), $it)
?>
<?php
if ($ca['ca_include_tail'] && is_include_path_check($ca['ca_include_tail']))
if ($ca['ca_include_tail'] && is_include_path_check($ca['ca_include_tail'])
&& (!function_exists('is_content_include_allowed') || is_content_include_allowed($ca['ca_include_tail'])))
@include_once($ca['ca_include_tail']);
else
include_once(G5_SHOP_PATH.'/_tail.php');
+4 -2
View File
@@ -50,7 +50,8 @@ if(!$is_admin && $config['cf_cert_use']) {
$g5['title'] = $ca['ca_name'].' 상품리스트';
if ($ca['ca_include_head'] && is_include_path_check($ca['ca_include_head']))
if ($ca['ca_include_head'] && is_include_path_check($ca['ca_include_head'])
&& (!function_exists('is_content_include_allowed') || is_content_include_allowed($ca['ca_include_head'])))
@include_once($ca['ca_include_head']);
else
include_once(G5_SHOP_PATH.'/_head.php');
@@ -174,7 +175,8 @@ var itemlist_ca_id = "<?php echo $ca_id; ?>";
<!-- } 상품 목록 끝 -->
<?php
if ($ca['ca_include_tail'] && is_include_path_check($ca['ca_include_tail']))
if ($ca['ca_include_tail'] && is_include_path_check($ca['ca_include_tail'])
&& (!function_exists('is_content_include_allowed') || is_content_include_allowed($ca['ca_include_tail'])))
@include_once($ca['ca_include_tail']);
else
include_once(G5_SHOP_PATH.'/_tail.php');