From e01c2a35ce8de209ac2628ae5b8472954614fe26 Mon Sep 17 00:00:00 2001 From: Nanne Baars Date: Tue, 6 Aug 2019 19:03:40 +0200 Subject: [PATCH] Add test case for security question assignment and the tracking is now done with a session scoped bean --- .../plugin/SecurityQuestionAssignment.java | 70 +++++++++------- .../owasp/webgoat/plugin/TriedQuestions.java | 22 +++++ .../resources/i18n/WebGoatLabels.properties | 1 + .../SecurityQuestionAssignmentTest.java | 82 +++++++++++++++++++ 4 files changed, 145 insertions(+), 30 deletions(-) create mode 100644 webgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/plugin/TriedQuestions.java create mode 100644 webgoat-lessons/password-reset/src/main/test/java/org/owasp/webgoat/plugin/SecurityQuestionAssignmentTest.java diff --git a/webgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/plugin/SecurityQuestionAssignment.java b/webgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/plugin/SecurityQuestionAssignment.java index cc823f45a..0210f8dee 100644 --- a/webgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/plugin/SecurityQuestionAssignment.java +++ b/webgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/plugin/SecurityQuestionAssignment.java @@ -3,6 +3,7 @@ package org.owasp.webgoat.plugin; import org.owasp.webgoat.assignments.AssignmentEndpoint; import org.owasp.webgoat.assignments.AssignmentPath; import org.owasp.webgoat.assignments.AttackResult; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; @@ -11,44 +12,53 @@ import org.springframework.web.bind.annotation.ResponseBody; import java.util.HashMap; import java.util.Map; +import static java.util.Optional.of; + /** * Assignment for picking a good security question. + * * @author Tobias Melzer * @since 11.12.18 */ @AssignmentPath("/PasswordReset/SecurityQuestions") public class SecurityQuestionAssignment extends AssignmentEndpoint { - private static int triedQuestions = 0; + @Autowired + private TriedQuestions triedQuestions; - private static Map questions; + private static Map questions; - static { - questions = new HashMap<>(); - questions.put("What is your favorite animal?", "The answer can easily be guessed and figured out through social media."); - questions.put("In what year was your mother born?", "Can be easily guessed."); - questions.put("What was the time you were born?", "This may first seem like a good question, but you most likely dont know the exact time, so it might be hard to remember."); - questions.put("What is the name of the person you first kissed?", "Can be figured out through social media, or even guessed by trying the most common names."); - questions.put("What was the house number and street name you lived in as a child?", "Answer can be figured out through social media, or worse it might be your current address."); - questions.put("In what town or city was your first full time job?", "In times of LinkedIn and Facebook, the answer can be figured out quite easily."); - questions.put("In what city were you born?", "Easy to figure out through social media."); - questions.put("What was the last name of your favorite teacher in grade three?", "Most people would probably not know the answer to that."); - questions.put("What is the name of a college/job you applied to but didn't attend?", "It might not be easy to remember and an hacker could just try some company's/colleges in your area."); - questions.put("What are the last 5 digits of your drivers license?", "Is subject to change, and the last digit of your driver license might follow a specific pattern. (For example your birthday)."); - questions.put("What was your childhood nickname?", "Not all people had a nickname."); - questions.put("Who was your childhood hero?", "Most Heroes we had as a child where quite obvious ones, like Superman for example."); - questions.put("On which wrist do you were your watch?", "There are only to possible real answers, so really easy to guess."); - questions.put("What is your favorite color?", "Can easily be guessed."); - } - @RequestMapping(method = RequestMethod.POST) - public - @ResponseBody - AttackResult completed(@RequestParam String question) { - triedQuestions+=1; - String answer = questions.get(question); - answer = "" + answer + ""; - if(triedQuestions > 1) - return trackProgress(success().output(answer).build()); - return failed().output(answer).build(); - } + static { + questions = new HashMap<>(); + questions.put("What is your favorite animal?", "The answer can easily be guessed and figured out through social media."); + questions.put("In what year was your mother born?", "Can be easily guessed."); + questions.put("What was the time you were born?", "This may first seem like a good question, but you most likely dont know the exact time, so it might be hard to remember."); + questions.put("What is the name of the person you first kissed?", "Can be figured out through social media, or even guessed by trying the most common names."); + questions.put("What was the house number and street name you lived in as a child?", "Answer can be figured out through social media, or worse it might be your current address."); + questions.put("In what town or city was your first full time job?", "In times of LinkedIn and Facebook, the answer can be figured out quite easily."); + questions.put("In what city were you born?", "Easy to figure out through social media."); + questions.put("What was the last name of your favorite teacher in grade three?", "Most people would probably not know the answer to that."); + questions.put("What is the name of a college/job you applied to but didn't attend?", "It might not be easy to remember and an hacker could just try some company's/colleges in your area."); + questions.put("What are the last 5 digits of your drivers license?", "Is subject to change, and the last digit of your driver license might follow a specific pattern. (For example your birthday)."); + questions.put("What was your childhood nickname?", "Not all people had a nickname."); + questions.put("Who was your childhood hero?", "Most Heroes we had as a child where quite obvious ones, like Superman for example."); + questions.put("On which wrist do you were your watch?", "There are only to possible real answers, so really easy to guess."); + questions.put("What is your favorite color?", "Can easily be guessed."); + } + + @RequestMapping(method = RequestMethod.POST) + @ResponseBody + public AttackResult completed(@RequestParam String question) { + var answer = of(questions.get(question)); + if (answer.isPresent()) { + triedQuestions.incr(question); + if (triedQuestions.isComplete()) { + return trackProgress(success().output("" + answer + "").build()); + } + } + return informationMessage() + .feedback("password-questions-one-successful") + .output(answer.orElse("Unknown question, please try again...")) + .build(); + } } diff --git a/webgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/plugin/TriedQuestions.java b/webgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/plugin/TriedQuestions.java new file mode 100644 index 000000000..92bcd38c8 --- /dev/null +++ b/webgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/plugin/TriedQuestions.java @@ -0,0 +1,22 @@ +package org.owasp.webgoat.plugin; + +import com.google.common.collect.Sets; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.SessionScope; + +import java.util.Set; + +@Component +@SessionScope +public class TriedQuestions { + + private Set answeredQuestions = Sets.newHashSet(); + + public void incr(String question) { + answeredQuestions.add(question); + } + + public boolean isComplete() { + return answeredQuestions.size() > 1; + } +} diff --git a/webgoat-lessons/password-reset/src/main/resources/i18n/WebGoatLabels.properties b/webgoat-lessons/password-reset/src/main/resources/i18n/WebGoatLabels.properties index be4edc2b1..283ab18b4 100644 --- a/webgoat-lessons/password-reset/src/main/resources/i18n/WebGoatLabels.properties +++ b/webgoat-lessons/password-reset/src/main/resources/i18n/WebGoatLabels.properties @@ -7,6 +7,7 @@ password-reset-simple.email_mismatch=Of course you can send mail to user {0} how password-questions-wrong-user=You need to find a different user you are logging in with 'webgoat'. password-questions-unknown-user=User {0} is not a valid user. +password-questions-one-successful=You answered one question successfully please try another one. password-reset-no-user=Please supply a valid e-mail address. password-reset-solved=Congratulations you solved the assignment, please type in the following code in the e-mail field: {0} diff --git a/webgoat-lessons/password-reset/src/main/test/java/org/owasp/webgoat/plugin/SecurityQuestionAssignmentTest.java b/webgoat-lessons/password-reset/src/main/test/java/org/owasp/webgoat/plugin/SecurityQuestionAssignmentTest.java new file mode 100644 index 000000000..2ce1b1711 --- /dev/null +++ b/webgoat-lessons/password-reset/src/main/test/java/org/owasp/webgoat/plugin/SecurityQuestionAssignmentTest.java @@ -0,0 +1,82 @@ +package org.owasp.webgoat.plugin; + +import org.hamcrest.CoreMatchers; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.owasp.webgoat.plugins.LessonTest; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringJUnit4ClassRunner.class) +public class SecurityQuestionAssignmentTest extends LessonTest { + + @Before + public void setup() { + PasswordReset assignment = new PasswordReset(); + Mockito.when(webSession.getCurrentLesson()).thenReturn(assignment); + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + Mockito.when(webSession.getUserName()).thenReturn("unit-test"); + } + + @Test + public void oneQuestionShouldNotSolveTheAssignment() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.post("/PasswordReset/SecurityQuestions") + .param("question", "What is your favorite animal?")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("password-questions-one-successful")))) + .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false))) + .andExpect(jsonPath("$.output", CoreMatchers.notNullValue())); + } + + @Test + public void twoQuestionsShouldSolveTheAssignment() throws Exception { + MockHttpSession mocksession = new MockHttpSession(); + mockMvc.perform(MockMvcRequestBuilders.post("/PasswordReset/SecurityQuestions") + .param("question", "What is your favorite animal?").session(mocksession)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false))); + + mockMvc.perform(MockMvcRequestBuilders.post("/PasswordReset/SecurityQuestions") + .param("question", "In what year was your mother born?").session(mocksession)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("assignment.solved")))) + .andExpect(jsonPath("$.output", CoreMatchers.notNullValue())) + .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(true))); + } + + @Test + public void answeringSameQuestionTwiceShouldNotSolveAssignment() throws Exception { + MockHttpSession mocksession = new MockHttpSession(); + mockMvc.perform(MockMvcRequestBuilders.post("/PasswordReset/SecurityQuestions") + .param("question", "What is your favorite animal?").session(mocksession)) + .andExpect(status().isOk()); + mockMvc.perform(MockMvcRequestBuilders.post("/PasswordReset/SecurityQuestions") + .param("question", "What is your favorite animal?").session(mocksession)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("password-questions-one-successful")))) + .andExpect(jsonPath("$.output", CoreMatchers.notNullValue())) + .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false))); + } + + @Test + public void solvingForOneUserDoesNotSolveForOtherUser() throws Exception { + MockHttpSession mocksession = new MockHttpSession(); + mockMvc.perform(MockMvcRequestBuilders.post("/PasswordReset/SecurityQuestions") + .param("question", "What is your favorite animal?").session(mocksession)); + mockMvc.perform(MockMvcRequestBuilders.post("/PasswordReset/SecurityQuestions") + .param("question", "In what year was your mother born?").session(mocksession)) + .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(true))); + + MockHttpSession mocksession2 = new MockHttpSession(); + mockMvc.perform(MockMvcRequestBuilders.post("/PasswordReset/SecurityQuestions") + .param("question", "What is your favorite animal?").session(mocksession2)). + andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false))); + } +} \ No newline at end of file