[security]회원 ID/이메일 열거 공격 차단

This commit is contained in:
thisgun
2026-04-16 02:54:05 +00:00
parent 5331aa8be5
commit dccb50d8a3
5 changed files with 57 additions and 7 deletions
+5
View File
@@ -2,6 +2,11 @@
include_once('./_common.php');
include_once(G5_LIB_PATH.'/register.lib.php');
// 회원 ID 열거 공격 방지를 위한 Rate Limit (분당 30회)
if (function_exists('check_rate_limit') && !check_rate_limit('ajax_mb_id_check', 30, 60)) {
die('너무 많은 요청이 발생했습니다. 잠시 후 다시 시도해 주세요.');
}
$mb_id = isset($_POST['reg_mb_id']) ? trim($_POST['reg_mb_id']) : '';
set_session('ss_check_mb_id', '');
+5
View File
@@ -2,6 +2,11 @@
include_once('./_common.php');
include_once(G5_LIB_PATH.'/register.lib.php');
// 회원 닉네임 열거 공격 방지를 위한 Rate Limit (분당 30회)
if (function_exists('check_rate_limit') && !check_rate_limit('ajax_mb_nick_check', 30, 60)) {
die('너무 많은 요청이 발생했습니다. 잠시 후 다시 시도해 주세요.');
}
$mb_nick = isset($_POST['reg_mb_nick']) ? trim($_POST['reg_mb_nick']) : '';
$mb_id = isset($_POST['reg_mb_id']) ? trim($_POST['reg_mb_id']) : '';
+5
View File
@@ -2,6 +2,11 @@
include_once("./_common.php");
include_once(G5_LIB_PATH."/register.lib.php");
// 추천인 ID 열거 공격 방지를 위한 Rate Limit (분당 30회)
if (function_exists('check_rate_limit') && !check_rate_limit('ajax_mb_recommend_check', 30, 60)) {
die('너무 많은 요청이 발생했습니다. 잠시 후 다시 시도해 주세요.');
}
$mb_recommend = isset($_POST["reg_mb_recommend"]) ? trim($_POST["reg_mb_recommend"]) : '';
if ($msg = valid_mb_id($mb_recommend)) {
+14 -7
View File
@@ -16,17 +16,24 @@ $email = get_email_address(trim($_POST['mb_email']));
if (!$email)
alert_close('메일주소 오류입니다.');
// OWASP 권장: 이메일 존재 여부와 무관하게 동일한 응답 메시지 사용
// (이메일 열거 공격 방지)
$generic_message = $email.' 메일로 회원아이디와 비밀번호를 인증할 수 있는 메일이 발송 되었습니다.\\n\\n메일을 확인하여 주십시오.';
$sql = " select count(*) as cnt from {$g5['member_table']} where mb_email = '$email' ";
$row = sql_fetch($sql);
if ($row['cnt'] > 1)
alert('동일한 메일주소가 2개 이상 존재합니다.\\n\\n관리자에게 문의하여 주십시오.');
if ($row['cnt'] > 1) {
// 시스템 데이터 무결성 이슈 - 운영자 로그에만 기록하고 사용자에겐 일반 메시지
@error_log("[g5 password_lost2] Duplicate email detected: $email (count={$row['cnt']})");
alert_close($generic_message);
}
$sql = " select mb_no, mb_id, mb_name, mb_nick, mb_email, mb_datetime, mb_leave_date from {$g5['member_table']} where mb_email = '$email' ";
$mb = sql_fetch($sql);
if (empty($mb['mb_id']) || $mb['mb_leave_date']) {
alert('존재하지 않는 회원입니다.');
} elseif (is_admin($mb['mb_id'])) {
alert('관리자 아이디는 접근 불가합니다.');
// 회원이 없거나 탈퇴했거나 관리자이면 메일 발송 없이 동일한 메시지로 응답
if (empty($mb['mb_id']) || $mb['mb_leave_date'] || is_admin($mb['mb_id'])) {
alert_close($generic_message);
}
// 임시비밀번호 발급
@@ -74,4 +81,4 @@ mailer($config['cf_admin_email_name'], $config['cf_admin_email'], $mb['mb_email'
run_event('password_lost2_after', $mb, $mb_nonce, $mb_lost_certify);
alert_close($email.' 메일로 회원아이디와 비밀번호를 인증할 수 있는 메일이 발송 되었습니다.\\n\\n메일을 확인하여 주십시오.');
alert_close($generic_message);
+28
View File
@@ -2531,6 +2531,34 @@ function check_token()
return true;
}
/**
* 세션 기반 단순 Rate Limit. 자동화 도구의 무차별 호출을 늦추기 위한 용도.
*
* 같은 키에 대해 $window 초 동안 $max 회까지만 허용. 초과 시 false 반환.
* 세션 쿠키를 무시하는 정교한 봇은 우회 가능하지만, 세션 생성 비용으로 attack rate가 떨어지고
* 가장 흔한 form-only enumeration 시나리오는 효과적으로 차단된다.
*
* @param string $key 레이트 리밋 식별자 (예: 'ajax_mb_id_check')
* @param int $max 윈도우당 최대 허용 횟수
* @param int $window 윈도우 크기 (초)
* @return bool true = 허용, false = 차단
*/
function check_rate_limit($key, $max = 30, $window = 60)
{
$session_key = 'ss_rate_' . $key;
$now = time();
$data = get_session($session_key);
if (!is_array($data) || !isset($data['reset']) || $data['reset'] < $now) {
$data = array('count' => 0, 'reset' => $now + $window);
}
$data['count']++;
set_session($session_key, $data);
return $data['count'] <= $max;
}
/**
* 이메일 미인증 회원의 메일주소 변경 페이지 접근 토큰을 생성한다.
*