diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/lessons/Category.java b/webgoat-container/src/main/java/org/owasp/webgoat/lessons/Category.java
index c2dc60343..e510ca14a 100644
--- a/webgoat-container/src/main/java/org/owasp/webgoat/lessons/Category.java
+++ b/webgoat-container/src/main/java/org/owasp/webgoat/lessons/Category.java
@@ -48,6 +48,7 @@ public enum Category {
XSS("(A7) Cross-Site Scripting (XSS)", 307),
INSECURE_DESERIALIZATION("(A8) Insecure Deserialization", 308),
VULNERABLE_COMPONENTS("(A9) Vulnerable Components", 309),
+ SESSION_MANAGEMENT("(A10) Session Management Flaws", 310),
REQUEST_FORGERIES("(A8:2013) Request Forgeries", 318),
@@ -66,7 +67,6 @@ public enum Category {
DOS("Denial of Service", 1500),
MALICIOUS_EXECUTION("Malicious Execution", 1600),
CLIENT_SIDE("Client side", 1700),
- SESSION_MANAGEMENT("Session Management Flaws", 1800),
WEB_SERVICES("Web Services", 1900),
ADMIN_FUNCTIONS("Admin Functions", 2000),
CHALLENGE("Challenges", 3000);
diff --git a/webgoat-lessons/pom.xml b/webgoat-lessons/pom.xml
index 42be3473c..52cf5b8f0 100644
--- a/webgoat-lessons/pom.xml
+++ b/webgoat-lessons/pom.xml
@@ -41,6 +41,7 @@
webgoat-lesson-templatecryptopath-traversal
+ spoof-cookie
diff --git a/webgoat-lessons/spoof-cookie/pom.xml b/webgoat-lessons/spoof-cookie/pom.xml
new file mode 100644
index 000000000..a1542e2ed
--- /dev/null
+++ b/webgoat-lessons/spoof-cookie/pom.xml
@@ -0,0 +1,58 @@
+
+ 4.0.0
+ spoof-cookie
+ jar
+
+ org.owasp.webgoat.lesson
+ webgoat-lessons-parent
+ 8.2.1-SNAPSHOT
+
+
+
+ 0.8.7
+
+
+
+
+
+ coverage
+
+ false
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco.version}
+
+
+ default-prepare-agent
+
+ prepare-agent
+
+
+
+ default-report
+ verify
+
+ report
+
+
+ ${project.build.directory}/jacoco.exec
+ ${project.reporting.outputDirectory}/jacoco
+
+ **/SpoofCookie.*
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webgoat-lessons/spoof-cookie/src/main/java/org/owasp/webgoat/spoofcookie/SpoofCookie.java b/webgoat-lessons/spoof-cookie/src/main/java/org/owasp/webgoat/spoofcookie/SpoofCookie.java
new file mode 100644
index 000000000..41382a4d0
--- /dev/null
+++ b/webgoat-lessons/spoof-cookie/src/main/java/org/owasp/webgoat/spoofcookie/SpoofCookie.java
@@ -0,0 +1,47 @@
+/*
+ * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/
+ *
+ * Copyright (c) 2002 - 2021 Bruce Mayhew
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Getting Source ==============
+ *
+ * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects.
+ */
+
+package org.owasp.webgoat.spoofcookie;
+
+import org.owasp.webgoat.lessons.Category;
+import org.owasp.webgoat.lessons.Lesson;
+import org.springframework.stereotype.Component;
+
+/***
+ *
+ * @author Angel Olle Blazquez
+ *
+ */
+
+@Component
+public class SpoofCookie extends Lesson {
+
+ @Override
+ public Category getDefaultCategory() {
+ return Category.SESSION_MANAGEMENT;
+ }
+
+ @Override
+ public String getTitle() {
+ return "spoofcookie.title";
+ }
+}
diff --git a/webgoat-lessons/spoof-cookie/src/main/java/org/owasp/webgoat/spoofcookie/SpoofCookieAssignment.java b/webgoat-lessons/spoof-cookie/src/main/java/org/owasp/webgoat/spoofcookie/SpoofCookieAssignment.java
new file mode 100644
index 000000000..2f7fd4a26
--- /dev/null
+++ b/webgoat-lessons/spoof-cookie/src/main/java/org/owasp/webgoat/spoofcookie/SpoofCookieAssignment.java
@@ -0,0 +1,121 @@
+/*
+ * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/
+ *
+ * Copyright (c) 2002 - 2021 Bruce Mayhew
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Getting Source ==============
+ *
+ * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects.
+ */
+
+package org.owasp.webgoat.spoofcookie;
+
+import java.util.Map;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.StringUtils;
+import org.owasp.webgoat.assignments.AssignmentEndpoint;
+import org.owasp.webgoat.assignments.AttackResult;
+import org.owasp.webgoat.spoofcookie.encoders.EncDec;
+import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;
+import org.springframework.web.bind.annotation.CookieValue;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+/***
+ *
+ * @author Angel Olle Blazquez
+ *
+ */
+
+@RestController
+public class SpoofCookieAssignment extends AssignmentEndpoint {
+
+ private static final String COOKIE_NAME = "spoof_auth";
+ private static final String COOKIE_INFO = "Cookie details for user %s: " + COOKIE_NAME + "=%s";
+ private static final String ATTACK_USERNAME = "tom";
+
+ private static final Map users = Map.of(
+ "webgoat", "webgoat",
+ "admin", "admin",
+ ATTACK_USERNAME, "apasswordfortom");
+
+ @PostMapping(path = "/SpoofCookie/login")
+ @ResponseBody
+ @ExceptionHandler(UnsatisfiedServletRequestParameterException.class)
+ public AttackResult login(
+ @RequestParam String username,
+ @RequestParam String password,
+ @CookieValue(value = COOKIE_NAME, required = false) String cookieValue,
+ HttpServletResponse response) {
+
+ if (StringUtils.isEmpty(cookieValue)) {
+ return credentialsLoginFlow(username, password, response);
+ } else {
+ return cookieLoginFlow(cookieValue);
+ }
+ }
+
+ @GetMapping(path = "/SpoofCookie/cleanup")
+ public void cleanup(HttpServletResponse response) {
+ Cookie cookie = new Cookie(COOKIE_NAME, "");
+ cookie.setMaxAge(0);
+ response.addCookie(cookie);
+ }
+
+ private AttackResult credentialsLoginFlow(String username, String password, HttpServletResponse response) {
+ String lowerCasedUsername = username.toLowerCase();
+ if (ATTACK_USERNAME.equals(lowerCasedUsername) && users.get(lowerCasedUsername).equals(password)) {
+ return informationMessage(this).feedback("spoofcookie.cheating").build();
+ }
+
+ String authPassword = users.getOrDefault(lowerCasedUsername, "");
+ if (!authPassword.isBlank() && authPassword.equals(password)) {
+ String newCookieValue = EncDec.encode(lowerCasedUsername);
+ Cookie newCookie = new Cookie(COOKIE_NAME, newCookieValue);
+ newCookie.setPath("/WebGoat");
+ newCookie.setSecure(true);
+ response.addCookie(newCookie);
+ return informationMessage(this).feedback("spoofcookie.login").output(String.format(COOKIE_INFO, lowerCasedUsername, newCookie.getValue())).build();
+ }
+
+ return informationMessage(this).feedback("spoofcookie.wrong-login").build();
+ }
+
+ private AttackResult cookieLoginFlow(String cookieValue) {
+ String cookieUsername;
+ try {
+ cookieUsername = EncDec.decode(cookieValue).toLowerCase();
+ } catch (Exception e) {
+ // for providing some instructive guidance, we won't return 4xx error here
+ return failed(this).output(e.getMessage()).build();
+ }
+ if (users.containsKey(cookieUsername)) {
+ if (cookieUsername.equals(ATTACK_USERNAME)) {
+ return success(this).build();
+ }
+ return failed(this).feedback("spoofcookie.cookie-login").output(String.format(COOKIE_INFO, cookieUsername, cookieValue)).build();
+ }
+
+ return failed(this).feedback("spoofcookie.wrong-cookie").build();
+ }
+
+}
diff --git a/webgoat-lessons/spoof-cookie/src/main/java/org/owasp/webgoat/spoofcookie/encoders/EncDec.java b/webgoat-lessons/spoof-cookie/src/main/java/org/owasp/webgoat/spoofcookie/encoders/EncDec.java
new file mode 100644
index 000000000..71ffc8b3d
--- /dev/null
+++ b/webgoat-lessons/spoof-cookie/src/main/java/org/owasp/webgoat/spoofcookie/encoders/EncDec.java
@@ -0,0 +1,98 @@
+/*
+ * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/
+ *
+ * Copyright (c) 2002 - 2021 Bruce Mayhew
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Getting Source ==============
+ *
+ * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects.
+ */
+
+package org.owasp.webgoat.spoofcookie.encoders;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.security.crypto.codec.Hex;
+
+/***
+ *
+ * @author Angel Olle Blazquez
+ *
+ */
+
+public class EncDec {
+
+ // PoC: weak encoding method
+
+ private static final String SALT = RandomStringUtils.randomAlphabetic(10);
+
+ private EncDec() {
+
+ }
+
+ public static String encode(final String value) {
+ if (value == null) {
+ return null;
+ }
+
+ String encoded = value.toLowerCase() + SALT;
+ encoded = revert(encoded);
+ encoded = hexEncode(encoded);
+ return base64Encode(encoded);
+ }
+
+ public static String decode(final String encodedValue) throws IllegalArgumentException {
+ if (encodedValue == null) {
+ return null;
+ }
+
+ String decoded = base64Decode(encodedValue);
+ decoded = hexDecode(decoded);
+ decoded = revert(decoded);
+ return decoded.substring(0, decoded.length() - SALT.length());
+ }
+
+ private static String revert(final String value) {
+ return new StringBuilder(value)
+ .reverse()
+ .toString();
+ }
+
+ private static String hexEncode(final String value) {
+ char[] encoded = Hex.encode(value.getBytes(StandardCharsets.UTF_8));
+ return new String(encoded);
+ }
+
+ private static String hexDecode(final String value) {
+ byte[] decoded = Hex.decode(value);
+ return new String(decoded);
+ }
+
+ private static String base64Encode(final String value) {
+ return Base64
+ .getEncoder()
+ .encodeToString(value.getBytes());
+ }
+
+ private static String base64Decode(final String value) {
+ byte[] decoded = Base64
+ .getDecoder()
+ .decode(value.getBytes());
+ return new String(decoded);
+ }
+
+}
diff --git a/webgoat-lessons/spoof-cookie/src/main/resources/html/SpoofCookie.html b/webgoat-lessons/spoof-cookie/src/main/resources/html/SpoofCookie.html
new file mode 100644
index 000000000..7a41f948d
--- /dev/null
+++ b/webgoat-lessons/spoof-cookie/src/main/resources/html/SpoofCookie.html
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webgoat-lessons/spoof-cookie/src/main/resources/i18n/WebGoatLabels.properties b/webgoat-lessons/spoof-cookie/src/main/resources/i18n/WebGoatLabels.properties
new file mode 100644
index 000000000..4f4aed4aa
--- /dev/null
+++ b/webgoat-lessons/spoof-cookie/src/main/resources/i18n/WebGoatLabels.properties
@@ -0,0 +1,7 @@
+spoofcookie.title=Spoofing an Authentication Cookie
+
+spoofcookie.wrong-login=Login failed.
+spoofcookie.login=Logged in using credentials. Cookie created, see below.
+spoofcookie.cookie-login=Logged in using cookie.
+spoofcookie.wrong-cookie=Wrong cookie sent.
+spoofcookie.cheating=Don't cheat!
diff --git a/webgoat-lessons/spoof-cookie/src/main/resources/js/handler.js b/webgoat-lessons/spoof-cookie/src/main/resources/js/handler.js
new file mode 100644
index 000000000..1a1cddbb4
--- /dev/null
+++ b/webgoat-lessons/spoof-cookie/src/main/resources/js/handler.js
@@ -0,0 +1,31 @@
+function getCookieValue() {
+ var cookie = document.cookie.match(new RegExp('(^| )spoof_auth=([^;]+)'));
+ if (cookie != null)
+ return [2];
+ return null;
+}
+
+function cleanup() {
+ document.cookie = 'spoof_auth=;Max-Age=0;secure=true';
+ $('#spoof_username').removeAttr('disabled');
+ $('#spoof_password').removeAttr('disabled');
+ $('#spoof_submit').removeAttr('disabled');
+ $('#spoof_attack_feedback').html('');
+ $('#spoof_attack_output').html('');
+}
+
+var target = document.getElementById('spoof_attack_feedback');
+
+var obs = new MutationObserver(function(mutations) {
+ mutations.forEach(function() {
+ var cookie = getCookieValue();
+ if (cookie) {
+ $('#spoof_username').prop('disabled', true);
+ $('#spoof_password').prop('disabled', true);
+ $('#spoof_submit').prop('disabled', true);
+ }
+ });
+});
+
+obs.observe(target, { characterData: false, attributes: false, childList: true, subtree: false });
+
diff --git a/webgoat-lessons/spoof-cookie/src/main/resources/lessonPlans/en/SpoofCookie_content0.adoc b/webgoat-lessons/spoof-cookie/src/main/resources/lessonPlans/en/SpoofCookie_content0.adoc
new file mode 100644
index 000000000..132e11bc4
--- /dev/null
+++ b/webgoat-lessons/spoof-cookie/src/main/resources/lessonPlans/en/SpoofCookie_content0.adoc
@@ -0,0 +1,30 @@
+= Spoofing an Authentication Cookie
+
+Bypass the authentication mechanism by spoofing an authentication cookie.
+
+*Notes about the login system*
+
+When an authentication cookie is sent, the system will log in the user directly if the cookie is valid.
+
+When a cookie is not sent, but credentials provided are correct, the system will create an authentication cookie.
+
+The login will be denied on any other cases.
+
+Pay attention to the feedback message that you will get during the attacks.
+
+Known credentials:
+
+[frame=ends]
+|===
+|user name |password
+
+|webgoat
+|webgoat
+
+|admin
+|admin
+|===
+
+*Goal*
+
+When you understand how the authentication cookie is generated, try to _spoof_ the cookie and login as Tom.
diff --git a/webgoat-lessons/spoof-cookie/src/main/resources/lessonPlans/en/SpoofCookie_plan.adoc b/webgoat-lessons/spoof-cookie/src/main/resources/lessonPlans/en/SpoofCookie_plan.adoc
new file mode 100644
index 000000000..cad7f7ba7
--- /dev/null
+++ b/webgoat-lessons/spoof-cookie/src/main/resources/lessonPlans/en/SpoofCookie_plan.adoc
@@ -0,0 +1,18 @@
+= Spoofing an Authentication Cookie
+
+== Concept
+
+Authentication Cookies are used for services that require authentication, when the user logs in with a personal user name and password, the server validates the provided credentials and if those are valid, it creates a session.
+
+Every session usually has a unique ID that identifies the user's session; when the server returns the response to the user, it includes a Set-Cookie header that contains, among other things, the cookie name and value.
+
+The authentication cookie is typically stored on the client and server side.
+
+On the one hand, having the cookie stored on the client side implies that can be stolen by exploiting certain vulnerabilities or intercepted using man in the middle attacks or XSS. On the other, cookie values can be guessed if the algorithm for generating the cookie can be obtained.
+
+Many applications will automatically login a user if the right authentication cookie is provided.
+
+
+== Goals
+
+The user should be able to guess the cookie generation algorithm and bypass the authentication mechanism by logging in as a different user.
diff --git a/webgoat-lessons/spoof-cookie/src/main/resources/lessonSolutions/en/SpoofCookie_solution.adoc b/webgoat-lessons/spoof-cookie/src/main/resources/lessonSolutions/en/SpoofCookie_solution.adoc
new file mode 100644
index 000000000..6d867e3a3
--- /dev/null
+++ b/webgoat-lessons/spoof-cookie/src/main/resources/lessonSolutions/en/SpoofCookie_solution.adoc
@@ -0,0 +1,88 @@
+= Spoofing an Authentication Cookie
+
+== Solution
+
+Some standard Linux tools have been used on this solution; other kind of tools -like online tools- can be used and the same results should be obtained.
+
+=== Analysis and reversing
+
+Inspect the webgoat user spoof_auth cookie value:
+
+[source, text]
+----
+NjM3OTRhNGY0ODRiNTQ0OTU3NDU3NDYxNmY2NzYyNjU3Nw==
+----
+
+It look like a base64 encoded text.
+
+Decoding the base64 text:
+
+[source, sh]
+----
+echo NjM3OTRhNGY0ODRiNTQ0OTU3NDU3NDYxNmY2NzYyNjU3Nw== | base64 -d
+63794a4f484b5449574574616f67626577
+----
+
+Now, it look like a hexadecimal encoded text.
+
+Hexadecimal text decoding:
+
+[source, sh]
+----
+echo 63794a4f484b5449574574616f67626577 | xxd -p -r
+cyJOHKTIWEtaogbew
+----
+
+Now, reverse the text:
+
+[source, text]
+----
+webgoatEWITKHOJyc
+----
+
+We can see the user name with some random text appended. If we request some more different cookies for the same user, we will observe that the cookie generation appends random text of ten characters together with the user name, after it reverses the whole string and encodes it as hexadecimal and base64 respectively.
+
+=== Attacking the system
+
+Let's see how to forge our authentication cookie for tom.
+
+Our initial string will be the user name and a random text of ten characters.
+
+[source,text]
+----
+tomAAAAAAAAAA
+----
+
+Reverse it:
+
+[source, text]
+----
+AAAAAAAAAAmot
+----
+
+Now, encode the text to hexadecimal:
+
+[source,text]
+----
+# warn: do not encode any whitespace or new line character
+echo -n AAAAAAAAAAmot | xxd -ps
+414141414141414141416d6f74
+----
+
+Encode to base64:
+
+[source,text]
+----
+# warn: do not encode any whitespace or new line character
+echo -n 414141414141414141416d6f74 | base64
+NDE0MTQxNDE0MTQxNDE0MTQxNDE2ZDZmNzQ=
+----
+
+The spoof_auth cookie value is:
+
+[source,text]
+----
+NDE0MTQxNDE0MTQxNDE0MTQxNDE2ZDZmNzQ=
+----
+
+Finally, send the cookie to the system using any method that you prefer; OWASP ZAP, curl, the browser developer tools, etc.
diff --git a/webgoat-lessons/spoof-cookie/src/main/resources/lessonSolutions/html/SpoofCookie.html b/webgoat-lessons/spoof-cookie/src/main/resources/lessonSolutions/html/SpoofCookie.html
new file mode 100644
index 000000000..c2dafc5aa
--- /dev/null
+++ b/webgoat-lessons/spoof-cookie/src/main/resources/lessonSolutions/html/SpoofCookie.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webgoat-lessons/spoof-cookie/src/main/resources/templates/form.html b/webgoat-lessons/spoof-cookie/src/main/resources/templates/form.html
new file mode 100644
index 000000000..dce39cd1a
--- /dev/null
+++ b/webgoat-lessons/spoof-cookie/src/main/resources/templates/form.html
@@ -0,0 +1,30 @@
+
+
+
+
+
diff --git a/webgoat-lessons/spoof-cookie/src/test/java/org/owasp/webgoat/spoofcookie/SpoofCookieAssignmentTest.java b/webgoat-lessons/spoof-cookie/src/test/java/org/owasp/webgoat/spoofcookie/SpoofCookieAssignmentTest.java
new file mode 100644
index 000000000..acec30454
--- /dev/null
+++ b/webgoat-lessons/spoof-cookie/src/test/java/org/owasp/webgoat/spoofcookie/SpoofCookieAssignmentTest.java
@@ -0,0 +1,205 @@
+/*
+ * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/
+ *
+ * Copyright (c) 2002 - 2021 Bruce Mayhew
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Getting Source ==============
+ *
+ * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects.
+ */
+
+package org.owasp.webgoat.spoofcookie;
+
+import static org.hamcrest.Matchers.emptyString;
+import static org.hamcrest.Matchers.not;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
+
+import java.util.stream.Stream;
+
+import javax.servlet.http.Cookie;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.owasp.webgoat.assignments.AssignmentEndpointTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+
+/***
+ *
+ * @author Angel Olle Blazquez
+ *
+ */
+
+@ExtendWith(MockitoExtension.class)
+class SpoofCookieAssignmentTest extends AssignmentEndpointTest {
+
+ private MockMvc mockMvc;
+ private static final String COOKIE_NAME = "spoof_auth";
+ private static final String LOGIN_CONTEXT_PATH = "/SpoofCookie/login";
+ private static final String ERASE_COOKIE_CONTEXT_PATH = "/SpoofCookie/cleanup";
+
+ @BeforeEach
+ void setup() {
+ SpoofCookieAssignment spoofCookieAssignment = new SpoofCookieAssignment();
+ init(spoofCookieAssignment);
+ mockMvc = standaloneSetup(spoofCookieAssignment).build();
+ }
+
+ @Test
+ @DisplayName("Lesson completed")
+ void success() throws Exception {
+ Cookie cookie = new Cookie(COOKIE_NAME, "NjI2MTcwNGI3YTQxNGE1OTU2NzQ2ZDZmNzQ=");
+
+ ResultActions result = mockMvc.perform(MockMvcRequestBuilders
+ .post(LOGIN_CONTEXT_PATH)
+ .cookie(cookie)
+ .param("username", "")
+ .param("password", ""));
+
+ result.andExpect(status().isOk());
+ result.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(true)));
+
+ }
+
+ @Test
+ @DisplayName("Valid credentials login without authentication cookie")
+ void validLoginWithoutCookieTest() throws Exception {
+ String username = "webgoat";
+ String password = "webgoat";
+
+ ResultActions result = mockMvc.perform(MockMvcRequestBuilders
+ .post(LOGIN_CONTEXT_PATH)
+ .param("username", username)
+ .param("password", password));
+
+ result.andExpect(status().isOk());
+ result.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
+ result.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE));
+ result.andExpect(cookie().value(COOKIE_NAME, not(emptyString())));
+
+ }
+
+ @ParameterizedTest
+ @MethodSource("providedCookieValues")
+ @DisplayName("Tests different invalid/valid -but not solved- cookie flow scenarios: "
+ + "1.- Invalid encoded cookie sent. "
+ + "2.- Valid cookie login (not tom) sent. "
+ + "3.- Valid cookie with not known username sent ")
+ void cookieLoginNotSolvedFlow(String cookieValue) throws Exception {
+ Cookie cookie = new Cookie(COOKIE_NAME, cookieValue);
+ mockMvc.perform(MockMvcRequestBuilders
+ .post(LOGIN_CONTEXT_PATH)
+ .cookie(cookie)
+ .param("username", "")
+ .param("password", ""))
+ .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
+ }
+
+ @Test
+ @DisplayName("UnsatisfiedServletRequestParameterException test for missing username")
+ void invalidLoginWithUnsatisfiedServletRequestParameterExceptionOnUsernameMissing() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders
+ .post(LOGIN_CONTEXT_PATH)
+ .param("password", "anypassword"))
+ .andExpect(status().is4xxClientError());
+ }
+
+ @Test
+ @DisplayName("UnsatisfiedServletRequestParameterException test for missing password")
+ void invalidLoginWithUnsatisfiedServletRequestParameterExceptionOnPasswordMissing() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders
+ .post(LOGIN_CONTEXT_PATH)
+ .param("username", "webgoat"))
+ .andExpect(status().is4xxClientError());
+ }
+
+ @Test
+ @DisplayName("Invalid blank credentials login")
+ void invalidLoginWithBlankCredentials() throws Exception {
+ ResultActions result = mockMvc.perform(MockMvcRequestBuilders
+ .post(LOGIN_CONTEXT_PATH)
+ .param("username", "")
+ .param("password", ""));
+
+ result.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
+
+ }
+
+ @Test
+ @DisplayName("Invalid blank password login")
+ void invalidLoginWithBlankPassword() throws Exception {
+ ResultActions result = mockMvc.perform(MockMvcRequestBuilders
+ .post(LOGIN_CONTEXT_PATH)
+ .param("username", "webgoat")
+ .param("password", ""));
+
+ result.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
+
+ }
+
+ @Test
+ @DisplayName("cheater test")
+ void cheat() throws Exception {
+ ResultActions result = mockMvc.perform(MockMvcRequestBuilders
+ .post(LOGIN_CONTEXT_PATH)
+ .param("username", "tom")
+ .param("password", "apasswordfortom"));
+
+ result.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
+ }
+
+ @Test
+ @DisplayName("Invalid login with tom username")
+ void invalidTomLogin() throws Exception {
+ ResultActions result = mockMvc.perform(MockMvcRequestBuilders
+ .post(LOGIN_CONTEXT_PATH)
+ .param("username", "tom")
+ .param("password", ""));
+
+ result.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
+ }
+
+ @Test
+ @DisplayName("Erase authentication cookie")
+ void eraseAuthenticationCookie() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders
+ .get(ERASE_COOKIE_CONTEXT_PATH))
+ .andExpect(status().isOk())
+ .andExpect(cookie().maxAge(COOKIE_NAME, 0))
+ .andExpect(cookie().value(COOKIE_NAME, ""));
+
+ }
+
+ private static Stream providedCookieValues() {
+ return Stream.of(
+ Arguments.of("NjI2MTcwNGI3YTQxNGE1OTUNzQ2ZDZmNzQ="),
+ Arguments.of("NjI2MTcwNGI3YTQxNGE1OTU2NzQ3NDYxNmY2NzYyNjU3Nw=="),
+ Arguments.of("NmQ0NjQ1Njc0NjY4NGY2Mjc0NjQ2YzY1Njc2ZTYx"));
+ }
+
+}
diff --git a/webgoat-lessons/spoof-cookie/src/test/java/org/owasp/webgoat/spoofcookie/encoders/EncDecTest.java b/webgoat-lessons/spoof-cookie/src/test/java/org/owasp/webgoat/spoofcookie/encoders/EncDecTest.java
new file mode 100644
index 000000000..9edc3481f
--- /dev/null
+++ b/webgoat-lessons/spoof-cookie/src/test/java/org/owasp/webgoat/spoofcookie/encoders/EncDecTest.java
@@ -0,0 +1,89 @@
+/*
+ * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/
+ *
+ * Copyright (c) 2002 - 2021 Bruce Mayhew
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Getting Source ==============
+ *
+ * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects.
+ */
+
+package org.owasp.webgoat.spoofcookie.encoders;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/***
+ *
+ * @author Angel Olle Blazquez
+ *
+ */
+
+class EncDecTest {
+
+ @ParameterizedTest
+ @DisplayName("Encode test")
+ @MethodSource("providedForEncValues")
+ void testEncode(String decoded, String encoded) {
+ String result = EncDec.encode(decoded);
+
+ assertTrue(result.endsWith(encoded));
+ }
+
+ @ParameterizedTest
+ @DisplayName("Decode test")
+ @MethodSource("providedForDecValues")
+ void testDecode(String decoded, String encoded) {
+ String result = EncDec.decode(encoded);
+
+ assertThat(decoded, is(result));
+ }
+
+ @Test
+ @DisplayName("null encode test")
+ void testNullEncode() {
+ assertNull(EncDec.encode(null));
+ }
+
+ @Test
+ @DisplayName("null decode test")
+ void testNullDecode() {
+ assertNull(EncDec.decode(null));
+ }
+
+ private static Stream providedForEncValues() {
+ return Stream.of(
+ Arguments.of("webgoat", "YxNmY2NzYyNjU3Nw=="),
+ Arguments.of("admin", "2ZTY5NmQ2NDYx"),
+ Arguments.of("tom", "2ZDZmNzQ="));
+ }
+
+ private static Stream providedForDecValues() {
+ return Stream.of(
+ Arguments.of("webgoat", "NjI2MTcwNGI3YTQxNGE1OTU2NzQ3NDYxNmY2NzYyNjU3Nw=="),
+ Arguments.of("admin", "NjI2MTcwNGI3YTQxNGE1OTU2NzQ2ZTY5NmQ2NDYx"),
+ Arguments.of("tom", "NjI2MTcwNGI3YTQxNGE1OTU2NzQ2ZDZmNzQ="));
+ }
+}
diff --git a/webgoat-server/pom.xml b/webgoat-server/pom.xml
index dce260880..76026ec18 100644
--- a/webgoat-server/pom.xml
+++ b/webgoat-server/pom.xml
@@ -149,6 +149,11 @@
secure-passwords${project.version}
+
+ org.owasp.webgoat.lesson
+ spoof-cookie
+ ${project.version}
+ org.owasp.webgoat.lessonwebgoat-lesson-template