- Added new challenges

- Added new webapplication called WebWolf to make attacks more realistic
- Added WebWolf lesson to explain the concepts behind this new application
This commit is contained in:
Nanne Baars 2017-08-13 11:22:52 +02:00
parent 56f19caed6
commit 46c536554c
104 changed files with 4199 additions and 70 deletions

View File

@ -172,9 +172,11 @@
</properties>
<modules>
<module>webgoat-commons</module>
<module>webgoat-container</module>
<module>webgoat-lessons</module>
<module>webgoat-server</module>
<module>webwolf</module>
</modules>
<distributionManagement>

37
webgoat-commons/pom.xml Normal file
View File

@ -0,0 +1,37 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>webgoat-commons</artifactId>
<packaging>jar</packaging>
<parent>
<groupId>org.owasp.webgoat</groupId>
<artifactId>webgoat-parent</artifactId>
<version>8.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>ISO-8859-1</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -34,6 +34,23 @@
</plugins>
</build>
</profile>
<profile>
<id>local</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
</dependency>
</dependencies>
</profile>
<profile>
<id>ctf</id>
<!-- Connect to real mongodb -->
</profile>
</profiles>
@ -132,6 +149,19 @@
</build>
<dependencies>
<dependency>
<groupId>org.owasp.webgoat</groupId>
<artifactId>webgoat-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
@ -144,6 +174,19 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
</dependency>
<dependency>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctorj</artifactId>
@ -153,10 +196,6 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>

View File

