Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Jason White 2015-06-28 18:08:06 -04:00
commit bcfc1be59c
29 changed files with 473 additions and 231 deletions

5
.gitignore vendored
View File

@ -24,5 +24,10 @@ src/main/main.iml
*.LOCAL.*.jsp *.LOCAL.*.jsp
*.REMOTE.*.jsp *.REMOTE.*.jsp
src/main/webapp/plugin_extracted/* src/main/webapp/plugin_extracted/*
src/main/webapp/users/*.jar
src/main/webapp/plugin_lessons/*.jar
src/main/webapp/users/*.props
classes/*
/*.iml /*.iml
.extract/*

View File

@ -96,9 +96,14 @@ Follow These instructions if you wish to run Webgoat and modify the source code
Building the project (Developers) Building the project (Developers)
--------------------------------- ---------------------------------
> cd webgoat Using a command shell/window:
> cd webgoat-classloader
> mvn clean install
> cd ..
> mvn clean package > mvn clean package
Building the webgoat-classloader is only necessary once, the classloader needs to be present in your local repository.
After opening the project in Netbeans or Eclipse, you can easily run the project using: After opening the project in Netbeans or Eclipse, you can easily run the project using:
1. Maven-Tomcat Plugin 1. Maven-Tomcat Plugin

3
catalina.policy Normal file
View File

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

67
pom.xml
View File

@ -44,6 +44,19 @@
<encoding>ISO-8859-1</encoding> <encoding>ISO-8859-1</encoding>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>create-jar</id>
<phase>compile</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId> <artifactId>maven-war-plugin</artifactId>
@ -64,38 +77,6 @@
</archive> </archive>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<attachClasses>true</attachClasses>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>copy</goal>
</goals>
<phase>package</phase>
<configuration>
<artifactItems>
<artifactItem>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
<type>jar</type>
<classifier>classes</classifier>
<outputDirectory>${project.build.directory}</outputDirectory>
<destFileName>webgoat-container-${project.version}.jar</destFileName>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin> <plugin>
<groupId>org.codehaus.mojo</groupId> <groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId> <artifactId>build-helper-maven-plugin</artifactId>
@ -125,7 +106,24 @@
<url>http://localhost:8080/manager</url> <url>http://localhost:8080/manager</url>
<path>/WebGoat</path> <path>/WebGoat</path>
<attachArtifactClassifier>exec</attachArtifactClassifier> <attachArtifactClassifier>exec</attachArtifactClassifier>
<contextReloadable>true</contextReloadable>
<useSeparateTomcatClassLoader>true</useSeparateTomcatClassLoader>
<contextFile>${project.basedir}/src/main/webapp/WEB-INF/context.xml</contextFile>
<extraDependencies>
<extraDependency>
<groupId>org.owasp.webgoat</groupId>
<artifactId>webgoat-classloader</artifactId>
<version>${project.version}</version>
</extraDependency>
</extraDependencies>
</configuration> </configuration>
<dependencies>
<dependency>
<groupId>org.owasp.webgoat</groupId>
<artifactId>webgoat-container</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<executions> <executions>
<execution> <execution>
<id>tomcat-run</id> <id>tomcat-run</id>
@ -140,6 +138,11 @@
</build> </build>
<dependencies> <dependencies>
<dependency>
<groupId>org.owasp.webgoat</groupId>
<artifactId>webgoat-classloader</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>javax.activation</groupId> <groupId>javax.activation</groupId>
<artifactId>activation</artifactId> <artifactId>activation</artifactId>

View File

@ -1,17 +1,5 @@
package org.owasp.webgoat; package org.owasp.webgoat;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.owasp.webgoat.lessons.AbstractLesson; import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.WelcomeScreen; import org.owasp.webgoat.lessons.WelcomeScreen;
import org.owasp.webgoat.lessons.admin.WelcomeAdminScreen; import org.owasp.webgoat.lessons.admin.WelcomeAdminScreen;
@ -24,6 +12,19 @@ import org.owasp.webgoat.session.WebgoatContext;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
/** /**
* ************************************************************************************************* * *************************************************************************************************
* *
@ -192,8 +193,7 @@ public class HammerHead extends HttpServlet {
logger.debug("Screen: " + screen); logger.debug("Screen: " + screen);
request.getRequestDispatcher(viewPage).forward(request, response); request.getRequestDispatcher(viewPage).forward(request, response);
} catch (Throwable t) { } catch (Throwable t) {
logger.error("Error handling request", t); logger.error("Error handling request", t); screen = new ErrorScreen(mySession, t);
screen = new ErrorScreen(mySession, t);
} finally { } finally {
try { try {
if (screen instanceof ErrorScreen) { if (screen instanceof ErrorScreen) {

View File

@ -5,13 +5,13 @@
*/ */
package org.owasp.webgoat.application; package org.owasp.webgoat.application;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.jar.Attributes; import java.util.jar.Attributes;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/** /**
* Web application lifecycle listener. * Web application lifecycle listener.

View File

@ -355,8 +355,7 @@ public abstract class AbstractLesson extends Screen implements Comparable<Object
* @return The lessonPlan value * @return The lessonPlan value
*/ */
protected String getLessonName() { protected String getLessonName() {
int index = this.getClass().getName().indexOf("lessons."); return this.getClass().getSimpleName();
return this.getClass().getName().substring(index + "lessons.".length());
} }
/** /**
@ -734,6 +733,7 @@ public abstract class AbstractLesson extends Screen implements Comparable<Object
Form form = new Form(getFormAction(), Form.POST).setName("form").setEncType(""); Form form = new Form(getFormAction(), Form.POST).setName("form").setEncType("");
form.addElement(createContent(s)); form.addElement(createContent(s));
setContent(form); setContent(form);
s.getRequest().getRequestURL();
} }
public String getFormAction() { public String getFormAction() {
@ -802,4 +802,17 @@ public abstract class AbstractLesson extends Screen implements Comparable<Object
} }
return labelManager; return labelManager;
} }
protected final String buildImagePath(WebSession w, String imgResourceName) {
return w.getRequest().getContextPath() + "/plugin_extracted/plugin/" + getLessonName() + "/images/" + imgResourceName;
}
protected final String buildJspPath(WebSession w, String jspResourceName) {
return w.getRequest().getContextPath() + "/plugin_extracted/plugin/" + getLessonName() + "/jsp/" + jspResourceName;
}
protected final String buildJsPath(WebSession w, String jsResourceName) {
return w.getRequest().getContextPath() + "/plugin_extracted/plugin/" + getLessonName() + "/js/" + jsResourceName;
}
} }

View File

@ -1,21 +1,18 @@
package org.owasp.webgoat.plugins; package org.owasp.webgoat.plugins;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.servlet.ServletContext;
import org.owasp.webgoat.HammerHead;
import org.owasp.webgoat.lessons.AbstractLesson; import org.owasp.webgoat.lessons.AbstractLesson;
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.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.servlet.ServletContext;
import java.io.File;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/** /**
* ************************************************************************************************* * *************************************************************************************************
* <p/> * <p/>
@ -178,7 +175,7 @@ public class LegacyLoader {
for (String file : files) { for (String file : files) {
String className = getClassFile(file, path); String className = getClassFile(file, path);
if (className != null && !className.endsWith("_i")) { if (className != null && !className.endsWith("_i") && className.startsWith("org.owasp.webgoat.lessons.admin")) {
try { try {
Class c = Class.forName(className); Class c = Class.forName(className);
Object o = c.newInstance(); Object o = c.newInstance();

View File

@ -1,16 +1,16 @@
package org.owasp.webgoat.plugins; package org.owasp.webgoat.plugins;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import org.owasp.webgoat.classloader.PluginClassLoader;
import org.owasp.webgoat.lessons.AbstractLesson; import org.owasp.webgoat.lessons.AbstractLesson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -20,54 +20,48 @@ import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static org.owasp.webgoat.plugins.PluginFileUtils.fileEndsWith; import static org.owasp.webgoat.plugins.PluginFileUtils.fileEndsWith;
import static org.owasp.webgoat.plugins.PluginFileUtils.hasParentDirectoryWithName; import static org.owasp.webgoat.plugins.PluginFileUtils.hasParentDirectoryWithName;
import static org.owasp.webgoat.plugins.PluginFileUtils.replaceInFiles;
public class Plugin { 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 static final String NAME_LESSON_I18N_DIRECTORY = "i18n"; private static final String NAME_LESSON_I18N_DIRECTORY = "i18n";
private final Logger logger = LoggerFactory.getLogger(Plugin.class);
private final Path pluginDirectory; private final Path pluginDirectory;
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 File lessonSourceFile; private File lessonSourceFile;
public static class PluginLoadingFailure extends RuntimeException {
public PluginLoadingFailure(String message) {
super(message);
}
public PluginLoadingFailure(String message, Exception e) {
super(message, e);
}
}
public Plugin(Path pluginDirectory) { public Plugin(Path pluginDirectory) {
this.pluginDirectory = pluginDirectory; this.pluginDirectory = pluginDirectory;
} }
public void loadClasses(Map<String, byte[]> classes) { public Plugin(Path pluginDirectory, List<String> classes) {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); this.pluginDirectory = pluginDirectory;
PluginClassLoader pluginClassLoader = new PluginClassLoader(contextClassLoader); findLesson(classes);
for (Map.Entry<String, byte[]> clazz : classes.entrySet()) { }
loadClass(pluginClassLoader, clazz.getKey(), clazz.getValue());
} private void findLesson(List<String> classes) {
if (lesson == null) { for (String clazzName : classes) {
throw new PluginLoadingFailure(String findLesson(clazzName);
.format("Lesson class not found, following classes were detected in the plugin: %s",
StringUtils.collectionToCommaDelimitedString(classes.keySet())));
} }
} }
private void loadClass(PluginClassLoader pluginClassLoader, String name, byte[] classFile) { private void findLesson(String name) {
String realClassName = name.replaceFirst("/", "").replaceAll("/", ".").replaceAll(".class", ""); String realClassName = name.replaceFirst("/", "").replaceAll("/", ".").replaceAll(".class", "");
PluginClassLoader cl = (PluginClassLoader) Thread.currentThread().getContextClassLoader();
Class clazz = pluginClassLoader.loadClass(realClassName, classFile); try {
if (AbstractLesson.class.isAssignableFrom(clazz)) { Class clazz = cl.loadClass(realClassName, true);
this.lesson = clazz;
if (AbstractLesson.class.isAssignableFrom(clazz)) {
this.lesson = clazz;
}
} catch (ClassNotFoundException ce) {
throw new PluginLoadingFailure("Class " + realClassName + " listed in jar but unable to load the class.", ce);
} }
} }
@ -85,6 +79,9 @@ public class Plugin {
if (fileEndsWith(file, ".properties") && hasParentDirectoryWithName(file, NAME_LESSON_I18N_DIRECTORY)) { if (fileEndsWith(file, ".properties") && hasParentDirectoryWithName(file, NAME_LESSON_I18N_DIRECTORY)) {
copyProperties(reload, file); copyProperties(reload, file);
} }
if (fileEndsWith(file, ".css", ".jsp", ".js")) {
pluginFiles.add(file.toFile());
}
} }
} }
@ -94,6 +91,7 @@ public class Plugin {
Files.copy(file, bos); Files.copy(file, bos);
Path propertiesPath = createPropertiesDirectory(); Path propertiesPath = createPropertiesDirectory();
ResourceBundleClassLoader.setPropertiesPath(propertiesPath); ResourceBundleClassLoader.setPropertiesPath(propertiesPath);
PluginFileUtils.createDirsIfNotExists(file.getParent());
if (reload) { if (reload) {
Files.write(propertiesPath.resolve(file.getFileName()), bos.toByteArray(), CREATE, APPEND); Files.write(propertiesPath.resolve(file.getFileName()), bos.toByteArray(), CREATE, APPEND);
} else { } else {
@ -114,25 +112,48 @@ public class Plugin {
public void rewritePaths(Path pluginTarget) { public void rewritePaths(Path pluginTarget) {
try { try {
PluginFileUtils.replaceInFiles(this.lesson.getSimpleName() + "_files", replaceInFiles(this.lesson.getSimpleName() + "_files",
pluginTarget.getFileName().toString() + "/plugin/" + this.lesson pluginTarget.getFileName().toString() + "/plugin/" + this.lesson
.getSimpleName() + "/lessonSolutions/en/" + this.lesson.getSimpleName() + "_files", .getSimpleName() + "/lessonSolutions/en/" + this.lesson.getSimpleName() + "_files",
solutionLanguageFiles.values()); solutionLanguageFiles.values());
PluginFileUtils.replaceInFiles(this.lesson.getSimpleName() + "_files", replaceInFiles(this.lesson.getSimpleName() + "_files",
pluginTarget.getFileName().toString() + "/plugin/" + this.lesson pluginTarget.getFileName().toString() + "/plugin/" + this.lesson
.getSimpleName() + "/lessonPlans/en/" + this.lesson.getSimpleName() + "_files", .getSimpleName() + "/lessonPlans/en/" + this.lesson.getSimpleName() + "_files",
lessonPlansLanguageFiles.values()); lessonPlansLanguageFiles.values());
String[] replacements = {"jsp", "js"};
for ( String replacement : replacements ) {
String s = String.format("plugin/%s/%s/", this.lesson.getSimpleName(), replacement);
String r = String.format("%s/plugin/%s/%s/", pluginTarget.getFileName().toString(),
this.lesson.getSimpleName(), replacement);
replaceInFiles(s,r, pluginFiles);
replaceInFiles(s,r, Arrays.asList(lessonSourceFile));
}
//CSS with url('/plugin/images') should not begin with / otherwise image cannot be found
String s = String.format("/plugin/%s/images/", this.lesson.getSimpleName());
String r = String.format("%s/plugin/%s/images/", pluginTarget.getFileName().toString(), this.lesson.getSimpleName());
replaceInFiles(s,r, pluginFiles);
replaceInFiles(s,r, Arrays.asList(lessonSourceFile));
} catch (IOException e) { } catch (IOException e) {
throw new PluginLoadingFailure("Unable to rewrite the paths in the solutions", e); throw new PluginLoadingFailure("Unable to rewrite the paths in the solutions", e);
} }
} }
public AbstractLesson getLesson() { /**
* Lesson is optional, it is also possible that the supplied jar contains only helper classes.
*/
public Optional<AbstractLesson> getLesson() {
try { try {
return lesson.newInstance(); if (lesson != null) {
return Optional.of(lesson.newInstance());
}
} catch (IllegalAccessException | InstantiationException e) { } catch (IllegalAccessException | InstantiationException e) {
throw new PluginLoadingFailure("Unable to instantiate the lesson " + lesson.getName(), e); throw new PluginLoadingFailure("Unable to instantiate the lesson " + lesson.getName(), e);
} }
return Optional.absent();
} }
public Optional<File> getLessonSolution(String language) { public Optional<File> getLessonSolution(String language) {

View File

@ -17,6 +17,7 @@ public class PluginBackgroundLoader implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) { public void contextInitialized(ServletContextEvent event) {
String pluginPath = event.getServletContext().getRealPath("plugin_lessons"); String pluginPath = event.getServletContext().getRealPath("plugin_lessons");
String targetPath = event.getServletContext().getRealPath("plugin_extracted"); String targetPath = event.getServletContext().getRealPath("plugin_extracted");
scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(new PluginsLoader(Paths.get(pluginPath), Paths.get(targetPath)), 0, 5, TimeUnit.MINUTES); scheduler.scheduleAtFixedRate(new PluginsLoader(Paths.get(pluginPath), Paths.get(targetPath)), 0, 5, TimeUnit.MINUTES);
} }

View File

@ -1,43 +0,0 @@
package org.owasp.webgoat.plugins;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class PluginClassLoader extends ClassLoader {
private final List<Class<?>> classes = new ArrayList<>();
private final Logger logger = LoggerFactory.getLogger(Plugin.class);
public Class<?> loadClass(String nameOfClass, byte[] classFile) {
Class<?> clazz = defineClass(nameOfClass, classFile, 0, classFile.length);
classes.add(clazz);
return clazz;
}
public PluginClassLoader(ClassLoader contextClassLoader) {
super(contextClassLoader);
}
public Class findClass(final String name) throws ClassNotFoundException {
logger.debug("Finding class " + name);
Optional<Class<?>> foundClass = FluentIterable.from(classes)
.firstMatch(new Predicate<Class<?>>() {
@Override
public boolean apply(Class<?> clazz) {
return clazz.getName().equals(name);
}
});
if (foundClass.isPresent()) {
return foundClass.get();
}
throw new ClassNotFoundException("Class " + name + " not found");
}
}

View File

@ -1,6 +1,7 @@
package org.owasp.webgoat.plugins; package org.owasp.webgoat.plugins;
import java.io.ByteArrayOutputStream; import com.google.common.collect.Lists;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.nio.file.FileSystem; import java.nio.file.FileSystem;
@ -14,20 +15,19 @@ import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import static java.lang.String.format; import static java.lang.String.format;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static org.owasp.webgoat.plugins.PluginFileUtils.createDirsIfNotExists; import static org.owasp.webgoat.plugins.PluginFileUtils.createDirsIfNotExists;
/** /**
* Extract the wpf file and place them in the system temp directory in the folder webgoat and collect the files * Extract the jar file and place them in the system temp directory in the folder webgoat and collect the files
* and classes. * and classes.
*/ */
public class PluginExtractor { public class PluginExtractor {
private final Path pluginArchive; private final Path pluginArchive;
private final Map<String, byte[]> classes = new HashMap<>(); private final List<String> classes = Lists.newArrayList();
private final List<Path> files = new ArrayList<>(); private final List<Path> files = new ArrayList<>();
public PluginExtractor(Path pluginArchive) { public PluginExtractor(Path pluginArchive) {
@ -41,20 +41,18 @@ public class PluginExtractor {
@Override @Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (file.toString().endsWith(".class")) { if (file.toString().endsWith(".class")) {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); classes.add(file.toString());
Files.copy(file, bos);
classes.put(file.toString(), bos.toByteArray());
} }
files.add(Files.copy(file, createDirsIfNotExists(Paths.get(target.toString(), file.toString())), REPLACE_EXISTING)); files.add(Files.copy(file, createDirsIfNotExists(Paths.get(target.toString(), file.toString())), REPLACE_EXISTING));
return FileVisitResult.CONTINUE; return FileVisitResult.CONTINUE;
} }
}); });
} catch (Exception e) { } catch (Exception e) {
new Plugin.PluginLoadingFailure(format("Unable to extract: %s", pluginArchive.getFileName()), e); new PluginLoadingFailure(format("Unable to extract: %s", pluginArchive.getFileName()), e);
} }
} }
public Map<String, byte[]> getClasses() { public List<String> getClasses() {
return this.classes; return this.classes;
} }

