Endpoints provided by lessons now work

This commit is contained in:
Nanne Baars 2016-05-13 14:45:53 +02:00
parent 79102c6ddd
commit 4a19ddf40a
14 changed files with 287 additions and 78 deletions

View File

@ -210,6 +210,10 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId> <artifactId>spring-boot-starter</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.apache.tomcat.embed</groupId> <groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId> <artifactId>tomcat-embed-jasper</artifactId>

View File

@ -1,19 +0,0 @@
package org.owasp.webgoat;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Info {
public static class Information {
}
@Bean(name = "information")
public Information information() {
return null;
}
}

View File

@ -0,0 +1,64 @@
package org.owasp.webgoat;
import org.owasp.webgoat.lessons.LessonEndpointMapping;
import org.owasp.webgoat.plugins.PluginsLoader;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import java.util.Map;
/**
* Each lesson can define an endpoint which can support the lesson. So for example if you create a lesson which uses JavaScript and
* needs to call out to the server to fetch data you can define an endpoint in that lesson. WebGoat will pick up this endpoint and
* Spring will publish it.
* <p/>
* Find all the defined endpoints in the lessons and register those endpoints in the Spring context so later on the
* Actuator will pick them up and expose them as real endpoints.
* <p/>
* We use the Actuator here so we don't have to do all the hard work ourselves (endpoint strategy pattern etc) so in a
* lesson you can just define a subclass of LessonEndpoint which this class will publish as an endpoint. So we can
* dynamically load endpoints from our plugins.
*/
public class LessonEndpointProvider {
private final String pluginBasePackage;
private final ApplicationContext parentContext;
private ListableBeanFactory context;
private DefaultListableBeanFactory providedBeans;
private BeanFactory beanFactory;
public LessonEndpointProvider(String pluginBasePackage, ApplicationContext parentContext, BeanFactory beanFactory) {
this.pluginBasePackage = pluginBasePackage;
this.parentContext = parentContext;
this.providedBeans = new DefaultListableBeanFactory(this.parentContext.getParentBeanFactory());
this.beanFactory = beanFactory;
}
public void registerEndpoints() {
if (context == null) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.setParent(parentContext);
context.setClassLoader(PluginsLoader.classLoader);
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, false);
scanner.addIncludeFilter(new AnnotationTypeFilter(LessonEndpointMapping.class));
scanner.scan(pluginBasePackage);
context.refresh();
Map<String, MvcEndpoint> beansOfType = context.getBeansOfType(MvcEndpoint.class);
ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;
beansOfType.forEach((k, v) -> {
configurableBeanFactory.registerSingleton(k, v);
});
this.context = context;
}
}
}

View File