@ -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;
/**
* *************************************************************************************************
* <p>
@ -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<Cookie> getWebGoatCookie(HttpServletRequest request) {
for (Cookie c : request.getCookies()) {
if (c.getName().equals("JSESSIONID")) {
return of(c);
}
}
return empty();
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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<Cookie> findSessionCookie(Cookie[] cookies) {
for (Cookie cookie : cookies) {
if ("JSESSIONID".equals(cookie.getName())) {
return Optional.of(cookie);
}
}
return Optional.empty();
}
}

View File

@ -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);
}

View File

@ -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);
}
}
}

View File

@ -45,10 +45,11 @@ public class Scoreboard {
}
private List<String> challengesSolved(UserTracker userTracker) {
List<String> challenges = Lists.newArrayList("Challenge1", "Challenge2", "Challenge3", "Challenge4", "Challenge5");
List<String> 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());

View File

@ -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;
}

View File

@ -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/

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -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;

View File

@ -1,5 +1,5 @@
<div class="scoreboard-title">WebGoat Challenge - AppSec EU 2017</div>
<div class="appseceu-banner">banner here</div>
<div class="scoreboard-title">WebGoat Challenge</div>
<div class="appseceu-banner"></div>
<table class="scoreboard-table">
<% _.each(rankings, function(userRanking, index) { %>
<tr>

View File

@ -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);
}
}

View File

@ -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

View File

@ -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()));
}

View File

@ -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";
}

View File

@ -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;
}

View File

@ -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();

View File

@ -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 " +
"<a target='_blank' href='%s:8080/WebGoat/challenge/7/reset-password/%s'>link</a> 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<String> resetPassword(@PathVariable(value = "link") String link) {
if (link.equals(SolutionConstants.ADMIN_PASSWORD_LINK)) {
return ResponseEntity.accepted().body("<h1>Success!!</h1>" +
"<img src='/WebGoat/images/hi-five-cat.jpg'>" +
"<br/><br/>Here is your flag: " + "<b>" + FLAGS.get(7) + "</b>");
}
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");
}
}

View File

@ -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<String> getHints() {
return Lists.newArrayList();
}
@Override
public Integer getDefaultRanking() {
return 10;
}
@Override
public String getTitle() {
return "challenge7.title";
}
@Override
public String getId() {
return "Challenge7";
}
}

View File

@ -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 <a target="_top" href=
* "http://ostermiller.org/utils/MD5.html">ostermiller.org</a>.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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;
}
}

View File

@ -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));
}
}

View File

@ -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<Integer, Integer> 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<String, Object> 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<Map<String, Integer>> 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);
}
}

View File

@ -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<String> getHints() {
return Lists.newArrayList();
}
@Override
public Integer getDefaultRanking() {
return 10;
}
@Override
public String getTitle() {
return "challenge8.title";
}
@Override
public String getId() {
return "Challenge8";
}
}

View File

@ -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<String, String> userToTomResetLink = Maps.newHashMap();
private static Map<String, String> 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 " +
"<a target='_blank' href='http://%s/WebGoat/challenge/9/reset-password/%s'>link</a> 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);
}
}

View File

@ -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<String> getHints() {
return Lists.newArrayList();
}
@Override
public Integer getDefaultRanking() {
return 10;
}
@Override
public String getTitle() {
return "challenge9.title";
}
@Override
public String getId() {
return "Challenge9";
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,82 @@
<!DOCTYPE html>
<!--
** Revision history (automatically added by: /challenge/7/.git/hooks)
2e29cacb85ce5066b8d011bb9769b666812b2fd9 Updated copyright to 2017
ac937c7aab89e042ca32efeb00d4ca08a95b50d6 Removed hardcoded key
f94008f801fceb8833a30fe56a8b26976347edcf First version of WebGoat Cloud website
-->
<html xmlns:th="http://www.thymeleaf.org">
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:Challenge_7.adoc"></div>
<div class="attack-container">
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
<div class="container-fluid">
<div class="row">
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-body">
<div class="text-center">
<h3><i class="fa fa-lock fa-4x"></i></h3>
<h2 class="text-center">Forgot Password?</h2>
<p>You can reset your password here.</p>
<div class="panel-body">
<form id="login-form" class="attack-form" accept-charset="UNKNOWN"
method="POST" name="form"
action="/WebGoat/challenge/7"
enctype="application/json;charset=UTF-8" role="form">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon"><i
class="glyphicon glyphicon-envelope color-blue"></i></span>
<input id="email" name="email" placeholder="email address"
class="form-control" type="email"/>
</div>
</div>
<div class="form-group">
<input name="recover-submit" class="btn btn-lg btn-primary btn-block"
value="Reset Password" type="submit"/>
</div>
<div class="form-group">
<p>(c) 2017 WebGoat Cloud Platform</p>
</div>
<input type="hidden" class="hide" name="token" id="token" value=""/>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<br/>
<form class="attack-form" method="POST" name="form" action="/WebGoat/challenge/flag">
<div class="form-group">
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-flag-checkered" aria-hidden="true"
style="font-size:20px"></i></div>
<input type="text" class="form-control" id="flag" name="flag"
placeholder="a7179f89-906b-4fec-9d99-f15b796e7208"/>
</div>
<div class="input-group" style="margin-top: 10px">
<button type="submit" class="btn btn-primary">Submit flag</button>
</div>
</div>
</form>
<br/>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
</div>
</html>

View File

@ -0,0 +1,255 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:Challenge_8.adoc"></div>
<link rel="stylesheet" type="text/css" th:href="@{/lesson_css/challenge8.css}"/>
<script th:src="@{/lesson_js/challenge8.js}" language="JavaScript"></script>
<div class="attack-container">
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
<div class="container">
<div class="row">
<div class="col-sm-3">
<div class="rating-block">
<h4>Average user rating</h4>
<h2 class="bold padding-bottom-7">4.3
<small>/ 5</small>
</h2>
<button id="star1" onClick="doVote(1)" type="button" class="btn btn-warning btn-sm" aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
<button id="star2" onClick="doVote(2)" type="button" class="btn btn-warning btn-sm" aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
<button id="star3" onClick="doVote(3)" type="button" class="btn btn-warning btn-sm" aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
<button id="star4" onClick="doVote(4)" type="button" class="btn btn-default btn-grey btn-sm" aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
<button id="star5" onClick="doVote(5)" type="button" class="btn btn-default btn-grey btn-sm" aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
</div>
</div>
<div class="col-sm-3">
<h4>Rating breakdown</h4>
<div class="pull-left">
<div class="pull-left" style="width:35px; line-height:1;">
<div style="height:9px; margin:5px 0;">5 <span class="glyphicon glyphicon-star"></span>
</div>
</div>
<div class="pull-left" style="width:180px;">
<div class="progress" style="height:9px; margin:8px 0;">
<div id="progressBar5" class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="5"
aria-valuemin="0" aria-valuemax="5">
<span class="sr-only">5</span>
</div>
</div>
</div>
<div id="nrOfVotes5" class="pull-right" style="margin-left:10px;">0</div>
</div>
<div class="pull-left">
<div class="pull-left" style="width:35px; line-height:1;">
<div style="height:9px; margin:5px 0;">4 <span class="glyphicon glyphicon-star"></span>
</div>
</div>
<div class="pull-left" style="width:180px;">
<div class="progress" style="height:9px; margin:8px 0;">
<div id="progressBar4" class="progress-bar progress-bar-primary" role="progressbar" aria-valuenow="5"
aria-valuemin="0" aria-valuemax="5">
<span class="sr-only">4</span>
</div>
</div>
</div>
<div id="nrOfVotes4" class="pull-right" style="margin-left:10px;">0</div>
</div>
<div class="pull-left">
<div class="pull-left" style="width:35px; line-height:1;">
<div style="height:9px; margin:5px 0;">3 <span class="glyphicon glyphicon-star"></span>
</div>
</div>
<div class="pull-left" style="width:180px;">
<div class="progress" style="height:9px; margin:8px 0;">
<div id="progressBar3" class="progress-bar progress-bar-info" role="progressbar" aria-valuenow="5"
aria-valuemin="0" aria-valuemax="5">
<span class="sr-only">4</span>
</div>
</div>
</div>
<div id="nrOfVotes3" class="pull-right" style="margin-left:10px;">0</div>
</div>
<div class="pull-left">
<div class="pull-left" style="width:35px; line-height:1;">
<div style="height:9px; margin:5px 0;">2 <span class="glyphicon glyphicon-star"></span>
</div>
</div>
<div class="pull-left" style="width:180px;">
<div class="progress" style="height:9px; margin:8px 0;">
<div id="progressBar2" class="progress-bar progress-bar-warning" role="progressbar" aria-valuenow="5"
aria-valuemin="0" aria-valuemax="5">
<span class="sr-only">2</span>
</div>
</div>
</div>
<div id="nrOfVotes2" class="pull-right" style="margin-left:10px;">0</div>
</div>
<div class="pull-left">
<div class="pull-left" style="width:35px; line-height:1;">
<div style="height:9px; margin:5px 0;">1 <span class="glyphicon glyphicon-star"></span>
</div>
</div>
<div class="pull-left" style="width:180px;">
<div class="progress" style="height:9px; margin:8px 0;">
<div id="progressBar1" class="progress-bar progress-bar-danger" role="progressbar" aria-valuenow="5"
aria-valuemin="0" aria-valuemax="5">
<span class="sr-only">4</span>
</div>
</div>
</div>
<div id="nrOfVotes1" class="pull-right" style="margin-left:10px;">0</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-7">
<hr/>
<div id = "voteResultMsg" class="alert alert-dismissable" style="display: none;">
</div>
<div class="alert alert-info">
Please login or register in order to vote (comments are disabled)
</div>
<div class="review-block">
<div class="row">
<div class="col-sm-3">
<img src="images/user1.png" class="img-rounded"/>
<div class="review-block-name"><a href="#">nktailor</a></div>
<div class="review-block-date">August 22, 2017<br/>1 day ago</div>
</div>
<div class="col-sm-9">
<div class="review-block-rate">
<button type="button" class="btn btn-warning btn-xs" aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
<button type="button" class="btn btn-warning btn-xs" aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
<button type="button" class="btn btn-warning btn-xs" aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
<button type="button" class="btn btn-warning btn-xs"
aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
<button type="button" class="btn btn-warning btn-xs"
aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
</div>
<div class="review-block-title">WebGoat rocks!</div>
<div class="review-block-description">This is a great tool to learn about security
and have some fun with a couple challenges.
</div>
</div>
</div>
<hr/>
<div class="row">
<div class="col-sm-3">
<img src="images/user3.png" class="img-rounded"/>
<div class="review-block-name"><a href="#">Sarah</a></div>
<div class="review-block-date">July 29, 2017<br/>12 day ago</div>
</div>
<div class="col-sm-9">
<div class="review-block-rate">
<button type="button" class="btn btn-warning btn-xs" aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
<button type="button" class="btn btn-warning btn-xs" aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
<button type="button" class="btn btn-warning btn-xs" aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
<button type="button" class="btn btn-warning btn-xs"
aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
<button type="button" class="btn btn-default btn-grey btn-xs"
aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
</div>
<div class="review-block-title">Nice</div>
<div class="review-block-description">I liked it and learned a couple of things.
Still some bugs sometimes though.
</div>
</div>
</div>
<hr/>
<div class="row">
<div class="col-sm-3">
<img src="images/user2.png" class="img-rounded"/>
<div class="review-block-name"><a href="#">Tom</a></div>
<div class="review-block-date">January 27, 2017<br/>100 days ago</div>
</div>
<div class="col-sm-9">
<div class="review-block-rate">
<button type="button" class="btn btn-warning btn-xs" aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
<button type="button" class="btn btn-warning btn-xs" aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
<button type="button" class="btn btn-warning btn-xs" aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
<button type="button" class="btn btn-default btn-grey btn-xs"
aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
<button type="button" class="btn btn-default btn-grey btn-xs"
aria-label="Left Align">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</button>
</div>
<div class="review-block-title">WebGoat is great</div>
<div class="review-block-description">WebGoat teaches you web security with some great
lessons
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<br/>
<form class="attack-form" method="POST" name="form" action="/WebGoat/challenge/flag">
<div class="form-group">
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-flag-checkered" aria-hidden="true"
style="font-size:20px"></i></div>
<input type="text" class="form-control" id="flag" name="flag"
placeholder="a7179f89-906b-4fec-9d99-f15b796e7208"/>
</div>
<div class="input-group" style="margin-top: 10px">
<button type="submit" class="btn btn-primary">Submit flag</button>
</div>
</div>
</form>
<br/>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
</div>
</html>

View File

@ -0,0 +1,109 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:Challenge_9.adoc"></div>
<script th:src="@{/lesson_js/challenge9.js}" language="JavaScript"></script>
<div class="attack-container">
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
<div class="container-fluid">
<div class="row">
<div class="col-md-6">
<h4 style="border-bottom: 1px solid #c5c5c5;">
<i class="glyphicon glyphicon-user"></i>
Account Access
</h4>
<div style="padding: 20px;" id="form-login">
<form id="login-form" class="attack-form" accept-charset="UNKNOWN"
method="POST" name="form"
action="/WebGoat/challenge/9/login"
enctype="application/json;charset=UTF-8" role="form">
<fieldset>
<div class="form-group input-group">
<span class="input-group-addon"> @ </span>
<input class="form-control" placeholder="Email" name="email" type="email"
required="" autofocus=""/>
</div>
<div class="form-group input-group">
<span class="input-group-addon">
<i class="glyphicon glyphicon-lock">
</i>
</span>
<input class="form-control" placeholder="Password" name="password" type="password"
value="" required=""/>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary btn-block">
Access
</button>
<p class="help-block">
<a class="pull-right text-muted" href="#" id="login">
<small>Forgot your password?</small>
</a>
</p>
</div>
</fieldset>
</form>
</div>
<div style="display: none;" id="form-login">
<h4 class="">
Forgot your password?
</h4>
<form id="login-form" class="attack-form" accept-charset="UNKNOWN"
method="POST" name="form"
action="/WebGoat/challenge/9/create-password-reset-link"
enctype="application/json;charset=UTF-8" role="form">
<fieldset>
<span class="help-block">
Email address you use to log in to your account
<br/>
We'll send you an email with instructions to choose a new password.
</span>
<div class="form-group input-group">
<span class="input-group-addon">
@
</span>
<input class="form-control" placeholder="Email" name="email" type="email"
required=""/>
</div>
<button type="submit" class="btn btn-primary btn-block" id="btn-login">
Continue
</button>
<p class="help-block">
<a class="text-muted" href="#" id="forgot">
<small>Account Access</small>
</a>
</p>
</fieldset>
</form>
</div>
</div>
</div>
</div>
<br/>
<form class="attack-form" method="POST" name="form" action="/WebGoat/challenge/flag">
<div class="form-group">
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-flag-checkered" aria-hidden="true"
style="font-size:20px"></i></div>
<input type="text" class="form-control" id="flag" name="flag"
placeholder="a7179f89-906b-4fec-9d99-f15b796e7208"/>
</div>
<div class="input-group" style="margin-top: 10px">
<button type="submit" class="btn btn-primary">Submit flag</button>
</div>
</div>
</form>
<br/>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
</div>
</html>

View File

@ -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.
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}.

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -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();
}

View File

@ -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');
});
});

View File

@ -0,0 +1 @@
Try to reset the password for admin.

View File

@ -0,0 +1 @@
Can you still vote?

View File

@ -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.

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<link rel="stylesheet" type="text/css" th:href="@{/plugins/bootstrap/css/bootstrap.min.css}"/>
<link rel="stylesheet" type="text/css" th:href="@{/css/font-awesome.min.css}"/>
<script th:src="@{/plugins/bootstrap/js/bootstrap.min.js}"/>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="alert alert-danger">
<h4>Password reset link is not valid please try again.</h4>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<link rel="stylesheet" type="text/css" th:href="@{/plugins/bootstrap/css/bootstrap.min.css}"/>
<link rel="stylesheet" type="text/css" th:href="@{/css/font-awesome.min.css}"/>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-8 col-md-6 col-sm-offset-2 col-md-offset-3">
<form role="form" method="POST" action="/WebGoat/challenge/9/change-password" th:object="${form}">
<h2 class="sign_up_title">Reset your password</h2>
<!--<div class="form-group" th:classappend="${#fields.hasErrors('email')}? 'has-error'">-->
<!--<div class="form-group">-->
<!--<label for="email" class="control-label">Email</label>-->
<!--<input autofocus="dummy_for_thymeleaf_parser" type="text" class="form-control"-->
<!--th:field="*{email}"-->
<!--id="email" placeholder="email" name='email'/>-->
<!--<span th:if="${#fields.hasErrors('email')}" th:errors="*{email}">Email error</span>-->
<!--</div>-->
<div class="form-group" th:classappend="${#fields.hasErrors('password')}? 'has-error'">
<input type="hidden" name="resetLink" th:field="*{resetLink}" />
<label for="password" class="control-label" th:text="#{password}">Password</label>
<input type="password" class="form-control" id="password" placeholder="Password"
name='password' th:value="*{password}"/>
<span th:if="${#fields.hasErrors('password')}" th:errors="*{password}">Password error</span>
</div>
<!---->
<!--<div class="form-group">-->
<!--<input type="email" required="" autofocus="" name="email" id="email" class="form-control input-lg" placeholder="Email"-->
<!--tabindex="4"/>-->
<!--<input type="newPassword" required="" autofocus="" name="newPassword" id="newPassword" class="form-control input-lg" placeholder="New password"-->
<!--tabindex="4"/>-->
<!--</div>-->
<div class="row">
<div class="col-xs-12 col-md-12">
<button type="submit" class="btn btn-success btn-block btn-lg">Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<link rel="stylesheet" type="text/css" th:href="@{/plugins/bootstrap/css/bootstrap.min.css}"/>
<link rel="stylesheet" type="text/css" th:href="@{/css/font-awesome.min.css}"/>
<script th:src="@{/plugins/bootstrap/js/bootstrap.min.js}"/>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="alert alert-success">
<h4>Password changed successfully, please login again with your new password</h4>
</div>
</div>
</div>
</body>
</html>

View File

@ -27,6 +27,7 @@
<module>xxe</module>
<module>idor</module>
<module>vulnerable-components</module>
<module>webwolf-introduction</module>
<module>auth-bypass</module>
<module>missing-function-ac</module>
<!-- uncomment below to include lesson template in build, also uncomment the dependency in webgoat-server/pom.xml to have it run in the project fully -->

View File

@ -84,7 +84,7 @@
<div class="col-lg-12">
<form id="login-form" class="attack-form" accept-charset="UNKNOWN"
method="POST" name="form"
action="SqlInjection/attack7"
action="SqlInjection/challenge"
enctype="application/json;charset=UTF-8" role="form">
<div class="form-group">
<input type="text" name="username_login" id="username4" tabindex="1"
@ -120,7 +120,7 @@
</form>
<form id="register-form" class="attack-form" accept-charset="UNKNOWN"
method="PUT" name="form"
action="SqlInjection/attack7"
action="SqlInjection/challenge"
enctype="application/json;charset=UTF-8" style="display: none;" role="form">
<div class="form-group">
<input type="text" name="username_reg" id="username" tabindex="1"

View File

@ -0,0 +1,11 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>webwolf-introduction</artifactId>
<packaging>jar</packaging>
<parent>
<groupId>org.owasp.webgoat.lesson</groupId>
<artifactId>webgoat-lessons-parent</artifactId>
<version>8.0-SNAPSHOT</version>
</parent>
</project>

View File

@ -0,0 +1,48 @@
package org.owasp.webgoat.plugin;
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.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;
import java.net.URI;
import java.net.URISyntaxException;
/**
* @author nbaars
* @since 8/20/17.
*/
@AssignmentPath("/WebWolf/landing")
public class LandingAssignment extends AssignmentEndpoint {
private RestTemplate restTemplate = new RestTemplate();
@PostMapping
@ResponseBody
public AttackResult click(String uniqueCode) {
if (StringUtils.reverse(getWebSession().getUserName()).equals(uniqueCode)) {
return trackProgress(success().build());
}
return failed().feedback("webwolf.landing_wrong").build();
}
@GetMapping("/password-reset")
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("uniqueCode", StringUtils.reverse(getWebSession().getUserName()));
modelAndView.setViewName("webwolfPasswordReset");
return modelAndView;
}
}

