security: KVE-2026-2269 KVE-2026-2331 보안 메일 링크를 고정 도메인으로 제한
검증된 G5_DOMAIN만 인증 및 수신 거부 링크에 사용하고 요청 Host로 대체하지 않는다. 설정 오류 시 인증값과 관련 회원정보를 변경하기 전에 발급을 중단한다. 검증: Host 변조 및 고정 주소 미설정·오류의 격리 메일 검증, PHP 문법 검사 통과.
This commit is contained in:
@@ -8,6 +8,8 @@ $html_title = '회원메일 발송';
|
||||
|
||||
check_demo();
|
||||
|
||||
$security_mail_url = g5_require_security_mail_url();
|
||||
|
||||
check_admin_token();
|
||||
|
||||
require_once './admin.head.php';
|
||||
@@ -59,7 +61,7 @@ for ($i = 0; $i < count($member_list); $i++) {
|
||||
$content = preg_replace("/{회원아이디}/", $mb_id, (string)$content);
|
||||
$content = preg_replace("/{이메일}/", $to_email, (string)$content);
|
||||
|
||||
$content = $content . "<hr size=0><p><span style='font-size:9pt; font-family:굴림'>▶ 더 이상 정보 수신을 원치 않으시면 [<a href='" . G5_BBS_URL . "/email_stop.php?mb_id={$mb_id}&mb_md5={$mb_md5}' target='_blank'>수신거부</a>] 해 주십시오.</span></p>";
|
||||
$content = $content . "<hr size=0><p><span style='font-size:9pt; font-family:굴림'>▶ 더 이상 정보 수신을 원치 않으시면 [<a href='" . ($security_mail_url . '/' . G5_BBS_DIR) . "/email_stop.php?mb_id={$mb_id}&mb_md5={$mb_md5}' target='_blank'>수신거부</a>] 해 주십시오.</span></p>";
|
||||
|
||||
mailer($config['cf_admin_email_name'], $config['cf_admin_email'], $to_email, $subject, $content, 1);
|
||||
|
||||
|
||||
+3
-1
@@ -12,6 +12,8 @@ auth_check_menu($auth, $sub_menu, 'w');
|
||||
|
||||
check_demo();
|
||||
|
||||
$security_mail_url = g5_require_security_mail_url();
|
||||
|
||||
$g5['title'] = '회원메일 테스트';
|
||||
|
||||
$name = get_text($member['mb_name']);
|
||||
@@ -33,7 +35,7 @@ $content = preg_replace("/{이메일}/", $email, (string)$content);
|
||||
|
||||
$mb_md5 = md5($member['mb_id'] . $member['mb_email'] . $member['mb_datetime']);
|
||||
|
||||
$content = $content . '<p>더 이상 정보 수신을 원치 않으시면 [<a href="' . G5_BBS_URL . '/email_stop.php?mb_id=' . $mb_id . '&mb_md5=' . $mb_md5 . '" target="_blank">수신거부</a>] 해 주십시오.</p>';
|
||||
$content = $content . '<p>더 이상 정보 수신을 원치 않으시면 [<a href="' . ($security_mail_url . '/' . G5_BBS_DIR) . '/email_stop.php?mb_id=' . $mb_id . '&mb_md5=' . $mb_md5 . '" target="_blank">수신거부</a>] 해 주십시오.</p>';
|
||||
|
||||
mailer($config['cf_title'], $member['mb_email'], $member['mb_email'], $subject, $content, 1);
|
||||
|
||||
|
||||
@@ -20,6 +20,12 @@ if (!$email)
|
||||
// (이메일 열거 공격 방지)
|
||||
$generic_message = $email.' 메일로 회원아이디와 비밀번호를 인증할 수 있는 메일이 발송 되었습니다.\\n\\n메일을 확인하여 주십시오.';
|
||||
|
||||
$security_mail_url = g5_security_mail_base_url();
|
||||
if ($security_mail_url === false) {
|
||||
error_log('[g5 security mail] Valid G5_DOMAIN is required.');
|
||||
alert_close($generic_message);
|
||||
}
|
||||
|
||||
$sql = " select count(*) as cnt from {$g5['member_table']} where mb_email = '$email' ";
|
||||
$row = sql_fetch($sql);
|
||||
if ($row['cnt'] > 1) {
|
||||
@@ -48,7 +54,7 @@ $sql = " update {$g5['member_table']} set mb_lost_certify = '$mb_nonce $mb_lost_
|
||||
sql_query($sql);
|
||||
|
||||
// 인증 링크 생성
|
||||
$href = G5_BBS_URL.'/password_lost_certify.php?mb_no='.$mb['mb_no'].'&mb_nonce='.$mb_nonce;
|
||||
$href = $security_mail_url.'/'.G5_BBS_DIR.'/password_lost_certify.php?mb_no='.$mb['mb_no'].'&mb_nonce='.$mb_nonce;
|
||||
|
||||
$subject = "[".$config['cf_title']."] 요청하신 회원정보 찾기 안내 메일입니다.";
|
||||
|
||||
@@ -60,7 +66,7 @@ $content .= '<h1 style="padding:30px 30px 0;background:#f7f7f7;color:#555;font-s
|
||||
$content .= '회원정보 찾기 안내';
|
||||
$content .= '</h1>';
|
||||
$content .= '<span style="display:block;padding:10px 30px 30px;background:#f7f7f7;text-align:right">';
|
||||
$content .= '<a href="'.G5_URL.'" target="_blank">'.$config['cf_title'].'</a>';
|
||||
$content .= '<a href="'.htmlspecialchars($security_mail_url, ENT_QUOTES, 'UTF-8').'" target="_blank">'.$config['cf_title'].'</a>';
|
||||
$content .= '</span>';
|
||||
$content .= '<p style="margin:20px 0 0;padding:30px 30px 30px;border-bottom:1px solid #eee;line-height:1.7em">';
|
||||
$content .= addslashes($mb['mb_name'])." (".addslashes($mb['mb_nick']).")"." 회원님은 ".G5_TIME_YMDHIS." 에 회원정보 찾기 요청을 하셨습니다.<br>";
|
||||
|
||||
@@ -40,17 +40,19 @@ if ($row['cnt']) {
|
||||
alert("{$mb_email} 메일은 이미 존재하는 메일주소 입니다.\\n\\n다른 메일주소를 입력해 주십시오.");
|
||||
}
|
||||
|
||||
$security_mail_url = g5_require_security_mail_url();
|
||||
|
||||
// 인증메일 발송
|
||||
$subject = '['.$config['cf_title'].'] 인증확인 메일입니다.';
|
||||
|
||||
$mb_name = $mb['mb_name'];
|
||||
|
||||
// 어떠한 회원정보도 포함되지 않은 일회용 난수를 생성하여 인증에 사용 (CSPRNG 사용)
|
||||
$mb_md5 = get_email_certify_token();
|
||||
$mb_md5 = get_email_certify_token();
|
||||
|
||||
sql_query(" update {$g5['member_table']} set mb_email_certify2 = '$mb_md5' where mb_id = '{$esc_mb_id}' ");
|
||||
|
||||
$certify_href = G5_BBS_URL.'/email_certify.php?mb_id='.$mb_id.'&mb_md5='.$mb_md5;
|
||||
$certify_href = $security_mail_url.'/'.G5_BBS_DIR.'/email_certify.php?mb_id='.$mb_id.'&mb_md5='.$mb_md5;
|
||||
|
||||
ob_start();
|
||||
include_once ('./register_form_update_mail3.php');
|
||||
@@ -62,4 +64,4 @@ mailer($config['cf_admin_email_name'], $config['cf_admin_email'], $mb_email, $su
|
||||
$sql = " update {$g5['member_table']} set mb_email = '$mb_email' where mb_id = '{$esc_mb_id}' ";
|
||||
sql_query($sql);
|
||||
|
||||
alert("인증메일을 {$mb_email} 메일로 다시 보내 드렸습니다.\\n\\n잠시후 {$mb_email} 메일을 확인하여 주십시오.", G5_URL);
|
||||
alert("인증메일을 {$mb_email} 메일로 다시 보내 드렸습니다.\\n\\n잠시후 {$mb_email} 메일을 확인하여 주십시오.", G5_URL);
|
||||
|
||||
@@ -65,6 +65,10 @@ $mb_9 = isset($_POST['mb_9']) ? trim($_POST['mb_9'])
|
||||
$mb_10 = isset($_POST['mb_10']) ? trim($_POST['mb_10']) : "";
|
||||
$mb_name = addslashes(clean_xss_tags(stripslashes($mb_name), 1, 1));
|
||||
$mb_email = get_email_address($mb_email);
|
||||
$security_mail_url = g5_security_mail_base_url();
|
||||
if ($config['cf_use_email_certify'] && ($w == '' || $member['mb_email'] != $mb_email)) {
|
||||
$security_mail_url = g5_require_security_mail_url();
|
||||
}
|
||||
$mb_homepage = addslashes(clean_xss_tags(stripslashes($mb_homepage), 1, 1));
|
||||
$mb_tel = addslashes(clean_xss_tags(stripslashes($mb_tel), 1, 1));
|
||||
$mb_zip1 = preg_replace('/[^0-9]/', '', $mb_zip1);
|
||||
@@ -327,7 +331,7 @@ if ($w == '') {
|
||||
if ($config['cf_use_email_certify']) {
|
||||
$mb_md5 = get_email_certify_token();
|
||||
sql_query(" update {$g5['member_table']} set mb_email_certify2 = '$mb_md5' where mb_id = '$mb_id' ");
|
||||
$certify_href = G5_BBS_URL.'/email_certify.php?mb_id='.$mb_id.'&mb_md5='.$mb_md5;
|
||||
$certify_href = $security_mail_url.'/'.G5_BBS_DIR.'/email_certify.php?mb_id='.$mb_id.'&mb_md5='.$mb_md5;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
@@ -613,7 +617,7 @@ if ($config['cf_use_email_certify'] && $old_email != $mb_email) {
|
||||
|
||||
sql_query(" update {$g5['member_table']} set mb_email_certify2 = '$mb_md5' where mb_id = '$mb_id' ");
|
||||
|
||||
$certify_href = G5_BBS_URL.'/email_certify.php?mb_id='.$mb_id.'&mb_md5='.$mb_md5;
|
||||
$certify_href = $security_mail_url.'/'.G5_BBS_DIR.'/email_certify.php?mb_id='.$mb_id.'&mb_md5='.$mb_md5;
|
||||
|
||||
ob_start();
|
||||
include_once ('./register_form_update_mail3.php');
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
// 회원가입축하 메일 (회원님께 발송)
|
||||
if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
|
||||
$mail_site_url = g5_security_mail_base_url();
|
||||
?>
|
||||
|
||||
<!doctype html>
|
||||
@@ -18,7 +19,9 @@ if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
|
||||
회원가입을 축하합니다.
|
||||
</h1>
|
||||
<span style="display:block;padding:10px 30px 30px;background:#f7f7f7;text-align:right">
|
||||
<a href="<?php echo G5_URL ?>" target="_blank"><?php echo $config['cf_title'] ?></a>
|
||||
<?php if ($mail_site_url) { ?>
|
||||
<a href="<?php echo htmlspecialchars($mail_site_url, ENT_QUOTES, 'UTF-8') ?>" target="_blank"><?php echo $config['cf_title'] ?></a>
|
||||
<?php } else { echo $config['cf_title']; } ?>
|
||||
</span>
|
||||
<p style="margin:20px 0 0;padding:30px 30px 50px;min-height:200px;height:auto !important;height:200px;border-bottom:1px solid #eee">
|
||||
<b><?php echo $mb_name ?></b> 님의 회원가입을 진심으로 축하합니다.<br>
|
||||
@@ -31,7 +34,9 @@ if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
|
||||
<?php if ($config['cf_use_email_certify']) { ?>
|
||||
<a href="<?php echo $certify_href ?>" target="_blank" style="display:block;padding:30px 0;background:#484848;color:#fff;text-decoration:none;text-align:center">메일인증</a>
|
||||
<?php } else { ?>
|
||||
<a href="<?php echo G5_URL ?>" target="_blank" style="display:block;padding:30px 0;background:#484848;color:#fff;text-decoration:none;text-align:center">사이트바로가기</a>
|
||||
<?php if ($mail_site_url) { ?>
|
||||
<a href="<?php echo htmlspecialchars($mail_site_url, ENT_QUOTES, 'UTF-8') ?>" target="_blank" style="display:block;padding:30px 0;background:#484848;color:#fff;text-decoration:none;text-align:center">사이트바로가기</a>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
// 회원가입 메일 (관리자 메일로 발송)
|
||||
if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
|
||||
$mail_site_url = g5_security_mail_base_url();
|
||||
?>
|
||||
|
||||
<!doctype html>
|
||||
@@ -18,7 +19,9 @@ if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
|
||||
회원가입 알림 메일
|
||||
</h1>
|
||||
<span style="display:block;padding:10px 30px 30px;background:#f7f7f7;text-align:right">
|
||||
<a href="<?php echo G5_URL ?>" target="_blank"><?php echo $config['cf_title'] ?></a>
|
||||
<?php if ($mail_site_url) { ?>
|
||||
<a href="<?php echo htmlspecialchars($mail_site_url, ENT_QUOTES, 'UTF-8') ?>" target="_blank"><?php echo $config['cf_title'] ?></a>
|
||||
<?php } else { echo $config['cf_title']; } ?>
|
||||
</span>
|
||||
<p style="margin:20px 0 0;padding:30px 30px 50px;min-height:200px;height:auto !important;height:200px;border-bottom:1px solid #eee">
|
||||
<b><?php echo $mb_name ?></b> 님께서 회원가입 하셨습니다.<br>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
// E-mail 수정시 인증 메일 (회원님께 발송)
|
||||
if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
|
||||
$mail_site_url = g5_security_mail_base_url();
|
||||
?>
|
||||
|
||||
<!doctype html>
|
||||
@@ -18,7 +19,9 @@ if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
|
||||
회원 인증 메일입니다.
|
||||
</h1>
|
||||
<span style="display:block;padding:10px 30px 30px;background:#f7f7f7;text-align:right">
|
||||
<a href="<?php echo G5_URL ?>" target="_blank"><?php echo $config['cf_title'] ?></a>
|
||||
<?php if ($mail_site_url) { ?>
|
||||
<a href="<?php echo htmlspecialchars($mail_site_url, ENT_QUOTES, 'UTF-8') ?>" target="_blank"><?php echo $config['cf_title'] ?></a>
|
||||
<?php } else { echo $config['cf_title']; } ?>
|
||||
</span>
|
||||
<p style="margin:20px 0 0;padding:30px 30px 50px;min-height:200px;height:auto !important;height:200px;border-bottom:1px solid #eee">
|
||||
<?php if($w == 'u') { ?>
|
||||
|
||||
+47
-5
@@ -2696,11 +2696,53 @@ function get_email_cert_key($mb_id, $mb_datetime)
|
||||
return hash_hmac('sha256', $payload, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 발급 시각을 포함한 메일 인증용 일회용 토큰을 생성한다.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
// 보안 메일은 요청 헤더가 아닌 운영자가 지정한 공개 URL만 사용한다.
|
||||
function g5_security_mail_base_url($domain = null)
|
||||
{
|
||||
if ($domain === null) {
|
||||
$domain = defined('G5_DOMAIN') ? G5_DOMAIN : '';
|
||||
}
|
||||
if (!is_string($domain) || $domain === '' || preg_match('/[\x00-\x20\x7f\\\\<>"\']/', $domain)) {
|
||||
return false;
|
||||
}
|
||||
$parts = @parse_url($domain);
|
||||
if (!$parts || empty($parts['scheme']) || !in_array(strtolower($parts['scheme']), array('http', 'https'), true)
|
||||
|| empty($parts['host']) || isset($parts['user']) || isset($parts['pass'])
|
||||
|| isset($parts['query']) || isset($parts['fragment'])) {
|
||||
return false;
|
||||
}
|
||||
// 호스트에는 포트 구분용 IPv6 대괄호 외의 URL 구문을 허용하지 않는다.
|
||||
$host = $parts['host'];
|
||||
if ($host[0] === '[') {
|
||||
if (substr($host, -1) !== ']' || !filter_var(substr($host, 1, -1), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) return false;
|
||||
} elseif (!preg_match('/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9.])?$/i', $host)) {
|
||||
return false;
|
||||
}
|
||||
if ($host[0] !== '[') {
|
||||
if (strlen($host) > 254) return false;
|
||||
$hostname = substr($host, -1) === '.' ? substr($host, 0, -1) : $host;
|
||||
foreach (explode('.', $hostname) as $label) {
|
||||
if (!preg_match('/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i', $label)) return false;
|
||||
}
|
||||
}
|
||||
if (isset($parts['port']) && ($parts['port'] < 1 || $parts['port'] > 65535)) return false;
|
||||
if (isset($parts['path']) && (preg_match('/%(?:0[0-9a-f]|1[0-9a-f]|7f|2f|5c)/i', $parts['path'])
|
||||
|| preg_match('~(?:^|/)(?:\.|%2e){1,2}(?:/|$)~i', $parts['path']))) return false;
|
||||
|
||||
return rtrim($domain, '/');
|
||||
}
|
||||
|
||||
function g5_require_security_mail_url()
|
||||
{
|
||||
$url = g5_security_mail_base_url();
|
||||
if ($url === false) {
|
||||
error_log('[g5 security mail] Valid G5_DOMAIN is required.');
|
||||
alert('메일 인증을 위한 사이트 주소가 설정되지 않았습니다. 사이트 관리자에게 문의해 주십시오.');
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
// 발급 시각을 포함한 메일 인증용 일회용 토큰을 생성한다.
|
||||
function get_email_certify_token()
|
||||
{
|
||||
return get_random_token_string(16) . '.' . G5_SERVER_TIME;
|
||||
|
||||
@@ -86,10 +86,12 @@ $data = array(
|
||||
'mb_name' => $mb_name,
|
||||
);
|
||||
|
||||
$security_mail_url = g5_security_mail_base_url();
|
||||
$mb_email_certify = G5_TIME_YMDHIS;
|
||||
|
||||
//메일인증을 사용한다면
|
||||
if( defined('G5_SOCIAL_CERTIFY_MAIL') && G5_SOCIAL_CERTIFY_MAIL && $config['cf_use_email_certify'] ){
|
||||
$security_mail_url = g5_require_security_mail_url();
|
||||
$mb_email_certify = '';
|
||||
}
|
||||
|
||||
@@ -277,7 +279,7 @@ if($result) {
|
||||
|
||||
sql_query(" update {$g5['member_table']} set mb_email_certify2 = '$mb_md5' where mb_id = '$mb_id' ");
|
||||
|
||||
$certify_href = G5_BBS_URL.'/email_certify.php?mb_id='.$mb_id.'&mb_md5='.$mb_md5;
|
||||
$certify_href = $security_mail_url.'/'.G5_BBS_DIR.'/email_certify.php?mb_id='.$mb_id.'&mb_md5='.$mb_md5;
|
||||
|
||||
ob_start();
|
||||
include_once (G5_BBS_PATH.'/register_form_update_mail3.php');
|
||||
|
||||
Reference in New Issue
Block a user