PR: 12. 취약한 비밀번호 복구 절차
분류: Web Application(웹)
중요도: 상
개요
점검 내용
비밀번호 복구 기능 사용 시, 단순 정보(이름, 사번, 아이디 등)만을 활용하거나 SMS나 이메일 인증 시 발급되는 임시 비밀번호가 동일하거나 유추 가능 여부 점검
점검 목적
비밀번호 복구 로직을 유추하기 어렵게 구현하고, 인증된 사용자 메일이나 SMS에서만 복구 비밀번호를 확인할 수 있도록 하여 비인가자가 사용자 비밀번호를 획득 및 변경하지 못하도록 방지하기 위함
보안 위협
❌ 취약한 비밀번호 복구 로직(비밀번호 찾기 등)으로 인하여 공격자가 불법적으로 다른 사용자의 비밀번호를 획득, 변경할 수 있음
참고
소스코드 및 취약점 점검 필요
점검 대상 및 판단 기준
대상
웹 애플리케이션 소스코드
판단 기준
✅ 양호: 비밀번호 재설정 시 난수를 이용하여, 인증된 사용자 메일이나 SMS로 임시 비밀번호 또는 비밀번호 재설정을 위한 링크가 전송될 경우
❌ 취약: 비밀번호 재설정 시 일정 패턴으로 재설정되고 웹 사이트 화면에 바로 출력될 경우
조치 방법
비밀번호 복구 로직을 강화하고, 인증된 사용자 메일이나 SMS에서만 재설정된 비밀번호를 확인할 수 있도록 하여 비인가자가 비밀번호를 획득하지 못하도록 조치
조치 시 영향
일반적인 경우 영향 없음
점검 및 조치 사례
점검 방법
Step 1) 비밀번호 복구 기능 유무를 파악하고, 복구 과정에서 보안 질문이 추측 가능하거나 소셜 엔지니어링으로 쉽게 답을 찾을 수 있는 단순 정보를 요구하는지 확인
[ 비밀번호 복구 시 보안 질문 단순성 확인 ]
Step 2) 비밀번호 복구 시 재설정된 비밀번호에 대하여 추측가능한 일정 패턴으로 발급 유무 확인
[ 발급 비밀번호에 대한 복잡성 판단 ]
Step 3) 비밀번호 복구 과정에서 해당 계정에 등록된 이메일 및 전화번호가 아닌 공격자의 정보로 변조가 가능한 포인트의 점검 및 패킷 변조를 통해 공격자 측으로 인증 번호 및 임시 비밀번호 발급 여부 확인
[ 비밀번호 초기화 시 사용자 정보에 대한 무결성 검증 ]
조치 방법
- 추측이 어렵고 공개적으로 알 수 없는 정보를 기반으로 한 보안 질문 사용
- 비밀번호 재설정 과정에서 사용자의 메일이나 SMS로 인증 코드 전송 후 이를 확인하는 2단계 인증 도입
- 사용자의 개인정보(연락처, 주소, 메일 주소 등)로 비밀번호의 생성을 제한하며, 불규칙적이고 최소 길이(6자 이상 권고) 이상의 복잡도를 만족하는 비밀번호를 발급 및 웹 사이트 화면에 바로 출력하지 않고 인증된 사용자의 메일이나 SMS로 전송되게 구현
- 비밀번호 재발급 검증 실패에 대한 임계값을 설정하여 일정 횟수 이상 실패한 경우 다른 방식으로 비밀번호 찾기 기능을 제공하고, 검증 후 기존의 비밀번호가 아닌 임시 비밀번호를 발급하도록 설계하며, 사용자가 임시 비밀번호를 재사용하지 못하도록 발급받은 즉시 새로운 비밀번호로 재설정하도록 구현
Java
SecureRandom 클래스를 사용하여 안전한 임시 비밀번호를 생성하는 예시
SecureRandom : 난수를 생성하는 데 사용되는 클래스로, 일반 Random 클래스와 달리 시드를 명시적으로 설정할 수 없으므로 키 생성, 보안 토큰 등 보안이 필요한 애플리케이션의 경우 SecureRandom 사용
private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; //대소문자 영문, 숫자 조합으로 12자리의 난수 생성 규칙 정의
private static final int PASSWORD_LENGTH = 12;
private String generateTemporaryPassword() {
SecureRandom secureRandom = new SecureRandom();
StringBuilder password = new StringBuilder(PASSWORD_LENGTH);
for (int i = 0; i < PASSWORD_LENGTH; i++) {
int randomIndex = secureRandom.nextInt(CHARACTERS.length());
password.append(CHARACTERS.charAt(randomIndex));
}
return password.toString();
} //임시 비밀번호로 사용될 난수 생성
ASP.NET
RNGCryptoServiceProvider 클래스를 사용하여 안전한 임시 비밀번호를 생성하는 예시
RNGCryptoServiceProvider : 난수를 생성하는 데 사용되는 클래스로, 사전에 정의된 문자 집합에서 안전한 암호화 알고리즘을 기반으로 문자를 선택하여 복잡한 난수를 생성
protected void btnCheckRandom_Click(object sender, EventArgs e) {
lblRandomNumber.Text = GenerateAlphanumericRandom(12); // 자리수 정의(12)
}
private string GenerateAlphanumericRandom(int length) {
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; //대소문자 영문, 숫자 조합으로 생성 규칙 정의
StringBuilder result = new StringBuilder(length);
byte[] randomBytes = new byte[4 * length];
using (var rng = new RNGCryptoServiceProvider()) {
rng.GetBytes(randomBytes);
for (int i = 0; i < length; i++) {
uint randomInt = BitConverter.ToUInt32(randomBytes, i * 4);
result.Append(chars[(int)(randomInt % (uint)chars.Length)]);
}
}
return result.ToString();
}
PHP
random_int() 메소드를 사용하여 안전한 임시 비밀번호를 생성하는 예시
rand 메소드 보다 random_int 메소드가 암호학적으로 안전한 난수를 생성 (PHP 7.0 버전부터 사용 가능)
function generateRandomPassword($length = 12) {
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()';
$charactersLength = strlen($characters);
$randomPassword = '';
for ($i = 0; $i < $length; $i++) {
$randomPassword .= $characters[random_int(0, $charactersLength - 1)];
}
return $randomPassword;
}
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
//세션 정보와 'userid' 값이 일치하는지 검증
$userid = $_POST['userid'];
$email = $_POST['email'];
if (isset($_SESSION['userid']) && $_SESSION['userid'] === $userid) {
$temporaryPassword = generateRandomPassword();
updateUserPassword($userid, password_hash($temporaryPassword, PASSWORD_DEFAULT));
if (sendPasswordEmail($email, $temporaryPassword)) {
echo "귀하의 이메일로 임시 비밀번호가 발송되었습니다.";
}
}
}