View File

@ -0,0 +1,55 @@
package org.owasp.webgoat.plugin;
import lombok.AllArgsConstructor;
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.owasp.webgoat.mail.IncomingMailEvent;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.time.LocalDateTime;
/**
* @author nbaars
* @since 8/20/17.
*/
@AssignmentPath("/WebWolf/mail")
@AllArgsConstructor
public class MailAssignment extends AssignmentEndpoint {
private JmsTemplate jmsTemplate;
@PostMapping("send")
@ResponseBody
public AttackResult sendEmail(@RequestParam String email) {
String username = email.substring(0, email.indexOf("@"));
if (username.equals(getWebSession().getUserName())) {
IncomingMailEvent mailEvent = IncomingMailEvent.builder()
.recipient(username)
.title("Test messages from WebWolf")
.time(LocalDateTime.now())
.contents("This is a test message from WebWolf, your unique code is" + StringUtils.reverse(username))
.sender("webgoat@owasp.org")
.build();
jmsTemplate.convertAndSend("mailbox", mailEvent);
return informationMessage().feedback("webwolf.email_send").feedbackArgs(email).build();
} else {
return informationMessage().feedback("webwolf.email_mismatch").feedbackArgs(username).build();
}
}
@PostMapping
@ResponseBody
public AttackResult completed(@RequestParam String uniqueCode) {
if (uniqueCode.equals(StringUtils.reverse(getWebSession().getUserName()))) {
return trackProgress(success().build());
} else {
return trackProgress(failed().feedbackArgs("webwolf.code_incorrect").feedbackArgs(uniqueCode).build());
}
}
}

