코코야이야기
[시큐어코딩] 계정 및 인증관리 본문
[시큐어코딩] 계정 및 인증관리
인증관리 | 인가관리 | |
설명 | 허락된 사용자만 시스템을 사용하도록 하기 위한 제약 | 권한을 가진 사용자만 시스템을 사용하도록 하는 제약 |
취약점의 원인 | 패스워드 관리 정책이나 로그인 관리 정책 부재 |
- 사용자의 요청에 대해 권한 체크 부재 - 부적절한 권한 체크 |
* 계정관리
패스워드 선택 정책 |
- 패스워드로 사용되는 문자열에 대한 정책 정의 방법 - 사용자들이 안전한 문자열을 패스워드로 선택하는 것을 강제하는 시스템이 구축되어야 함 |
패스워드 관리 정책 |
- 문자열 선택 정책 - 패스워드 관리 정책 - 초기 패스워드 변경 정책 - 사용자 패스워드 분실 처리 정책 |
* 패스워드 선택 정책 5가지 진단
1) 진단 1
"패스워드에 문자열 정책이 적용되어 있는가?"
- 회원가입 시 입력되는 패스워드에 대한 정책이 적용되는지 확인함
- 회원가입 시 영문자, 또는 숫자로만 구성된 문자열을 패스워드로 입력하여 회원가입이 승인되는지 확인함.
- 잘 알려진 패스워드, 또는 최악의 패스워드 25와 같은 패스워드를 입력하여 회원가입이 승인되는지 확인함.
Rank 2018 Password
1 123456 Unchanged
2 password Unchanged
3 123456789 Up 3
4 12345678 Down 1
5 12345 Unchanged
6 111111 New
7 1234567 Up 1
8 sunshine New
9 qwerty Down 5
10 iloveyou Unchanged
11 princess New
12 admin Down 1
13 welcome Down 1
14 666666 New
15 abc123 Unchanged
16 football Down 7
17 123123 Unchanged
18 monkey Down 5
19 654321 New
20 !@#$%^&* New
21 charlie New
22 aa123456 New
23 donald New
24 password1 New
25 qwerty123 New
(https://www.teamsid.com/splashdatas-top-100-worst-passwords-of-2018/)
2) 진단 2
"패스워드 정책 설정이 클라이언트와 서버에 동등하게 적용되었는가?"
- 회원가입 또는 패스워드 변경 작업 요청 시 Paros와 같은 Proxy 툴을 이용하여 전달되는 패스워드 입력값을 패스워드 정책이 적용되지 않도록 변조하여 전송 후, 서버에서 패스워드 설정/변경 작업이 차단되는지 확인함
1] 대응기법 1
패스워드 설정/변경 시 패스워드 문자열 정책이 적용되도록 함
if(memberModel.getuserPw() == null || memberModel.getUserPw().trim().isEmpty()){
errors.rejectValue("userPw", "required");
}
- 입력된 패스워드에 대한 검증 작업을 수행하고는 있지만, 패스워드가 입력되지 않은 경우만 체크하는 로직이 구현되는 경우 보안 약점이 됨
조치사항
if(memberModel.getuserPw() == null || memberModel.getUserPw().trim().isEmpty()){
errors.rejectValue("userPw", "required");
} else {
if(!verify(memberModel.getUserPw()){
errors.rejectValue("userPw", "required");
}
}
- 패스워드를 설정/변경 시 패스워드 정책을 적용하는 로직을 추가하여 허가된 범위의 패스워드만 사용 가능하도록 입력값에 제약을 줌
* 안전한 패스워드
- 두 가지 타입의 문자 : 10글자 이상의 패스워드 설정
- 세 가지 타입의 문자 : 8글자 이상의 패스워드 설정
구분 | 안전한 패스워드 | 취약한 패스워드 예 |
문자구성 및 길이조건 |
- 3가지 종류 이상의 문자구성으로 8자리 이상의 길이로 구성된 패스워드 - 2가지 종류 이상의 문자구성으로 10자리 이상의 길이로 구성된 패스워드 (문자종류는 알파벳 대문자, 소문자, 특수문자, 숫자) |
- 2가지 종류 이하의 문자구성으로 8자리 이하로 구성된 패스워드 - 문자구성과 관계없이 7자리 이하 길이로 구성된 패스워드 ex) 123456 |
특정 정보 이용 및 패턴 조건 | - 한글, 영어 등의 사전적 단어를 포함하지 않은 패스워드 |
- 한글, 영어 등을 포함한 사전적인 단어로 구성된 패스워드 (스펠링을 거꾸로 구성한 패스워드도 포함) |
- 널리 알려진 단어를 포함하지 않거나 예측이 어렵도록 가공한 패스워드 (널리 알려진 단어는 컴퓨터 용어, 기업명칭을 가공하지 않고 그대로 사용하는 경우) |
- 널리 알려진 단어로 구성된 패스워드 ex) password, tiger |
|
- 사용자 ID와 연관성이 있는 단어를 포함하지 않은 패스워드 | - 사용자 ID를 이용한 패스워드 | |
- 제3자가 쉽게 알 수 있는 개인정보를 포함하지 않은 패스워드 (개인정보는 가족 이름, 생일, 주소, 전화번호 등이 포함된다) |
- 제3자가 쉽게 알 수 있는 개인정보를 바탕으로 구성된 패스워드 | |
기타조건 | - 해당 시스템에서 사용자가 이전에 사용하지 않고 이전 패스워드와 연관성이 있는 단어구성을 포함하고 있지 않은 패스워드 |
- 시스템에서 예제로 제시되는 패스워드 - 시스템에서 초기 설정된 패스워드 - 해당 시스템에서 사용자가 이전에 사용했던 패스워드 |
(출처 : KISA-패스워드선택및이용안내서)
* 자바 스크립트로 사용자 측에서 패스워드 정책 적용
//비밀번호 유효성 확인
function isValidFormPassword(pw){
var check = /^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,20}$/;
if(!check.test(pw)){
alert("비밀번호는 문자, 숫자, 특수문자의 조합으로 입력해주세요.");
return false;
}
if(pw.length < 8 || pw.length > 20){
alert("비밀번호는 8~20 자리로 입력해주세요.");
return false;
}
return true;
}
* Validator 컴포넌트에서 서버 측 패스워드 정책 적용
/**
* 패스워드 정책은 알파벳, 숫자, 특수문자를 조합하여 8~20자리로 설정한다.
* (?=.*[a-zA-Z]) 하나 이상의 알파벳을 포함한다
* (?=.*[0-9@#$%]) 하나 이상의 숫자나 특수문자를 포함한다
* {8,} 최소 8글자 이상이어야 한다
*/
public boolean verify(String password){
String passwordPolicy = "((?=.*[a-zA-Z])(?=.*[0-9@#$%]).{8,20})";
Pattern pattern = Pattern.compile(passwordPolicy);
Matcher matcher = pattern.matcher(password);
return matcher.matches();
}
3) 진단 3
"패스워드 변경 정책이 적용되어 있는가?"
- 초기 설정된 패스워드의 경우 최초 로그인 시, 패스워드를 변경해야 로그인이 가능하도록 시스템 구현
- 패스워드 변경 시 기존 패스워드를 입력받아 패스워드 변경 권한을 확인하는지 진단함
2] 대응기법 2
패스워드 변경 정책을 정의하여 구현 시 적용함
- 초기화된 패스워드를 사용하는 경우 반드시 사용자가 새로운 패스워드를 설정하도록 로직이 구현되어야 함
- 임시로 설정된 패스워드를 이용하여 로그인하는 경우 반드시 사용자가 새로운 패스워드를 재설정하도록 구현되어야 함
- 패스워드 변경 시 사용자 인증 및 인가 로직이 구현되어야 함
4) 진단 4
"패스워드 관리 정책이 적용되어 있는가?"
- 패스워드를 관리하기 위한 정책이 적용되어 있는지 진단함
˙ 패스워드 최대 사용 일자
˙ 패스워드 최소 사용 일자
˙ 패스워드 분실 시 관리 정책 등
3] 대응기법 3
패스워드 관리 정책을 정의하여 적용함
- 분실 패스워드 재설정 정책, 패스워드 사용 수명 정책 등을 정의하여 적용함
분실 패스워드 재설정 정책 | 패스워드 사용 수명 정책 |
- 본인인증(i-pin 인증, 휴대폰 인증)과 같은 다중매체 인증방식을 사용해 사용자 인증 작업을 수행함 |
- 패스워드 변경 일자를 관리하여 최소 사용 기간, 최대 사용 기간을 체크할 수 있도록 함 (패스워드 유효기간을 저장하여 해당 유효기간 전에 패스워드를 변경하도록 안내) |
- 새로운 패스워드를 설정할 수 있는 링크를 메일로 발송해, 해당 링크를 통해서만 새로운 패스워드를 설정하도록 함 |
5) 진단 5
"패스워드 저장 및 전송이 안전하게 처리되는가?"
- 패스워드가 저장된 DB 정보를 읽어 안전하게 암호화되어서 저장되었는지 확인함
- 전송되는 패스워드 정보가 암호화되어서 전송되는지 확인함
4] 대응기법 4
패스워드를 저장할 때는 반드시 해쉬함수로 암호화해서 저장하고, 전송 시에도 해쉬함수로 암호화해서 전송하거나 HTTPS를 이용해 전송함
- 패스워드는 복호화되지 않는 알고리즘인 해쉬함수 중 안전한 암호알고리즘을 사용하는 SHA-224, SHA-256, SHA-512을 이용해서 암호화함
- 해쉬함수 사용 시 안전한 알고리즘을 사용하는 해쉬함수라도 반드시 솔트(충분히 긴 난수값)을 사용하여 무작위 대입 공격이 불가능하도록 보안을 강화해야 함
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.reset();
md.update(salt);
md.digest(password.getBytes());
- 클라이언트에서 솔트값 사용이 어렵기 때문에 안전한 통신 채널인 HTTPS를 이용하여 패스워드를 전송하도록 구현하는 것이 적절함
* 인증관리에서 확인할 사항
- 로그인 요청처리 암호화
- 로그인 시도 횟수 제한 및 반복 로그인 실패에 대한 대응
- 다중 로그인 허용 여부
- 오랫동안 사용하지 않는 세션에 대한 자동 로그아웃
- 사용자 화면 설계 시 로그아웃 접근성
* 인증관리 5가지 진단
1) 진단 1
"로그인 요청 데이터가 암호화 채널을 이용하거나 자체 암호화된 데이터로 전송되는가?"
- 로그인 프로세스가 암호화되지 않은 페이지에서 발생하는 것을 허용하는지 확인하며, 모든 자격 증명은 해쉬 형태로 저장되어 있는지 확인함
1] 대응기법 #1
로그인 요청이 암호화되어서 처리 되도록 구현함
- 로그인 처리 프로세스에서 로그인 요청이 HTTPS로 들어온 요청인지를 확인하는 절차를 추가함
if(!request.isSecure()){
//HTTPS를 통한 요청이 아니기 때문에 처리할 수 없음으로 에러 처리
}
- 로그인 처리에 사용되는 패스워드는 SHA-256과 같은 안전한 해쉬 알고리즘을 적용하여 DB에 저장하고, 로그인을 위해 입력된 데이터는 안전하게 해쉬 처리하여 DB에 저장된 패스워드 값과 비교하도록 프로그램을 작성함
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(password.getBytes());
md.update(salt.getBytes());
byte[] bytePassword = md.digest();
loginService.login(userid, bytePassword);
2) 진단 2
"로그인 화면에서 3~5회 틀린 로그인 정보를 입력했을 때 로그인 시도 횟수가 체크되는가? 그 후 로그인 반복 실패에 대한 대응 정책이 있는가?"
- 로그인 화면에서 틀린 로그인 정보를 3~5회 입력해 계정 잠금이나 추가 정보 요구와 같은 기능이 구현되어 있는지 확인함
- 만약 5회 이상 입력이 가능하다면 무작위 대입 공격이 가능한 도구를 사용하여 패스워드 인증을 시도해 봄
- 반복 로그인 실패 시 로그인 시도 간격 정책만 적용되어 있다면, 공격자는 조금 더 많은 시간을 들이겠지만 지속적인 로그인을 시도할 수 있음
2] 대응기법 #2
로그인 시도 횟수 제한을 위해 로그인 시도 횟수에 대한 정보가 관리되어야 함
메모리 저장관리 | 하드디스크 저장관리 |
- 세션 메모리를 사용할 수 있으나 공격자가 지정된 횟수보다 한번 적은 횟수까지 로그인을 시도, 세션을 끊고 연결하기를 반복한다면 무작위 대입 공격을 방어하기 어려움 | - 데이터베이스에 로그인 히스토리 정보를 관리할 수 있는 테이블을 구현함 |
- 사용자 ID, 세션 ID, IP 주소, 로그인 시도 횟수, 로그인 시간, 로그아웃 시간과 같은 정보를 이용해 로그인 정책을 구현할 수 있음 |
- 로그인 시도 횟수 제한을 구혀하기 위해서는 분석/설계 단계에서부터 정책을 적용하기 위한 작업이 요구됨
- 로그인 정책은 이미 잘 분석/설계되어서 구현된 보안 프레임워크나 보안 라이브러리를 활용하는 것도 좋은 방법 중의 하나임
Login History 테이블 샘플 | |||||
user_id | session_id | ip | retry_count | success_login_time | logout_time |
모든 요청에 대해 일괄적인 로그인 정책이 적용되도록 구현함
- Spring MVC 프레임워크 사용시 SessionInterceptor를 이용한 로그인 인증 여부 체크 샘플
public class SessionInterceptor extends HandlerInterceptorAdapter{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
if(request.getRequestURI().equals("/work/login.do") || request.getRequestURI().equals("/work/member/join.do")){
if(userId != null){
response.sendRedirect(request.getContextPath() + "/board/list.do");
return true;
} else {
return true;
}
}
if(userId == null){
response.sendRedirect(request.getContext() + "/login.do");
HttpSession session = request.getSession();
session.setAttribute("errorCode","4");
return false;
} else {
return true;
}
}
}
- Spring MVC 프레임워크 사용시 모든 요청에 대해 SessionInterceptor가 적용되도록 설정
<mvc:interceptors>
<bean class="kr.co.lab.common.interceptor.SessionInterceptor"/>
</mvc:interceptors>
- 로그인 시도 횟수 제한 정책을 적용한 Controller 컴포넌트 샘플
@RequestMapping(value="/login.do", method=RequestMethod.POST)
public ModelAndView loginProc(@ModelAttribute("LoginModel") LoginSessionModel loginModel, BindingResult result, HttpSession session){
ModelAndView mav = new ModelAndView();
//LoginHistory 테이블에서 세션ID 와 userID를 이용하여 로그인 시도 정보를 조회
LoginHistory hs = service.getLoginHistory(getSessionID(), loginModel.getUserId());
//처음 로그인하는 경우라면 새로운 LoginHistory 레코드를 생성
if(hs == null){
hs = service.createLoginHistory(getSessionID(), loginModel.getUserId());
}
int tryCount = hs.getLoginFailedCount();
if(tryCount >= 5){
//로그인 시도 횟수가 5회 이상이면 정책 적용
}
//입력값 검증이 실패하면 tryCount를 1증가 시키고 LoginHistory 를 업데이트하고
//로그인 화면으로 이동
new LoginValidator().validate(loginModel, result);
if(result.hasErrors()){
hs.setLoginFailedCount(++tryCount);
service.updateLoginHistory(hs);
mav.setViewName("/board/login");
return mav;
}
//시도 횟수가 5회보다 적거나 입력값 검증이 완료되면 로그인 처리(DB 쿼리 수행)
LoginSessionModel loginCheckResult = service.checkUserId(userId,userPw);
if(loginCheckresult == null){
//로그인이 실패한 경우 LoginHistory 테이블을 업데이트하고, 로그인 화면으로 이동
hs.setLoginFailedCount(++tryCount);
hs.setLastFailedLogin(new Date().getTime());
service.updateLoginHistory(hs);
mav.addObject("errorCode", 1); //not exist userId in db
mav.setViewName("/board/login");
return mav;
} else {
//로그인이 성공되면, LoginHistory 테이블을 업데이트하고 메인 화면으로 이동
hs.setLoginFailedCount(0);
hs.setLastSuccessedLogin(new Date().getTime());
hs.setLoginStatus(1);
service.updateLoginHistory(hs);
mav.setViewName("redirect:/board/main.do");
return mav;
}
}
로그인 제한에 대한 적절한 처리가 수행되도록 로그인 프로세스 수정
- 로그인 시도 횟수를 체크하여 지정된 횟수가 초과되는 경우, 정책에 따라 무작위 대입공격이 불가능하도록 함
˙ 계정 잠금
˙ 지정시간 로그인 불가
˙ 추가 인증정보 요구
3) 진단 3
"다중 로그인 제한 정책이 적용되어 있는가?"
- 필요에 따라 다중 로그인을 제한하기 위해 다중 세션 로그인과 다중 IP 로그인을 제한할 수 있음
다중 세션 로그인 제한 확인 | 두 가지 종류의 브라우저를 사용하여 서버에 접속한 뒤 로그인을 시도하여 두 개의 브라우저에서 로그인이 가능한지 확인 |
다중 IP 로그인 제한 확인 | 두 개의 PC를 이용하여 해당 서버에 접속한 뒤 로그인을 시도하여 두 개의 PC에서 로그인이 가능한지 확인 |
3] 대응기법 #3
다중 로그인 제한 정책 적용이 적용되도록 로그인 프로세스를 변경함
- 로그인 정책 테이블에 정의된 session ID, user ID, IP address, success Login Time, logout Time 컬럼을 활용하여 정책을 적용함
다중 세션 로그인을 허용하지 않는 경우 | user ID - session ID가 Key로 관리되어야 함 |
다중 IP 접속을 허용하지 않는 경우 | user ID - IP Address가 Key로 관리되어야 함 |
다중 세션/IP 접속을 허용하지 않는 경우 | 기존 접속을 끊고 새로운 접속의 로그인을 허용하는 정책과 새로운 접속을 차단하는 정책으로 구분함 |
* 다중 로그인 제한 정책에서 고려할 점
- 사용자가 정상적인 로그아웃이 아닌, 윈도우 창을 close 시켜버린 경우 로그인 정책 테이블에 대한 자동 로그아웃 타임 업데이트가 구현되어야 함
4) 진단 4
"오랫동안 사용하지 않는 세션에 대한 자동 로그아웃이 되는가?"
- 기본적으로 웹서버는 30분동안 세션을 사용하지 않으면 자동적으로 로그아웃하도록 설정되어 있는 경우가 대부분임
- 오랫동안 연결된 브라우저를 사용하지 않고 그대로 두었을 때 자동 로그아웃이 실행되는지 점검함
4] 대응기법 #4
오랫동안 사용하지 않는 세션에 대한 자동 로그아웃이 실행되도록 웹서버를 설정하거나 프로그램에서 세션의 옵션을 설정함
- 웹서버 설정을 통해 세션 타임아웃을 분 단위로 설정함
<session-config>
<session-timeout>30</session-timeout>
</session-config>
- 프로그램에서 setMaxInactiveInterval() 메서드를 사용하여 초 단위로 설정함
HttpSession session = request.getSession();
session.setMaxInactiveInterval(30*60);
5) 진단 5
"사용자 화면에서 로그아웃 접근성이 높은가?"
- 사용하는 모든 페이지에서 로그아웃 버튼이나 링크를 쉽게 사용할 수 있도록 화면이 설계되어 있는지 점검함
- 사용자에 의해 로그아웃 요청을 보내기 쉽도록 UI가 설계되어야 함
5] 대응기법 #5
모든페이지에서 로그아웃 버튼이나 링크가 노출될 수 있도록 로그아웃 기능을 헤더나 사이드에 배치하도록 UI 프로그램을 구현함
Q : 사용자가 로그아웃 없이 윈도우를 닫을경우 로그아웃처리를 어떻게 하는가?
A :
사용자가 로그아웃 처리를 요청하지 않고 윈도우를 닫아버리는 경우 서버는 세션 타임아웃이 될 때 까지 클라이언트를 빠져나갔다는 것을 감지할 수 없다. 그래서 서버의 타임아웃 시간을 짧게 설정하여 윈도우를 닫아버린 사용자를 빨리 감지하여 로그아웃을 처리하게 할 수 있다. 하지만 이 경우 세션 타임아웃을 너무 짧게 잡을 수 없기 때문에 최소한 10분 이상의 시간을 설정하게 된다.
다른 방법으로는 사용자가 정상적으로 로그아웃을 요청하지 않고 닫기를 통해 종료 시 자바 스크립트를 사용하여 이벤트가 발생하는 경우 로그아웃을 요청하도록 구현할 수 있다.
//no event f5 event.clientY < 0
//event.altKey When press Alt+F4
//event.ctrlKey When press Ctrl+F4
//event.clientY 107 or 129 is Alt F4 position on window screen it may change base on screen resolution
$(window).bind('beforeunload',function(){
if((event.clientY < 0) || (event.altKey) || (event.ctrlKey) || ((event.clientY < 129) && (event.clientY > 107))){
$.ajax({
url:"logout" //custom logout url
});
}
});
* 총정리
계정 및 인증관리
- 로그인 프로세스가 암호화되지 않은 페이지로부터 발생하는 것을 허용하지 않음
- 모든 인증 작업은 SSL 통신하에서 이루어지도록 프로그래밍 되어야 하며, 모든 자격증명은 해쉬 형태로 저장되어 있어야 함
- 로그인 인증 시도 횟수를 제한하여 무작위 대입 공격을 예방함
- 강력한 단일인증과 세션 관리 정책을 적용해 별도의 자신의 인증메커니즘을 사용하지 않음
- 사용자가 인증되면, 새로운 세션 쿠키를 실행하고 이전 세션 쿠키를 무효화하여 세션 하이재킹을 방지함
- URL에 세션 토큰 같은 세션 식별자를 노출하지 않도록 함
- 전자 메일과 같은 안전하지 않은 채널을 통해 자격 증명 (사용자 이름 포함) 을 전송하지 않음
- 안전한 패스워드를 선택할 수 있도록 패스워드 정책을 적용함
'프로그래밍 > 보안' 카테고리의 다른 글
사용자 식별 방법 - 세션 (0) | 2023.04.03 |
---|---|
[시큐어코딩] 취약한 난수생성 - Math.random() (0) | 2023.03.31 |
[시큐어코딩] 접근제어 (1) | 2019.07.01 |
[시큐어코딩] 쿠키 및 세션관리 (1) | 2019.06.22 |