@ -0,0 +1,60 @@
package org.owasp.webgoat;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
import org.owasp.webgoat.session.WebSession;
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;
/**
*
*/
public class LessonTemplateResolver extends TemplateResolver {
private final static String PREFIX = "lesson:";
private final File pluginTargetDirectory;
private final WebSession webSession;
public LessonTemplateResolver(File pluginTargetDirectory, WebSession webSession) {
this.pluginTargetDirectory = pluginTargetDirectory;
this.webSession = webSession;
setResourceResolver(new LessonResourceResolver());
setResolvablePatterns(Sets.newHashSet(PREFIX + "*"));
}
@Override
protected String computeResourceName(TemplateProcessingParameters params) {
String templateName = params.getTemplateName();
return templateName.substring(PREFIX.length());
}
private class LessonResourceResolver implements IResourceResolver {
@Override
public InputStream getResourceAsStream(TemplateProcessingParameters params, String resourceName) {
String lessonName = webSession.getCurrentLesson().getClass().getSimpleName();
File lesson = new File(pluginTargetDirectory, "/plugin/" + lessonName + "/html/" + lessonName + ".html");
if (lesson != null) {
try {
return new ByteArrayInputStream(Files.toByteArray(lesson));
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
@Override
public String getName() {
return "lessonResourceResolver";
}
}
}

View File

@ -1,15 +1,21 @@
package org.owasp.webgoat; package org.owasp.webgoat;
import com.google.common.collect.Sets;
import org.owasp.webgoat.session.LabelDebugger; import org.owasp.webgoat.session.LabelDebugger;
import org.owasp.webgoat.session.WebSession; import org.owasp.webgoat.session.WebSession;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.embedded.ServletRegistrationBean; import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.thymeleaf.extras.springsecurity4.dialect.SpringSecurityDialect;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.templateresolver.TemplateResolver;
import java.io.File; import java.io.File;
@ -30,6 +36,33 @@ public class MvcConfiguration extends WebMvcConfigurerAdapter {
registry.addViewController("/start.mvc").setViewName("main_new"); registry.addViewController("/start.mvc").setViewName("main_new");
} }
@Bean
public TemplateResolver springThymeleafTemplateResolver(ApplicationContext applicationContext) {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setPrefix("classpath:/templates/");
resolver.setSuffix(".html");
resolver.setOrder(1);
resolver.setApplicationContext(applicationContext);
return resolver;
}
@Bean
public LessonTemplateResolver lessonTemplateResolver(WebSession webSession) {
LessonTemplateResolver resolver = new LessonTemplateResolver(pluginTargetDirectory, webSession);
resolver.setOrder(2);
return resolver;
}
@Bean
public SpringTemplateEngine thymeleafTemplateEngine(TemplateResolver springThymeleafTemplateResolver, LessonTemplateResolver lessonTemplateResolver) {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.addDialect(new SpringSecurityDialect());
engine.setTemplateResolvers(
Sets.newHashSet(springThymeleafTemplateResolver, lessonTemplateResolver));
return engine;
}
@Bean @Bean
public ServletRegistrationBean servletRegistrationBean(HammerHead hammerHead) { public ServletRegistrationBean servletRegistrationBean(HammerHead hammerHead) {
return new ServletRegistrationBean(hammerHead, "/attack/*"); return new ServletRegistrationBean(hammerHead, "/attack/*");

View File

@ -5,11 +5,13 @@ import org.owasp.webgoat.session.Course;
import org.owasp.webgoat.session.WebSession; import org.owasp.webgoat.session.WebSession;
import org.owasp.webgoat.session.WebgoatContext; import org.owasp.webgoat.session.WebgoatContext;
import org.owasp.webgoat.session.WebgoatProperties; import org.owasp.webgoat.session.WebgoatProperties;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer; import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
@ -40,22 +42,27 @@ public class WebGoat extends SpringBootServletInitializer {
@Bean @Bean
public PluginsLoader pluginsLoader(@Qualifier("pluginTargetDirectory") File pluginTargetDirectory) { public PluginsLoader pluginsLoader(@Qualifier("pluginTargetDirectory") File pluginTargetDirectory) {
System.out.println("Plugin target directory: " + pluginTargetDirectory.toString());
return new PluginsLoader(pluginTargetDirectory); return new PluginsLoader(pluginTargetDirectory);
} }
@Bean @Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public WebSession webSession(Course course, WebgoatContext webgoatContext, ServletContext context) { public WebSession webSession(Course course, WebgoatContext webgoatContext, ServletContext context, ApplicationContext applicationContext ) {
return new WebSession(course, webgoatContext, context); return new WebSession(course, webgoatContext, context);
} }
@Bean @Bean
public Course course(PluginsLoader pluginsLoader, WebgoatContext webgoatContext, ServletContext context, public LessonEndpointProvider lessonEndpointProvider(ApplicationContext applicationContext, BeanFactory factory) {
WebgoatProperties webgoatProperties) { LessonEndpointProvider lessonEndpointProvider = new LessonEndpointProvider("org.owasp.webgoat", applicationContext, factory);
return lessonEndpointProvider;
}
@Bean
public Course course(PluginsLoader pluginsLoader, WebgoatContext webgoatContext, ServletContext context, WebgoatProperties webgoatProperties, LessonEndpointProvider endpointProvider) {
Course course = new Course(webgoatProperties); Course course = new Course(webgoatProperties);
course.loadCourses(webgoatContext, context, "/"); course.loadCourses(webgoatContext, context, "/");
course.loadLessonFromPlugin(pluginsLoader.loadPlugins()); course.loadLessonFromPlugin(pluginsLoader.loadPlugins());
endpointProvider.registerEndpoints();
return course; return course;
} }
} }

View File

@ -5,22 +5,16 @@
*/ */
package org.owasp.webgoat.application; package org.owasp.webgoat.application;
import org.owasp.webgoat.lessons.LessonServletMapping;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener; import javax.servlet.ServletContextListener;
import javax.servlet.ServletRegistration;
import java.sql.Driver; import java.sql.Driver;
import java.sql.DriverManager; import java.sql.DriverManager;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Set;
/** /**
* Web application lifecycle listener. * Web application lifecycle listener.
@ -37,26 +31,6 @@ public class WebGoatServletListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) { public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext(); ServletContext context = sce.getServletContext();
context.log("WebGoat is starting"); context.log("WebGoat is starting");
context.log("Adding extra mappings for lessions");
loadServlets(sce);
}
private void loadServlets(ServletContextEvent sce) {
final ServletContext servletContext = sce.getServletContext();
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
false);
provider.addIncludeFilter(new AnnotationTypeFilter(LessonServletMapping.class));
Set<BeanDefinition> candidateComponents = provider.findCandidateComponents("org.owasp.webgoat");
try {
for (BeanDefinition beanDefinition : candidateComponents) {
Class controllerClass = Class.forName(beanDefinition.getBeanClassName());
LessonServletMapping pathAnnotation = (LessonServletMapping) controllerClass.getAnnotation(LessonServletMapping.class);
final ServletRegistration.Dynamic dynamic = servletContext.addServlet(controllerClass.getSimpleName(), controllerClass);
dynamic.addMapping(pathAnnotation.path());
}
} catch (Exception e) {
logger.error("Error", e);
}
} }
/** {@inheritDoc} */ /** {@inheritDoc} */

View File

@ -0,0 +1,77 @@
/**
* ************************************************************************************************
* 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>
*/
package org.owasp.webgoat.lessons;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import java.io.File;
/**
* Each lesson can define an endpoint which can support the lesson. So for example if you create a lesson which uses JavaScript and
* needs to call out to the server to fetch data you can define an endpoint in that lesson. WebGoat will pick up this endpoint and
* Spring will publish it.
* </p>
* Extend this class and implement the met
* </p>
* Note: each subclass should declare this annotation otherwise the WebGoat framework cannot find your endpoint.
*/
@LessonEndpointMapping
public abstract class LessonEndpoint 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;
}
@Override
public final Class<? extends Endpoint> getEndpointType() {
return null;
}
}

View File

@ -1,7 +1,11 @@
package org.owasp.webgoat.lessons; package org.owasp.webgoat.lessons;
import org.springframework.stereotype.Component;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** /**
************************************************************************************************* *************************************************************************************************
@ -28,14 +32,13 @@ import java.lang.annotation.RetentionPolicy;
* projects. * projects.
* <p> * <p>
* *
* @author Nanne Baars * @author WebGoat
* @since December 12, 2015 * @since December 12, 2015
* @version $Id: $Id * @version $Id: $Id
*/ */
@Component
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface LessonServletMapping { public @interface LessonEndpointMapping { }
String path();
}

View File

@ -3,10 +3,12 @@ package org.owasp.webgoat.plugins;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.owasp.webgoat.lessons.AbstractLesson; import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.LessonEndpointMapping;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Annotation;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
@ -27,13 +29,14 @@ public class Plugin {
private static final String NAME_LESSON_SOLUTION_DIRECTORY = "lessonSolutions"; private static final String NAME_LESSON_SOLUTION_DIRECTORY = "lessonSolutions";
private static final String NAME_LESSON_PLANS_DIRECTORY = "lessonPlans"; private static final String NAME_LESSON_PLANS_DIRECTORY = "lessonPlans";
private final PluginClassLoader classLoader; public static PluginClassLoader classLoader;
private Class<AbstractLesson> lesson; private Class<AbstractLesson> lesson;
private Map<String, File> solutionLanguageFiles = new HashMap<>(); private Map<String, File> solutionLanguageFiles = new HashMap<>();
private Map<String, File> lessonPlansLanguageFiles = new HashMap<>(); private Map<String, File> lessonPlansLanguageFiles = new HashMap<>();
private List<File> pluginFiles = Lists.newArrayList(); private List<File> pluginFiles = Lists.newArrayList();
private File lessonSourceFile; private File lessonSourceFile;
private List<Class> lessonEndpoints = Lists.newArrayList();
public Plugin(PluginClassLoader classLoader) { public Plugin(PluginClassLoader classLoader) {
this.classLoader = classLoader; this.classLoader = classLoader;
@ -47,6 +50,22 @@ public class Plugin {
public void findLesson(List<String> classes) { public void findLesson(List<String> classes) {
for (String clazzName : classes) { for (String clazzName : classes) {
findLesson(clazzName); findLesson(clazzName);
findLessonEndpoints(clazzName);
}
}
private void findLessonEndpoints(String name) {
String realClassName = StringUtils.trimLeadingCharacter(name, '/').replaceAll("/", ".").replaceAll(".class", "");
try {
Class endpointClass = classLoader.loadClass(realClassName);
Annotation annotation = endpointClass.getAnnotation(LessonEndpointMapping.class);
if (annotation != null ) {
this.lessonEndpoints.add(endpointClass);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
//ignore
} }
} }
@ -85,6 +104,10 @@ public class Plugin {
} }
} }
public List<Class> getLessonEndpoints() {
return lessonEndpoints;
}
/** /**
* <p>rewritePaths.</p> * <p>rewritePaths.</p>
* *

View File

@ -64,20 +64,6 @@ public class PluginFileUtils {
return hasParentDirectoryWithName(p.getParent(), s); return hasParentDirectoryWithName(p.getParent(), s);
} }
/**
* <p>createDirsIfNotExists.</p>
*
* @param p a {@link java.nio.file.Path} object.
* @return a {@link java.nio.file.Path} object.
* @throws java.io.IOException if any.
*/
public static Path createDirsIfNotExists(Path p) throws IOException {
if (Files.notExists(p)) {
Files.createDirectories(p);
}
return p;
}
/** /**
* <p>replaceInFiles.</p> * <p>replaceInFiles.</p>
* *

View File

@ -44,6 +44,8 @@ public class PluginsLoader {
private static final int BUFFER_SIZE = 32 * 1024; private static final int BUFFER_SIZE = 32 * 1024;
private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final File pluginTargetDirectory; private final File pluginTargetDirectory;
public static PluginClassLoader classLoader = new PluginClassLoader(PluginClassLoader.class.getClassLoader());
@Autowired @Autowired
public PluginsLoader(File pluginTargetDirectory) { public PluginsLoader(File pluginTargetDirectory) {
@ -153,8 +155,6 @@ public class PluginsLoader {
private List<Callable<Plugin>> extractJars(List<URL> jars) { private List<Callable<Plugin>> extractJars(List<URL> jars) {
List<Callable<Plugin>> extractorCallables = Lists.newArrayList(); List<Callable<Plugin>> extractorCallables = Lists.newArrayList();
ClassLoader parentClassLoader = PluginClassLoader.class.getClassLoader();
final PluginClassLoader classLoader = new PluginClassLoader(parentClassLoader);
for (final URL jar : jars) { for (final URL jar : jars) {
classLoader.addURL(jar); classLoader.addURL(jar);

View File

@ -1,16 +1,10 @@
#spring.mvc.view.prefix=/WEB-INF/jsp/
#spring.mvc.view.suffix=.jsp
#server.servlet-path=/*
#server.jsp-servlet.class-name=org.apache.jasper.servlet.JspServlet
#server.jsp-servlet.registered=true
server.error.include-stacktrace=always server.error.include-stacktrace=always
server.session.timeout=600 server.session.timeout=600
server.contextPath=/WebGoat server.contextPath=/WebGoat
server.port=8080 server.port=8080
logging.level.org.springframework=INFO logging.level.org.springframework=DEBUG
logging.level.org.hibernate=ERROR logging.level.org.hibernate=ERROR
spring.thymeleaf.cache=false spring.thymeleaf.cache=false
security.enable-csrf=false security.enable-csrf=false

View File

@ -1,9 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"> <html xmlns:th="http://www.thymeleaf.org">
<!-- Model is setup in the class StartLesson -->
<div id="lessonInstructions" th:utext="${instructions}"></div> <div id="lessonInstructions" th:utext="${instructions}"></div>
<div id="message" class="info" th:utext="${message}"></div> <div id="message" class="info" th:utext="${message}"></div>
<br/> <br/>
<div th:utext="${lesson.content}"></div> <div th:utext="${lesson.content}"></div>
<div th:replace="lesson:showLesson"></div>
</html> </html>