- Introduced user registration

- Now using Spring Boot for classloading, this way local development does not need to restart the complete server
- Fixed all kinds of dependencies on the names of the lessons necessary to keep in mind during the creation of a lesson.
- Simplied loading of resources, by adding resource mappings in MvcConfig.
- Refactored plugin loading, now only one class is left for loading the lessons.
This commit is contained in:
Nanne Baars 2017-02-25 12:15:07 +01:00
parent 9b86aaba05
commit 259fd19c1b
221 changed files with 1179 additions and 1083 deletions

View File

@ -73,7 +73,7 @@ mvn clean package
Now we are ready to run the project. WebGoat 8.x is using Spring-Boot.
```Shell
mvn -pl webgoat-container spring-boot:run
mvn -pl webgoat-assembly spring-boot:run
```
... you should be running webgoat on localhost:8080/WebGoat momentarily

View File

@ -1,3 +0,0 @@
grant {
permission java.security.AllPermission;
};

View File

@ -171,6 +171,7 @@
<modules>
<module>webgoat-container</module>
<module>webgoat-lessons</module>
<module>webgoat-server</module>
</modules>
<distributionManagement>

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<groupId>org.owasp.webgoat</groupId>
<name>webgoat-container</name>
<modelVersion>4.0.0</modelVersion>
<artifactId>webgoat-container</artifactId>
<packaging>war</packaging>
<packaging>jar</packaging>
<parent>
<groupId>org.owasp.webgoat</groupId>
@ -13,68 +13,6 @@
<version>8.0-SNAPSHOT</version>
</parent>
<properties>
<start-class>org.owasp.webgoat.WebGoat</start-class>
</properties>
<profiles>
<profile>
<id>raspberry-pi-3</id>
<activation>
<property>
<name>rpi</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.4.10</version>
<configuration>
<imageName>webgoat/webgoat-8.0</imageName>
<dockerDirectory>src/main/docker_rpi3</dockerDirectory>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.war</include>
</resource>
</resources>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>default</id>
<activation>
<property>
<name>!rpi</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.4.10</version>
<configuration>
<imageName>webgoat/webgoat-8.0</imageName>
<dockerDirectory>src/main/docker</dockerDirectory>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.war</include>
</resource>
</resources>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<build>
<resources>
@ -93,41 +31,6 @@
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
<executions>
<execution>
<id>create-jar</id>
<phase>compile</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>${build-helper-maven-plugin.version}</version>
<executions>
<execution>
<id>attach-artifacts</id>
<phase>package</phase>
<goals>
<goal>attach-artifact</goal>
</goals>
<configuration>
<artifacts>
<artifact>
<file>${project.build.directory}/webgoat-container-${project.version}.jar</file>
</artifact>
</artifacts>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
@ -157,35 +60,6 @@
<forkMode>never</forkMode>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- See http://docs.spring.io/spring-boot/docs/current/reference/html/howto-build.html#howto-extract-specific-libraries-when-an-executable-jar-runs -->
<requiresUnpack>
<dependency>
<groupId>org.thymeleaf.extra</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
<dependency>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctorj</artifactId>
</dependency>
<dependency>
<groupId>org.jruby</groupId>
<artifactId>jruby-complete</artifactId>
</dependency>
</requiresUnpack>
<fork>true</fork>
</configuration>
<!--<dependencies>-->
<!--<dependency>-->
<!--<groupId>org.springframework</groupId>-->
<!--<artifactId>springloaded</artifactId>-->
<!--<version>1.2.5.RELEASE</version>-->
<!--</dependency>-->
<!--</dependencies>-->
</plugin>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<configuration>
@ -242,6 +116,11 @@
<artifactId>asciidoctorj</artifactId>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
@ -251,6 +130,14 @@
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
@ -297,17 +184,8 @@
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.4.10</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.6</version>
</dependency>
<!-- ************* END spring MVC and related dependencies ************** -->
<!-- ************* START: Dependencies for Unit and Integration Testing ************** -->

View File

