- 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

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>

View File

@ -0,0 +1,37 @@
<!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"/>
<div class="container">
<div class="media">
<span class="media-left">
<img th:src="@{/images/wolf.png}" class="img-fluid"></img>
</span>
<div class="media-body">
<h1 class="media-heading">WebWolf</h1>
<br/>
<p>
Some challenges requires to have a local web server running. WebWolf is for you the attacker it
helps you while solving some of the assignments and challenges within
WebGoat. An assignment might for example require you to serve a file or connect back to your own
environment or to receive an e-mail.
In order to not let you run WebGoat open and connected to the internet we provided these tools in this
application, called WebWolf.
</p>
</div>
</div>
</div>
<!-- /.container -->
<div th:replace="fragments/footer :: footer"/>
</body>
</html>

View File

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
>
<head>
<title>WebWolf</title>
<div th:replace="fragments/header :: header-css"/>
</head>
<body>
<div th:replace="fragments/header :: header"/>
<div class="container">
<div class="row" style="margin-top:20px">
<div class="col-xs-12 col-sm-8 col-md-6 col-sm-offset-2 col-md-offset-3" th:style="'background:url(' + @{/images/wolf.png} + ') no-repeat right;'">
<form th:action="@{/login}" method="post">
<fieldset>
<h2>Sign in</h2>
Use your WebGoat account.
<br/>
<div th:if="${param.error}">
<div class="alert alert-danger">
Invalid username or password.
</div>
</div>
<div th:if="${param.logout}">
<div class="alert alert-info">
You have been logged out.
</div>
</div>
<div class="form-group">
<input type="text" name="username" id="username" class="form-control input-lg"
placeholder="UserName" required="true" autofocus="true"/>
</div>
<div class="form-group">
<input type="password" name="password" id="password" class="form-control input-lg"
placeholder="Password" required="true"/>
</div>
<div class="row">
<div class="col-xs-6 col-sm-6 col-md-6">
<input type="submit" class="btn btn-lg btn-primary btn-block" value="Sign In"/>
</div>
<div class="col-xs-6 col-sm-6 col-md-6">
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
<div th:replace="fragments/footer :: footer"/>
</body>
</html>

View File

@ -0,0 +1,150 @@
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>WebWolf</title>
<div th:replace="fragments/header :: header-css"/>
</head>
<body>
<div th:replace="fragments/header :: header"/>
<script type="text/javascript" th:src="@{/js/mail.js}"></script>
<div class="container">
<div class="alert alert-info fade in">
<a href="#" class="close" data-dismiss="alert">&times;</a>
<p>
The mailbox of you as an attacker, all the mail send to {user}@{random} will be send to this mailbox.
</p>
<p>
Only the user part is important the domain can be anything
</p>
</div>
<div class="panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-sm-3 col-md-2">
<div class="btn-group">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
Mail <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="#">Mail</a></li>
<li><a href="#">Contacts</a></li>
<li><a href="#">Tasks</a></li>
</ul>
</div>
</div>
<div class="col-sm-9 col-md-10">
<button type="button" class="btn btn-default" data-toggle="tooltip" title="Refresh" onclick="refreshEmails()">
   <span class="glyphicon glyphicon-refresh"></span>   
</button>
<!-- Single button -->
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
More <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="#">Mark all as read</a></li>
<li class="divider"></li>
<li class="text-center">
<small class="text-muted">Select messages to see more actions</small>
</li>
</ul>
</div>
<div class="pull-right">
<span class="text-muted"><b>1</b><b><span th:text="${total}"> 50</span> </b> of <b><span th:utext="${total}"></span></b></span>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-default">
<span class="glyphicon glyphicon-chevron-left"></span>
</button>
<button type="button" class="btn btn-default">
<span class="glyphicon glyphicon-chevron-right"></span>
</button>
</div>
</div>
</div>
</div>
<hr/>
<div class="row">
<div class="col-sm-3 col-md-2">
<a href="#" class="btn btn-danger btn-sm btn-block" role="button">COMPOSE</a>
<hr/>
<ul class="nav nav-pills nav-stacked">
<li class="active"><a href="#"><span class="badge pull-right" th:utext="${total}">42</span>
Inbox </a>
</li>
</ul>
</div>
<div class="col-sm-9 col-md-10">
<!-- Nav tabs -->
<ul class="nav nav-tabs">
<li class="active"><a href="#home" data-toggle="tab"><span class="glyphicon glyphicon-inbox">
</span>Primary</a></li>
<li><a href="#profile" data-toggle="tab"><span class="glyphicon glyphicon-user"></span>
Social</a></li>
<li><a href="#messages" data-toggle="tab"><span class="glyphicon glyphicon-tags"></span>
Promotions</a></li>
<li><a href="#settings" data-toggle="tab"><span class="glyphicon glyphicon-plus no-margin">
</span></a></li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div class="tab-pane fade in active" id="home">
<span th:each="mail : ${emails}" class="list-group">
<a href="#" class="showMail list-group-item">
<span class="glyphicon glyphicon-star-empty"></span>
<span class="name"
style="min-width: 120px; display: inline-block;"
th:text="${mail.shortSender}">WebGoat</span>
<span class="" th:text="${mail.title}">Title</span>
<span class="text-muted" style="font-size: 11px;" th:text="${mail.summary}">- summary</span>
<span class="badge" th:text="${mail.time}">12:10 AM</span>
<span class="pull-right">
<span class="glyphicon glyphicon-paperclip"></span>
</span>
</a>
<!---->
<div class="left15 contents panel panel-default top10" style="display:none" >
<div class="panel-heading" >
<b><span th:text="${mail.title}"></span></b>
<b><span class="pull-right" th:text="${mail.sender}"></span></b>
</div>
<div class="panel-body">
<pre th:utext="${mail.contents}"/>
</div>
</div>
</span>
</div>
<div class="tab-pane fade in" id="profile">
<div class="list-group">
<div class="list-group-item">
<span class="text-center">This tab is empty.</span>
</div>
</div>
</div>
<div class="tab-pane fade in" id="messages">
<div class="list-group-item">
<span class="text-center">Why the name "WebGoat"? Developers should not feel bad about not knowing security. Even the best programmers make security errors. What they need is a scapegoat, right? Just blame it on the 'Goat!</span>
</div>
</div>
<div class="tab-pane fade in" id="settings">
This tab is empty.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,56 @@
<!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>
Challenges in which you need to call your hacker machine WebWolf offers a simple httpd
server functionality which only logs the incoming request. You can use the following URL:
http://localhost:8081/ and the incoming request will be available below.
</p>
<p>
This is by no means a substitution of httpd but it offers enough functionality to callback to a safe
environment and does not require you to host your own httpd server on your local machine.
</p>
</div>
<h3>Requests</h3>
<div th:each="trace,iter : ${traces}" class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
<div class="panel panel-default">
<div class="panel-heading" role="tab" th:id="'heading' + ${iter.index}">
<h4 class="panel-title">
<a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion"
th:href="'#collapse' + ${iter.index}" aria-expanded="false" th:aria-controls="'collapse' + ${iter.index}">
<span th:utext="${trace.date}"/> | <span th:utext="${trace.path}"/>
</a>
</h4>
</div>
<div th:id="'collapse' + ${iter.index}" class="panel-collapse collapse" role="tabpanel"
th:aria-labelledby="'heading' + ${iter.index}">
<div class="panel-body">
<div>
<pre th:utext="${trace.json}"/>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>