[보안/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.io.UnsupportedEncodingException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
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'};
|
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();
|
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++ ){
|
for( int i = 0 ; i < 20 ; i++ ){
|
||||||
sb.append( charaters[ rn.nextInt( charaters.length ) ] );
|
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")
|
@RequestMapping(value = "/uat/uia/actionDiapause.do")
|
||||||
public String actionDiapause(HttpServletRequest request, HttpServletResponse response
|
public String actionDiapause(HttpServletRequest request, HttpServletResponse response
|
||||||
,@RequestParam(value="mber_id", required=true) String mber_id
|
|
||||||
,ModelMap model) throws Exception {
|
,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();
|
LoginVO vo = new LoginVO();
|
||||||
vo.setMber_id(mber_id);
|
vo.setMber_id(diapauseVO.getMber_id());
|
||||||
loginService.deleteDiapause(vo);
|
loginService.deleteDiapause(vo);
|
||||||
|
request.getSession().removeAttribute("diapauseVO"); // 재사용 방지
|
||||||
|
model.addAttribute("resCode", "001");
|
||||||
return JSON;
|
return JSON;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -26,10 +26,15 @@ function actionDiapause() {
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
url : "/uat/uia/actionDiapause.do",
|
url : "/uat/uia/actionDiapause.do",
|
||||||
type : "POST",
|
type : "POST",
|
||||||
data : {"mber_id" : $("#mber_id").val(), "esntl_id" : $("#esntl_id").val()},
|
|
||||||
dataType : "json",
|
dataType : "json",
|
||||||
success:function(object){
|
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";
|
location.href = "/uat/uia/egovLoginUsr.do";
|
||||||
},
|
},
|
||||||
error:function(xhr,status,error){alert(status);}
|
error:function(xhr,status,error){alert(status);}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user