[KVE-2026-1806] 콘텐츠 include 경로 및 미완료주문 출력 처리 보강
콘텐츠 상단/하단 파일 경로를 저장 전에 먼저 정규화한 뒤 확장자와 위치를 검증하도록 순서를 조정한다. 정규화 과정에서 확장자가 사라져 검증을 우회하던 경로를 차단한다. 실행 직전 bbs/content.php 에서도 승인 확장자와 data 디렉터리 귀결 여부를 다시 확인하는 is_content_include_allowed() 를 적용하고, 콘텐츠 상단/하단 이미지 업로드는 이미지 파일만 허용하도록 검사한다. 미완료주문 상세의 결제방법 출력에 get_text() 인코딩을 적용하고, 임시주문 저장 시 정제한 결제수단 값을 원본에도 반영한다. 개인결제 주문 세션은 결제정보 검증을 통과한 뒤에만 설정한다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 196255d76912531d7e13861821bd59ca15c967d1)
This commit is contained in:
@@ -40,6 +40,12 @@ if ($is_admin !== 'super') {
|
||||
$co_include_tail = '';
|
||||
}
|
||||
}
|
||||
// 저장·검증 전에 경로를 먼저 정규화하여, 검증 이후 경로가 달라지지 않도록 한다.
|
||||
if (function_exists('filter_input_include_path')) {
|
||||
$co_include_head = filter_input_include_path($co_include_head);
|
||||
$co_include_tail = filter_input_include_path($co_include_tail);
|
||||
}
|
||||
|
||||
$co_tag_filter_use = isset($_POST['co_tag_filter_use']) ? (int) $_POST['co_tag_filter_use'] : 1;
|
||||
$co_himg_del = (isset($_POST['co_himg_del']) && $_POST['co_himg_del']) ? 1 : 0;
|
||||
$co_timg_del = (isset($_POST['co_timg_del']) && $_POST['co_timg_del']) ? 1 : 0;
|
||||
@@ -86,6 +92,14 @@ if ($co_include_tail) {
|
||||
}
|
||||
}
|
||||
|
||||
if ($co_include_head && function_exists('is_content_include_allowed') && !is_content_include_allowed($co_include_head)) {
|
||||
alert('상단 파일 경로로 사용할 수 없는 위치입니다.');
|
||||
}
|
||||
|
||||
if ($co_include_tail && function_exists('is_content_include_allowed') && !is_content_include_allowed($co_include_tail)) {
|
||||
alert('하단 파일 경로로 사용할 수 없는 위치입니다.');
|
||||
}
|
||||
|
||||
if ($co_include_head && !is_include_path_check($co_include_head, 1)) {
|
||||
$co_include_head = '';
|
||||
$error_msg = '/data/file/ 또는 /data/editor/ 포함된 문자를 상단 파일 경로에 포함시킬수 없습니다.';
|
||||
@@ -96,11 +110,6 @@ if ($co_include_tail && !is_include_path_check($co_include_tail, 1)) {
|
||||
$error_msg = '/data/file/ 또는 /data/editor/ 포함된 문자를 하단 파일 경로에 포함시킬수 없습니다.';
|
||||
}
|
||||
|
||||
if (function_exists('filter_input_include_path')) {
|
||||
$co_include_head = filter_input_include_path($co_include_head);
|
||||
$co_include_tail = filter_input_include_path($co_include_tail);
|
||||
}
|
||||
|
||||
$co_seo_title = exist_seo_title_recursive('content', generate_seo_title($co_subject), $g5['content_table'], $co_id);
|
||||
|
||||
$sql_common = " co_include_head = '$co_include_head',
|
||||
@@ -147,6 +156,20 @@ if (function_exists('get_admin_captcha_by')) {
|
||||
g5_delete_cache_by_prefix('content-' . $co_id . '-');
|
||||
|
||||
if ($w == "" || $w == "u") {
|
||||
foreach (array('co_himg', 'co_timg') as $content_img_field) {
|
||||
if (empty($_FILES[$content_img_field]['name']) || empty($_FILES[$content_img_field]['tmp_name']))
|
||||
continue;
|
||||
|
||||
// 상단/하단 이미지 슬롯에는 이미지만 허용하고, 실행 가능한 태그가 담긴 파일은 거부한다.
|
||||
$tmp_file = $_FILES[$content_img_field]['tmp_name'];
|
||||
$is_valid_image = @getimagesize($tmp_file);
|
||||
$head_bytes = @file_get_contents($tmp_file, false, null, 0, 8192);
|
||||
|
||||
if ($is_valid_image === false || ($head_bytes !== false && preg_match('/<\?php|<\?=|<\?|<script/i', $head_bytes))) {
|
||||
alert('상단/하단 이미지에는 이미지 파일만 등록할 수 있습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($_FILES['co_himg']['name']) {
|
||||
$dest_path = G5_DATA_PATH . "/content/" . $co_id . "_h";
|
||||
@move_uploaded_file($_FILES['co_himg']['tmp_name'], $dest_path);
|
||||
|
||||
@@ -367,7 +367,7 @@ $pg_anchor = '<ul class="anchor">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><?php echo $od['od_id']; ?></td>
|
||||
<td class="td_paybybig"><?php echo $s_receipt_way; ?></td>
|
||||
<td class="td_paybybig"><?php echo get_text($s_receipt_way); ?></td>
|
||||
<td class="td_numbig td_numsum"><?php echo display_price($amount['order']); ?></td>
|
||||
<td class="td_numbig"><?php echo display_price($od_send_cost + $od_send_cost2); ?></td>
|
||||
<td class="td_numbig"><?php echo display_point($od_temp_point); ?></td>
|
||||
|
||||
+4
-2
@@ -31,7 +31,8 @@ if (! (isset($co['co_id']) && $co['co_id']))
|
||||
|
||||
$g5['title'] = $co['co_subject'];
|
||||
|
||||
if ($co['co_include_head'] && is_include_path_check($co['co_include_head']))
|
||||
if ($co['co_include_head'] && is_include_path_check($co['co_include_head'])
|
||||
&& (!function_exists('is_content_include_allowed') || is_content_include_allowed($co['co_include_head'])))
|
||||
@include_once($co['co_include_head']);
|
||||
else
|
||||
include_once('./_head.php');
|
||||
@@ -99,7 +100,8 @@ if(is_file($skin_file)) {
|
||||
echo '<p>'.str_replace(G5_PATH.'/', '', $skin_file).'이 존재하지 않습니다.</p>';
|
||||
}
|
||||
|
||||
if ($co['co_include_tail'] && is_include_path_check($co['co_include_tail']))
|
||||
if ($co['co_include_tail'] && is_include_path_check($co['co_include_tail'])
|
||||
&& (!function_exists('is_content_include_allowed') || is_content_include_allowed($co['co_include_tail'])))
|
||||
@include_once($co['co_include_tail']);
|
||||
else
|
||||
include_once('./_tail.php');
|
||||
@@ -4853,6 +4853,33 @@ function filter_input_include_path($path){
|
||||
return str_replace('//', '/', strip_tags($path));
|
||||
}
|
||||
|
||||
// 콘텐츠 상단/하단 include 대상이 템플릿 파일인지 실행 직전 재확인한다.
|
||||
// 승인 확장자만 허용하고, 업로드가 저장되는 data 디렉터리로 귀결되는 경로는 거부한다.
|
||||
function is_content_include_allowed($path){
|
||||
if( !is_string($path) || $path === '' ){
|
||||
return false;
|
||||
}
|
||||
|
||||
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
|
||||
if( !in_array($ext, array('php', 'htm', 'html')) ){
|
||||
return false;
|
||||
}
|
||||
|
||||
$real = @realpath($path);
|
||||
if( $real !== false && defined('G5_DATA_PATH') ){
|
||||
$data_real = @realpath(G5_DATA_PATH);
|
||||
if( $data_real !== false ){
|
||||
$real_norm = str_replace('\\', '/', $real);
|
||||
$data_norm = rtrim(str_replace('\\', '/', $data_real), '/').'/';
|
||||
if( strpos($real_norm, $data_norm) === 0 ){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function option_array_checked($option, $arr=array()){
|
||||
$checked = '';
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ sql_query($sql);
|
||||
*/
|
||||
|
||||
$od_settle_case = isset($_POST['od_settle_case']) ? clean_xss_tags($_POST['od_settle_case'], 1, 1) : '';
|
||||
// 저장되는 원본에도 정제한 값을 반영한다.
|
||||
$_POST['od_settle_case'] = $od_settle_case;
|
||||
|
||||
if(isset($_POST['pp_id']) && $_POST['pp_id']) {
|
||||
$od_id = get_session('ss_personalpay_id');
|
||||
|
||||
@@ -16,8 +16,6 @@ if($default['de_pg_service'] == 'lg' && ! $post_lgd_paykey)
|
||||
if($default['de_pg_service'] == 'toss' && ! $paymentKey)
|
||||
alert('결제등록 요청 후 주문해 주십시오.', $page_return_url);
|
||||
|
||||
set_session('ss_order_id', $pp_id);
|
||||
|
||||
// 개인결제 정보
|
||||
$pp_check = false;
|
||||
$sql = " select * from {$g5['g5_shop_personalpay_table']} where pp_id = '{$pp_id}' and pp_use = '1' ";
|
||||
@@ -32,6 +30,9 @@ $hash_data = md5($pp_id.$good_mny.$pp['pp_time']);
|
||||
if($pp_id !== get_session('ss_personalpay_id') || $hash_data !== get_session('ss_personalpay_hash'))
|
||||
die('개인결제 정보가 올바르지 않습니다.');
|
||||
|
||||
// 개인결제 정보 검증을 통과한 뒤에만 주문 세션을 설정한다.
|
||||
set_session('ss_order_id', $pp_id);
|
||||
|
||||
// PG사의 가상계좌 또는 계좌이체의 자동 현금영수증 초기배열값
|
||||
$pg_receipt_infos = array(
|
||||
'od_cash' => 0,
|
||||
|
||||
Reference in New Issue
Block a user