[시큐어코딩 가이드] 취약한 비밀번호 허용
- 정보보안/시큐어코딩 가이드
- 2022. 8. 12.
■ 보안기능
보안기능(인증, 접근제어, 기밀성, 암호화, 권한관리 등)을 부적절하게 구현 시 발생할 수 있는 보안약점으로 적절한 인증 없는 중요기능 허용, 부적절한 인가 등이 포함된다.
취약한 비밀번호 허용
■ 개요
사용자에게 강한 패스워드 조합규칙을 요구하지 않으면, 사용자 계정이 취약하게 된다. 안전한 패스워드를 생성하기 위해서는 「패스워드 선택 및 이용 안내서」의 패스워드 설정규칙을 적용해야 한다.
■ 안전한 코딩 기법
패스워드 생성 시 강한 조건 검증을 수행한다. 비밀번호(패스워드)는 숫자와 영문자, 특수문자 등을 혼합하여 사용하고, 주기적으로 변경하여 사용하도록 해야 한다.
코드예제
사용자가 입력한 패스워드에 대한 복잡도 검증 없이 가입 승인 처리를 수행하고 있다.
안전하지 않은 코드의 예 |
from flask import request, redirect from Models import User from Models import db @app.route('/register', methods=['POST']) def register(): userid = request.form.get('userid') password = request.form.get('password') confirm_password = request.form.get('confirm_password') if password != confirm_password: return make_response("비밀번호가 일치하지 않습니다", 200) else: usertable=User() usertable.userid = userid usertable.password = password # 패스워드 생성 규칙을 확인하지 않고 회원 가입 db.session.add(usertable) db.session.commit() return make_response("회원가입 성공", 200) |
사용자 계정을 보호하기 위해 가입 시, 패스워드 복잡도와 길이를 검증 후 가입 승인처리를 수행한다.
안전한 코드의 예 |
from flask import request, redirect from Models import User from Models import db import re @app.route('/register', methods=['POST']) def register(): userid = request.form.get('userid') password = request.form.get('password') confirm_password = request.form.get('confirm_password') if password != confirm_password: return make_response("비밀번호가 일치하지 않습니다.", 200) if not check_password(password): return make_response("비밀번호 조합규칙에 맞지 않습니다.", 200) else: usertable=User() usertable.userid = userid usertable.password = password db.session.add(usertable) db.session.commit() return make_response("회원가입 성공", 200) def check_password(password): # 2종 이상 문자로 구성된 8자리 이상 비밀번호 검사 정규식 PT1 = re.compile('^(?=.*[A-Z])(?=.*[a-z])[A-Za-z\d!@#$%^&*]{8,}$') PT2 = re.compile('^(?=.*[A-Z])(?=.*\d)[A-Za-z\d!@#$%^&*]{8,}$') PT3 = re.compile('^(?=.*[A-Z])(?=.*[!@#$%^&*])[A-Za-z\d!@#$%^&*]{8,}$') PT4 = re.compile('^(?=.*[a-z])(?=.*\d)[A-Za-z\d!@#$%^&*]{8,}$') PT5 = re.compile('^(?=.*[a-z])(?=.*[!@#$%^&*])[A-Za-z\d!@#$%^&*]{8,}$') PT6 = re.compile('^(?=.*\d)(?=.*[!@#$%^&*])[A-Za-z\d!@#$%^&*]{8,}$') # 문자 구성 상관없이 10자리 이상 비밀번호 검사 정규식 PT7 = re.compile('^[A-Za-z\d!@#$%^&*]{10,}$') for pattern in [PT1, PT2, PT3, PT4, PT5, PT6, PT7]: if pattern.match(password): return True return False |
참고로, 위 코드의 특수문자(‘!@#$%^&*’)는 기업 내부 정책에 따라 변경하여 사용하면 되며, 패스워드를 숫자로만 10자리 구성할 경우 취약할 수 있으니 사용자에게 주의할 것을 안내하는 것이 필요하다.
■ Django 프레임워크의 VALIDATORS 사용
Django에서는 미들웨어의 AUTH_PASSWORD_VALIDATORS 설정에서 비밀번호에 대한 검증을 하고 있다. 기본적으로 아래와 같은 검증을 해준다.
⦁UserAttributeSimilarityValidator : user의 attributes(username, firstname, lastname, email)등과 유사한지 체크
⦁MinimumLengthValidator : 비밀번호 길이의 최소 값 (default 8)
⦁CommonPasswordValidator : 사람들이 가장 많이 사용하는 패스워드 20,000개에 해당하는지 검증.
⦁NumericPasswordValidator : 비밀번호가 완전히 숫자인지 검증.
위의 Validator외에 필요한 검증 기준은 사용자 Validator를 생성해서 AUTH_PASSWORD_VALIDATORS에 등록하여 사용가능하다. 아래는 사용자 Validator 예제이다.
(검증 통과 시 None 반환, 실패 시 ValidationError 발생하도록 구현 필요)
안전한 코드의 예 |
import re from django.core.exceptions import ValidationError from django.utils.translation import ugettext as _ class CustomValidator(object): def validate(self, password, user=None): # 2종 이상 문자로 구성된 8자리 이상 비밀번호 검사 정규식 PT1 = re.compile('^(?=.*[A-Z])(?=.*[a-z])[A-Za-z\d!@#$%^&*]{8,}$') PT2 = re.compile('^(?=.*[A-Z])(?=.*\d)[A-Za-z\d$@$!%*?&]{8,}$') PT3 = re.compile('^(?=.*[A-Z])(?=.*[!@#$%^&*])[A-Za-z\d!@#$%^&*]{8,}$') PT4 = re.compile('^(?=.*[a-z])(?=.*\d)[A-Za-z\d!@#$%^&*]{8,}$') PT5 = re.compile('^(?=.*[a-z])(?=.*[!@#$%^&*])[A-Za-z\d!@#$%^&*]{8,}$') PT6 = re.compile('^(?=.*\d)(?=.*[!@#$%^&*])[A-Za-z\d!@#$%^&*]{8,}$') # 문자 구성 상관없이 10자리 이상 비밀번호 검사 정규식 PT7 = re.compile('^[A-Za-z\d!@#$%^&*]{10,}$') for pattern in [PT1, PT2, PT3, PT4, PT5, PT6, PT7]: if pattern.match(password): return None raise ValidationError( _("비밀번호 조합규칙에 적합하지 않습니다.."), code='improper_password', ) def get_help_text(self): return _( "비밀번호는 영문 대문자, 소문자, 숫자, 특수문자 조합 중 2가지 이상 8자리이거나 문자 구성 상 관없이 10자리 이상이어야 합니다." ) |