security: KVE-2026-2330 비회원 게시물 수정 인증과 토큰 발급 검증 강화

기존 비밀번호 확인 이력을 게시판·게시물·비밀번호에 결합하고 저장 전 검증한다. 인증은 30분 후 만료되며 저장 후 폐기한다. 일반 쓰기 토큰과 답변 세션으로 수정 인증을 대체할 수 없도록 한다.

1:1문의 토큰 발급을 로그인 회원의 해당 용도로 제한하고 게시판 토큰의 요청 경로와 대상을 확인한다. 최고관리자의 그누보드4 자료 이전은 유지한다.

검증: 비인증 수정 차단, 정상 비밀번호 변경 및 인증 폐기, 토큰 발급 엔드포인트 격리 검증과 PHP 문법 검사 통과.
This commit is contained in:
whitedot committed 2026-09-09 07:58:16 +00:00
1 parent fcc749bde3
commit 65875b7f35
5 files changed
+77 -37

No files matched your search

+8 -6
View File
@@ -1,10 +1,12 @@
<?php
include_once('./_common.php');
$token_case = isset($_POST['token_case']) ? preg_replace('/[^0-9a-z_]/i', '', $_POST['token_case']) : '';
if( $token_case ){
$token = _token();
set_session('ss_'.$token_case.'_token', $token);
die(json_encode(array('error'=>'', 'token'=>$token, 'url'=>'')));
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !$is_member ||
!isset($_POST['token_case']) || $_POST['token_case'] !== 'qa_write') {
die(json_encode(array('error'=>'올바른 방법으로 이용해 주십시오.', 'token'=>'', 'url'=>'')));
}
check_request_origin();
$token = _token();
set_session('ss_qa_write_token', $token);
die(json_encode(array('error'=>'', 'token'=>$token, 'url'=>'')));
+3
View File
@@ -309,12 +309,15 @@ if ($w == '') {
$password_required = 'required';
} else if ($w == 'u') {
$password_required = '';
set_session(g5_write_edit_auth_key($bo_table, $wr_id), '');
if (!$is_admin) {
if (!($is_member && $member['mb_id'] === $write['mb_id'])) {
if (!check_password($wr_password, $write['wr_password'])) {
$is_wrong = run_replace('invalid_password', false, 'write', $write);
if(!$is_wrong) alert('비밀번호가 틀립니다.');
} else if (empty($write['mb_id'])) {
g5_grant_write_edit_auth($bo_table, $write);
}
}
}
+6 -1
View File
@@ -1,9 +1,14 @@
<?php
include_once('./_common.php');
if(!$bo_table)
// 그누보드4 자료 이전은 최고관리자에게만 허용되는 별도 용도이다.
$is_import_token = $bo_table === 'g4_import' && $is_admin === 'super';
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !$bo_table ||
(!$is_import_token && (empty($board['bo_table']) || $board['bo_table'] !== $bo_table)))
die(json_encode(array('error'=>'게시판 정보가 올바르지 않습니다.', 'url'=>G5_URL)));
check_request_origin();
set_session('ss_write_'.$bo_table.'_token', '');
$token = get_write_token($bo_table);
+35 -28
View File
@@ -96,6 +96,39 @@ if ($w == 'u' || $w == 'r') {
}
}
// 수정 권한은 스킨 및 저장 처리 전에 검증한다.
if ($w == 'u') {
if (($is_member || $is_admin) && (get_session('ss_bo_table') != $bo_table || get_session('ss_wr_id') != $wr_id)) {
alert('올바른 방법으로 수정하여 주십시오.', get_pretty_url($bo_table));
}
$return_url = get_pretty_url($bo_table, $wr_id);
if ($is_admin == 'super') // 최고관리자 통과
;
else if ($is_admin == 'group') { // 그룹관리자
$mb = get_member($wr['mb_id']);
if ($member['mb_id'] != $group['gr_admin']) // 자신이 관리하는 그룹인가?
alert('자신이 관리하는 그룹의 게시판이 아니므로 수정할 수 없습니다.', $return_url);
else if ($member['mb_level'] < $mb['mb_level']) // 자신의 레벨이 크거나 같다면 통과
alert('자신의 권한보다 높은 권한의 회원이 작성한 글은 수정할 수 없습니다.', $return_url);
} else if ($is_admin == 'board') { // 게시판관리자이면
$mb = get_member($wr['mb_id']);
if ($member['mb_id'] != $board['bo_admin']) // 자신이 관리하는 게시판인가?
alert('자신이 관리하는 게시판이 아니므로 수정할 수 없습니다.', $return_url);
else if ($member['mb_level'] < $mb['mb_level']) // 자신의 레벨이 크거나 같다면 통과
alert('자신의 권한보다 높은 권한의 회원이 작성한 글은 수정할 수 없습니다.', $return_url);
} else if ($member['mb_id']) {
if ($member['mb_id'] != $wr['mb_id'])
alert('자신의 글이 아니므로 수정할 수 없습니다.', $return_url);
} else {
if (!g5_has_write_edit_auth($bo_table, $wr))
alert('비밀번호 확인 후 다시 수정하여 주십시오.', $return_url);
if ($wr['mb_id'])
alert('로그인 후 수정하세요.', G5_BBS_URL.'/login.php?url='.urlencode($return_url));
}
}
// 외부에서 글을 등록할 수 있는 버그가 존재하므로 비밀글은 사용일 경우에만 가능해야 함
if (!$is_admin && !$board['bo_use_secret'] && (stripos($_POST['html'], 'secret') !== false || stripos($_POST['secret'], 'secret') !== false || stripos($_POST['mail'], 'secret') !== false)) {
alert('비밀글 미사용 게시판 이므로 비밀글로 등록할 수 없습니다.');
@@ -335,34 +368,6 @@ if ($w == '' || $w == 'r') {
insert_point($member['mb_id'], $board['bo_comment_point'], "{$board['bo_subject']} {$wr_id} 글답변", $bo_table, $wr_id, '쓰기');
}
} else if ($w == 'u') {
if (get_session('ss_bo_table') != $_POST['bo_table'] || get_session('ss_wr_id') != $_POST['wr_id']) {
alert('올바른 방법으로 수정하여 주십시오.', get_pretty_url($bo_table));
}
$return_url = get_pretty_url($bo_table, $wr_id);
if ($is_admin == 'super') // 최고관리자 통과
;
else if ($is_admin == 'group') { // 그룹관리자
$mb = get_member($write['mb_id']);
if ($member['mb_id'] != $group['gr_admin']) // 자신이 관리하는 그룹인가?
alert('자신이 관리하는 그룹의 게시판이 아니므로 수정할 수 없습니다.', $return_url);
else if ($member['mb_level'] < $mb['mb_level']) // 자신의 레벨이 크거나 같다면 통과
alert('자신의 권한보다 높은 권한의 회원이 작성한 글은 수정할 수 없습니다.', $return_url);
} else if ($is_admin == 'board') { // 게시판관리자이면
$mb = get_member($write['mb_id']);
if ($member['mb_id'] != $board['bo_admin']) // 자신이 관리하는 게시판인가?
alert('자신이 관리하는 게시판이 아니므로 수정할 수 없습니다.', $return_url);
else if ($member['mb_level'] < $mb['mb_level']) // 자신의 레벨이 크거나 같다면 통과
alert('자신의 권한보다 높은 권한의 회원이 작성한 글은 수정할 수 없습니다.', $return_url);
} else if ($member['mb_id']) {
if ($member['mb_id'] != $write['mb_id'])
alert('자신의 글이 아니므로 수정할 수 없습니다.', $return_url);
} else {
if ($write['mb_id'])
alert('로그인 후 수정하세요.', G5_BBS_URL.'/login.php?url='.urlencode($return_url));
}
if ($member['mb_id']) {
// 자신의 글이라면
if ($member['mb_id'] === $wr['mb_id']) {
@@ -428,6 +433,8 @@ if ($w == '' || $w == 'r') {
where wr_id = '{$wr['wr_id']}' ";
sql_query($sql);
set_session(g5_write_edit_auth_key($bo_table, $wr_id), '');
// 분류가 수정되는 경우 해당되는 코멘트의 분류명도 모두 수정함
// 코멘트의 분류를 수정하지 않으면 검색이 제대로 되지 않음
$sql = " update {$write_table} set ca_name = '{$ca_name}' where wr_parent = '{$wr['wr_id']}' ";
+25 -2
View File
@@ -4691,7 +4691,29 @@ function get_sql_affected_rows($link=null)
}
}
// 불법접근을 막도록 토큰을 생성하면서 토큰값을 리턴
// 비회원의 기존 비밀번호 확인 이력을 게시판과 게시물에 결합한다.
function g5_write_edit_auth_key($bo_table, $wr_id)
{
return 'ss_write_edit_'.hash('sha256', $bo_table.':'.(int)$wr_id);
}
function g5_grant_write_edit_auth($bo_table, $write)
{
set_session(g5_write_edit_auth_key($bo_table, $write['wr_id']), array(
'expires' => G5_SERVER_TIME + 1800,
'password' => hash('sha256', $write['wr_password'])
));
}
function g5_has_write_edit_auth($bo_table, $write)
{
$auth = get_session(g5_write_edit_auth_key($bo_table, $write['wr_id']));
return empty($write['mb_id']) && is_array($auth) && isset($auth['expires'], $auth['password'])
&& $auth['expires'] >= G5_SERVER_TIME
&& slow_equals(hash('sha256', $write['wr_password']), $auth['password']);
}
// 글쓰기 요청의 CSRF 토큰을 발급한다. 수정 권한은 별도로 확인한다.
function get_write_token($bo_table)
{
$token = get_random_token_string(16);
@@ -4710,7 +4732,8 @@ function check_write_token($bo_table)
$token = get_session('ss_write_'.$bo_table.'_token');
set_session('ss_write_'.$bo_table.'_token', '');
if(!$token || !$_REQUEST['token'] || $token != $_REQUEST['token'])
if (!is_string($token) || !$token || !isset($_POST['token']) || !is_string($_POST['token'])
|| !slow_equals($token, $_POST['token']))
alert('올바른 방법으로 이용해 주십시오.', G5_URL);
return true;