@ -34,25 +34,20 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.asciidoctor.Asciidoctor;
import org.owasp.webgoat.i18n.Language;
import org.springframework.util.StringUtils;
import org.thymeleaf.TemplateProcessingParameters;
import org.thymeleaf.resourceresolver.IResourceResolver;
import org.thymeleaf.templateresolver.TemplateResolver;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import static org.asciidoctor.Asciidoctor.Factory.create;
/**
* Thymeleaf resolver for AsciiDoc used in the lesson, can be used as follows inside a lesson file:
*
* <p>
* <code>
* <div th:replace="doc:AccessControlMatrix_plan.adoc"></div>
* <div th:replace="doc:AccessControlMatrix_plan.adoc"></div>
* </code>
*/
public class AsciiDoctorTemplateResolver extends TemplateResolver {
@ -80,34 +75,26 @@ public class AsciiDoctorTemplateResolver extends TemplateResolver {
@Override
public InputStream getResourceAsStream(TemplateProcessingParameters params, String resourceName) {
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(computeResourceName(resourceName));
try {
Optional<Path> adocFile = resolveAdocFile(resourceName);
if (adocFile.isPresent()) {
try (FileReader reader = new FileReader(adocFile.get().toFile())) {
StringWriter writer = new StringWriter();
asciidoctor.convert(reader, writer, createAttributes());
return new ByteArrayInputStream(writer.getBuffer().toString().getBytes());
}
}
return new ByteArrayInputStream(new byte[0]);
StringWriter writer = new StringWriter();
asciidoctor.convert(new InputStreamReader(is), writer, createAttributes());
return new ByteArrayInputStream(writer.getBuffer().toString().getBytes());
} catch (IOException e) {
//no html yet
return new ByteArrayInputStream(new byte[0]);
}
}
private Optional<Path> resolveAdocFile(String resourceName) throws IOException {
Optional<Path> path = Optional.empty();
if (language.getLocale() != null) {
path = find(pluginTargetDirectory.toPath(), resourceName, language.getLocale().toString());
}
if (!path.isPresent()) {
path = find(pluginTargetDirectory.toPath(), resourceName, null);
}
return path;
/**
* The resource name is for example HttpBasics_content1.adoc. This is always located in the following directory:
* <code>plugin/HttpBasics/lessonPlans/en/HttpBasics_content1.adoc</code>
*/
private String computeResourceName(String resourceName) {
return String.format("lessonPlans/%s/%s", language.getLocale().getLanguage(), resourceName);
}
private Map<String, Object> createAttributes() {
Map<String, Object> attributes = Maps.newHashMap();
attributes.put("source-highlighter", "coderay");
@ -119,14 +106,6 @@ public class AsciiDoctorTemplateResolver extends TemplateResolver {
return options;
}
private Optional<Path> find(Path path, String resourceName, String language) throws IOException {
Predicate<Path> languageFilter = p -> StringUtils.hasText(language) ? p.getParent().getFileName().toString().equals(language) : true;
return Files.walk(path)
.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(resourceName))
.filter(languageFilter).findFirst();
}
@Override
public String getName() {
return "adocResourceResolver";

View File

@ -30,16 +30,19 @@
*/
package org.owasp.webgoat;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
import com.google.common.io.ByteStreams;
import lombok.SneakyThrows;
import org.springframework.core.io.ResourceLoader;
import org.thymeleaf.TemplateProcessingParameters;
import org.thymeleaf.resourceresolver.IResourceResolver;
import org.thymeleaf.templateresolver.TemplateResolver;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
/**
* Dynamically resolve a lesson. In the html file this can be invoked as:
@ -48,15 +51,18 @@ import java.io.InputStream;
* <div th:case="true" th:replace="lesson:__${lesson.class.simpleName}__"></div>
* </code>
*
* Thymeleaf will invoke this resolver based on the prefix and this implementqtion will resolve the html in the plugins directory
* Thymeleaf will invoke this resolver based on the prefix and this implementation will resolve the html in the plugins directory
*/
public class LessonTemplateResolver extends TemplateResolver {
private final static String PREFIX = "lesson:";
private final File pluginTargetDirectory;
private ResourceLoader resourceLoader;
private Map<String, byte[]> resources = Maps.newHashMap();
public LessonTemplateResolver(File pluginTargetDirectory) {
public LessonTemplateResolver(File pluginTargetDirectory, ResourceLoader resourceLoader) {
this.pluginTargetDirectory = pluginTargetDirectory;
this.resourceLoader = resourceLoader;
setResourceResolver(new LessonResourceResolver());
setResolvablePatterns(Sets.newHashSet(PREFIX + "*"));
}
@ -70,17 +76,14 @@ public class LessonTemplateResolver extends TemplateResolver {
private class LessonResourceResolver implements IResourceResolver {
@Override
@SneakyThrows
public InputStream getResourceAsStream(TemplateProcessingParameters params, String resourceName) {
File lesson = new File(pluginTargetDirectory, "/plugin/" + resourceName + "/html/" + resourceName + ".html");
if (lesson != null) {
try {
return new ByteArrayInputStream(Files.toByteArray(lesson));
} catch (IOException e) {
//no html yet
return new ByteArrayInputStream(new byte[0]);
}
byte[] resource = resources.get(resourceName);
if (resource == null) {
resource = ByteStreams.toByteArray(resourceLoader.getResource("classpath:/html/" + resourceName + ".html").getInputStream());
resources.put(resourceName, resource);
}
return null;
return new ByteArrayInputStream(resource);
}
@Override

View File

@ -1,32 +1,32 @@
/**
*************************************************************************************************
*
*
* ************************************************************************************************
* <p>
* <p>
* 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.
*
* @author WebGoat
* @since October 28, 2003
* @version $Id: $Id
* @since October 28, 2003
*/
package org.owasp.webgoat;
@ -39,8 +39,11 @@ 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;
@ -70,6 +73,7 @@ public class MvcConfiguration extends WebMvcConfigurerAdapter {
registry.addViewController("/start.mvc").setViewName("main_new");
}
@Bean
public TemplateResolver springThymeleafTemplateResolver(ApplicationContext applicationContext) {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
@ -82,8 +86,8 @@ public class MvcConfiguration extends WebMvcConfigurerAdapter {
}
@Bean
public LessonTemplateResolver lessonTemplateResolver() {
LessonTemplateResolver resolver = new LessonTemplateResolver(pluginTargetDirectory);
public LessonTemplateResolver lessonTemplateResolver(ResourceLoader resourceLoader) {
LessonTemplateResolver resolver = new LessonTemplateResolver(pluginTargetDirectory, resourceLoader);
resolver.setOrder(2);
resolver.setCacheable(false);
return resolver;
@ -92,7 +96,7 @@ public class MvcConfiguration extends WebMvcConfigurerAdapter {
@Bean
public AsciiDoctorTemplateResolver asciiDoctorTemplateResolver(Language language) {
AsciiDoctorTemplateResolver resolver = new AsciiDoctorTemplateResolver(pluginTargetDirectory, language);
resolver.setCacheable(true);
resolver.setCacheable(false);
resolver.setOrder(3);
return resolver;
}
@ -116,11 +120,18 @@ public class MvcConfiguration extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/plugin_lessons/**").addResourceLocations("file:///" + pluginTargetDirectory.toString() + "/");
//registry.addResourceHandler("/images/**").addResourceLocations("classpath:/plugin/VulnerableComponents/images/");
registry.addResourceHandler("/images/**").addResourceLocations("classpath:/images/");
registry.addResourceHandler("/lesson_js/**").addResourceLocations("classpath:/js/");
registry.addResourceHandler("/lesson_css/**").addResourceLocations("classpath:/css/");
super.addResourceHandlers(registry);
}
@Bean
public PluginMessages pluginMessages(Messages messages, Language language) {
return new PluginMessages(messages, language);
PluginMessages pluginMessages = new PluginMessages(messages, language);
pluginMessages.setBasenames("i18n/WebGoatLabels");
return pluginMessages;
}
@Bean
@ -131,7 +142,7 @@ public class MvcConfiguration extends WebMvcConfigurerAdapter {
@Bean
public Messages messageSource(Language language) {
Messages messages = new Messages(language);
messages.setBasename("classpath:/i18n/messages");
messages.setBasename("classpath:i18n/messages");
return messages;
}

View File

@ -34,13 +34,9 @@ import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.Context;
import org.owasp.webgoat.i18n.PluginMessages;
import org.owasp.webgoat.plugins.PluginClassLoader;
import org.owasp.webgoat.plugins.PluginEndpointPublisher;
import org.owasp.webgoat.plugins.PluginsExtractor;
import org.owasp.webgoat.plugins.PluginsLoader;
import org.owasp.webgoat.session.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@ -86,16 +82,6 @@ public class WebGoat extends SpringBootServletInitializer {
return new File(webgoatHome);
}
@Bean
public PluginClassLoader pluginClassLoader() {
return new PluginClassLoader(PluginClassLoader.class.getClassLoader());
}
@Bean
public PluginsExtractor pluginsLoader(@Qualifier("pluginTargetDirectory") File pluginTargetDirectory, PluginClassLoader classLoader, PluginMessages messages) {
return new PluginsExtractor(pluginTargetDirectory, classLoader, messages);
}
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public WebSession webSession(WebgoatContext webgoatContext) {
@ -114,8 +100,8 @@ public class WebGoat extends SpringBootServletInitializer {
}
@Bean
public Course course(PluginsExtractor extractor, PluginEndpointPublisher pluginEndpointPublisher) {
return new PluginsLoader(extractor, pluginEndpointPublisher).loadPlugins();
public Course course(PluginEndpointPublisher pluginEndpointPublisher) {
return new PluginsLoader(pluginEndpointPublisher).loadPlugins();
}
@Bean

View File

@ -1,6 +1,6 @@
/**
*************************************************************************************************
* ************************************************************************************************
* This file is part of WebGoat, an Open Web Application Security Project utility. For details,
* please see http://www.owasp.org/
* <p>
@ -25,11 +25,13 @@
* <p>
*
* @author WebGoat
* @since December 12, 2015
* @version $Id: $Id
* @since December 12, 2015
*/
package org.owasp.webgoat;
import lombok.AllArgsConstructor;
import org.owasp.webgoat.users.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -45,16 +47,20 @@ 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/**", "/plugins/**").permitAll()
.antMatchers("/css/**", "/images/**", "/js/**", "fonts/**", "/plugins/**", "/registration", "/register.mvc").permitAll()
.antMatchers("/servlet/AdminServlet/**").hasAnyRole("WEBGOAT_ADMIN", "SERVER_ADMIN") //
.antMatchers("/JavaSource/**").hasRole("SERVER_ADMIN") //
.anyRequest().hasAnyRole("WEBGOAT_USER", "WEBGOAT_ADMIN", "SERVER_ADMIN");
.anyRequest().authenticated();
security.and()
.formLogin()
.loginPage("/login")
@ -79,15 +85,12 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("guest").password("guest").roles("WEBGOAT_USER").and() //
.withUser("webgoat").password("webgoat").roles("WEBGOAT_ADMIN").and() //
.withUser("server").password("server").roles("SERVER_ADMIN");
auth.userDetailsService(userDetailsService); //.passwordEncoder(bCryptPasswordEncoder());
}
@Bean
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
return userDetailsService;
}
}

View File

@ -25,35 +25,10 @@
package org.owasp.webgoat.assignments;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import java.io.File;
public abstract class Endpoint implements MvcEndpoint {
@Autowired
@Qualifier("pluginTargetDirectory")
private File pluginDirectory;
/**
* The directory of the plugin directory in which the lessons resides, so if you want to access the lesson 'ClientSideFiltering' you will
* need to:
*
* <code>
* File lessonDirectory = new File(getPluginDirectory(), "ClientSideFiltering");
* </code>
*
* The directory structure of the lesson is exactly the same as the directory structure in the plugins project.
*
* @return the top level
*/
protected File getPluginDirectory() {
return new File(this.pluginDirectory, "plugin");
}
@Override
public final boolean isSensitive() {
return false;

View File

@ -55,7 +55,7 @@ public class Welcome {
* @param request a {@link javax.servlet.http.HttpServletRequest} object.
* @return a {@link org.springframework.web.servlet.ModelAndView} object.
*/
@RequestMapping(path = "welcome.mvc", method = RequestMethod.GET)
@RequestMapping(path = {"welcome.mvc", "/"}, method = RequestMethod.GET)
public ModelAndView welcome(HttpServletRequest request) {
// set the welcome attribute

View File

@ -27,29 +27,45 @@ package org.owasp.webgoat.i18n;
import lombok.SneakyThrows;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.UrlResource;
import java.io.File;
import java.net.URL;
import java.util.Enumeration;
import java.util.Properties;
/**
* Message resource bundle for plugins. The files is created after startup during the init of the plugins so we
* need to load this file through a ResourceLoader instead of location on the classpath.
* Message resource bundle for plugins.
*
* @author nbaars
* @date 2/4/17
*/
public class PluginMessages extends ReloadableResourceBundleMessageSource {
private static final String PROPERTIES_SUFFIX = ".properties";
private Language language;
public PluginMessages(Messages messages, Language language) {
this.language = language;
this.setParentMessageSource(messages);
this.setBasename("WebGoatLabels");
}
@Override
@SneakyThrows
protected PropertiesHolder refreshProperties(String filename, PropertiesHolder propHolder) {
Properties properties = new Properties();
long lastModified = System.currentTimeMillis();
Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(filename + PROPERTIES_SUFFIX);
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
String sourcePath = resource.toURI().toString().replace(PROPERTIES_SUFFIX, "");
PropertiesHolder holder = super.refreshProperties(sourcePath, propHolder);
properties.putAll(holder.getProperties());
}
return new PropertiesHolder(properties, lastModified);
}
public Properties getMessages() {
return getMergedProperties(language.getLocale()).getProperties();
}
@ -61,20 +77,4 @@ public class PluginMessages extends ReloadableResourceBundleMessageSource {
public String getMessage(String code, String defaultValue, Object... args) {
return super.getMessage(code, args, defaultValue, language.getLocale());
}
public void addPluginMessageBundles(final File i18nPluginDirectory) {
this.setBasename("WebGoatLabels");
this.setResourceLoader(new ResourceLoader() {
@Override
@SneakyThrows
public Resource getResource(String location) {
return new UrlResource(new File(i18nPluginDirectory, location).toURI());
}
@Override
public ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
});
}
}

View File

@ -1,72 +0,0 @@
/*
* 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 - 2017 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>
*/
package org.owasp.webgoat.plugins;
import lombok.SneakyThrows;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import static com.google.common.io.Files.createParentDirs;
/**
* Merges the main message.properties with the plugins WebGoatLabels
*/
public class MessagePropertyMerger {
private final File targetDirectory;
public MessagePropertyMerger(File targetDirectory) {
this.targetDirectory = targetDirectory;
}
@SneakyThrows
public void merge(ZipFile zipFile, ZipEntry zipEntry) {
Properties messageProperties = new Properties();
try (InputStream zis = zipFile.getInputStream(zipEntry)) {
messageProperties.load(zis);
}
Properties messagesFromHome = new Properties();
File pluginMessageFiles = new File(targetDirectory, zipEntry.getName());
if (pluginMessageFiles.exists()) {
try (FileInputStream fis = new FileInputStream(pluginMessageFiles)) {
messagesFromHome.load(fis);
}
}
messageProperties.putAll(messagesFromHome);
createParentDirs(pluginMessageFiles);
try (FileOutputStream fos = new FileOutputStream(pluginMessageFiles)) {
messageProperties.store(fos, "Plugin message properties");
}
}
}

View File

@ -1,132 +0,0 @@
package org.owasp.webgoat.plugins;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import lombok.Getter;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentHints;
import org.owasp.webgoat.assignments.AssignmentPath;
import org.owasp.webgoat.assignments.Endpoint;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.Assignment;
import org.owasp.webgoat.lessons.NewLesson;
import org.springframework.util.StringUtils;
import java.io.File;
import java.nio.file.Path;
import java.util.List;
import static java.util.stream.Collectors.toList;
import static org.owasp.webgoat.plugins.PluginFileUtils.fileEndsWith;
/**
* <p>Plugin class.</p>
*
* @author dm
* @version $Id: $Id
*/
public class Plugin {
@Getter
private final String originationJar;
private PluginClassLoader classLoader;
private Class<NewLesson> newLesson;
@Getter
private List<Class<AssignmentEndpoint>> assignments = Lists.newArrayList();
@Getter
private List<Class<Endpoint>> endpoints = Lists.newArrayList();
private List<File> pluginFiles = Lists.newArrayList();
public Plugin(PluginClassLoader classLoader, String originatingJar) {
this.classLoader = classLoader;
this.originationJar = originatingJar;
}
/**
* <p>findLesson.</p>
*
* @param classes a {@link java.util.List} object.
*/
public void findLesson(List<String> classes) {
for (String clazzName : classes) {
findLesson(clazzName);
}
}
private void findLesson(String name) {
String realClassName = StringUtils.trimLeadingCharacter(name, '/').replaceAll("/", ".").replaceAll(".class", "");
try {
Class clazz = classLoader.loadClass(realClassName);
if (NewLesson.class.isAssignableFrom(clazz)) {
this.newLesson = clazz;
}
} catch (ClassNotFoundException ce) {
throw new PluginLoadingFailure("Class " + realClassName + " listed in jar but unable to load the class.", ce);
}
}
public void findEndpoints(List<String> classes) {
for (String clazzName : classes) {
String realClassName = StringUtils.trimLeadingCharacter(clazzName, '/').replaceAll("/", ".").replaceAll(".class", "");
try {
Class clazz = classLoader.loadClass(realClassName);
if (AssignmentEndpoint.class.isAssignableFrom(clazz)) {
this.assignments.add(clazz);
} else
if (Endpoint.class.isAssignableFrom(clazz)) {
this.endpoints.add(clazz);
}
} catch (ClassNotFoundException ce) {
throw new PluginLoadingFailure("Class " + realClassName + " listed in jar but unable to load the class.", ce);
}
}
}
/**
* <p>loadFiles.</p>
*
* @param file a {@link java.nio.file.Path} object.
*/
public void loadFiles(Path file) {
if (fileEndsWith(file, ".css", ".jsp", ".js")) {
pluginFiles.add(file.toFile());
}
}
/**
* Lesson is optional, it is also possible that the supplied jar contains only helper classes.
*
* @return a {@link com.google.common.base.Optional} object.
*/
public Optional<AbstractLesson> getLesson() {
try {
if (newLesson != null) {
AbstractLesson lesson = newLesson.newInstance();
lesson.setAssignments(createAssignment(assignments));
return Optional.of(lesson);
}
} catch (IllegalAccessException | InstantiationException e) {
throw new PluginLoadingFailure("Unable to instantiate the lesson " + newLesson.getName(), e);
}
return Optional.absent();
}
private List<Assignment> createAssignment(List<Class<AssignmentEndpoint>> endpoints) {
return endpoints.stream().map(e -> new Assignment(e.getSimpleName(), getPath(e), getHints(e))).collect(toList());
}
private String getPath(Class<AssignmentEndpoint> e) {
return e.getAnnotationsByType(AssignmentPath.class)[0].value();
}
private List<String> getHints(Class<AssignmentEndpoint> e) {
if (e.isAnnotationPresent(AssignmentHints.class)) {
return Lists.newArrayList(e.getAnnotationsByType(AssignmentHints.class)[0].value());
}
return Lists.newArrayList();
}
}

View File

@ -1,16 +0,0 @@
package org.owasp.webgoat.plugins;
import java.net.URL;
import java.net.URLClassLoader;
public class PluginClassLoader extends URLClassLoader {
public PluginClassLoader(ClassLoader parent) {
super(new URL[] {}, parent);
}
@Override
public void addURL(URL url) {
super.addURL(url);
}
}

View File

@ -1,6 +1,7 @@
package org.owasp.webgoat.plugins;
import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.assignments.Endpoint;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
@ -9,6 +10,8 @@ import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import java.util.List;
/**
* ************************************************************************************************
* This file is part of WebGoat, an Open Web Application Security Project utility. For details,
@ -47,9 +50,8 @@ public class PluginEndpointPublisher {
this.applicationContext = (AbstractApplicationContext) applicationContext;
}
public void publish(Plugin plugin) {
plugin.getAssignments().forEach(e -> publishEndpoint(e));
plugin.getEndpoints().forEach(e -> publishEndpoint(e));
public void publish(List<Class<Endpoint>> endpoints) {
endpoints.forEach(e -> publishEndpoint(e));
}
private void publishEndpoint(Class<? extends MvcEndpoint> e) {

View File

@ -1,123 +0,0 @@
package org.owasp.webgoat.plugins;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import org.apache.commons.fileupload.util.Streams;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* Extract the jar file and place them in the system temp directory in the folder webgoat and collect the files
* and classes.
*
* @author dm
* @version $Id: $Id
*/
public class PluginExtractor {
private final List<String> classes = Lists.newArrayList();
private final List<Path> files = new ArrayList<>();
/**
* <p>extractJarFile.</p>
*
* @param archive a {@link java.io.File} object.
* @param targetDirectory a {@link java.io.File} object.
* @return a {@link org.owasp.webgoat.plugins.Plugin} object.
* @throws java.io.IOException if any.
*/
public Plugin extractJarFile(final File archive, final File targetDirectory, PluginClassLoader cl) throws IOException {
ZipFile zipFile = new ZipFile(archive);
Plugin plugin = new Plugin(cl, zipFile.getName());
try {
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
final ZipEntry zipEntry = entries.nextElement();
if (shouldProcessFile(zipEntry)) {
boolean processed = processClassFile(zipFile, zipEntry, targetDirectory);
if (!processed) {
processed = processPropertyFile(zipFile, zipEntry, targetDirectory);
}
if (!processed) {
processFile(plugin, zipFile, zipEntry, targetDirectory);
}
}
}
} finally {
plugin.findLesson(this.classes);
plugin.findEndpoints(this.classes);
zipFile.close();
}
return plugin;
}
private void processFile(Plugin plugin, ZipFile zipFile, ZipEntry zipEntry, File targetDirectory)
throws IOException {
final File targetFile = new File(targetDirectory, zipEntry.getName());
copyFile(zipFile, zipEntry, targetFile, false);
plugin.loadFiles(targetFile.toPath());
}
private boolean processPropertyFile(ZipFile zipFile, ZipEntry zipEntry, File targetDirectory)
throws IOException {
if (zipEntry.getName().endsWith(".properties")) {
final File targetFile = new File(targetDirectory, zipEntry.getName());
if ("WebGoatLabels.properties".equals(targetFile.getName())) {
new MessagePropertyMerger(targetDirectory).merge(zipFile, zipEntry);
}
copyFile(zipFile, zipEntry, targetFile, true);
return true;
}
return false;
}
private boolean processClassFile(ZipFile zipFile, ZipEntry zipEntry, File targetDirectory) throws IOException {
if (zipEntry.getName().endsWith(".class")) {
classes.add(zipEntry.getName());
final File targetFile = new File(targetDirectory, zipEntry.getName());
copyFile(zipFile, zipEntry, targetFile, false);
return true;
}
return false;
}
private boolean shouldProcessFile(ZipEntry zipEntry) {
return !zipEntry.isDirectory() && !zipEntry.getName().startsWith("META-INF");
}
private File copyFile(ZipFile zipFile, ZipEntry zipEntry, File targetFile, boolean append) throws IOException {
Files.createParentDirs(targetFile);
try (FileOutputStream fos = new FileOutputStream(targetFile, append)) {
Streams.copy(zipFile.getInputStream(zipEntry), fos, true);
}
return targetFile;
}
/**
* <p>Getter for the field <code>classes</code>.</p>
*
* @return a {@link java.util.List} object.
*/
public List<String> getClasses() {
return this.classes;
}
/**
* <p>Getter for the field <code>files</code>.</p>
*
* @return a {@link java.util.List} object.
*/
public List<Path> getFiles() {
return this.files;
}
}

View File

@ -1,107 +0,0 @@
package org.owasp.webgoat.plugins;
import com.google.common.base.Preconditions;
import lombok.experimental.UtilityClass;
import org.apache.commons.io.IOUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
/**
* <p>PluginFileUtils class.</p>
*
* @version $Id: $Id
* @author dm
*/
@UtilityClass
public class PluginFileUtils {
/**
* <p>fileEndsWith.</p>
*
* @param p a {@link java.nio.file.Path} object.
* @param s a {@link java.lang.String} object.
* @return a boolean.
*/
public static boolean fileEndsWith(Path p, String s) {
return p.getFileName().toString().endsWith(s);
}
/**
* <p>fileEndsWith.</p>
*
* @param p a {@link java.nio.file.Path} object.
* @param suffixes a {@link java.lang.String} object.
* @return a boolean.
*/
public static boolean fileEndsWith(Path p, String... suffixes) {
for (String suffix : suffixes) {
if (fileEndsWith(p, suffix)) {
return true;
}
}
return false;
}
/**
* <p>hasParentDirectoryWithName.</p>
*
* @param p a {@link java.nio.file.Path} object.
* @param s a {@link java.lang.String} object.
* @return a boolean.
*/
public static boolean hasParentDirectoryWithName(Path p, String s) {
if (p == null || p.getParent() == null || p.getParent().equals(p.getRoot())) {
return false;
}
if (p.getParent().getFileName().toString().equals(s)) {
return true;
}
return hasParentDirectoryWithName(p.getParent(), s);
}
/**
* <p>replaceInFiles.</p>
*
* @param replace a {@link java.lang.String} object.
* @param with a {@link java.lang.String} object.
* @param files a {@link java.util.Collection} object.
* @throws java.io.IOException if any.
*/
public static void replaceInFiles(String replace, String with, Collection<File> files) throws IOException {
Preconditions.checkNotNull(replace);
Preconditions.checkNotNull(with);
Preconditions.checkNotNull(files);
for (File file : files) {
replaceInFile(replace, with, file);
}
}
/**
* <p>replaceInFile.</p>
*
* @param replace a {@link java.lang.String} object.
* @param with a {@link java.lang.String} object.
* @param file a {@link java.nio.file.Path} object.
* @throws java.io.IOException if any.
*/
public static void replaceInFile(String replace, String with, File file) throws IOException {
Preconditions.checkNotNull(replace);
Preconditions.checkNotNull(with);
Preconditions.checkNotNull(file);
String fileAsString = "";
try (FileInputStream fis = new FileInputStream(file);) {
fileAsString = IOUtils.toString(fis, StandardCharsets.UTF_8.name());
fileAsString = fileAsString.replaceAll(replace, with);
}
Files.write(file.toPath(), fileAsString.getBytes());
}
}

View File

@ -0,0 +1,46 @@
package org.owasp.webgoat.plugins;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.Endpoint;
import org.owasp.webgoat.lessons.NewLesson;
import java.net.URL;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* Plugin resource
*
* @author nbaars
* @since 3/4/17.
*/
@AllArgsConstructor
@Getter
public class PluginResource {
private final URL location;
private final List<Class> classes;
public Optional<Class> getLesson() {
return classes.stream().filter(c -> c.getSuperclass() == NewLesson.class).findFirst();
}
public List<Class<Endpoint>> getEndpoints() {
return classes.stream().
filter(c -> c.getSuperclass() == AssignmentEndpoint.class || c.getSuperclass() == Endpoint.class).
map(c -> (Class<Endpoint>)c).
collect(Collectors.toList());
}
public List<Class<AssignmentEndpoint>> getAssignments() {
return classes.stream().
filter(c -> c.getSuperclass() == AssignmentEndpoint.class).
map(c -> (Class<AssignmentEndpoint>)c).
collect(Collectors.toList());
}
}

View File

@ -1,162 +0,0 @@
package org.owasp.webgoat.plugins;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.owasp.webgoat.i18n.PluginMessages;
import org.springframework.util.ResourceUtils;
import java.io.*;
import java.net.URL;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* <p>PluginsLoader class.</p>
*
* @author dm
* @version $Id: $Id
*/
@Slf4j
public class PluginsExtractor {
private static final String WEBGOAT_PLUGIN_EXTENSION = "jar";
private static final int BUFFER_SIZE = 32 * 1024;
private final File pluginTargetDirectory;
private final PluginClassLoader classLoader;
private final PluginMessages messages;
public PluginsExtractor(File pluginTargetDirectory, PluginClassLoader pluginClassLoader, PluginMessages messages) {
this.classLoader = pluginClassLoader;
this.pluginTargetDirectory = pluginTargetDirectory;
this.messages = messages;
}
/**
* <p>loadPlugins.</p>
*
* @return a {@link java.util.List} object.
*/
public List<Plugin> loadPlugins() {
List<Plugin> plugins = Lists.newArrayList();
try {
URL location = this.getClass().getProtectionDomain().getCodeSource().getLocation();
log.trace("Determining whether we run as standalone jar or as directory...");
if (ResourceUtils.isFileURL(location)) {
log.trace("Running from directory, copying lessons from {}", location.toString());
extractToTargetDirectoryFromExplodedDirectory(ResourceUtils.getFile(location));
} else {
log.trace("Running from standalone jar, extracting lessons from {}", location.toString());
extractToTargetDirectoryFromJarFile(ResourceUtils.getFile(ResourceUtils.extractJarFileURL(location)));
}
List<URL> jars = listJars();
plugins = processPlugins(jars);
} catch (Exception e) {
log.error("Loading plugins failed", e);
}
return plugins;
}
private void extractToTargetDirectoryFromJarFile(File jarFile) throws IOException {
ZipFile jar = new ZipFile(jarFile);
Enumeration<? extends ZipEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
ZipEntry zipEntry = entries.nextElement();
if (zipEntry.getName().contains("plugin_lessons") && zipEntry.getName().endsWith(".jar")) {
unpack(jar, zipEntry);
}
}
}
private void unpack(ZipFile jar, ZipEntry zipEntry) throws IOException {
try (InputStream inputStream = jar.getInputStream(zipEntry)) {
String name = zipEntry.getName();
if (name.lastIndexOf("/") != -1) {
name = name.substring(name.lastIndexOf("/") + 1);
}
try (OutputStream outputStream = new FileOutputStream(new File(pluginTargetDirectory, name))) {
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead = -1;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
}
}
log.trace("Extracting {} to {}", jar.getName(), pluginTargetDirectory);
}
private void extractToTargetDirectoryFromExplodedDirectory(File directory) throws IOException {
Files.walkFileTree(directory.toPath(), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
if (dir.endsWith("plugin_lessons")) {
log.trace("Copying {} to {}", dir.toString(), pluginTargetDirectory);
FileUtils.copyDirectory(dir.toFile(), pluginTargetDirectory);
}
return FileVisitResult.CONTINUE;
}
});
}
private List<URL> listJars() throws Exception {
final List<URL> jars = Lists.newArrayList();
Files.walkFileTree(Paths.get(pluginTargetDirectory.toURI()), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (PluginFileUtils.fileEndsWith(file, WEBGOAT_PLUGIN_EXTENSION)) {
jars.add(file.toUri().toURL());
log.trace("Found jar file at location: {}", file.toString());
}
return FileVisitResult.CONTINUE;
}
});
return jars;
}
private List<Plugin> processPlugins(List<URL> jars) throws Exception {
final ExecutorService executorService = Executors.newFixedThreadPool(10);
try {
final List<Plugin> plugins = Lists.newArrayList();
final CompletionService<Plugin> completionService = new ExecutorCompletionService<>(executorService);
final List<Callable<Plugin>> callables = extractJars(jars);
callables.forEach(s -> completionService.submit(s));
int n = callables.size();
for (int i = 0; i < n; i++) {
Plugin plugin = completionService.take().get();
if (plugin.getLesson().isPresent()) {
log.trace("Plugin jar '{}' contains a lesson, loading into WebGoat...", plugin.getOriginationJar());
plugins.add(plugin);
} else {
log.trace("Plugin jar: '{}' does not contain a lesson not processing as a plugin (can be a utility jar)",
plugin.getOriginationJar());
}
}
messages.addPluginMessageBundles(new File(pluginTargetDirectory, "plugin/i18n"));
return plugins;
} finally {
executorService.shutdown();
}
}
private List<Callable<Plugin>> extractJars(List<URL> jars) {
List<Callable<Plugin>> extractorCallables = Lists.newArrayList();
for (final URL jar : jars) {
classLoader.addURL(jar);
extractorCallables.add(() -> {
PluginExtractor extractor = new PluginExtractor();
return extractor.extractJarFile(ResourceUtils.getFile(jar), pluginTargetDirectory, classLoader);
});
}
return extractorCallables;
}
}

View File

@ -1,13 +1,29 @@
package org.owasp.webgoat.plugins;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentHints;
import org.owasp.webgoat.assignments.AssignmentPath;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.Assignment;
import org.owasp.webgoat.lessons.NewLesson;
import org.owasp.webgoat.session.Course;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.RegexPatternTypeFilter;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;
/**
* ************************************************************************************************
@ -42,7 +58,6 @@ import java.util.List;
@Slf4j
public class PluginsLoader {
private final PluginsExtractor extractor;
private final PluginEndpointPublisher pluginEndpointPublisher;
/**
@ -50,11 +65,15 @@ public class PluginsLoader {
*/
public Course loadPlugins() {
List<AbstractLesson> lessons = Lists.newArrayList();
for (Plugin plugin : extractor.loadPlugins()) {
for (PluginResource plugin : findPluginResources()) {
try {
NewLesson lesson = (NewLesson) plugin.getLesson().get();
Class lessonClazz = plugin.getLesson()
.orElseThrow(() -> new PluginLoadingFailure("Plugin resource does not contain lesson"));
NewLesson lesson = (NewLesson) lessonClazz.newInstance();
List<Class<AssignmentEndpoint>> assignments = plugin.getAssignments();
lesson.setAssignments(createAssignment(assignments));
lessons.add(lesson);
pluginEndpointPublisher.publish(plugin);
pluginEndpointPublisher.publish(plugin.getEndpoints());
} catch (Exception e) {
log.error("Error in loadLessons: ", e);
}
@ -67,4 +86,43 @@ public class PluginsLoader {
return new Course(lessons);
}
private List<Assignment> createAssignment(List<Class<AssignmentEndpoint>> endpoints) {
return endpoints.stream().map(e -> new Assignment(e.getSimpleName(), getPath(e), getHints(e))).collect(toList());
}
private String getPath(Class<AssignmentEndpoint> e) {
return e.getAnnotationsByType(AssignmentPath.class)[0].value();
}
private List<String> getHints(Class<AssignmentEndpoint> e) {
if (e.isAnnotationPresent(AssignmentHints.class)) {
return Lists.newArrayList(e.getAnnotationsByType(AssignmentHints.class)[0].value());
}
return Lists.newArrayList();
}
@SneakyThrows
public List<PluginResource> findPluginResources() {
final ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
provider.addIncludeFilter(new RegexPatternTypeFilter(Pattern.compile(".*")));
final Set<BeanDefinition> classes = provider.findCandidateComponents("org.owasp.webgoat.plugin");
Map<URL, List<Class>> pluginClasses = Maps.newHashMap();
for (BeanDefinition bean : classes) {
Class<?> clazz = Class.forName(bean.getBeanClassName());
URL location = clazz.getProtectionDomain().getCodeSource().getLocation();
List<Class> classFiles = pluginClasses.get(location);
if (classFiles == null) {
classFiles = Lists.newArrayList(clazz);
} else {
classFiles.add(clazz);
}
pluginClasses.put(location, classFiles);
}
return pluginClasses.entrySet().parallelStream()
.map(e -> new PluginResource(e.getKey(), e.getValue()))
.collect(Collectors.toList());
}
}

View File

@ -6,11 +6,12 @@ import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.Assignment;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.SerializationUtils;
import org.springframework.core.serializer.DefaultDeserializer;
import org.springframework.core.serializer.DefaultSerializer;
import java.io.File;
import java.util.HashMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Map;
import java.util.stream.Collectors;
@ -50,7 +51,6 @@ public class UserTracker {
private final String webgoatHome;
private final String user;
private Map<String, LessonTracker> storage = new HashMap<>();
public UserTracker(final String webgoatHome, final String user) {
this.webgoatHome = webgoatHome;
@ -64,53 +64,72 @@ public class UserTracker {
* @return the optional lesson tracker
*/
public LessonTracker getLessonTracker(AbstractLesson lesson) {
return getLessonTracker(load(), lesson);
}
/**
* Returns the lesson tracker for a specific lesson if available.
*
* @param lesson the lesson
* @return the optional lesson tracker
*/
public LessonTracker getLessonTracker(Map<String, LessonTracker> storage, AbstractLesson lesson) {
LessonTracker lessonTracker = storage.get(lesson.getTitle());
if (lessonTracker == null) {
lessonTracker = new LessonTracker(lesson);
storage.put(lesson.getTitle(), lessonTracker);
save(storage);
}
return lessonTracker;
}
public void assignmentSolved(AbstractLesson lesson, String assignmentName) {
LessonTracker lessonTracker = getLessonTracker(lesson);
Map<String, LessonTracker> storage = load();
LessonTracker lessonTracker = storage.get(lesson.getTitle());
lessonTracker.incrementAttempts();
lessonTracker.assignmentSolved(assignmentName);
save();
save(storage);
}
public void assignmentFailed(AbstractLesson lesson) {
LessonTracker lessonTracker = getLessonTracker(lesson);
Map<String, LessonTracker> storage = load();
LessonTracker lessonTracker = storage.get(lesson.getTitle());
lessonTracker.incrementAttempts();
save();
save(storage);
}
public void load() {
public Map<String, LessonTracker> load() {
File file = new File(webgoatHome, user + ".progress");
if (file.exists() && file.isFile()) {
try {
this.storage = (Map<String, LessonTracker>) SerializationUtils.deserialize(FileCopyUtils.copyToByteArray(file));
DefaultDeserializer deserializer = new DefaultDeserializer(Thread.currentThread().getContextClassLoader());
return (Map<String, LessonTracker>) deserializer.deserialize(new FileInputStream(file));
} catch (Exception e) {
log.error("Unable to read the progress file, creating a new one...");
this.storage = Maps.newHashMap();
}
}
return Maps.newHashMap();
}
@SneakyThrows
private void save() {
private void save(Map<String, LessonTracker> storage) {
File file = new File(webgoatHome, user + ".progress");
FileCopyUtils.copy(SerializationUtils.serialize(this.storage), file);
DefaultSerializer serializer = new DefaultSerializer();
serializer.serialize(storage, new FileOutputStream(file));
}
public void reset(AbstractLesson al) {
getLessonTracker(al).reset();
save();
Map<String, LessonTracker> storage = load();
LessonTracker lessonTracker = getLessonTracker(storage, al);
lessonTracker.reset();
save(storage);
}
public int numberOfLessonsSolved() {
int numberOfLessonsSolved = 0;
Map<String, LessonTracker> storage = load();
for (LessonTracker lessonTracker : storage.values()) {
if (lessonTracker.isLessonSolved()) {
numberOfLessonsSolved = numberOfLessonsSolved + 1;
@ -121,6 +140,7 @@ public class UserTracker {
public int numberOfAssignmentsSolved() {
int numberOfAssignmentsSolved = 0;
Map<String, LessonTracker> storage = load();
for (LessonTracker lessonTracker : storage.values()) {
Map<Assignment, Boolean> lessonOverview = lessonTracker.getLessonOverview();
numberOfAssignmentsSolved = lessonOverview.values().stream().filter(b -> b).collect(Collectors.counting()).intValue();

View File

@ -0,0 +1,71 @@
package org.owasp.webgoat.session;
import lombok.Getter;
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 javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Transient;
import java.util.Collection;
import java.util.Collections;
/**
* @author nbaars
* @since 3/19/17.
*/
@Getter
@Entity
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

@ -3,7 +3,6 @@ package org.owasp.webgoat.session;
import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import java.sql.Connection;
import java.sql.SQLException;
@ -40,10 +39,9 @@ import java.sql.SQLException;
@Slf4j
public class WebSession {
private final User currentUser;
private final WebGoatUser currentUser;
private final WebgoatContext webgoatContext;
private AbstractLesson currentLesson;
private UserTracker userTracker;
/**
* Constructor for the WebSession object
@ -52,7 +50,7 @@ public class WebSession {
*/
public WebSession(WebgoatContext webgoatContext) {
this.webgoatContext = webgoatContext;
this.currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
this.currentUser = (WebGoatUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
/**

View File

@ -0,0 +1,61 @@
package org.owasp.webgoat.users;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.session.WebGoatUser;
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.validation.Valid;
/**
* @author nbaars
* @since 3/19/17.
*/
@Controller
@AllArgsConstructor
@Slf4j
public class RegistrationController {
private UserValidator userValidator;
private UserService userService;
private AuthenticationManager authenticationManager;
@GetMapping("/registration")
public String showForm(UserForm userForm) {
return "registration";
}
@PostMapping("/register.mvc")
public String registration(@ModelAttribute("userForm") @Valid UserForm userForm, BindingResult bindingResult) {
userValidator.validate(userForm, bindingResult);
if (bindingResult.hasErrors()) {
return "registration";
}
userService.addUser(userForm.getUsername(), userForm.getPassword());
autologin(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

@ -0,0 +1,28 @@
package org.owasp.webgoat.users;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
* @author nbaars
* @since 3/19/17.
*/
@Getter
@Setter
public class UserForm {
@NotNull
@Size(min=6, max=10)
private String username;
@NotNull
@Size(min=6, max=10)
private String password;
@NotNull
@Size(min=6, max=10)
private String matchingPassword;
@NotNull
private String agree;
}

View File

@ -0,0 +1,13 @@
package org.owasp.webgoat.users;
import org.owasp.webgoat.session.WebGoatUser;
import org.springframework.data.repository.CrudRepository;
/**
* @author nbaars
* @since 3/19/17.
*/
public interface UserRepository extends CrudRepository<WebGoatUser, Long> {
WebGoatUser findByUsername(String username);
}

View File

@ -0,0 +1,29 @@
package org.owasp.webgoat.users;
import lombok.AllArgsConstructor;
import org.owasp.webgoat.session.WebGoatUser;
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);
webGoatUser.createUser();
return webGoatUser;
}
public void addUser(String username, String password) {
userRepository.save(new WebGoatUser(username, password));
}
}

View File

@ -0,0 +1,34 @@
package org.owasp.webgoat.users;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
/**
* @author nbaars
* @since 3/19/17.
*/
@Component
public class UserValidator implements Validator {
// @Autowired
// private UserService userService;
@Override
public boolean supports(Class<?> aClass) {
return UserForm.class.equals(aClass);
}
@Override
public void validate(Object o, Errors errors) {
UserForm userForm = (UserForm) o;
// if (userService.findByUsername(userForm.getUsername()) != null) {
// errors.rejectValue("username", "Duplicate.userForm.username");
// }
if (!userForm.getMatchingPassword().equals(userForm.getPassword())) {
errors.rejectValue("matchingPassword", "password.diff");
}
}
}

View File

@ -10,12 +10,13 @@ logging.level.org.springframework.boot.devtools=WARN
logging.level.org.owasp=DEBUG
logging.level.org.owasp.webgoat=TRACE
# Needed for creating a vulnerable web application
security.enable-csrf=false
spring.devtools.restart.enabled=false
spring.resources.cache-period=0
spring.thymeleaf.cache=false
webgoat.server.directory=${user.home}/.webgoat/
webgoat.user.directory=${user.home}/.webgoat/
webgoat.build.version=@project.version@
webgoat.build.number=@build.number@
@ -30,4 +31,12 @@ webgoat.database.connection.string=jdbc:hsqldb:mem:test
webgoat.default.language=en
liquibase.change-log=classpath:db/changelog/db.changelog-master.xml
spring.datasource.url=jdbc:hsqldb:file:${user.home}/.webgoat/WebGoatDatabase
spring.datasource.driverClassName=org.hsqldb.jdbcDriver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.HSQLDialect
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none

View File

@ -0,0 +1,17 @@
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd">
<changeSet author="WebGoat" id="init_schema">
<createTable tableName="web_goat_user">
<column name="username" type="varchar(32)">
<constraints unique="true"/>
</column>
<column name="password" type="varchar(32)"/>
<column name="role" type="varchar(32)"/>
</createTable>
<addPrimaryKey columnNames="username" constraintName="pk_user" tableName="web_goat_user"/>
</changeSet>
</databaseChangeLog>

View File

@ -32,6 +32,7 @@ ErrorGenerating=Error generating
InvalidData=Invalid Data
Go!=Go!
password=Password
password.confirm=Confirm password
username=Username
logged_out=You've been logged out successfully.
invalid_username_password=Invalid username and password.
@ -50,3 +51,13 @@ show.hints=Show hints
lesson.overview=Lesson overview
reset.lesson=Reset lesson
sign.in=Sign in
register.new=Register new user
sign.up=Sign up
register.title=Register
not.empty=This field is required.
username.size=Please use between 6 and 10 characters.
username.duplicate=User already exists.
password.size=Password should at least contain 6 characters
password.diff=The passwords do not match.

View File

@ -1 +0,0 @@
Lesson plugins stored under this directory.

View File

@ -57,7 +57,7 @@ function($,
* from the model where the assignment name is contained in the assignmentPath. We do this not to mess
* with contextRoots etc and try to select the name from the url.
*
* @todo we can of course try to add the assigment name to the html form as attribute.
* @todo we can of course try to add the assignment name to the html form as attribute.
*
* @param nav the json structure for navigating
*/
@ -95,7 +95,7 @@ function($,
displayHint: function(curHint) {
if(this.hintsToShow.length == 0) {
this.hideHints();
// this.hideHints();
} else {
this.$el.find('#lesson-hint-content').html(polyglot.t(this.hintsToShow[curHint].get('hint')));
}

View File

@ -29,7 +29,7 @@
<p th:text="#{logged_out}">You've been logged out successfully.</p>
</div>
<br/><br/>
<form th:action="@{/login}" method='POST' style="width: 400px;">
<form th:action="@{/login}" method='POST' style="width: 200px;">
<div class="form-group">
<label for="exampleInputEmail1" th:text="#{username}">Username</label>
<input autofocus="dummy_for_thymeleaf_parser" type="text" class="form-control"
@ -40,32 +40,10 @@
<input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password"
name='password' value="guest"/>
</div>
<button class="btn btn-large btn-primary" type="submit" th:text="#{sign.in}">Sign in</button>
<button class="btn btn-primary btn-block" type="submit" th:text="#{sign.in}">Sign in</button>
<div class="text-center"><a th:href="@{/registration}" th:text="#{register.new}"></a></div>
</form>
<br/><br/>
<h4 th:text="#{accounts.build.in}">The following accounts are built into Webgoat</h4>
<table class="table table-bordered" style="width:400px;">
<thead>
<tr class="warning">
<th th:text="#{accounts.table.account}">Account</th>
<th th:text="#{accounts.table.user}">User</th>
<th th:text="#{accounts.table.password}">Password</th>
</tr>
</thead>
<tbody>
<tr>
<td>Webgoat User</td>
<td>guest</td>
<td>guest</td>
</tr>
<tr>
<td>Webgoat Admin</td>
<td>webgoat</td>
<td>webgoat</td>
</tr>
</tbody>
</table>
<br/><br/>
</section>
</section>
</section>

View File

@ -0,0 +1,104 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="#{login.page.title}">Login Page</title>
<link rel="stylesheet" type="text/css" th:href="@{/css/main.css}"/>
<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}"/>
<link rel="stylesheet" type="text/css" th:href="@{/css/animate.css}"/>
</head>
<body>
<section id="container">
<header id="header">
<div class="brand">
<a th:href="@{/start.mvc}" class="logo"><span>Web</span>Goat</a>
</div>
<div class="toggle-navigation toggle-left">
</div>
<div class="lessonTitle">
</div>
</header>
<section class="main-content-wrapper">
<section id="main-content">
<br/><br/>
<fieldset>
<legend th:text="#{register.title}">Please Sign Up</legend>
<form class="form-horizontal" action="#" th:action="@{/register.mvc}" th:object="${userForm}"
method='POST'>
<div class="form-group" th:classappend="${#fields.hasErrors('username')}? 'has-error'">
<label for="username" class="col-sm-2 control-label" th:text="#{username}">Username</label>
<div class="col-sm-4">
<input autofocus="dummy_for_thymeleaf_parser" type="text" class="form-control"
th:field="*{username}"
id="username" placeholder="Username" name='username'/>
</div>
<span th:if="${#fields.hasErrors('username')}" th:errors="*{username}">Username error</span>
</div>
<div class="form-group" th:classappend="${#fields.hasErrors('password')}? 'has-error'">
<label for="password" class="col-sm-2 control-label" th:text="#{password}">Password</label>
<div class="col-sm-4">
<input type="password" class="form-control" id="password" placeholder="Password"
name='password' th:value="*{password}"/>
</div>
<span th:if="${#fields.hasErrors('password')}" th:errors="*{password}">Password error</span>
</div>
<div class="form-group" th:classappend="${#fields.hasErrors('matchingPassword')}? 'has-error'">
<label for="matchingPassword" class="col-sm-2 control-label" th:text="#{password.confirm}">Confirm
password</label>
<div class="col-sm-4">
<input type="password" class="form-control" id="matchingPassword" placeholder="Password"
name='matchingPassword' th:value="*{matchingPassword}"/>
</div>
<span th:if="${#fields.hasErrors('matchingPassword')}" th:errors="*{matchingPassword}">Password error</span>
</div>
<div class="form-group" th:classappend="${#fields.hasErrors('agree')}? 'has-error'">
<label class="col-sm-2 control-label">Terms of use</label>
<div class="col-sm-6">
<div style="border: 1px solid #e5e5e5; height: 200px; overflow: auto; padding: 10px;">
<p>
While running this program your machine will be extremely
vulnerable to attack. You should disconnect from the Internet while using
this program. WebGoat's default configuration binds to localhost to minimize
the exposure.
</p>
<p>
This program is for educational purposes only. If you attempt
these techniques without authorization, you are very likely to get caught. If
you are caught engaging in unauthorized hacking, most companies will fire you.
Claiming that you were doing security research will not work as that is the
first thing that all hackers claim.
</p>
</div>
</div>
</div>
<div class="form-group" th:classappend="${#fields.hasErrors('agree')}? 'has-error'">
<div class="col-sm-6 col-sm-offset-2">
<div class="checkbox">
<label>
<input type="checkbox" name="agree" value="agree"/>Agree with the terms and
conditions
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-6">
<button type="submit" class="btn btn-primary" th:text="#{sign.up}">Sign up</button>
</div>
</div>
</form>
</fieldset>
</section>
</section>
</section>
</body>
</html>

View File

@ -1,19 +0,0 @@
#lesson.BufferOverflow.hidden=true
#
#
# Hide lessons using name of source file,
# For Example: BlindScript.java
# lesson.BlindScript.hidden=true;
#
# These lesson need to be refactored
lesson.BasicAuthentication.hidden=false
lesson.BlindScript.hidden=true
lesson.RemoteAdminFlaw.hidden=true
lesson.HttpSplitting.hidden=true
lesson.SameOriginPolicyProtection.hidden=true
lesson.SilentTransactions.hidden=true
lesson.TraceXSS.hidden=true
lesson.DBSQLInjection.hidden=true
lesson.DBCrossSiteScripting.hidden=true
lesson.XPATHInjection.hidden=true
lesson.ForcedBrowsing.hidden=true

View File

@ -55,7 +55,7 @@ public class AssignmentEndpointTest {
protected PluginMessages pluginMessages = new PluginMessages(messages, language);
public void init(AssignmentEndpoint a) {
messages.setBasenames("classpath:/i18n/messages", "classpath:/plugin/i18n/WebGoatLabels");
messages.setBasenames("classpath:/i18n/messages", "classpath:/i18n/WebGoatLabels");
ReflectionTestUtils.setField(a, "userTracker", userTracker);
ReflectionTestUtils.setField(a, "userSessionData", userSessionData);
ReflectionTestUtils.setField(a, "webSession", webSession);

View File

@ -8,9 +8,9 @@ import org.owasp.webgoat.lessons.Assignment;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -68,7 +68,7 @@ public class UserTrackerTest {
@Test
public void assignmentFailedShouldIncrementAttempts() {
UserTracker userTracker = new UserTracker(home.getParent(), "test");
UserTracker userTracker = new UserTracker(home.getParent(), UUID.randomUUID().toString());
AbstractLesson lesson = mock(AbstractLesson.class);
when(lesson.getAssignments()).thenReturn(Lists.newArrayList(new Assignment("assignment", "assignment")));
userTracker.getLessonTracker(lesson);
@ -83,6 +83,7 @@ public class UserTrackerTest {
UserTracker userTracker = new UserTracker(home.getParent(), "test");
AbstractLesson lesson = mock(AbstractLesson.class);
when(lesson.getAssignments()).thenReturn(Lists.newArrayList(new Assignment("assignment", "assignment")));
userTracker.getLessonTracker(lesson);
userTracker.assignmentSolved(lesson, "assignment");
assertThat(userTracker.getLessonTracker(lesson).isLessonSolved()).isTrue();
@ -95,6 +96,7 @@ public class UserTrackerTest {
UserTracker userTracker = new UserTracker(home.getParent(), "test");
AbstractLesson lesson = mock(AbstractLesson.class);
when(lesson.getAssignments()).thenReturn(Lists.newArrayList(new Assignment("assignment", "assignment")));
userTracker.getLessonTracker(lesson);
userTracker.assignmentSolved(lesson, "assignment");
assertThat(userTracker.numberOfAssignmentsSolved()).isEqualTo(1);

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div class="lesson-page-wrapper">
<!-- reuse this lesson-page-wrapper block for each 'page' of content in your lesson -->
<!-- include content here, or can be placed in another location. Content will be presented via asciidocs files,
which you put in src/main/resources/plugin/lessonplans/{lang}/{fileName}.adoc -->
<div class="adoc-content" th:replace="doc:Challenge_content1.adoc"></div>
</div>
</html>

View File

@ -0,0 +1 @@
challenge.title=WebGoat Challenge

View File

@ -0,0 +1 @@
This is the challenge

View File

@ -1,6 +1,7 @@
package org.owasp.webgoat.plugin;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentHints;
import org.owasp.webgoat.assignments.AssignmentPath;
import org.owasp.webgoat.assignments.AttackResult;
import org.springframework.web.bind.annotation.RequestMapping;
@ -40,14 +41,15 @@ import java.io.IOException;
* @since August 11, 2016
*/
@AssignmentPath("/clientSideFiltering/attack1")
public class Attack extends AssignmentEndpoint {
@AssignmentHints({"ClientSideFilteringHint1", "ClientSideFilteringHint2", "ClientSideFilteringHint3", "ClientSideFilteringHint4"})
public class ClientSideFilteringAssignment extends AssignmentEndpoint {
@RequestMapping(method = RequestMethod.POST)
public @ResponseBody AttackResult completed(@RequestParam String answer) throws IOException {
if ("450000".equals(answer)) {
return trackProgress(success().build());
} else {
return trackProgress(failed().build());
}
public
@ResponseBody
AttackResult completed(@RequestParam String answer) throws IOException {
return trackProgress("450000".equals(answer) ?
success().feedback("assignment.solved").build() :
failed().feedback("ClientSideFiltering.incorrect").build());
}
}

View File

@ -6,34 +6,51 @@ package org.owasp.webgoat.plugin;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.SneakyThrows;
import org.owasp.webgoat.assignments.Endpoint;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.annotation.PostConstruct;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public class Salaries extends Endpoint {
@Value("${webgoat.user.directory}")
private String webGoatHomeDirectory;
@PostConstruct
@SneakyThrows
public void copyFiles() {
ClassPathResource classPathResource = new ClassPathResource("employees.xml");
File targetDirectory = new File(webGoatHomeDirectory, "/ClientSideFiltering");
if (!targetDirectory.exists()) {
targetDirectory.mkdir();
}
FileCopyUtils.copy(classPathResource.getInputStream(), new FileOutputStream(new File(targetDirectory, "employees.xml")));
}
@RequestMapping(produces = {"application/json"})
@ResponseBody
public List<Map<String, Object>> invoke(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String userId = req.getParameter("userId");
public List<Map<String, Object>> invoke() throws ServletException, IOException {
NodeList nodes = null;
File d = new File(getPluginDirectory(), "ClientSideFiltering/html/employees.xml");
File d = new File(webGoatHomeDirectory, "ClientSideFiltering/employees.xml");
XPathFactory factory = XPathFactory.newInstance();
XPath xPath = factory.newXPath();
InputSource inputSource = new InputSource(new FileInputStream(d));
@ -49,8 +66,7 @@ public class Salaries extends Endpoint {
String expression = sb.toString();
try {
nodes = (NodeList) xPath.evaluate(expression, inputSource,
XPathConstants.NODESET);
nodes = (NodeList) xPath.evaluate(expression, inputSource, XPathConstants.NODESET);
} catch (XPathExpressionException e) {
e.printStackTrace();
}
@ -58,7 +74,7 @@ public class Salaries extends Endpoint {
List json = Lists.newArrayList();
java.util.Map<String, Object> employeeJson = Maps.newHashMap();
for (int i = 0; i < nodes.getLength(); i++) {
if (i != 0 && i % COLUMNS == 0) {
if (i % COLUMNS == 0) {
employeeJson = Maps.newHashMap();
json.add(employeeJson);
}

View File

@ -1,3 +1,3 @@
#lesson_wrapper {height: 435px;width: 500px;}
#lesson_header {background-image: url(../images/lesson1_header.jpg); width: 490px;padding-right: 10px;padding-top: 60px;background-repeat: no-repeat;}
.lesson_workspace {background-image: url(../images/lesson1_workspace.jpg); width: 489px;height: 325px;padding-left: 10px;padding-top: 10px;background-repeat: no-repeat;}
.lesson_workspace {background-image: url(../images/lesson1_workspace.jpg); width: 490px;height: 325px;padding-left: 10px;padding-top: 10px;background-repeat: no-repeat;}

View File

@ -1,22 +1,25 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div class="lesson-page-wrapper"><!-- reuse this block for each 'page' of content -->
<!-- include content here ... will be first page/tab multiple -->
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:ClientSideFiltering_plan.adoc"></div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:ClientSideFiltering_assignment.adoc"></div>
<br/>
<div class="attack-container">
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
<input type="hidden" id="user_id" value="102"/>
<!-- using attack-form class on your form, will allow your request to be ajaxified and stay within the display framework for webgoat -->
<form class="attack-form" accept-charset="UNKNOWN" method="POST" name="form" action="/WebGoat/clientSideFiltering/attack1">
<form class="attack-form" accept-charset="UNKNOWN" method="POST" name="form"
action="/WebGoat/clientSideFiltering/attack1">
<link rel="stylesheet" type="text/css"
th:href="@{/plugin_lessons/plugin/ClientSideFiltering/html/clientSideFiltering-stage1.css}"/>
<script th:src="@{/plugin_lessons/plugin/ClientSideFiltering/js/clientSideFiltering.js}"
th:href="@{/lesson_css/clientSideFiltering-stage1.css}"/>
<script th:src="@{/lesson_js/clientSideFiltering.js}"
language="JavaScript"></script>
<input id="userID" value="102" name="userID" type="HIDDEN"/>
<input id="userID" value="101" name="userID" type="HIDDEN"/>
<div id="lesson_wrapper">
<div id="lesson_header"></div>
<div class="lesson_workspace"><br/><br/>
@ -34,7 +37,8 @@
<option value="110" label="Joanne McDougal">Joanne McDougal</option>
</select></p>
<p></p>
<table style="display: none" id="hiddenEmployeeRecords" align="center" border="1" cellpadding="2"
<table style="display: none" id="hiddenEmployeeRecords" align="center" border="1"
cellpadding="2"
cellspacing="0" width="90%">
<div>
</div>
@ -63,11 +67,11 @@
</tbody>
</table>
</form>
<!-- do not remove the two following div's, this is where your feedback/output will land -->
<div class="attack-feedback"></div>
<div class="attack-output"></div>
<!-- ... of course, you can move them if you want to, but that will not look consistent to other lessons -->
</div>
<!-- do not remove the two following div's, this is where your feedback/output will land -->
<div class="attack-feedback"></div>
<div class="attack-output"></div>
<!-- ... of course, you can move them if you want to, but that will not look consistent to other lessons -->
</div>

View File

@ -10,10 +10,10 @@ ClientSideFilteringStage1Question=What is Neville Bartholomew's salary?
ClientSideFilteringStage1SubmitAnswer=Submit Answer
ClientSideFilteringStage2Finish=Click here when you believe you have completed the lesson.
ClientSideFilteringChoose=Choose Employee
ClientSideFilteringHint1=Stage 1: The information displayed when an employee is chosen from the drop down menu is stored on the client side.
ClientSideFilteringHint2=Stage 1: Use Firebug to find where the information is stored on the client side.
ClientSideFilteringHint3=Stage 1: Examine the hidden table to see if there is anyone listed who is not in the drop down menu.
ClientSideFilteringHint4=Stage 1: Look in the last row of the hidden table.
ClientSideFilteringHint1=The information displayed when an employee is chosen from the drop down menu is stored on the client side.
ClientSideFilteringHint2=Use Firebug to find where the information is stored on the client side.
ClientSideFilteringHint3=Examine the hidden table to see if there is anyone listed who is not in the drop down menu.
ClientSideFilteringHint4=Look in the last row of the hidden table.
ClientSideFilteringHint5a=Stage 1: You can access the server directly
ClientSideFilteringHint5b=here
ClientSideFilteringHint5c=to see what results are being returned
@ -22,5 +22,6 @@ ClientSideFilteringHint7=Stage 2: The query currently returns all of the content
ClientSideFilteringHint8=Stage 2: The query should only return the information of employees who are managed by Moe Stooge, whose userID is 102
ClientSideFilteringHint9=Stage 2: Try using a filter operator.
ClientSideFilteringHint10=Stage 2: Your filter operator should look something like: [Managers/Manager/text()=
ClientSideFilteringInstructions1=STAGE 1: You are logged in as Moe Stooge, CSO of Goat Hills Financial. You have access to everyone in the company's information, except the CEO, Neville Bartholomew. Or at least you shouldn't have access to the CEO's information. For this exercise, examine the contents of the page to see what extra information you can find.
ClientSideFilteringInstructions1=STAGE 1: You are logged in as Moe Stooge, CSO of Goat Hills Financial. You have access to everyone in the company's information, except the CEO, . Or at least you shouldn't have access to the CEO's information. For this exercise, examine the contents of the page to see what extra information you can find.
ClientSideFilteringInstructions2=STAGE 2: Now, fix the problem. Modify the server to only return results that Moe Stooge is allowed to see.
ClientSideFiltering.incorrect=This is not the salary from Neville Bartholomew...

View File

@ -0,0 +1,5 @@
== Salary manager
You are logged in as Moe Stooge, CSO of Goat Hills Financial. You have access to everyone in the company's information,
except the CEO, Neville Bartholomew. Or at least you shouldn't have access to the CEO's information. For this assignment,
examine the contents of the page to see what extra information you can find.

View File

@ -1,9 +1,9 @@
== HTTP Proxy Overview
Many times proxies are used as a way of accessing otehrwise blocked content. A user might connect to server A, which relays content from server B
... Because Server B is blocked wihtin the user's network. That's not the use case we will be dealing with here, but the concept is the same.
HTTP Proxies receive requesets from a client and relay them. They also typically record them. They act as a man-in-the-middle (keep that in mind if you decide to
Many times proxies are used as a way of accessing otherwise blocked content. A user might connect to server A, which relays content from server B
... Because Server B is blocked within the user's network. That's not the use case we will be dealing with here, but the concept is the same.
HTTP Proxies receive requests from a client and relay them. They also typically record them. They act as a man-in-the-middle (keep that in mind if you decide to
use a proxy server to connect to some other system that is otherwise blocked). We won't get into HTTP vs HTTPS just yet, but that's an important topic in
relationship to proxies.
@ -17,4 +17,4 @@ analyzing the security of a website.
ZAP specifically can also be used in the development process in a CI/CD, DevOps or otherwise automated build/test environment. This lesson does
not currently have any details on that, but it is worth mentioning. There are a number of examples on the internet of it being integrated into a
CI/CD with Jenkins, maven or other build processes.
CI/CD with Jenkins, Maven or other build processes.

View File

@ -24,7 +24,7 @@
</goals>
<configuration>
<backend>html</backend>
<sourceDirectory>src/main/resources/plugin/CrossSiteScripting/lessonPlans/en/</sourceDirectory>
<sourceDirectory>src/main/resources/lessonPlans/en/</sourceDirectory>
</configuration>
</execution>

View File

@ -64,7 +64,7 @@
<!-- include content here, or can be placed in another location. Content will be presented via asciidocs files,
which you put in src/main/resources/plugin/lessonplans/{lang}/{fileName}.adoc -->
<div class="adoc-content" th:replace="doc:CrossSiteScripting_content5.adoc"></div>
<img align="middle" th:src="@{/plugin_lessons/plugin/CrossSiteScripting/images/Reflected-XSS.png}" />
<img align="middle" th:src="@{/images/Reflected-XSS.png}" />
</div>
<div class="lesson-page-wrapper">
<!-- reuse this lesson-page-wrapper block for each 'page' of content in your lesson -->

View File

@ -5,7 +5,7 @@
<div class="lesson-page-wrapper">
<!-- reuse this lesson-page-wrapper block for each 'page' of content in your lesson -->
<!-- include content here, or can be placed in another location. Content will be presented via asciidocs files,
which you put in src/main/resources/plugin/lessonplans/{lang}/{fileName}.adoc -->
which you put in src/main/resources/lessonplans/{lang}/{fileName}.adoc -->
<div class="adoc-content" th:replace="doc:HttpBasics_plan.adoc"></div>
</div>

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