View File

@ -0,0 +1,63 @@
package org.owasp.webgoat.plugin;
import org.owasp.webgoat.lessons.Category;
import org.owasp.webgoat.lessons.NewLesson;
import java.util.ArrayList;
import java.util.List;
/**
* ************************************************************************************************
* This file is part of WebGoat, an Open Web Application Security Project utility. For details,
* please see http://www.owasp.org/
* <p>
* Copyright (c) 2002 - 20014 Bruce Mayhew
* <p>
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
* <p>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License along with this program; if
* not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
* <p>
* Getting Source ==============
* <p>
* Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software
* projects.
* <p>
*
* @author WebGoat
* @version $Id: $Id
* @since October 12, 2016
*/
public class WebWolfIntroduction extends NewLesson {
@Override
public Category getDefaultCategory() {
return Category.INTRODUCTION;
}
@Override
public List<String> getHints() {
return new ArrayList();
}
@Override
public Integer getDefaultRanking() {
return 1;
}
@Override
public String getTitle() {
return "webwolf.title";
}
@Override
public String getId() {
return "WebWolfIntroduction";
}
}

View File

@ -0,0 +1,99 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:Introduction.adoc"></div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:Uploading_files.adoc"></div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:Receiving_mail.adoc"></div>
<div class="attack-container">
<form accept-charset="UNKNOWN"
method="POST" name="form"
action="/WebGoat/WebWolf/send"
enctype="application/json;charset=UTF-8">
<div class="container-fluid">
<div class="row">
<div class="col-md-4">
<div class="form-group input-group">
<span class="input-group-addon">
@
</span>
<input class="form-control" placeholder="test1233@webgoat.org" name="email" type="email"
required=""/>
</div>
<button type="submit" class="btn btn-primary btn-block" id="btn-login">
Send e-mail
</button>
</div>
</div>
</div>
</form>
<br/>
<br/>
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
<form class="attack-form" accept-charset="UNKNOWN"
method="POST" name="form"
action="/WebGoat/WebWolf/mail/"
enctype="application/json;charset=UTF-8">
<div class="container-fluid">
<div class="row">
<div class="col-md-4">
<div class="input-group">
<input type="text" class="form-control"
placeholder="Type in your unique code"
name='uniqueCode'/>
<div class="input-group-btn">
<button class="btn btn-primary" type="submit">Go</button>
</div>
</div>
</div>
</div>
</div>
</form>
<br/>
<br/>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:Landing_page.adoc"></div>
<div class="attack-container">
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
<a href="/WebGoat/WebWolf/landing/password-reset" target="_blank">Click here to reset your password</a>
<br/>
<br/>
<form class="attack-form" accept-charset="UNKNOWN"
method="POST" name="form"
action="/WebGoat/WebWolf/landing/"
enctype="application/json;charset=UTF-8">
<div class="container-fluid">
<div class="row">
<div class="col-md-4">
<div class="input-group">
<input type="text" class="form-control"
placeholder="Type in your unique code"
name='uniqueCode'/>
<div class="input-group-btn">
<button class="btn btn-primary" type="submit">Go</button>
</div>
</div>
</div>
</div>
</div>
</form>
<br/>
<br/>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
</div>
</html>

View File

@ -0,0 +1,9 @@
webwolf.title=WebWolf
webwolf.email_send=An email has been send to {0} please check your inbox.
webwolf.code_incorrect=That is not the correct code: {0}, please try again.
webwolf.email_mismatch=Of course you can send mail to user {0} however you will not be able to read this e-mail in WebWolf, please use your own username.
webwolf.landing_wrong=This is the wrong code, try to look for the uniqueCode in the parameters in WebWolf.

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -0,0 +1,21 @@
== Introducing WebWolf
WebWolf is a separate web application which simulates an attackers machine. It makes it possible for us to
make a clear distinction between what takes place on the attacked website and the actions you need to do as
an "attacker". WebWolf was introduced after a couple of workshops where we received feedback about the fact there
was no clear distinction between what was part of the "attackers" role and what was part of the "users" role on the
website. The following items are supported in WebWolf:
* Hosting a file
* Receiving email
* Landing page for incoming requests
WebWolf runs as a separate web application and is started automatically when using the Docker image. If you
are not using the Docker image you will need to download the jar file and start it:
```
java -jar webwolf-<<version>>.jar
```
This will start the application on port 8081, in your browser type: `http://localhost:8081/WebWolf`
You will be redirected to the login page where you need to login with your WebGoat username and password

View File

