diff --git a/.travis.yml b/.travis.yml index 6a7c00b22..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; else mvn clean install; 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/pom.xml b/pom.xml index 79c12a753..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 @@ -172,9 +168,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..7414a54ae 100644 --- a/webgoat-container/src/main/resources/application.properties +++ b/webgoat-container/src/main/resources/application.properties @@ -29,7 +29,16 @@ 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 + +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/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-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 000000000..1f98298c8 Binary files /dev/null and b/webgoat-container/src/main/resources/static/css/img/owasp_logo.jpg differ diff --git a/webgoat-container/src/main/resources/static/css/main.css b/webgoat-container/src/main/resources/static/css/main.css index 0c3d66785..d9347acb8 100644 --- a/webgoat-container/src/main/resources/static/css/main.css +++ b/webgoat-container/src/main/resources/static/css/main.css @@ -1146,7 +1146,7 @@ div.captured-flag { } .appseceu-banner { - background: url('img/appseceu-17.png') no-repeat 0px 0px; + background: url('img/owasp_logo.jpg') no-repeat 0px 0px; height: 117px; width: 1268px; margin-bottom: 20px; diff --git a/webgoat-container/src/main/resources/static/js/goatApp/templates/scoreboard.html b/webgoat-container/src/main/resources/static/js/goatApp/templates/scoreboard.html index cae628c71..af53d5771 100644 --- a/webgoat-container/src/main/resources/static/js/goatApp/templates/scoreboard.html +++ b/webgoat-container/src/main/resources/static/js/goatApp/templates/scoreboard.html @@ -1,5 +1,5 @@ -

WebGoat 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-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-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 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/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 000000000..0e01d46f0 Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/challenge7/git.zip differ 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 000000000..be29a5312 Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/hi-five-cat.jpg differ diff --git a/webgoat-lessons/challenge/src/main/resources/images/user1.png b/webgoat-lessons/challenge/src/main/resources/images/user1.png new file mode 100644 index 000000000..f608b046c Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/user1.png differ diff --git a/webgoat-lessons/challenge/src/main/resources/images/user2.png b/webgoat-lessons/challenge/src/main/resources/images/user2.png new file mode 100644 index 000000000..f34e841a9 Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/user2.png differ 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 000000000..b5b18a54a Binary files /dev/null and b/webgoat-lessons/challenge/src/main/resources/images/user3.png differ 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