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>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<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;
import com.google.common.collect.Sets;
import org.owasp.webgoat.session.LabelDebugger;
import org.owasp.webgoat.session.WebSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import 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;
@ -30,6 +36,33 @@ public class MvcConfiguration extends WebMvcConfigurerAdapter {
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
public ServletRegistrationBean servletRegistrationBean(HammerHead hammerHead) {
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.WebgoatContext;
import org.owasp.webgoat.session.WebgoatProperties;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.Scope;
@ -40,22 +42,27 @@ public class WebGoat extends SpringBootServletInitializer {
@Bean
public PluginsLoader pluginsLoader(@Qualifier("pluginTargetDirectory") File pluginTargetDirectory) {
System.out.println("Plugin target directory: " + pluginTargetDirectory.toString());
return new PluginsLoader(pluginTargetDirectory);
}
@Bean
@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);
}
@Bean
public Course course(PluginsLoader pluginsLoader, WebgoatContext webgoatContext, ServletContext context,
WebgoatProperties webgoatProperties) {
public LessonEndpointProvider lessonEndpointProvider(ApplicationContext applicationContext, BeanFactory factory) {
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.loadCourses(webgoatContext, context, "/");
course.loadLessonFromPlugin(pluginsLoader.loadPlugins());
endpointProvider.registerEndpoints();
return course;
}
}

View File

@ -5,22 +5,16 @@
*/
package org.owasp.webgoat.application;
import org.owasp.webgoat.lessons.LessonServletMapping;
import org.slf4j.Logger;
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.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRegistration;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Set;
/**
* Web application lifecycle listener.
@ -37,26 +31,6 @@ public class WebGoatServletListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
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} */

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

View File

@ -3,10 +3,12 @@ package org.owasp.webgoat.plugins;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.LessonEndpointMapping;
import org.springframework.util.StringUtils;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.nio.file.Path;
import java.util.Arrays;
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_PLANS_DIRECTORY = "lessonPlans";
private final PluginClassLoader classLoader;
public static PluginClassLoader classLoader;
private Class<AbstractLesson> lesson;
private Map<String, File> solutionLanguageFiles = new HashMap<>();
private Map<String, File> lessonPlansLanguageFiles = new HashMap<>();
private List<File> pluginFiles = Lists.newArrayList();
private File lessonSourceFile;
private List<Class> lessonEndpoints = Lists.newArrayList();
public Plugin(PluginClassLoader classLoader) {
this.classLoader = classLoader;
@ -47,6 +50,22 @@ public class Plugin {
public void findLesson(List<String> classes) {
for (String clazzName : classes) {
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>
*

View File

@ -64,20 +64,6 @@ public class PluginFileUtils {
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>
*

View File

@ -44,6 +44,8 @@ public class PluginsLoader {
private static final int BUFFER_SIZE = 32 * 1024;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final File pluginTargetDirectory;
public static PluginClassLoader classLoader = new PluginClassLoader(PluginClassLoader.class.getClassLoader());
@Autowired
public PluginsLoader(File pluginTargetDirectory) {
@ -153,8 +155,6 @@ public class PluginsLoader {
private List<Callable<Plugin>> extractJars(List<URL> jars) {
List<Callable<Plugin>> extractorCallables = Lists.newArrayList();
ClassLoader parentClassLoader = PluginClassLoader.class.getClassLoader();
final PluginClassLoader classLoader = new PluginClassLoader(parentClassLoader);
for (final URL jar : jars) {
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.session.timeout=600
server.contextPath=/WebGoat
server.port=8080
logging.level.org.springframework=INFO
logging.level.org.springframework=DEBUG
logging.level.org.hibernate=ERROR
spring.thymeleaf.cache=false
security.enable-csrf=false

View File

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