취약한 패스워드 허용
정의
사용자에게 강한 패스워드 조합규칙을 요구하지 않으면, 사용자 계정이 취약하게 된다. 안전한 패스워드를 생성하기 위해서는 '패스워드 선택 및 이용 안내서'에서 제시하는 패스워드 설정 규칙을 적용해야 한다.
안전한 코딩기법
패스워드 생성 시 강한 조건 검증을 수행한다. 패스워드(패스워드)는 숫자와 영문자, 특수문자 등을 혼합하여 사용하고, 주기적으로 변경하여 사용하도록 해야 한다.
코드예제
다음은 안전하지 않은 코드예제로, 사용자가 입력한 패스워드를 복잡도 검증 없이 가입 승인처리를 수행하고 있는 코드입니다.
<!--form.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post">
{% csrf_token %}
<label>
userid: <input type="text" name="userid"/><br>
pwd: <input type="text" name="pwd"/><br>
check_pwd: <input type="text" name="check_pwd"/><br>
</label>
<input type="submit" value="패스워드변경"/>
</form>
</body>
</html>
#취약한 패스워드 허용(안전X)
def register_bad(request):
if request.method == 'GET':
return render(request, 'form.html')
userid = request.POST.get('userid')
pwd = request.POST.get('pwd')
check_pwd = request.POST.get('check_pwd')
if pwd != check_pwd:
return render(request, 'error.html', {'msg': '패스워드가 일치하지 않습니다.'})
else:
# 지금 테이블 없음 ㅎㅎ
# usertable = User()
# usertable.userid = userid
# usertable.pwd = pwd
# db.session.add(usertable)
# db.session.commit()
return render(request, 'success.html', {'msg': '회원가입 성공'})
pwd: 123 / check_pwd: 123aaa
pwd: abc123 / check_pwd: abc123
다음은 안전한 코드예제로, 패스워드 복잡도와 길이를 검증 후 가입 승인처리를 수행하고 있는 코드입니다.
#취약한 패스워드 허용(안전O)
def register_good(request):
if request.method == 'GET':
return render(request, 'form.html')
userid = request.POST.get('userid')
pwd = request.POST.get('pwd')
check_pwd = request.POST.get('check_pwd')
if pwd != check_pwd:
return render(request, 'error.html', {'msg': '패스워드가 일치하지 않습니다.'})
if not check_password(pwd):
return render(request, 'error.html', {'msg': '패스워드 규칙에 맞지 않습니다.'})
else:
# 지금 테이블 없음 ㅎㅎ
# usertable = User()
# usertable.userid = userid
# usertable.pwd = pwd
# db.session.add(usertable)
# db.session.commit()
return render(request, 'success.html', {'msg': '회원가입 성공'})
def check_password(password):
#3종이상 문자로 구성된 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 i in [PT1, PT2, PT3, PT4, PT5, PT6, PT7]:
if i.match(password):
return True
return False
pwd: 123abc / check_pwd: 123abc
pwd: 33cheese!! / check_pwd: 33cheese!!
또 다른 안전한 코드예제로, Django 프레임워크의 VALIDATORS를 사용할 수 있습니다.
#Django 프레임워크 사용
#취약한 패스워드 허용(안전O)
import re
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _
class CustomValidator(object):
def Validate(self, password, user=None):
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,}$')
PT7 = re.compile('^[A-Za-z\d!@#$%^&*]{10,}$')
for i in [PT1, PT2, PT3, PT4, PT5, PT6, PT7]:
if i.match(password):
return None
raise ValidationError(_("패스워드 조합 규칙에 적합하지 않습니다."),
code='improper_password',)
def get_help_text(self):
return _("패스워드는 영문 대소문자, 숫자, 특수문자 조합중 2가지 이상 8자리이거나 문자 상관없이 10자리 이상이어야 합니다.")
보안기능/취약한 패스워드 허용
[참고문헌] Python 시큐어코딩 가이드(2022) / KISA(한국인터넷진흥원)