This is an automated email from the ASF dual-hosted git repository.
jleroux pushed a commit to branch release17.12 in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git The following commit(s) were added to refs/heads/release17.12 by this push: new c502a97 Improved: Prevent FreeMarker Template Injection (SSTI) c502a97 is described below commit c502a978a0138b3cc1906ddd915f0b9f50c3689c Author: Jacques Le Roux <[hidden email]> AuthorDate: Mon May 18 13:48:31 2020 +0200 Improved: Prevent FreeMarker Template Injection (SSTI) (OFBIZ-11709) Fixes all the conflicts previously handled by hand (no merge was possible) --- .../ofbiz/base/util/template/FreeMarkerWorker.java | 230 ++++++++------------- 1 file changed, 88 insertions(+), 142 deletions(-) diff --git a/framework/base/src/main/java/org/apache/ofbiz/base/util/template/FreeMarkerWorker.java b/framework/base/src/main/java/org/apache/ofbiz/base/util/template/FreeMarkerWorker.java index 9d6c67a..814031a 100644 --- a/framework/base/src/main/java/org/apache/ofbiz/base/util/template/FreeMarkerWorker.java +++ b/framework/base/src/main/java/org/apache/ofbiz/base/util/template/FreeMarkerWorker.java @@ -1,4 +1,4 @@ -/* +/******************************************************************************* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -15,7 +15,7 @@ * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. - */ + *******************************************************************************/ package org.apache.ofbiz.base.util.template; import java.io.File; @@ -23,20 +23,18 @@ import java.io.IOException; import java.io.Writer; import java.net.URL; import java.util.ArrayList; +import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Objects; import java.util.Properties; import java.util.Set; import java.util.TimeZone; -import java.util.stream.Stream; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; -import org.apache.ofbiz.base.component.ComponentConfig; import org.apache.ofbiz.base.location.FlexibleLocation; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.StringUtil; @@ -45,7 +43,6 @@ import org.apache.ofbiz.base.util.UtilMisc; import org.apache.ofbiz.base.util.UtilProperties; import org.apache.ofbiz.base.util.UtilValidate; import org.apache.ofbiz.base.util.cache.UtilCache; -import org.apache.ofbiz.widget.model.ModelWidget; import freemarker.cache.MultiTemplateLoader; import freemarker.cache.StringTemplateLoader; @@ -61,6 +58,7 @@ import freemarker.template.SimpleHash; import freemarker.template.SimpleScalar; import freemarker.template.Template; import freemarker.template.TemplateException; +import freemarker.template.TemplateExceptionHandler; import freemarker.template.TemplateHashModel; import freemarker.template.TemplateModel; import freemarker.template.TemplateModelException; @@ -71,28 +69,24 @@ import freemarker.template.utility.ClassUtil; * FreeMarkerWorker - Freemarker Template Engine Utilities. */ public final class FreeMarkerWorker { - /** The template used to retrieved Freemarker transforms from multiple component classpaths. */ - private static final String TRANSFORMS_PROPERTIES = "org/apache/ofbiz/%s/freemarkerTransforms.properties"; - private static final String MODULE = FreeMarkerWorker.class.getName(); + public static final String module = FreeMarkerWorker.class.getName(); - public static final Version VERSION = Configuration.VERSION_2_3_30; + public static final Version version = Configuration.VERSION_2_3_28; - private FreeMarkerWorker() { } + private FreeMarkerWorker () {} - // Use soft references for this so that things from Content records don't kill all of our memory, - // or maybe not for performance reasons... hmmm, leave to config file... - private static final UtilCache<String, Template> CACHED_TEMPLATES = - UtilCache.createUtilCache("template.ftl.general", 0, 0, false); - private static final BeansWrapper DEFAULT_OFBIZ_WRAPPER = new BeansWrapperBuilder(VERSION).build(); - private static final Configuration DEFAULT_OFBIZ_CONFIG = makeConfiguration(DEFAULT_OFBIZ_WRAPPER); + // use soft references for this so that things from Content records don't kill all of our memory, or maybe not for performance reasons... hmmm, leave to config file... + private static final UtilCache<String, Template> cachedTemplates = UtilCache.createUtilCache("template.ftl.general", 0, 0, false); + private static final BeansWrapper defaultOfbizWrapper = new BeansWrapperBuilder(version).build(); + private static final Configuration defaultOfbizConfig = makeConfiguration(defaultOfbizWrapper); public static BeansWrapper getDefaultOfbizWrapper() { - return DEFAULT_OFBIZ_WRAPPER; + return defaultOfbizWrapper; } public static Configuration newConfiguration() { - return new Configuration(VERSION); + return new Configuration(version); } public static Configuration makeConfiguration(BeansWrapper wrapper) { @@ -104,7 +98,7 @@ public final class FreeMarkerWorker { try { newConfig.setSharedVariable("EntityQuery", staticModels.get("org.apache.ofbiz.entity.util.EntityQuery")); } catch (TemplateModelException e) { - Debug.logError(e, MODULE); + Debug.logError(e, module); } newConfig.setLocalizedLookup(false); newConfig.setSharedVariable("StringUtil", new BeanModel(StringUtil.INSTANCE, wrapper)); @@ -116,17 +110,6 @@ public final class FreeMarkerWorker { newConfig.setAutoImports(freemarkerImports); } newConfig.setLogTemplateExceptions(false); - boolean verboseTemplate = ModelWidget.widgetBoundaryCommentsEnabled(null) - || UtilProperties.getPropertyAsBoolean("widget", "widget.freemarker.template.verbose", false); - newConfig.setTemplateExceptionHandler(verboseTemplate - ? FreeMarkerWorker::handleTemplateExceptionVerbosily - : FreeMarkerWorker::handleTemplateException); - try { - newConfig.setSetting("datetime_format", "yyyy-MM-dd HH:mm:ss.SSS"); - newConfig.setSetting("number_format", "0.##########"); - } catch (TemplateException e) { - Debug.logError("Unable to set date/time and number formats in FreeMarker: " + e, MODULE); - } String templateClassResolver = UtilProperties.getPropertyValue("security", "templateClassResolver", "SAFER_RESOLVER"); try { @@ -134,32 +117,35 @@ public final class FreeMarkerWorker { ClassUtil.forName("freemarker.core.TemplateClassResolver" + templateClassResolver) .cast(templateClassResolver)); } catch (ClassNotFoundException e) { - Debug.logError("No TemplateClassResolver." + templateClassResolver, MODULE); + Debug.logError("No TemplateClassResolver." + templateClassResolver, module); + } try { + newConfig.setSetting("datetime_format", "yyyy-MM-dd HH:mm:ss.SSS"); + newConfig.setSetting("number_format", "0.##########"); + } catch (TemplateException e) { + Debug.logError("Unable to set date/time and number formats in FreeMarker: " + e, module); } + newConfig.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER); // Transforms properties file set up as key=transform name, property=transform class name ClassLoader loader = Thread.currentThread().getContextClassLoader(); - transformsURL(loader).forEach(url -> { - Properties props = UtilProperties.getProperties(url); - if (props == null) { - Debug.logError("Unable to load properties file " + url, MODULE); + Enumeration<URL> resources; + try { + resources = loader.getResources("freemarkerTransforms.properties"); + } catch (IOException e) { + Debug.logError(e, "Could not load list of freemarkerTransforms.properties", module); + throw UtilMisc.initCause(new InternalError(e.getMessage()), e); + } + while (resources.hasMoreElements()) { + URL propertyURL = resources.nextElement(); + Debug.logInfo("loading properties: " + propertyURL, module); + Properties props = UtilProperties.getProperties(propertyURL); + if (UtilValidate.isEmpty(props)) { + Debug.logError("Unable to locate properties file " + propertyURL, module); } else { - Debug.logInfo("loading properties: " + url, MODULE); loadTransforms(loader, props, newConfig); } - }); - return newConfig; - } + } - /** - * Provides the sequence of existing {@code freemarkerTransforms.properties} files. - * - * @return a stream of resource location. - */ - private static Stream<URL> transformsURL(ClassLoader loader) { - return ComponentConfig.components() - .map(cc -> String.format(TRANSFORMS_PROPERTIES, cc.getComponentName())) - .map(loader::getResource) - .filter(Objects::nonNull); + return newConfig; } private static void loadTransforms(ClassLoader loader, Properties props, Configuration config) { @@ -167,12 +153,12 @@ public final class FreeMarkerWorker { String key = (String) object; String className = props.getProperty(key); if (Debug.verboseOn()) { - Debug.logVerbose("Adding FTL Transform " + key + " with class " + className, MODULE); + Debug.logVerbose("Adding FTL Transform " + key + " with class " + className, module); } try { - config.setSharedVariable(key, loader.loadClass(className).getDeclaredConstructor().newInstance()); + config.setSharedVariable(key, loader.loadClass(className).newInstance()); } catch (Exception e) { - Debug.logError(e, "Could not pre-initialize dynamically loaded class: " + className + ": " + e, MODULE); + Debug.logError(e, "Could not pre-initialize dynamically loaded class: " + className + ": " + e, module); } } } @@ -183,22 +169,18 @@ public final class FreeMarkerWorker { * @param context The context Map * @param outWriter The Writer to render to */ - public static void renderTemplate(String templateLocation, Map<String, Object> context, Appendable outWriter) - throws TemplateException, IOException { + public static void renderTemplate(String templateLocation, Map<String, Object> context, Appendable outWriter) throws TemplateException, IOException { Template template = getTemplate(templateLocation); renderTemplate(template, context, outWriter); } - public static void renderTemplateFromString(String templateName, String templateString, - Map<String, Object> context, Appendable outWriter, long lastModificationTime, boolean useCache) - throws TemplateException, IOException { + public static void renderTemplateFromString(String templateName, String templateString, Map<String, Object> context, Appendable outWriter, long lastModificationTime, boolean useCache) throws TemplateException, IOException { Template template = null; if (useCache) { - template = CACHED_TEMPLATES.get(templateName); + template = cachedTemplates.get(templateName); } if (template == null) { - MultiTemplateLoader templateLoader = (MultiTemplateLoader) DEFAULT_OFBIZ_CONFIG.getTemplateLoader(); - StringTemplateLoader stringTemplateLoader = (StringTemplateLoader) templateLoader.getTemplateLoader(1); + StringTemplateLoader stringTemplateLoader = (StringTemplateLoader)((MultiTemplateLoader)defaultOfbizConfig.getTemplateLoader()).getTemplateLoader(1); Object templateSource = stringTemplateLoader.findTemplateSource(templateName); if (templateSource == null || stringTemplateLoader.getLastModified(templateSource) < lastModificationTime) { stringTemplateLoader.putTemplate(templateName, templateString, lastModificationTime); @@ -210,11 +192,11 @@ public final class FreeMarkerWorker { } public static void clearTemplateFromCache(String templateLocation) { - CACHED_TEMPLATES.remove(templateLocation); + cachedTemplates.remove(templateLocation); try { - DEFAULT_OFBIZ_CONFIG.removeTemplateFromCache(templateLocation); - } catch (Exception e) { - Debug.logInfo("Template not found in Fremarker cache with name: " + templateLocation, MODULE); + defaultOfbizConfig.removeTemplateFromCache(templateLocation); + } catch(Exception e) { + Debug.logInfo("Template not found in Fremarker cache with name: " + templateLocation, module); } } @@ -224,8 +206,7 @@ public final class FreeMarkerWorker { * @param context The context Map * @param outWriter The Writer to render to */ - public static Environment renderTemplate(Template template, Map<String, Object> context, Appendable outWriter) - throws TemplateException, IOException { + public static Environment renderTemplate(Template template, Map<String, Object> context, Appendable outWriter) throws TemplateException, IOException { // make sure there is no "null" string in there as FreeMarker will try to use it context.remove("null"); // Since the template cache keeps a single instance of a Template that is shared among users, @@ -269,7 +250,7 @@ public final class FreeMarkerWorker { * @return A <code>Configuration</code> instance. */ public static Configuration getDefaultOfbizConfig() { - return DEFAULT_OFBIZ_CONFIG; + return defaultOfbizConfig; } /** @@ -278,11 +259,10 @@ public final class FreeMarkerWorker { * @param templateLocation Location of the template - file path or URL */ public static Template getTemplate(String templateLocation) throws IOException { - return getTemplate(templateLocation, CACHED_TEMPLATES, DEFAULT_OFBIZ_CONFIG); + return getTemplate(templateLocation, cachedTemplates, defaultOfbizConfig); } - public static Template getTemplate(String templateLocation, UtilCache<String, Template> cache, Configuration config) - throws IOException { + public static Template getTemplate(String templateLocation, UtilCache<String, Template> cache, Configuration config) throws IOException { Template template = cache.get(templateLocation); if (template == null) { template = config.getTemplate(templateLocation); @@ -296,8 +276,7 @@ public final class FreeMarkerWorker { return getArg(args, key, templateContext); } - public static String getArg(Map<String, ? extends Object> args, String key, - Map<String, ? extends Object> templateContext) { + public static String getArg(Map<String, ? extends Object> args, String key, Map<String, ? extends Object> templateContext) { Object o = args.get(key); String returnVal = (String) unwrap(o); if (returnVal == null) { @@ -306,7 +285,7 @@ public final class FreeMarkerWorker { returnVal = (String) templateContext.get(key); } } catch (ClassCastException e2) { - Debug.logInfo(e2.getMessage(), MODULE); + Debug.logInfo(e2.getMessage(), module); } } return returnVal; @@ -332,7 +311,7 @@ public final class FreeMarkerWorker { } } } catch (TemplateModelException e) { - Debug.logInfo(e.getMessage(), MODULE); + Debug.logInfo(e.getMessage(), module); } return UtilGenerics.<T>cast(obj); } @@ -342,7 +321,7 @@ public final class FreeMarkerWorker { try { o = args.get(key); } catch (TemplateModelException e) { - Debug.logVerbose(e.getMessage(), MODULE); + Debug.logVerbose(e.getMessage(), module); return null; } @@ -353,7 +332,7 @@ public final class FreeMarkerWorker { try { ctxObj = args.get("context"); } catch (TemplateModelException e) { - Debug.logInfo(e.getMessage(), MODULE); + Debug.logInfo(e.getMessage(), module); return returnObj; } Map<String, ?> ctx = null; @@ -367,7 +346,7 @@ public final class FreeMarkerWorker { @SuppressWarnings("unchecked") public static <T> T unwrap(Object o) { - Object returnObj; + Object returnObj = null; if (o == TemplateModel.NOTHING) { returnObj = null; @@ -375,8 +354,6 @@ public final class FreeMarkerWorker { returnObj = o.toString(); } else if (o instanceof BeanModel) { returnObj = ((BeanModel) o).getWrappedObject(); - } else { - returnObj = null; } return (T) returnObj; @@ -386,10 +363,9 @@ public final class FreeMarkerWorker { Map<String, Object> templateRoot = new HashMap<>(); Set<String> varNames = null; try { - varNames = UtilGenerics.cast(env.getKnownVariableNames()); + varNames = UtilGenerics.checkSet(env.getKnownVariableNames()); } catch (TemplateModelException e1) { - String msg = "Error getting FreeMarker variable names, will not put pass current context on to sub-content"; - Debug.logError(e1, msg, MODULE); + Debug.logError(e1, "Error getting FreeMarker variable names, will not put pass current context on to sub-content", module); } if (varNames != null) { for (String varName: varNames) { @@ -399,27 +375,26 @@ public final class FreeMarkerWorker { return templateRoot; } - public static void saveContextValues(Map<String, Object> context, String[] saveKeyNames, - Map<String, Object> saveMap) { + public static void saveContextValues(Map<String, Object> context, String [] saveKeyNames, Map<String, Object> saveMap) { for (String key: saveKeyNames) { Object o = context.get(key); if (o instanceof Map<?, ?>) { - o = UtilMisc.makeMapWritable(UtilGenerics.cast(o)); + o = UtilMisc.makeMapWritable(UtilGenerics.checkMap(o)); } else if (o instanceof List<?>) { - o = UtilMisc.makeListWritable(UtilGenerics.cast(o)); + o = UtilMisc.makeListWritable(UtilGenerics.checkList(o)); } saveMap.put(key, o); } } - public static Map<String, Object> saveValues(Map<String, Object> context, String[] saveKeyNames) { + public static Map<String, Object> saveValues(Map<String, Object> context, String [] saveKeyNames) { Map<String, Object> saveMap = new HashMap<>(); for (String key: saveKeyNames) { Object o = context.get(key); if (o instanceof Map<?, ?>) { - o = UtilMisc.makeMapWritable(UtilGenerics.cast(o)); + o = UtilMisc.makeMapWritable(UtilGenerics.checkMap(o)); } else if (o instanceof List<?>) { - o = UtilMisc.makeListWritable(UtilGenerics.cast(o)); + o = UtilMisc.makeListWritable(UtilGenerics.checkList(o)); } saveMap.put(key, o); } @@ -431,10 +406,10 @@ public final class FreeMarkerWorker { String key = entry.getKey(); Object o = entry.getValue(); if (o instanceof Map<?, ?>) { - context.put(key, UtilMisc.makeMapWritable(UtilGenerics.cast(o))); + context.put(key, UtilMisc.makeMapWritable(UtilGenerics.checkMap(o))); } else if (o instanceof List<?>) { List<Object> list = new ArrayList<>(); - list.addAll(UtilGenerics.cast(o)); + list.addAll(UtilGenerics.checkList(o)); context.put(key, list); } else { context.put(key, o); @@ -477,9 +452,9 @@ public final class FreeMarkerWorker { throw new IllegalArgumentException("Error in getSiteParameters, context/ctx cannot be null"); } ServletContext servletContext = request.getSession().getServletContext(); - String rootDir = (String) ctx.get("rootDir"); - String webSiteId = (String) ctx.get("webSiteId"); - String https = (String) ctx.get("https"); + String rootDir = (String)ctx.get("rootDir"); + String webSiteId = (String)ctx.get("webSiteId"); + String https = (String)ctx.get("https"); if (UtilValidate.isEmpty(rootDir)) { rootDir = servletContext.getRealPath("/"); ctx.put("rootDir", rootDir); @@ -495,13 +470,13 @@ public final class FreeMarkerWorker { } public static TemplateModel autoWrap(Object obj, Environment env) { - TemplateModel templateModelObj = null; - try { - templateModelObj = getDefaultOfbizWrapper().wrap(obj); - } catch (TemplateModelException e) { - throw new RuntimeException(e.getMessage()); - } - return templateModelObj; + TemplateModel templateModelObj = null; + try { + templateModelObj = getDefaultOfbizWrapper().wrap(obj); + } catch (TemplateModelException e) { + throw new RuntimeException(e.getMessage()); + } + return templateModelObj; } /* @@ -519,52 +494,23 @@ public final class FreeMarkerWorker { try { locationUrl = FlexibleLocation.resolveLocation(name); } catch (Exception e) { - Debug.logWarning("Unable to locate the template: " + name, MODULE); + Debug.logWarning("Unable to locate the template: " + name, module); } - return locationUrl != null && new File(locationUrl.getFile()).exists() ? locationUrl : null; + return locationUrl != null && new File(locationUrl.getFile()).exists()? locationUrl: null; } } /** - * Handles template exceptions quietly. - * <p> - * This is done by suppressing the exception and replacing it by a generic char for quiet alert. - * Note that exception is still logged. - * <p> - * This implements the {@link freemarker.template.TemplateExceptionHandler} functional interface. - * - * @param te the exception that occurred - * @param env the runtime environment of the template - * @param out this is where the output of the template is written + * OFBiz specific TemplateExceptionHandler. */ - private static void handleTemplateException(TemplateException te, Environment env, Writer out) { - try { - out.write(UtilProperties.getPropertyValue("widget", "widget.freemarker.template.exception.message", "∎")); - Debug.logError(te, MODULE); - } catch (IOException e) { - Debug.logError(e, MODULE); - } - } - - /** - * Handles template exceptions verbosely. - * <p> - * This is done by suppressing the exception and keeping the rendering going on. Messages - * present in the stack trace are sanitized before printing them to the output writer. - * Note that exception is still logged. - * <p> - * This implements the {@link freemarker.template.TemplateExceptionHandler} functional interface. - * - * @param te the exception that occurred - * @param env the runtime environment of the template - * @param out this is where the output of the template is written - */ - private static void handleTemplateExceptionVerbosily(TemplateException te, Environment env, Writer out) { - try { - out.write(te.getMessage()); - Debug.logError(te, MODULE); - } catch (IOException e) { - Debug.logError(e, MODULE); + static class OFBizTemplateExceptionHandler implements TemplateExceptionHandler { + public void handleTemplateException(TemplateException te, Environment env, Writer out) throws TemplateException { + try { + out.write(te.getMessage()); + Debug.logError(te, module); + } catch (IOException e) { + Debug.logError(e, module); + } } } } |
Free forum by Nabble | Edit this page |