Oh! JUN
[WebGoat] Authentication Bypasses 본문
1. 사례
paytal에서 발생했던 인증 우회 예시입니다.
클라이언트가 SMS를 통해서 코드를 받을 수 없었기 때문에 보안 질문 대체 방법을 사용해서 획득할려고 했습니다.
2. 시나리오
패스워드 재설정을 하기 위해 "좋아하는 선생님"과 "살아온 거리 이름" 두 가지 질문에 대답을 해야합니다.
3. 공격방법
'선생님(SecQuestion0)'과 '거리(secQuestion1)' 질문에 대답한 답변은 3자인 입장에서 저희는 모르니까 당연히 틀릴 수 밖에 없습니다.
secQuestion0와 secQuestion1 파라미터를 secQuestion2와 secQuestion3로 변경하니까 성공적으로 인증 우회한것을 확인할 수 있습니다.
4. 소스코드 분석
//AccountVerificationHelper.java
static {
userSecQuestions.put("secQuestion0", "Dr. Watson");
userSecQuestions.put("secQuestion1", "Baker Street");
}
'userSecQuestion'는 Java에서 사용하는 HashMap 형태의 데이터 구조로 key와 value값으로 저장되어있습니다.
'secQuestion0'에는 'Dr. Watson'과 'secQuestion1'에는 'Baker Street'가 저장되어있습니다.
그래서 'teacher'와 'street에 위에 내용을 입력하면
정상적으로 인증할 수 있겠으나 우리의 목적은 취약점을 찾는것이니까 계속 시도해보도록 하겠습니다.
//AccountVerificationHelper.java
public boolean didUserLikelylCheat(HashMap<String, String> submittedAnswers) {
boolean likely = false;
if (submittedAnswers.size() == secQuestionStore.get(verifyUserId).size()) {
likely = true;
}
if ((submittedAnswers.containsKey("secQuestion0") && submittedAnswers.get("secQuestion0").equals(secQuestionStore.get(verifyUserId).get("secQuestion0")))
&& (submittedAnswers.containsKey("secQuestion1") && submittedAnswers.get("secQuestion1").equals(secQuestionStore.get(verifyUserId).get("secQuestion1")))) {
likely = true;
} else {
likely = false;
}
return likely;
}
submittedAnswers.containsKey("secQuestion0")
'submittedAnswers'라는 HashMap에 'secQuestion0'이 존재하는지 확인합니다.
submittedAnswers.get("secQuestion0").equals(secQuestionStore.get(verifyUserId).get("secQuestion0")
'submittedAnswers'에서 'secQuestion0'의 값과
'secQuestionStore에 저장되어 있는 'verifyUserId'의 'secQuestion0'의 값과
일치하는지 확인합니다.
('secQuestion0'이 Dr. Watson인가? / 'secQuestion1'이 Baker Street'인가?)
* 'secQuestion1'에 대한 내용도 위와 동일합니다.
그래서 위 두개의 조건이 True이면 &&연산자에 의해 true를 return 해줍니다.
// VerifyAccount.java
if (verificationHelper.didUserLikelylCheat((HashMap) submittedAnswers)) {
return failed(this)
.feedback("verify-account.cheated")
.output("Yes, you guessed correctly, but see the feedback message")
.build();
}
didUserLikelyCheat 함수에서 true를 반환해주고 해당 조건문을 실행하게 됩니다.
public boolean verifyAccount(Integer userId, HashMap<String, String> submittedQuestions) {
//short circuit if no questions are submitted
if (submittedQuestions.entrySet().size() != secQuestionStore.get(verifyUserId).size()) {
return false;
}
if (submittedQuestions.containsKey("secQuestion0") && !submittedQuestions.get("secQuestion0").equals(secQuestionStore.get(verifyUserId).get("secQuestion0"))) {
return false;
}
if (submittedQuestions.containsKey("secQuestion1") && !submittedQuestions.get("secQuestion1").equals(secQuestionStore.get(verifyUserId).get("secQuestion1"))) {
return false;
}
// else
return true;
}
submittedQuestions.containsKey("secQuestion0")
'submittedQuestions'라는 HashMap에 'secQuestion0'이 존재하는지 확인합니다.
!submittedQuestions.get("secQuestion0").equals(secQuestionStore.get(verifyUserId).get("secQuestion0")
'submittedQuestions'에서 'secQuestion0'의 값과
'secQuestionStore에 저장되어 있는 'verifyUserId'의 'secQuestion0'의 값과
일치하는지 확인합니다.
그런데!!
submittedQuestions.containsKey("secQuestion0")
'secQuestion0'과 'secQuestion1'에 대한 key 검사를 실행하지 않고 임의의 다른 key로 변경시키니까
해당 코드가 False가 되어버리면 && 연산자로 인해
뒤에 여부와 상관없이 해당 조건은 false가 되면서 조건문을 실행하지 않고 true를 return 시켜줍니다.
if (verificationHelper.verifyAccount(Integer.valueOf(userId), (HashMap) submittedAnswers)) {
userSessionData.setValue("account-verified-id", userId);
return success(this)
.feedback("verify-account.success")
.build();
} else {
return failed(this)
.feedback("verify-account.failed")
.build();
}
결국 return success를 실행시키면서 해당 인증을 우회하게 됩니다.
'웹 해킹 > Broken Authentication' 카테고리의 다른 글
[WebGoat] JWT - 3 (Refresh Token) (0) | 2023.12.03 |
---|---|
[Tool] HashCat 설치하기 (1) | 2023.12.02 |
[WebGoat] JWT - 2 (HashCat 활용하여 비밀 키 알아내기) (0) | 2023.12.02 |
[Tool] JSON Web Token Attack 플러그인 설치하기 (0) | 2023.12.02 |
[WebGoat] JWT tokens - 1 (0) | 2023.12.02 |