/*
 * 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
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package freemarker.core;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.utility.ClassUtil;
import freemarker.template.utility.StringUtil;

A TemplateClassResolver that resolves only the classes whose name was specified in the constructor.
/** * A {@link TemplateClassResolver} that resolves only the classes whose name * was specified in the constructor. */
public class OptInTemplateClassResolver implements TemplateClassResolver { private final Set/*<String>*/ allowedClasses; private final List/*<String>*/ trustedTemplatePrefixes; private final Set/*<String>*/ trustedTemplateNames;
Creates a new instance.
Params:
  • allowedClasses – the Set of String-s that contains the full-qualified names of the allowed classes. Can be null (means not class is allowed).
  • trustedTemplates – the List of String-s that contains template names (i.e., template root directory relative paths) and prefix patterns (like "include/*") of templates for which TemplateClassResolver.SAFER_RESOLVER will be used (which is not as safe as OptInTemplateClassResolver). The list items need not start with "/" (if they are, it will be removed). List items ending with "*" are treated as prefixes (i.e. "foo*" matches "foobar", "foo/bar/baaz", "foowhatever/bar/baaz", etc.). The "*" has no special meaning anywhere else. The matched template name is the name (template root directory relative path) of the template that directly (lexically) contains the operation (like ?new) that wants to get the class. Thus, if a trusted template includes a non-trusted template, the allowedClasses restriction will apply in the included template. This parameter can be null (means no trusted templates).
/** * Creates a new instance. * * @param allowedClasses the {@link Set} of {@link String}-s that contains * the full-qualified names of the allowed classes. * Can be <code>null</code> (means not class is allowed). * @param trustedTemplates the {@link List} of {@link String}-s that contains * template names (i.e., template root directory relative paths) * and prefix patterns (like <code>"include/*"</code>) of templates * for which {@link TemplateClassResolver#SAFER_RESOLVER} will be * used (which is not as safe as {@link OptInTemplateClassResolver}). * The list items need not start with <code>"/"</code> (if they are, it * will be removed). List items ending with <code>"*"</code> are treated * as prefixes (i.e. <code>"foo*"</code> matches <code>"foobar"</code>, * <code>"foo/bar/baaz"</code>, <code>"foowhatever/bar/baaz"</code>, * etc.). The <code>"*"</code> has no special meaning anywhere else. * The matched template name is the name (template root directory * relative path) of the template that directly (lexically) contains the * operation (like <code>?new</code>) that wants to get the class. Thus, * if a trusted template includes a non-trusted template, the * <code>allowedClasses</code> restriction will apply in the included * template. * This parameter can be <code>null</code> (means no trusted templates). */
public OptInTemplateClassResolver( Set allowedClasses, List trustedTemplates) { this.allowedClasses = allowedClasses != null ? allowedClasses : Collections.EMPTY_SET; if (trustedTemplates != null) { trustedTemplateNames = new HashSet(); trustedTemplatePrefixes = new ArrayList(); Iterator it = trustedTemplates.iterator(); while (it.hasNext()) { String li = (String) it.next(); if (li.startsWith("/")) li = li.substring(1); if (li.endsWith("*")) { trustedTemplatePrefixes.add(li.substring(0, li.length() - 1)); } else { trustedTemplateNames.add(li); } } } else { trustedTemplateNames = Collections.EMPTY_SET; trustedTemplatePrefixes = Collections.EMPTY_LIST; } } public Class resolve(String className, Environment env, Template template) throws TemplateException { String templateName = safeGetTemplateName(template); if (templateName != null && (trustedTemplateNames.contains(templateName) || hasMatchingPrefix(templateName))) { return TemplateClassResolver.SAFER_RESOLVER.resolve(className, env, template); } else { if (!allowedClasses.contains(className)) { throw new _MiscTemplateException(env, "Instantiating ", className, " is not allowed in the template for security reasons. (If you " + "run into this problem when using ?new in a template, you may want to check the \"", Configurable.NEW_BUILTIN_CLASS_RESOLVER_KEY, "\" setting in the FreeMarker configuration.)"); } else { try { return ClassUtil.forName(className); } catch (ClassNotFoundException e) { throw new _MiscTemplateException(e, env); } } } }
Extract the template name from the template object which will be matched against the trusted template names and pattern.
/** * Extract the template name from the template object which will be matched * against the trusted template names and pattern. */
protected String safeGetTemplateName(Template template) { if (template == null) return null; String name = template.getName(); if (name == null) return null; // Detect exploits, return null if one is suspected: String decodedName = name; if (decodedName.indexOf('%') != -1) { decodedName = StringUtil.replace(decodedName, "%2e", ".", false, false); decodedName = StringUtil.replace(decodedName, "%2E", ".", false, false); decodedName = StringUtil.replace(decodedName, "%2f", "/", false, false); decodedName = StringUtil.replace(decodedName, "%2F", "/", false, false); decodedName = StringUtil.replace(decodedName, "%5c", "\\", false, false); decodedName = StringUtil.replace(decodedName, "%5C", "\\", false, false); } int dotDotIdx = decodedName.indexOf(".."); if (dotDotIdx != -1) { int before = dotDotIdx - 1 >= 0 ? decodedName.charAt(dotDotIdx - 1) : -1; int after = dotDotIdx + 2 < decodedName.length() ? decodedName.charAt(dotDotIdx + 2) : -1; if ((before == -1 || before == '/' || before == '\\') && (after == -1 || after == '/' || after == '\\')) { return null; } } return name.startsWith("/") ? name.substring(1) : name; } private boolean hasMatchingPrefix(String name) { for (int i = 0; i < trustedTemplatePrefixes.size(); i++) { String prefix = (String) trustedTemplatePrefixes.get(i); if (name.startsWith(prefix)) return true; } return false; } }