관리 메뉴

코코야이야기

[시큐어코딩] 계정 및 인증관리 본문

프로그래밍/보안

[시큐어코딩] 계정 및 인증관리

코코야 2019. 6. 18. 00:03
반응형

[시큐어코딩] 계정 및 인증관리

 

  인증관리 인가관리
설명 허락된 사용자만 시스템을 사용하도록 하기 위한 제약 권한을 가진 사용자만 시스템을 사용하도록 하는 제약
취약점의 원인 패스워드 관리 정책이나 로그인 관리 정책 부재

- 사용자의 요청에 대해 권한 체크 부재

- 부적절한 권한 체크

* 계정관리

패스워드 선택 정책

- 패스워드로 사용되는 문자열에 대한 정책 정의 방법

- 사용자들이 안전한 문자열을 패스워드로 선택하는 것을 강제하는 시스템이 구축되어야 함

패스워드 관리 정책

- 문자열 선택 정책

- 패스워드 관리 정책

- 초기 패스워드 변경 정책

- 사용자 패스워드 분실 처리 정책

* 패스워드 선택 정책 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-패스워드선택및이용안내서)

제2010-22호-패스워드선택및이용안내서-융합보호R_D팀.pdf
2.70MB

 

* 자바 스크립트로 사용자 측에서 패스워드 정책 적용

//비밀번호 유효성 확인
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에 세션 토큰 같은 세션 식별자를 노출하지 않도록 함

- 전자 메일과 같은 안전하지 않은 채널을 통해 자격 증명 (사용자 이름 포함) 을 전송하지 않음

- 안전한 패스워드를 선택할 수 있도록 패스워드 정책을 적용함

반응형
Comments