@ -0,0 +1,25 @@
== Landing page
This page will show all the requests made to '/' or '/challenge'. This means
you can use WebWolf as your landing page for harvesting cookies etc which
is helpful when you perform a XSS lesson.
image::images/requests.png[caption="Figure: ", style="lesson-image"]
{nbsp}
{nbsp}
{nbsp}
*For this exercise you need to login to WebWolf first.*
{nbsp}
{nbsp}
Suppose we tricked a user to click on a link he/she received in an email, this link will open up our crafted
password reset link page. The user does not see any difference with the normal password reset page of the company.
The user enters a new password and hits enter, the new password will be send to your host. In this case the new
password will be send to WebWolf. Try to locate the unique code.
Please be aware after resetting the password the user will receive an error page in a real attack scenario the
user would probably see a normal success page (this is due to a limit what we can control with WebWolf)

View File

@ -0,0 +1,18 @@
== Your own mailbox
WebWolf offers a mail client which will contain the e-mail send during a lesson.
This mailbox is user specific so each user has a separate mailbox. All e-mail
send to {user}@.... wil end up in this inbox.
{nbsp}
{nbsp}
{nbsp}
image::images/mailbox.png[caption="Figure: ", style="lesson-image"]
{nbsp}
{nbsp}
{nbsp}
Try it, type in your e-mail address below and check in
WebWolf your e-mail and type in the unique code below.

View File

@ -0,0 +1,12 @@
== Uploading files
In this section you can upload files these files will be available from outside
the application. For example in a XXE attack you want to reference a DTD which you
reference from a xml, you can use WebWolf to serve this DTD.
image::images/files.png[caption="Figure: ", style="lesson-image"]
{nbsp}
After uploading a file you can use the 'Link' to get the full URL to the uploaded
file.

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<link rel="stylesheet" type="text/css" th:href="@{/plugins/bootstrap/css/bootstrap.min.css}"/>
<link rel="stylesheet" type="text/css" th:href="@{/css/font-awesome.min.css}"/>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-8 col-md-6 col-sm-offset-2 col-md-offset-3">
<form role="form" method="POST" th:action="${webwolfUrl}">
<h2 class="sign_up_title">Reset your password</h2>
<input type="hidden" name="uniqueCode" th:value="${uniqueCode}"/>
<div class="form-group">
<label for="password" class="control-label">Password</label>
<input type="password" class="form-control" id="password" placeholder="Password"
name='password'/>
</div>
<div class="row">
<div class="col-xs-12 col-md-12">
<button type="submit" class="btn btn-success btn-block btn-lg">Save</button>
</div>
</div>
<div>
<a href="https://github.com/WebGoat">(c) 2017 WebGoat Company</a>
</div>
</form>
</div>
</div>
</div>
</body>
</html>

View File

@ -70,6 +70,22 @@
</plugins>
</build>
</profile>
<profile>
<id>ctf</id>
<dependencies>
<dependency>
<groupId>org.owasp.webgoat</groupId>
<artifactId>webgoat-container</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</profile>
</profiles>
<dependencies>
@ -154,6 +170,11 @@
<artifactId>auth-bypass</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.owasp.webgoat.lesson</groupId>
<artifactId>webwolf-introduction</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.owasp.webgoat.lesson</groupId>
<artifactId>missing-function-ac</artifactId>
@ -162,9 +183,9 @@
<!--uncommment below to run/include lesson template in WebGoat Build-->
<!--<dependency>-->
<!--<groupId>org.owasp.webgoat.lesson</groupId>-->
<!--<artifactId>webgoat-lesson-template</artifactId>-->
<!--<version>${project.version}</version>-->
<!--<groupId>org.owasp.webgoat.lesson</groupId>-->
<!--<artifactId>webgoat-lesson-template</artifactId>-->
<!--<version>${project.version}</version>-->
<!--</dependency>-->
<!-- /lessons -->
<dependency>

View File

