Merge pull request #105 from nbaars/master
Fixed classloading issues with Goathills lessons
This commit is contained in:
		
							
								
								
									
										1
									
								
								webgoat-container/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								webgoat-container/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -5,3 +5,4 @@ target/ | |||||||
| /src/main/webapp/plugin_extracted/* | /src/main/webapp/plugin_extracted/* | ||||||
| dependency-reduced-pom.xml | dependency-reduced-pom.xml | ||||||
| src/main/webapp/users/guest.org.owasp.webgoat.lessons.BackDoors.props | src/main/webapp/users/guest.org.owasp.webgoat.lessons.BackDoors.props | ||||||
|  | /src/main/webapp/WEB-INF/lib/*.jar | ||||||
| @ -2,12 +2,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.apache.catalina.loader.WebappClassLoader; | ||||||
| import org.owasp.webgoat.lessons.AbstractLesson; | import org.owasp.webgoat.lessons.AbstractLesson; | ||||||
| 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.net.URLClassLoader; |  | ||||||
| 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; | ||||||
| @ -43,10 +43,10 @@ public class Plugin { | |||||||
|     private void findLesson(String name) { |     private void findLesson(String name) { | ||||||
|         String realClassName = StringUtils.trimLeadingCharacter(name, '/').replaceAll("/", ".").replaceAll(".class", ""); |         String realClassName = StringUtils.trimLeadingCharacter(name, '/').replaceAll("/", ".").replaceAll(".class", ""); | ||||||
|         //TODO should be passed in (refactor) |         //TODO should be passed in (refactor) | ||||||
|         URLClassLoader cl = (URLClassLoader) Thread.currentThread().getContextClassLoader(); |         WebappClassLoader cl = (WebappClassLoader) Thread.currentThread().getContextClassLoader(); | ||||||
|  |  | ||||||
|         try { |         try { | ||||||
|             Class clazz = cl.loadClass(realClassName); |             Class clazz = cl.loadClass(realClassName, true); | ||||||
|  |  | ||||||
|             if (AbstractLesson.class.isAssignableFrom(clazz)) { |             if (AbstractLesson.class.isAssignableFrom(clazz)) { | ||||||
|                 this.lesson = clazz; |                 this.lesson = clazz; | ||||||
|  | |||||||
| @ -1,34 +0,0 @@ | |||||||
| package org.owasp.webgoat.plugins; |  | ||||||
|  |  | ||||||
| import javax.servlet.ServletContextEvent; |  | ||||||
| import javax.servlet.ServletContextListener; |  | ||||||
| import javax.servlet.annotation.WebListener; |  | ||||||
| import java.util.concurrent.Executors; |  | ||||||
| import java.util.concurrent.ScheduledExecutorService; |  | ||||||
|  |  | ||||||
| @WebListener |  | ||||||
| /** |  | ||||||
|  * <p>PluginBackgroundLoader class.</p> |  | ||||||
|  * |  | ||||||
|  * @version $Id: $Id |  | ||||||
|  */ |  | ||||||
| public class PluginBackgroundLoader implements ServletContextListener { |  | ||||||
|  |  | ||||||
|     private ScheduledExecutorService scheduler; |  | ||||||
|  |  | ||||||
|     /** {@inheritDoc} */ |  | ||||||
|     @Override |  | ||||||
|     public void contextInitialized(ServletContextEvent event) { |  | ||||||
|         String pluginPath = event.getServletContext().getRealPath("plugin_lessons"); |  | ||||||
|         String targetPath = event.getServletContext().getRealPath("plugin_extracted"); |  | ||||||
|  |  | ||||||
|         scheduler = Executors.newSingleThreadScheduledExecutor(); |  | ||||||
|         //scheduler.scheduleAtFixedRate(new PluginsLoader(Paths.get(pluginPath), Paths.get(targetPath)), 10, 5, TimeUnit.MINUTES); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** {@inheritDoc} */ |  | ||||||
|     @Override |  | ||||||
|     public void contextDestroyed(ServletContextEvent event) { |  | ||||||
|         scheduler.shutdownNow(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,9 +1,8 @@ | |||||||
| package org.owasp.webgoat.plugins; | package org.owasp.webgoat.plugins; | ||||||
|  |  | ||||||
| import com.google.common.collect.Lists; | import com.google.common.collect.Lists; | ||||||
|  | import org.apache.catalina.loader.WebappClassLoader; | ||||||
| import org.apache.commons.io.FileUtils; | import org.apache.commons.io.FileUtils; | ||||||
| import org.owasp.webgoat.plugins.classloader.PluginClassLoaderFactory; |  | ||||||
| import org.owasp.webgoat.plugins.classloader.PluginClassLoaderRepository; |  | ||||||
| import org.owasp.webgoat.util.LabelProvider; | import org.owasp.webgoat.util.LabelProvider; | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| @ -11,10 +10,10 @@ import org.springframework.util.ResourceUtils; | |||||||
|  |  | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.net.URL; | import java.net.URL; | ||||||
| import java.net.URLClassLoader; |  | ||||||
| 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.List; | import java.util.List; | ||||||
| @ -30,12 +29,12 @@ import java.util.concurrent.Executors; | |||||||
|  * |  * | ||||||
|  * @version $Id: $Id |  * @version $Id: $Id | ||||||
|  */ |  */ | ||||||
| public class PluginsLoader implements Runnable { | public class PluginsLoader { | ||||||
|  |  | ||||||
|     private static final String WEBGOAT_PLUGIN_EXTENSION = "jar"; |     private static final String WEBGOAT_PLUGIN_EXTENSION = "jar"; | ||||||
|  |     private static boolean alreadyLoaded = false; | ||||||
|     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 final PluginClassLoaderRepository repository; |  | ||||||
|     private Path pluginTarget; |     private Path pluginTarget; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @ -44,26 +43,48 @@ public class PluginsLoader implements Runnable { | |||||||
|      * @param pluginSource a {@link java.nio.file.Path} object. |      * @param pluginSource a {@link java.nio.file.Path} object. | ||||||
|      * @param pluginTarget a {@link java.nio.file.Path} object. |      * @param pluginTarget a {@link java.nio.file.Path} object. | ||||||
|      */ |      */ | ||||||
|     public PluginsLoader(PluginClassLoaderRepository repository, Path pluginSource, Path pluginTarget) { |     public PluginsLoader(Path pluginSource, Path pluginTarget) { | ||||||
|         this.pluginSource = Objects.requireNonNull(pluginSource, "plugin source cannot be null"); |         this.pluginSource = Objects.requireNonNull(pluginSource, "plugin source cannot be null"); | ||||||
|         this.pluginTarget = Objects.requireNonNull(pluginTarget, "plugin target cannot be null"); |         this.pluginTarget = Objects.requireNonNull(pluginTarget, "plugin target cannot be null"); | ||||||
|         this.repository = Objects.requireNonNull(repository, "repository cannot be null"); |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Copy jars to the lib directory | ||||||
|  |      */ | ||||||
|  |     public void copyJars() { | ||||||
|  |         try { | ||||||
|  |             if (!alreadyLoaded) { | ||||||
|  |                 WebappClassLoader cl = (WebappClassLoader) Thread.currentThread().getContextClassLoader(); | ||||||
|  |                 cl.setAntiJARLocking(true); | ||||||
|  |  | ||||||
|  |                 List<URL> jars = listJars(); | ||||||
|  |  | ||||||
|  |                 Path webInfLib = pluginTarget.getParent().resolve(cl.getJarPath().replaceFirst("\\/", "")); | ||||||
|  |                 for (URL jar : jars) { | ||||||
|  |                     Path sourceJarFile = Paths.get(jar.toURI()); | ||||||
|  |                     FileUtils.copyFileToDirectory(sourceJarFile.toFile(), webInfLib.toFile()); | ||||||
|  |                 } | ||||||
|  |                 alreadyLoaded = true; | ||||||
|  |             } | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             logger.error("Copying plugins failed", e); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * <p>loadPlugins.</p> |      * <p>loadPlugins.</p> | ||||||
|      * |      * | ||||||
|      * @param reload a boolean. |  | ||||||
|      * @return a {@link java.util.List} object. |      * @return a {@link java.util.List} object. | ||||||
|      */ |      */ | ||||||
|     public List<Plugin> loadPlugins(final boolean reload) { |     public List<Plugin> loadPlugins() { | ||||||
|  |         copyJars(); | ||||||
|         List<Plugin> plugins = Lists.newArrayList(); |         List<Plugin> plugins = Lists.newArrayList(); | ||||||
|  |  | ||||||
|         try { |         try { | ||||||
|             PluginFileUtils.createDirsIfNotExists(pluginTarget); |             PluginFileUtils.createDirsIfNotExists(pluginTarget); | ||||||
|             cleanupExtractedPluginsDirectory(); |             cleanupExtractedPluginsDirectory(); | ||||||
|             List<URL> jars = listJars(); |             List<URL> jars = listJars(); | ||||||
|             initClassLoader(jars); |  | ||||||
|             plugins = processPlugins(jars); |             plugins = processPlugins(jars); | ||||||
|         } catch (Exception e) { |         } catch (Exception e) { | ||||||
|             logger.error("Loading plugins failed", e); |             logger.error("Loading plugins failed", e); | ||||||
| @ -71,12 +92,6 @@ public class PluginsLoader implements Runnable { | |||||||
|         return plugins; |         return plugins; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void initClassLoader(List<URL> jars) { |  | ||||||
|         URLClassLoader classLoader = PluginClassLoaderFactory.createClassLoader(jars); |  | ||||||
|         this.repository.replaceClassLoader(classLoader); |  | ||||||
|         Thread.currentThread().setContextClassLoader(classLoader); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void cleanupExtractedPluginsDirectory() { |     private void cleanupExtractedPluginsDirectory() { | ||||||
|         Path i18nDirectory = pluginTarget.resolve("plugin/i18n/"); |         Path i18nDirectory = pluginTarget.resolve("plugin/i18n/"); | ||||||
|         FileUtils.deleteQuietly(i18nDirectory.toFile()); |         FileUtils.deleteQuietly(i18nDirectory.toFile()); | ||||||
| @ -131,10 +146,4 @@ public class PluginsLoader implements Runnable { | |||||||
|         } |         } | ||||||
|         return extractorCallables; |         return extractorCallables; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** {@inheritDoc} */ |  | ||||||
|     @Override |  | ||||||
|     public void run() { |  | ||||||
|         loadPlugins(true); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,31 +0,0 @@ | |||||||
| package org.owasp.webgoat.plugins.classloader; |  | ||||||
|  |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
|  |  | ||||||
| import java.net.URL; |  | ||||||
| import java.net.URLClassLoader; |  | ||||||
| import java.util.List; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Create a classloader for the plugins |  | ||||||
|  */ |  | ||||||
| public class PluginClassLoaderFactory { |  | ||||||
|  |  | ||||||
|     private static final Logger logger = LoggerFactory.getLogger(PluginClassLoaderFactory.class); |  | ||||||
|  |  | ||||||
|     public static URLClassLoader createClassLoader(List<URL> urls) { |  | ||||||
|         return new URLClassLoader(urls.toArray(new URL[urls.size()]), determineParentClassLoader()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static ClassLoader determineParentClassLoader() { |  | ||||||
|         ClassLoader parent = Thread.currentThread().getContextClassLoader(); |  | ||||||
|         try { |  | ||||||
|             parent = Thread.currentThread().getContextClassLoader().getParent() |  | ||||||
|                     .loadClass("org.apache.jasper.runtime.JspContextWrapper").getClassLoader(); |  | ||||||
|         } catch (ClassNotFoundException e) { |  | ||||||
|             logger.info("Tomcat JspContextWrapper not found, probably not running on Tomcat..."); |  | ||||||
|         } |  | ||||||
|         return parent; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,35 +0,0 @@ | |||||||
| package org.owasp.webgoat.plugins.classloader; |  | ||||||
|  |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.net.URLClassLoader; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Holds the classloaders for the plugins. For now all the plugins are loaded by the same |  | ||||||
|  * classloader. This class can be extended to contain a classloader per plugin. |  | ||||||
|  */ |  | ||||||
| public class PluginClassLoaderRepository { |  | ||||||
|  |  | ||||||
|     private static final Logger logger = LoggerFactory.getLogger(PluginClassLoaderRepository.class); |  | ||||||
|     private URLClassLoader currentPluginLoader; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @return the plugin classloader |  | ||||||
|      */ |  | ||||||
|     public URLClassLoader get() { |  | ||||||
|         return currentPluginLoader; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void replaceClassLoader(URLClassLoader classLoader) { |  | ||||||
|         if (this.currentPluginLoader != null) { |  | ||||||
|             try { |  | ||||||
|                 this.currentPluginLoader.close(); |  | ||||||
|             } catch (IOException e) { |  | ||||||
|                 logger.warn("Unable to close the current classloader", e); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         this.currentPluginLoader = classLoader; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -30,6 +30,7 @@ | |||||||
|  */ |  */ | ||||||
| package org.owasp.webgoat.service; | package org.owasp.webgoat.service; | ||||||
|  |  | ||||||
|  | import org.owasp.webgoat.plugins.PluginsLoader; | ||||||
| import org.owasp.webgoat.session.WebSession; | import org.owasp.webgoat.session.WebSession; | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| @ -40,6 +41,7 @@ import org.springframework.web.bind.annotation.RequestMapping; | |||||||
| import org.springframework.web.bind.annotation.ResponseBody; | import org.springframework.web.bind.annotation.ResponseBody; | ||||||
|  |  | ||||||
| import javax.servlet.http.HttpSession; | import javax.servlet.http.HttpSession; | ||||||
|  | import java.nio.file.Paths; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * <p>PluginReloadService class.</p> |  * <p>PluginReloadService class.</p> | ||||||
| @ -61,6 +63,11 @@ public class PluginReloadService extends BaseService { | |||||||
|     public @ResponseBody |     public @ResponseBody | ||||||
|     ResponseEntity<String> reloadPlugins(HttpSession session) { |     ResponseEntity<String> reloadPlugins(HttpSession session) { | ||||||
|         WebSession webSession = (WebSession) session.getAttribute(WebSession.SESSION); |         WebSession webSession = (WebSession) session.getAttribute(WebSession.SESSION); | ||||||
|  |         logger.debug("Loading plugins into cache"); | ||||||
|  |         String pluginPath = session.getServletContext().getRealPath("plugin_lessons"); | ||||||
|  |         String targetPath = session.getServletContext().getRealPath("plugin_extracted"); | ||||||
|  |         new PluginsLoader(Paths.get(pluginPath), Paths.get(targetPath)).copyJars(); | ||||||
|  |  | ||||||
|         webSession.getCourse().loadLessonFromPlugin(session.getServletContext()); |         webSession.getCourse().loadLessonFromPlugin(session.getServletContext()); | ||||||
|         return new ResponseEntity("Plugins reload refresh the WebGoat page!",HttpStatus.OK); |         return new ResponseEntity("Plugins reload refresh the WebGoat page!",HttpStatus.OK); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -5,7 +5,6 @@ import org.owasp.webgoat.lessons.AbstractLesson; | |||||||
| import org.owasp.webgoat.lessons.Category; | import org.owasp.webgoat.lessons.Category; | ||||||
| import org.owasp.webgoat.plugins.Plugin; | import org.owasp.webgoat.plugins.Plugin; | ||||||
| import org.owasp.webgoat.plugins.PluginsLoader; | import org.owasp.webgoat.plugins.PluginsLoader; | ||||||
| import org.owasp.webgoat.plugins.classloader.PluginClassLoaderRepository; |  | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
| @ -66,7 +65,6 @@ public class Course { | |||||||
|  |  | ||||||
|     final Logger logger = LoggerFactory.getLogger(Course.class); |     final Logger logger = LoggerFactory.getLogger(Course.class); | ||||||
|  |  | ||||||
|     private final PluginClassLoaderRepository repository = new PluginClassLoaderRepository(); |  | ||||||
|     private final List<AbstractLesson> lessons = new LinkedList<AbstractLesson>(); |     private final List<AbstractLesson> lessons = new LinkedList<AbstractLesson>(); | ||||||
|  |  | ||||||
|     private final static String PROPERTIES_FILENAME = HammerHead.propertiesPath; |     private final static String PROPERTIES_FILENAME = HammerHead.propertiesPath; | ||||||
| @ -337,7 +335,7 @@ public class Course { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         lessons.clear(); |         lessons.clear(); | ||||||
|         List<Plugin> plugins = new PluginsLoader(repository, Paths.get(pluginPath), Paths.get(targetPath)).loadPlugins(true); |         List<Plugin> plugins = new PluginsLoader(Paths.get(pluginPath), Paths.get(targetPath)).loadPlugins(); | ||||||
|         for (Plugin plugin : plugins) { |         for (Plugin plugin : plugins) { | ||||||
|             try { |             try { | ||||||
|                 AbstractLesson lesson = plugin.getLesson().get(); |                 AbstractLesson lesson = plugin.getLesson().get(); | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user