[보안/SER-004] 인증 없는 휴면계정 해제 차단 + 메일토큰 난수 강화
웹취약점 점검(SER-004)에서 확인된 휴면계정 관련 취약점 2건 조치.
1) 인증 없는 휴면계정 해제 (CWE-306, 긴급)
- actionDiapause.do 가 요청 파라미터 mber_id 만으로 즉시 deleteDiapause 실행 →
아이디만 알면 제3자가 임의 계정의 휴면을 해제할 수 있었음(로컬 PoC 실증).
- 파라미터를 신뢰하지 않고, ID/PW 인증 성공 시에만 세션에 적재되는 diapauseVO 의
mber_id 로만 해제. 세션 부재 시 resCode=002 거부, 성공 시 1회성 소거.
- 정상 경로 releaseDiapauseAjax(토큰 검증)와 동일 원칙. 정상 사용자는 원래도
세션값(${sessionScope.diapauseVO.mber_id})을 보냈으므로 결과 동일(회귀 없음).
- mbl/EgovDiapause.jsp: resCode 분기로 거부 시 재로그인 안내.
2) 예측 가능한 난수 (CWE-330, 중)
- 휴면 활성화 메일 토큰(20자) 생성을 java.util.Random → SecureRandom 으로 교체.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AbEjijyQdS54xso1d2ks3u
This commit is contained in:
parent
722fd978ce
commit
a085956a8b
@ -3,7 +3,7 @@ package egovframework.com.uat.uia.web;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@ -441,7 +441,9 @@ public class EgovLoginController {
|
||||
|
||||
char[] charaters = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5','6','7','8','9'};
|
||||
StringBuffer sb = new StringBuffer();
|
||||
Random rn = new Random();
|
||||
// [보안/SER-004] 휴면 활성화 메일 토큰 난수를 예측 가능한 java.util.Random 에서
|
||||
// 암호학적 안전 난수(SecureRandom)로 교체(CWE-330). 토큰 재현·추측 방지.
|
||||
SecureRandom rn = new SecureRandom();
|
||||
for( int i = 0 ; i < 20 ; i++ ){
|
||||
sb.append( charaters[ rn.nextInt( charaters.length ) ] );
|
||||
}
|
||||
@ -461,14 +463,30 @@ public class EgovLoginController {
|
||||
|
||||
/**
|
||||
* 휴면계정 활성화
|
||||
*
|
||||
* [보안/SER-004] 인증 없는 휴면계정 해제 차단.
|
||||
* 종전: 요청 파라미터 mber_id 만으로 즉시 deleteDiapause 실행 → 아이디만 알면
|
||||
* 제3자가 임의 계정의 휴면을 해제할 수 있었다(본인확인 부재, CWE-306).
|
||||
* 변경: 파라미터를 신뢰하지 않고, ID/PW 인증에 성공해야만 세션에 적재되는
|
||||
* diapauseVO(EgovSpringSecurityLoginFilter/diapauseLogin 에서 setAttribute)의
|
||||
* mber_id 로만 해제한다. 프론트 경로(releaseDiapauseAjax)가 이메일 토큰을
|
||||
* 검증하는 것과 동일하게, 모바일 경로는 '로그인 세션' 을 본인확인 근거로 삼는다.
|
||||
* 세션 부재·불일치 시 해제하지 않고 resCode=002 로 거부, 성공 시 1회성 소거.
|
||||
*/
|
||||
@RequestMapping(value = "/uat/uia/actionDiapause.do")
|
||||
public String actionDiapause(HttpServletRequest request, HttpServletResponse response
|
||||
,@RequestParam(value="mber_id", required=true) String mber_id
|
||||
,ModelMap model) throws Exception {
|
||||
LoginVO diapauseVO = (LoginVO) request.getSession().getAttribute("diapauseVO");
|
||||
if (diapauseVO == null || diapauseVO.getMber_id() == null || "".equals(diapauseVO.getMber_id())) {
|
||||
// 로그인(본인확인) 없이 직접 호출된 요청 — 해제 거부
|
||||
model.addAttribute("resCode", "002");
|
||||
return JSON;
|
||||
}
|
||||
LoginVO vo = new LoginVO();
|
||||
vo.setMber_id(mber_id);
|
||||
vo.setMber_id(diapauseVO.getMber_id());
|
||||
loginService.deleteDiapause(vo);
|
||||
request.getSession().removeAttribute("diapauseVO"); // 재사용 방지
|
||||
model.addAttribute("resCode", "001");
|
||||
return JSON;
|
||||
}
|
||||
|
||||
|
||||
@ -26,10 +26,15 @@ function actionDiapause() {
|
||||
$.ajax({
|
||||
url : "/uat/uia/actionDiapause.do",
|
||||
type : "POST",
|
||||
data : {"mber_id" : $("#mber_id").val(), "esntl_id" : $("#esntl_id").val()},
|
||||
dataType : "json",
|
||||
success:function(object){
|
||||
alert("휴먼계정이 해제 되었습니다. \n다시 로그인 하세요.");
|
||||
// [보안] 서버가 로그인 세션(diapauseVO)으로 본인확인 후 해제한다.
|
||||
// resCode 001=성공, 002=본인확인 실패(세션 만료 등) → 재로그인 유도.
|
||||
if(object && object.resCode === "001"){
|
||||
alert("휴면계정이 해제 되었습니다. \n다시 로그인 하세요.");
|
||||
} else {
|
||||
alert("본인확인이 만료되었습니다. \n다시 로그인 후 시도해 주세요.");
|
||||
}
|
||||
location.href = "/uat/uia/egovLoginUsr.do";
|
||||
},
|
||||
error:function(xhr,status,error){alert(status);}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user