- 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

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