@ -22,7 +22,9 @@
* projects.
* <p>
*/
package org.owasp.webgoat;import org.springframework.boot.SpringApplication;
package org.owasp.webgoat;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
@ -30,7 +32,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
* @date 2/21/17
*/
@SpringBootApplication
public class StartWebGoat {
public class StartWebGoat {
public static void main(String[] args) {
SpringApplication.run(WebGoat.class, args);

46
webwolf/README.md Normal file
View File

@ -0,0 +1,46 @@
# WebWolf
## Introduction
During workshops one of the feedback items was that in some lesson it was not clear what you controlled
as an attacker and what was part of the lesson. To make this separation more distinct we created
WebWolf which is completely controlled by you as the attacker and runs as a separate application.
Instead of using your own machine which would involve WebGoat being connected to your local network
or internet (remember WebGoat is a vulnerable webapplication) we created WebWolf which is the the
environment for you as an attacker.
At the moment WebWolf offers support for:
- Receiving e-mails
- Serving files
- Logging of incoming requests (cookies etc)
## Running
### Docker
If you use the Docker image of WebGoat this application will automatically be available. Use the following
URL: http://localhost:8081/WebWolf
### Standalone
```Shell
cd WebGoat
git checkout develop
mvn clean install
```
Now we are ready to run the project. WebGoat 8.x is using Spring-Boot.
```Shell
mvn -pl webwolf spring-boot:run
```
... you should be running WebWolf on localhost:8081/WebWolf momentarily
### Mapping
The web application runs on '/' and the controllers and Thymeleaf templates are hardcoded to '/WebWolf' we need
to have '/' available which acts as a landing page for incoming requests.

111
webwolf/pom.xml Normal file
View File

@ -0,0 +1,111 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>webwolf</artifactId>
<packaging>jar</packaging>
<parent>
<groupId>org.owasp.webgoat</groupId>
<artifactId>webgoat-parent</artifactId>
<version>8.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.owasp.webgoat</groupId>
<artifactId>webgoat-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>LATEST</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.7</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>ISO-8859-1</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,89 @@
package org.owasp.webwolf;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.owasp.webwolf.user.WebGoatUser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.util.List;
/**
* Controller for uploading a file
*/
@Controller
@Slf4j
public class FileServer {
@Value("${webwolf.fileserver.location}")
private String fileLocatation;
@PostMapping(value = "/WebWolf/fileupload")
@SneakyThrows
public ModelAndView importFile(@RequestParam("file") MultipartFile myFile) {
WebGoatUser user = (WebGoatUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
File destinationDir = new File(fileLocatation, user.getUsername());
destinationDir.mkdirs();
myFile.transferTo(new File(destinationDir, myFile.getOriginalFilename()));
log.debug("File saved to {}", new File(destinationDir, myFile.getOriginalFilename()));
Files.touch(new File(destinationDir, user.getUsername() + "_changed"));
ModelMap model = new ModelMap();
model.addAttribute("uploadSuccess", "File uploaded successful");
return new ModelAndView(
new RedirectView("files", true),
model
);
}
@AllArgsConstructor
@Getter
private class UploadedFile {
private final String name;
private final String size;
private final String link;
}
@GetMapping(value = "/WebWolf/files")
public ModelAndView getFiles(HttpServletRequest request) {
WebGoatUser user = (WebGoatUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String username = user.getUsername();
File destinationDir = new File(fileLocatation, username);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("files");
File changeIndicatorFile = new File(destinationDir, user.getUsername() + "_changed");
if (changeIndicatorFile.exists()) {
modelAndView.addObject("uploadSuccess", request.getParameter("uploadSuccess"));
}
changeIndicatorFile.delete();
List<UploadedFile> uploadedFiles = Lists.newArrayList();
File[] files = destinationDir.listFiles(File::isFile);
if (files != null) {
for (File file : files) {
String size = FileUtils.byteCountToDisplaySize(file.length());
String link = String.format("files/%s/%s", username, file.getName());
uploadedFiles.add(new UploadedFile(file.getName(), size, link));
}
}
modelAndView.addObject("files", uploadedFiles);
return modelAndView;
}
}

View File

@ -0,0 +1,43 @@
package org.owasp.webwolf;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import javax.annotation.PostConstruct;
import java.io.File;
/**
* @author nbaars
* @since 8/13/17.
*/
@Configuration
public class MvcConfiguration extends WebMvcConfigurerAdapter {
@Value("${webwolf.fileserver.location}")
private String fileLocatation;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/files/**").addResourceLocations("file:///" + fileLocatation + "/");
super.addResourceHandlers(registry);
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.addViewController("/WebWolf/home").setViewName("home");
}
@PostConstruct
public void createDirectory() {
File file = new File(fileLocatation);
if (!file.exists()) {
file.mkdirs();
}
}
}

View File

@ -0,0 +1,84 @@
/**
* ************************************************************************************************
* This file is part of WebGoat, an Open Web Application Security Project utility. For details,
* please see http://www.owasp.org/
* <p>
* Copyright (c) 2002 - 20014 Bruce Mayhew
* <p>
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
* <p>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License along with this program; if
* not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
* <p>
* Getting Source ==============
* <p>
* Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software
* projects.
* <p>
*
* @author WebGoat
* @version $Id: $Id
* @since December 12, 2015
*/
package org.owasp.webwolf;
import lombok.AllArgsConstructor;
import org.owasp.webwolf.user.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* Security configuration for WebGoat.
*/
@Configuration
@AllArgsConstructor
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry security = http
.authorizeRequests()
.antMatchers("/css/**", "/images/**", "/js/**", "/fonts/**", "/webjars/**").permitAll()
.antMatchers("/WebWolf/**").authenticated()
.anyRequest().permitAll();
security.and().csrf().disable().formLogin()
.loginPage("/login").failureUrl("/login?error=true");
security.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/WebWolf/home", true)
.permitAll();
security.and()
.logout()
.permitAll();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService); //.passwordEncoder(bCryptPasswordEncoder());
}
@Bean
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return userDetailsService;
}
}

View File

@ -0,0 +1,59 @@
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.WebGoatUserToCookieRepository;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.converter.MessageType;
import javax.jms.ConnectionFactory;
@SpringBootApplication
@Slf4j
public class WebWolf extends SpringBootServletInitializer {
@Bean
public TraceRepository traceRepository(WebGoatUserToCookieRepository repository) {
return new WebWolfTraceRepository(repository);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(WebWolf.class);
}
@Bean
public JmsListenerContainerFactory<?> jmsFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
// This provides all boot's default to this factory, including the message converter
configurer.configure(factory, connectionFactory);
// You could still override some of Boot's default if necessary.
return factory;
}
@Bean
public MessageConverter jacksonJmsMessageConverter(ObjectMapper objectMapper) {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
converter.setObjectMapper(objectMapper);
return converter;
}
public static void main(String[] args) throws Exception {
SpringApplication.run(WebWolf.class, args);
}
}

View File

@ -0,0 +1,42 @@
package org.owasp.webwolf.mailbox;
import lombok.Builder;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* @author nbaars
* @since 8/20/17.
*/
@Builder
@Data
@Document
public class Email implements Serializable {
@Id
private String id;
private LocalDateTime time;
private String contents;
private String sender;
private String title;
@Indexed
private String recipient;
public String getSummary() {
return "-" + this.contents.substring(0, 50);
}
public String getTime() {
return DateTimeFormatter.ofPattern("h:mm a").format(time);
}
public String getShortSender() {
return sender.substring(0, sender.indexOf("@"));
}
}

View File

@ -0,0 +1,35 @@
package org.owasp.webwolf.mailbox;
import lombok.AllArgsConstructor;
import org.owasp.webwolf.user.WebGoatUser;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
/**
* @author nbaars
* @since 8/17/17.
*/
@RestController
@AllArgsConstructor
public class MailboxController {
private final MailboxRepository mailboxRepository;
@GetMapping(value = "/WebWolf/mail")
public ModelAndView mail() {
WebGoatUser user = (WebGoatUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
ModelAndView modelAndView = new ModelAndView();
List<Email> emails = mailboxRepository.findByRecipientOrderByTimeDesc(user.getUsername());
if (emails != null && !emails.isEmpty()) {
modelAndView.addObject("total", emails.size());
modelAndView.addObject("emails", emails);
}
modelAndView.setViewName("mailbox");
return modelAndView;
}
}

View File

@ -0,0 +1,37 @@
package org.owasp.webwolf.mailbox;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.mail.IncomingMailEvent;
import org.owasp.webwolf.user.UserRepository;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
/**
* @author nbaars
* @since 8/20/17.
*/
@Component
@AllArgsConstructor
@Slf4j
public class MailboxListener {
private final MailboxRepository repository;
private final UserRepository userRepository;
@JmsListener(destination = "mailbox", containerFactory = "jmsFactory")
public void incomingMail(IncomingMailEvent event) {
if (userRepository.findByUsername(event.getRecipient()) != null) {
Email email = Email.builder()
.contents(event.getContents())
.sender(event.getSender())
.time(event.getTime())
.recipient(event.getRecipient())
.title(event.getTitle()).build();
repository.save(email);
} else {
log.trace("Mail received for unknown user: {}", event.getRecipient());
}
}
}

View File

@ -0,0 +1,16 @@
package org.owasp.webwolf.mailbox;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
import java.util.List;
/**
* @author nbaars
* @since 8/17/17.
*/
public interface MailboxRepository extends MongoRepository<Email, ObjectId> {
List<Email> findByRecipientOrderByTimeDesc(String recipient);
}

View File

@ -0,0 +1,69 @@
package org.owasp.webwolf.requests;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.owasp.webwolf.user.WebGoatUser;
import org.springframework.boot.actuate.trace.Trace;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;
import static java.util.stream.Collectors.toList;
/**
* Controller for fetching all the HTTP requests from WebGoat to WebWolf for a specific
* user.
*
* @author nbaars
* @since 8/13/17.
*/
@Controller
@AllArgsConstructor
@Slf4j
@RequestMapping(value = "/WebWolf/requests")
public class Requests {
private final WebWolfTraceRepository traceRepository;
private final ObjectMapper objectMapper;
@AllArgsConstructor
@Getter
private class Tracert {
private final Date date;
private final String path;
private final String json;
}
@GetMapping
public ModelAndView get(HttpServletRequest request) {
ModelAndView m = new ModelAndView("requests");
WebGoatUser user = (WebGoatUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
List<Tracert> traces = traceRepository.findTraceForUser(user.getUsername()).stream()
.map(t -> new Tracert(t.getTimestamp(), path(t), toJsonString(t))).collect(toList());
m.addObject("traces", traces);
return m;
}
private String path(Trace t) {
return (String) t.getInfo().getOrDefault("path", "");
}
private String toJsonString(Trace t) {
try {
return objectMapper.writeValueAsString(t.getInfo());
} catch (JsonProcessingException e) {
log.error("Unable to create json", e);
}
return "No request(s) found";
}
}

View File

@ -0,0 +1,105 @@
package org.owasp.webwolf.requests;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
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.WebGoatUser;
import org.owasp.webwolf.user.WebGoatUserCookie;
import org.owasp.webwolf.user.WebGoatUserToCookieRepository;
import org.springframework.boot.actuate.trace.Trace;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import java.net.HttpCookie;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedDeque;
import static java.util.Optional.of;
/**
* Keep track of all the incoming requests, we are only keeping track of request originating from
* WebGoat and only if there is a cookie (otherwise we can never relate it back to a user).
*
* @author nbaars
* @since 8/13/17.
*/
@Slf4j
public class WebWolfTraceRepository implements TraceRepository {
private final LoadingCache<String, ConcurrentLinkedDeque<Trace>> cookieTraces = CacheBuilder.newBuilder()
.maximumSize(4000).build(new CacheLoader<String, ConcurrentLinkedDeque<Trace>>() {
@Override
public ConcurrentLinkedDeque<Trace> load(String s) throws Exception {
return new ConcurrentLinkedDeque<>();
}
});
private final WebGoatUserToCookieRepository repository;
public WebWolfTraceRepository(WebGoatUserToCookieRepository repository) {
this.repository = repository;
}
@Override
public List<Trace> findAll() {
HashMap<String, Object> map = Maps.newHashMap();
map.put("nice", "Great you found the standard Spring Boot tracing endpoint!");
Trace trace = new Trace(new Date(), map);
return Lists.newArrayList(trace);
}
public List<Trace> findTraceForUser(String username) {
return Lists.newArrayList(cookieTraces.getUnchecked(username));
}
@Override
public void add(Map<String, Object> map) {
Optional<String> host = getFromHeaders("host", map);
String path = (String) map.getOrDefault("path", "");
if (host.isPresent() && ("/".equals(path) || path.contains("challenge"))) {
Optional<String> cookie = getFromHeaders("cookie", map);
cookie.ifPresent(c -> {
Optional<String> user = findUserBasedOnCookie(c);
user.ifPresent(u -> {
ConcurrentLinkedDeque<Trace> traces = this.cookieTraces.getUnchecked(u);
traces.addFirst(new Trace(new Date(), map));
cookieTraces.put(u, traces);
});
});
}
}
private Optional<String> findUserBasedOnCookie(String cookiesIncomingRequest) {
//Request from WebGoat to WebWolf will contain the session cookie of WebGoat try to map it to a user
//this mapping is added to userSession by the CookieFilter in WebGoat code
HttpCookie cookie = HttpCookie.parse(cookiesIncomingRequest).get(0);
Optional<WebGoatUserCookie> userToCookie = repository.findByCookie(cookie.getValue());
Optional<String> 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<String> getFromHeaders(String header, Map<String, Object> map) {
Map<String, Object> headers = (Map<String, Object>) map.get("headers");
if (headers != null) {
Map<String, Object> request = (Map<String, Object>) headers.get("request");
if (request != null) {
return Optional.ofNullable((String) request.get(header));
}
}
return Optional.empty();
}
}

View File

@ -0,0 +1,33 @@
package org.owasp.webwolf.user;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.login.LoginEvent;
import org.owasp.webgoat.login.LogoutEvent;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
/**
* @author nbaars
* @since 8/20/17.
*/
@Component
@Slf4j
@AllArgsConstructor
public class LoginListener {
private final WebGoatUserToCookieRepository repository;
@JmsListener(destination = "webgoat", containerFactory = "jmsFactory", selector = "type = 'LoginEvent'")
public void loginEvent(LoginEvent loginEvent) {
log.trace("Login event occurred for user: '{}'", loginEvent.getUser());
repository.save(new WebGoatUserCookie(loginEvent.getUser(), loginEvent.getCookie()));
}
@JmsListener(destination = "webgoat", containerFactory = "jmsFactory", selector = "type = 'LogoutEvent'")
public void logoutEvent(LogoutEvent logoutEvent) {
repository.delete(logoutEvent.getUser());
}
}

View File

@ -0,0 +1,12 @@
package org.owasp.webwolf.user;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @author nbaars
* @since 3/19/17.
*/
public interface UserRepository extends MongoRepository<WebGoatUser, String> {
WebGoatUser findByUsername(String username);
}

View File

@ -0,0 +1,30 @@
package org.owasp.webwolf.user;
import lombok.AllArgsConstructor;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* @author nbaars
* @since 3/19/17.
*/
@Service
@AllArgsConstructor
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public WebGoatUser loadUserByUsername(String username) throws UsernameNotFoundException {
WebGoatUser webGoatUser = userRepository.findByUsername(username);
if (webGoatUser == null) {
throw new UsernameNotFoundException("User not found");
} else {
webGoatUser.createUser();
}
return webGoatUser;
}
}

View File

@ -0,0 +1,69 @@
package org.owasp.webwolf.user;
import lombok.Getter;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Collections;
/**
* @author nbaars
* @since 3/19/17.
*/
@Getter
public class WebGoatUser implements UserDetails {
public static final String ROLE_USER = "WEBGOAT_USER";
public static final String ROLE_ADMIN = "WEBGOAT_ADMIN";
@Id
private String username;
private String password;
private String role = ROLE_USER;
@Transient
private User user;
protected WebGoatUser() {
}
public WebGoatUser(String username, String password) {
this.username = username;
this.password = password;
createUser();
}
public void createUser() {
this.user = new User(username, password, getAuthorities());
}
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singleton(new SimpleGrantedAuthority(getRole()));
}
@Override
public boolean isAccountNonExpired() {
return this.user.isAccountNonExpired();
}
@Override
public boolean isAccountNonLocked() {
return this.user.isAccountNonLocked();
}
@Override
public boolean isCredentialsNonExpired() {
return this.user.isCredentialsNonExpired();
}
@Override
public boolean isEnabled() {
return this.user.isEnabled();
}
}

View File

@ -0,0 +1,22 @@
package org.owasp.webwolf.user;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import java.io.Serializable;
/**
* @author nbaars
* @since 8/20/17.
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class WebGoatUserCookie implements Serializable {
@Id
private String username;
private String cookie;
}

View File

@ -0,0 +1,14 @@
package org.owasp.webwolf.user;
import org.springframework.data.mongodb.repository.MongoRepository;
import java.util.Optional;
/**
* @author nbaars
* @since 8/20/17.
*/
public interface WebGoatUserToCookieRepository extends MongoRepository<WebGoatUserCookie, String> {
Optional<WebGoatUserCookie> findByCookie(String cookie);
}

View File

@ -0,0 +1,41 @@
server.error.include-stacktrace=always
server.error.path=/error.html
server.session.timeout=600
#server.contextPath=/WebWolf
server.port=8081
server.session.cookie.name = WEBWOLFSESSION
logging.level.org.springframework=INFO
logging.level.org.springframework.boot.devtools=WARN
logging.level.org.owasp=DEBUG
logging.level.org.owasp.webwolf=TRACE
logging.level.org.apache.activemq=WARN
endpoints.trace.sensitive=false
management.trace.include=REQUEST_HEADERS,RESPONSE_HEADERS,COOKIES,ERRORS,TIME_TAKEN,PARAMETERS,QUERY_STRING
endpoints.trace.enabled=true
spring.resources.cache-period=0
spring.thymeleaf.cache=false
multipart.enabled=true
multipart.file-size-threshold=0 #
multipart.location=${java.io.tmpdir}
multipart.max-file-size=1Mb
multipart.max-request-size=1Mb
webwolf.fileserver.location=${java.io.tmpdir}/webwolf-fileserver
spring.data.mongodb.port=27017
spring.data.mongodb.database=webgoat
spring.jackson.serialization.indent_output=true
spring.jackson.serialization.write-dates-as-timestamps=false
spring.activemq.broker-url=tcp://localhost:61616
spring.activemq.in-memory=true
#For static file refresh ... and faster dev :D
spring.devtools.restart.additional-paths=webwolf/src/main/resources/static/

View File

@ -0,0 +1,87 @@
footer {
margin-top: 200px;
}
.thead-inverse th {
color: #fff;
background-color: #373a3c;
}
.left15 {
margin-left: 15px;
}
.top5 {
margin-top: 5px;
}
.top7 {
margin-top: 7px;
}
.top10 {
margin-top: 10px;
}
.top15 {
margin-top: 15px;
}
.top17 {
margin-top: 17px;
}
.top30 {
margin-top: 30px;
}
.bottom10 {
margin-bottom: 10px;
}
#accordion .panel-heading {
padding: 0;
}
#accordion .panel-title > a {
display: block;
padding: 0.4em 0.6em;
outline: none;
font-weight: bold;
text-decoration: none;
}
#accordion .panel-title > a.accordion-toggle::before, #accordion a[data-toggle="collapse"]::before {
content: "\e113";
float: left;
font-family: 'Glyphicons Halflings';
margin-right: 1em;
}
#accordion .panel-title > a.accordion-toggle.collapsed::before, #accordion a.collapsed[data-toggle="collapse"]::before {
content: "\e114";
}
textarea {
height: 250px;
overflow: auto;
width: 100%;
padding: 1%;
border: none;
}
/*Mailbox*/
.nav-tabs .glyphicon:not(.no-margin) { margin-right:10px; }
.tab-pane .list-group-item:first-child {border-top-right-radius: 0px;border-top-left-radius: 0px;}
.tab-pane .list-group-item:last-child {border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;}
.tab-pane .list-group .checkbox { display: inline-block;margin: 0px; }
.tab-pane .list-group input[type="checkbox"]{ margin-top: 2px; }
.tab-pane .list-group .glyphicon { margin-right:5px; }
.tab-pane .list-group .glyphicon:hover { color:#FFBC00; }
a.list-group-item.read { color: #222;background-color: #F3F3F3; }
hr { margin-top: 5px;margin-bottom: 10px; }
.nav-pills>li>a {padding: 5px 10px;}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@ -0,0 +1,15 @@
$(document).ready(function() {
window.setTimeout(function () {
$(".fileUploadAlert").fadeTo(500, 0).slideUp(500, function () {
$(this).hide();
});
}, 4000);
});
$(document).on('click','.fa-files-o',function(){
var link = $('#fileLink').attr("href");
console.log("testing" + document.protocol + "//" + (document.hostname || document.pathname + link));
document.execCommand('copy');
});

View File

@ -0,0 +1,10 @@
$(document).ready(function () {
$('.showMail').click(function (e) {
e.preventDefault();
$(this).parent().find('.contents').toggle()
});
});
function refreshEmails() {
location.reload();
}

View File

@ -0,0 +1,72 @@
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<div th:replace="fragments/header :: header-css"/>
</head>
<body>
<div th:replace="fragments/header :: header"/>
<script type="text/javascript" th:src="@{/js/fileUpload.js}"></script>
<div class="container">
<div class="alert alert-info fade in">
<a href="#" class="close" data-dismiss="alert">&times;</a>
<p>
Upload a file which you need to host as an attacker.
</p>
<p>
Each file will be available under the following url:
http://localhost:8081/files/{username}/{filename}.
</p>
<p>
You can copy and paste the location from the table below.
</p>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Upload a file</strong>
<small></small>
</div>
<div class="panel-body">
<!-- Standar Form -->
<form th:action="@{/WebWolf/fileupload}" method="post" enctype="multipart/form-data">
<div class="form-inline">
<div class="form-group">
<input type="file" name="file"/>
</div>
<button type="submit" class="btn btn-md btn-primary">Upload files</button>
</div>
</form>
<div class="fileUploadAlert alert-success top10" role="alert">
<span th:text="${uploadSuccess}"></span>
</div>
</div>
</div>
<table class="table">
<thead class="thead-inverse">
<tr>
<th>Filename</th>
<th>Size</th>
<th>Link</th>
</tr>
</thead>
<tbody>
<tr th:each="f : ${files}">
<td th:text="${f.name}">filename</td>
<td th:text="${f.size}">size</td>
<td><a th:id="fileLink" th:href="@{'/' + ${f.link}}">link</a>
<span class="fa fa-files-o" title="Click to copy to clipboard"></span>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

View File

@ -0,0 +1,19 @@
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
</head>
<body>
<div th:fragment="footer">
<div class="container">
<footer>
© 2017 WebGoat - Use WebWolf at your own risk
<script type="text/javascript"
src="webjars/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</footer>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,51 @@
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<title>WebWolf</title>
<div th:fragment="header-css">
<link rel="stylesheet" type="text/css"
href="/webjars/bootstrap/3.3.7/css/bootstrap.min.css"/>
<!--<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet"/>-->
<link rel="stylesheet" th:href="@{/css/main.css}"/>
<script src="/webjars/jquery/3.2.1/jquery.min.js"></script>
<script src="/webjars/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</div>
</head>
<body>
<div th:fragment="header">
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" th:href="@{/WebWolf/home}">WebWolf</a>
</div>
<ul class="nav navbar-nav">
<li class="active"><a th:href="@{/WebWolf/home}">Home</a></li>
</ul>
<ul class="nav navbar-nav">
<li><a th:href="@{/WebWolf/files}">Files</a></li>
</ul>
<ul class="nav navbar-nav">
<li><a th:href="@{/WebWolf/mail}">Mailbox</a></li>
</ul>
<ul class="nav navbar-nav">
<li><a th:href="@{/WebWolf/requests}">Incoming requests</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">
<span sec:authorize="isAuthenticated()">
<span class="glyphicon glyphicon-user"></span>
<span th:text="${#authentication.name}"></span></span></a>
</li>
<li><a th:href="@{/logout}">
<span sec:authorize="isAuthenticated()">
Sign out</span></a>
</li>
</ul>
</div>
</nav>
</div>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More