View File

@ -7,6 +7,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.DirectoryStream; import java.nio.file.DirectoryStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
@ -20,6 +21,15 @@ public class PluginFileUtils {
return p.getFileName().toString().endsWith(s); return p.getFileName().toString().endsWith(s);
} }
public static boolean fileEndsWith(Path p, String... suffixes) {
for (String suffix : suffixes) {
if (fileEndsWith(p, suffix)) {
return true;
}
}
return false;
}
public static boolean hasParentDirectoryWithName(Path p, String s) { public static boolean hasParentDirectoryWithName(Path p, String s) {
if (p == null || p.getParent() == null || p.getParent().equals(p.getRoot())) { if (p == null || p.getParent() == null || p.getParent().equals(p.getRoot())) {
return false; return false;
@ -69,4 +79,12 @@ public class PluginFileUtils {
Files.write(file, fileAsString.getBytes(), StandardOpenOption.TRUNCATE_EXISTING); Files.write(file, fileAsString.getBytes(), StandardOpenOption.TRUNCATE_EXISTING);
} }
public static void writeFile(Path targetFile, byte[] bytes, OpenOption... options) throws IOException {
createDirsIfNotExists(targetFile.getParent());
if (!Files.exists(targetFile)) {
Files.createFile(targetFile);
}
Files.write(targetFile, bytes, options);
}
} }

View File

@ -0,0 +1,8 @@
package org.owasp.webgoat.plugins;
public class PluginLoadingFailure extends RuntimeException {
public PluginLoadingFailure(String message, Exception e) {
super(message, e);
}
}

View File

@ -1,60 +1,84 @@
package org.owasp.webgoat.plugins; package org.owasp.webgoat.plugins;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import org.owasp.webgoat.classloader.PluginClassLoader;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.FileVisitResult; import java.nio.file.FileVisitResult;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor; import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class PluginsLoader implements Runnable { public class PluginsLoader implements Runnable {
protected static final String WEBGOAT_PLUGIN_EXTENSION = "jar"; protected static final String WEBGOAT_PLUGIN_EXTENSION = "jar";
private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final Path pluginSource; private final Path pluginSource;
private Path pluginTarget; private Path pluginTarget;
public PluginsLoader(Path pluginSource, Path pluginTarget) { public PluginsLoader(Path pluginSource, Path pluginTarget) {
Preconditions.checkNotNull(pluginSource, "plugin source cannot be null");
Preconditions.checkNotNull(pluginTarget, "plugin target cannot be null");
this.pluginSource = pluginSource; this.pluginSource = pluginSource;
this.pluginTarget = pluginTarget; this.pluginTarget = pluginTarget;
} }
public List<Plugin> loadPlugins(final boolean reload) { public List<Plugin> loadPlugins(final boolean reload) {
final List<Plugin> plugins = new ArrayList<Plugin>(); final PluginClassLoader cl = (PluginClassLoader)Thread.currentThread().getContextClassLoader();
List<Plugin> plugins = Lists.newArrayList();
try { try {
Files.walkFileTree(pluginSource, new SimpleFileVisitor<Path>() { List<URL> jars = listJars();
cl.addURL(jars);
@Override plugins = processPlugins(jars, reload);
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { } catch (IOException | URISyntaxException e) {
try {
if (PluginFileUtils.fileEndsWith(file, WEBGOAT_PLUGIN_EXTENSION)) {
PluginFileUtils.createDirsIfNotExists(pluginTarget);
PluginExtractor extractor = new PluginExtractor(file);
extractor.extract(pluginTarget);
Plugin plugin = new Plugin(pluginTarget);
plugin.loadClasses(extractor.getClasses());
plugin.loadFiles(extractor.getFiles(), reload);
plugin.rewritePaths(pluginTarget);
plugins.add(plugin);
}
} catch (Plugin.PluginLoadingFailure e) {
logger.error("Unable to load plugin, continue loading others...", e);
}
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
logger.error("Loading plugins failed", e); logger.error("Loading plugins failed", e);
} }
return plugins; return plugins;
} }
private List<URL> listJars() throws IOException {
final List<URL> jars = Lists.newArrayList();
Files.walkFileTree(pluginSource, 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());
}
return FileVisitResult.CONTINUE;
}
});
return jars;
}
private List<Plugin> processPlugins(List<URL> jars, boolean reload) throws URISyntaxException, IOException {
final List<Plugin> plugins = Lists.newArrayList();
for (URL jar : jars) {
PluginExtractor extractor = new PluginExtractor(Paths.get(jar.toURI()));
extractor.extract(pluginTarget);
Plugin plugin = new Plugin(pluginTarget, extractor.getClasses());
if (plugin.getLesson().isPresent()) {
PluginFileUtils.createDirsIfNotExists(pluginTarget);
plugin.loadFiles(extractor.getFiles(), reload);
plugin.rewritePaths(pluginTarget);
plugins.add(plugin);
}
}
return plugins;
}
@Override @Override
public void run() { public void run() {

View File

@ -19,13 +19,13 @@ public class ResourceBundleClassLoader {
classLoader.propertiesPath = path; classLoader.propertiesPath = path;
} }
public static ClassLoader createPropertyFilesClassLoader(ClassLoader parentClassLoader) { public static ClassLoader createPropertyFilesClassLoader() {
final List<URL> urls = new ArrayList<>(); final List<URL> urls = new ArrayList<>();
try { try {
urls.add(classLoader.propertiesPath.toUri().toURL()); urls.add(classLoader.propertiesPath.toUri().toURL());
} catch (IOException e) { } catch (IOException e) {
throw new Plugin.PluginLoadingFailure("Unable to load the properties for the classloader", e); throw new PluginLoadingFailure("Unable to load the properties for the classloader", e);
} }
return new URLClassLoader(urls.toArray(new URL[urls.size()]), Thread.currentThread().getContextClassLoader()); return new URLClassLoader(urls.toArray(new URL[urls.size()]), Thread.currentThread().getContextClassLoader());
} }

View File

@ -265,7 +265,6 @@ public class Course {
} }
Collections.sort(lessonList); Collections.sort(lessonList);
// System.out.println(java.util.Arrays.asList(lessonList));
return lessonList; return lessonList;
} }
@ -295,6 +294,7 @@ public class Course {
logger.debug("Loading plugins into cache"); logger.debug("Loading plugins into cache");
String pluginPath = context.getRealPath("plugin_lessons"); String pluginPath = context.getRealPath("plugin_lessons");
String targetPath = context.getRealPath("plugin_extracted"); String targetPath = context.getRealPath("plugin_extracted");
if (pluginPath == null) { if (pluginPath == null) {
logger.error("Plugins directory {} not found", pluginPath); logger.error("Plugins directory {} not found", pluginPath);
return; return;
@ -304,7 +304,7 @@ public class Course {
List<Plugin> plugins = new PluginsLoader(Paths.get(pluginPath), Paths.get(targetPath)).loadPlugins(true); List<Plugin> plugins = new PluginsLoader(Paths.get(pluginPath), Paths.get(targetPath)).loadPlugins(true);
for (Plugin plugin : plugins) { for (Plugin plugin : plugins) {
try { try {
AbstractLesson lesson = plugin.getLesson(); AbstractLesson lesson = plugin.getLesson().get();
lesson.setWebgoatContext(webgoatContext); lesson.setWebgoatContext(webgoatContext);
lesson.update(properties); lesson.update(properties);

View File

@ -48,7 +48,7 @@ public class LabelProvider
{ {
if (!labels.containsKey(locale)) if (!labels.containsKey(locale))
{ {
ClassLoader classLoader = ResourceBundleClassLoader.createPropertyFilesClassLoader(ResourceBundle.class.getClassLoader()); ClassLoader classLoader = ResourceBundleClassLoader.createPropertyFilesClassLoader();
ResourceBundle resBundle = ResourceBundle.getBundle("WebGoatLabels", locale, classLoader, localeController); ResourceBundle resBundle = ResourceBundle.getBundle("WebGoatLabels", locale, classLoader, localeController);
labels.put(locale, resBundle); labels.put(locale, resBundle);
} }

View File

@ -1,2 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/WebGoat"/> <Context antiJARLocking="true" path="/WebGoat">
<Loader delegate="true" loaderClass="org.owasp.webgoat.classloader.PluginClassLoader" searchExternalFirst="true"/>
</Context>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/WebGoat">
<Loader delegate="true" loaderClass="org.owasp.webgoat.classloader.PluginClassLoader" searchExternalFirst="true"/>
</Context>

View File

@ -23,7 +23,7 @@ public class GlobalPropertiesTest {
new GlobalProperties(pluginDirectory).loadProperties(directory); new GlobalProperties(pluginDirectory).loadProperties(directory);
ClassLoader propertyFilesClassLoader = ClassLoader propertyFilesClassLoader =
ResourceBundleClassLoader.createPropertyFilesClassLoader(this.getClass().getClassLoader()); ResourceBundleClassLoader.createPropertyFilesClassLoader();
assertNotNull(propertyFilesClassLoader.getResourceAsStream("global.properties")); assertNotNull(propertyFilesClassLoader.getResourceAsStream("global.properties"));
} }

View File

@ -1,45 +1,32 @@
package org.owasp.webgoat.plugins; package org.owasp.webgoat.plugins;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertThat;
import static org.junit.matchers.JUnitMatchers.containsString;
import static org.junit.matchers.JUnitMatchers.hasItem;
public class PluginTest { public class PluginTest {
@Test // @Test
public void pathShouldBeRewrittenInHtmlFile() throws Exception { // public void pathShouldBeRewrittenInHtmlFile() throws Exception {
Path tmpDir = PluginTestHelper.createTmpDir(); // Path tmpDir = PluginTestHelper.createTmpDir();
Path pluginSourcePath = PluginTestHelper.pathForLoading(); // Path pluginSourcePath = PluginTestHelper.pathForLoading();
Plugin plugin = PluginTestHelper.createPluginFor(TestPlugin.class); // Plugin plugin = PluginTestHelper.createPluginFor(TestPlugin.class);
Path htmlFile = Paths.get(pluginSourcePath.toString(), "lessonSolutions", "rewrite_test.html"); // Path htmlFile = Paths.get(pluginSourcePath.toString(), "lessonSolutions", "rewrite_test.html");
plugin.loadFiles(Arrays.asList(htmlFile), true); // plugin.loadFiles(Arrays.asList(htmlFile), true);
plugin.rewritePaths(tmpDir); // plugin.rewritePaths(tmpDir);
List<String> allLines = Files.readAllLines(htmlFile, StandardCharsets.UTF_8); // List<String> allLines = Files.readAllLines(htmlFile, StandardCharsets.UTF_8);
//
assertThat(allLines, // assertThat(allLines,
hasItem(containsString("plugin/TestPlugin/lessonSolutions/en/TestPlugin_files/image001.png"))); // hasItem(containsString("plugin/TestPlugin/lessonSolutions/en/TestPlugin_files/image001.png")));
} // }
//
@Test // @Test
public void shouldNotRewriteOtherLinks() throws Exception { // public void shouldNotRewriteOtherLinks() throws Exception {
Path tmpDir = PluginTestHelper.createTmpDir(); // Path tmpDir = PluginTestHelper.createTmpDir();
Path pluginSourcePath = PluginTestHelper.pathForLoading(); // Path pluginSourcePath = PluginTestHelper.pathForLoading();
Plugin plugin = PluginTestHelper.createPluginFor(TestPlugin.class); // Plugin plugin = PluginTestHelper.createPluginFor(TestPlugin.class);
Path htmlFile = Paths.get(pluginSourcePath.toString(), "lessonSolutions", "rewrite_test.html"); // Path htmlFile = Paths.get(pluginSourcePath.toString(), "lessonSolutions", "rewrite_test.html");
plugin.loadFiles(Arrays.asList(htmlFile), true); // plugin.loadFiles(Arrays.asList(htmlFile), true);
plugin.rewritePaths(tmpDir); // plugin.rewritePaths(tmpDir);
List<String> allLines = Files.readAllLines(htmlFile, StandardCharsets.UTF_8); // List<String> allLines = Files.readAllLines(htmlFile, StandardCharsets.UTF_8);
//
assertThat(allLines, // assertThat(allLines,
hasItem(containsString("Unknown_files/image001.png"))); // hasItem(containsString("Unknown_files/image001.png")));
} // }
} }

View File

@ -5,8 +5,6 @@ import java.net.URISyntaxException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class PluginTestHelper { public class PluginTestHelper {
@ -23,12 +21,11 @@ public class PluginTestHelper {
return Paths.get(path.toString(), "org/owasp/webgoat/plugins"); return Paths.get(path.toString(), "org/owasp/webgoat/plugins");
} }
public static Plugin createPluginFor(Class pluginClass) throws Exception { // public static Plugin createPluginFor(Class pluginClass) throws Exception {
Path pluginTargetPath = Files.createDirectory(Paths.get(tempDirectory.toString(), "pluginTargetPath")); // Path pluginTargetPath = Files.createDirectory(Paths.get(tempDirectory.toString(), "pluginTargetPath"));
Plugin plugin = new Plugin(pluginTargetPath); // Map<String, byte[]> classes = new HashMap<>();
Map<String, byte[]> classes = new HashMap<>(); // classes.put(pluginClass.getName(), Files.readAllBytes(Paths.get(pathForLoading().toString(), pluginClass.getSimpleName() + ".class")));
classes.put(pluginClass.getName(), Files.readAllBytes(Paths.get(pathForLoading().toString(), pluginClass.getSimpleName() + ".class"))); // Plugin plugin = new Plugin(pluginTargetPath, classes);
plugin.loadClasses(classes); // return plugin;
return plugin; // }
}
} }

4
webgoat-classloader/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
target/
.idea/
*.iml
dependency-reduced-pom.xml

View File

@ -0,0 +1,18 @@
<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">
<name>WebGoat</name>
<modelVersion>4.0.0</modelVersion>
<groupId>org.owasp.webgoat</groupId>
<artifactId>webgoat-classloader</artifactId>
<packaging>jar</packaging>
<version>6.1.0</version>
<dependencies>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>7.0.47</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,31 @@
package org.owasp.webgoat.classloader;
import org.apache.catalina.loader.WebappClassLoader;
import java.net.URL;
import java.util.List;
/**
* Classloader for Tomcat.
*
* We need to provide this classloader otherwise jsp files cannot be compiled. JspContextWrapper uses
* Thread.currentThread().getContextClassLoader() but during initialisation it loads the classloader which means
* this classloader will never pickup the plugin classes.
*
* With this loader we can add jars we load during the plugin loading and the jsp will pick it up because this is
* the same classloader.
*/
public class PluginClassLoader extends WebappClassLoader {
public PluginClassLoader() {
}
public PluginClassLoader(ClassLoader parent) {
super(parent);
}
public void addURL(List<URL> urls) {
for (URL url : urls) {
super.addURL(url);
}
}
}

4
webgoat-release/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
target/
.idea/
*.iml
dependency-reduced-pom.xml

18
webgoat-release/README.md Normal file
View File

@ -0,0 +1,18 @@
# Releasing WebGoat
## Introduction
This project will create a release for WebGoat ready for distribution.
This project creates a war with all the lessons included.
## Details
The following steps happen during the release:
* Download the webgoat-container.war from the repository
* Unpack the war
* Download the dist-plugin.zip from the repository
* Unpack the lessons
* Build the war again (webgoat-release-${version}.war)
* Create the executable jar (webgoat-release-${version}-war-exec.jar)

124
webgoat-release/pom.xml Normal file
View File

@ -0,0 +1,124 @@
<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">
<name>WebGoat</name>
<modelVersion>4.0.0</modelVersion>
<groupId>org.owasp.webgoat</groupId>
<artifactId>webgoat-release</artifactId>
<packaging>war</packaging>
<version>6.1.0</version>
<repositories>
<repository>
<id>maven2-repository.dev.java.net</id>
<name>Java.net Maven 2 Repository</name>
<url>http://download.java.net/maven/2</url>
</repository>
</repositories>
<!-- Shared version number properties -->
<properties>
<tiles.version>2.2.2</tiles.version>
<!-- If run from Bamboo this will be replaced with the bamboo build number -->
<build.number>local</build.number>
<lessons.version>1.0</lessons.version>
<war.output.dir>${project.build.directory}/war/</war.output.dir>
<lessons.output.dir>${war.output.dir}/plugin_lessons</lessons.output.dir>
</properties>
<!--
Step 1: Unpack the container WAR file
Step 2: Use the zip file and unpack it
Step 3: Build a new WAR file and install it in the repository
-->
<build>
<plugins>
<!-- Unpack the container.war and dist-plugins.jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.10</version>
<executions>
<execution>
<id>unpack-war</id>
<phase>generate-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.owasp.webgoat</groupId>
<artifactId>webgoat-container</artifactId>
<version>${project.version}</version>
<type>war</type>
</artifactItem>
</artifactItems>
<outputDirectory>${war.output.dir}</outputDirectory>
</configuration>
</execution>
<execution>
<id>unpack-lessons-zip</id>
<phase>generate-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<skip>false</skip>
<artifactItems>
<artifactItem>
<includes>**/*.jar</includes>
<groupId>org.owasp.webgoat.lesson</groupId>
<artifactId>dist</artifactId>
<version>${lessons.version}</version>
<type>zip</type>
<classifier>plugins</classifier>
</artifactItem>
</artifactItems>
<outputDirectory>${lessons.output.dir}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!-- Create the war -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<warSourceDirectory>${war.output.dir}</warSourceDirectory>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
<manifestEntries>
<Specification-Title>${project.name}</Specification-Title>
<Specification-Version>${project.version}</Specification-Version>
<Implementation-Version>${build.number}</Implementation-Version>
</manifestEntries>
</archive>
</configuration>
</plugin>
<!-- Create the executable jar -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<url>http://localhost:8080/manager</url>
<path>/WebGoat</path>
<attachArtifactClassifier>exec</attachArtifactClassifier>
</configuration>
<executions>
<execution>
<id>tomcat-run</id>
<goals>
<goal>exec-war-only</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>