Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue ID: CVE-2021-32827 #1466

Merged
merged 8 commits into from
Sep 17, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,12 @@ public static Configuration configuration() {
private String forwardProxyPrivateKey;
private String forwardProxyCertificateChain;

// security
private String javaScriptDeniedClasses;
private String javaScriptDeniedText;
private Boolean velocityDenyClasses;


public Level logLevel() {
if (logLevel == null) {
return ConfigurationProperties.logLevel();
Expand Down Expand Up @@ -1727,4 +1733,58 @@ private int nextPowerOfTwo(int value) {
}
return (int) Math.pow(2, 16);
}


// security
public String javaScriptDeniedClasses() {
if (javaScriptDeniedClasses == null) {
return ConfigurationProperties.javaScriptDeniedClasses();
}
return javaScriptDeniedClasses;
}

/**
* Specifies whether the Java class of the specified name be exposed to javascript
*
* @param javaScriptDeniedClasses specifies the list of denied java classes, separated by comma
*/
public Configuration javaScriptDeniedClasses(String javaScriptDeniedClasses) {
this.javaScriptDeniedClasses = javaScriptDeniedClasses;
return this;
}

public String javaScriptDeniedText() {
if (javaScriptDeniedText == null) {
return ConfigurationProperties.javaScriptDeniedText();
}
return javaScriptDeniedText;
}

/**
* Specifies the restricted text which when found in template will result in restricting execution of javascript
*
* @param javaScriptDeniedText specifies the restricted text (separated by comma), if found in template will prevent execution of javascript
*/
public Configuration javaScriptDeniedText(String javaScriptDeniedText) {
this.javaScriptDeniedText = javaScriptDeniedText;
return this;
}

public Boolean velocityDenyClasses() {
if (velocityDenyClasses == null) {
return ConfigurationProperties.velocityDenyClasses();
}
return velocityDenyClasses;
}

/**
* Specifies whether restricted Java class and packages should be exposed to exposed to javascript
*
* @param velocityDenyClasses specifies whether to enable or disable exposing restricted java packages/classes in javascript
*/
public Configuration velocityDenyClasses(Boolean velocityDenyClasses) {
this.velocityDenyClasses = velocityDenyClasses;
return this;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ public class ConfigurationProperties {
private static final String MOCKSERVER_FORWARD_PROXY_TLS_PRIVATE_KEY = "mockserver.forwardProxyPrivateKey";
private static final String MOCKSERVER_FORWARD_PROXY_TLS_X509_CERTIFICATE_CHAIN = "mockserver.forwardProxyCertificateChain";

// Security
private static final String MOCKSERVER_JAVASCRIPT_CLASS_DENY = "mockserver.javascript.class.deny";
private static final String MOCKSERVER_JAVASCRIPT_TEXT_DENY = "mockserver.javascript.text.deny";
private static final String MOCKSERVER_VELOCITY_CLASS_DENY = "mockserver.velocity.class.deny";

// properties file
private static final String MOCKSERVER_PROPERTY_FILE = "mockserver.propertyFile";
public static final Properties PROPERTIES = readPropertyFile();
Expand Down Expand Up @@ -1503,6 +1508,19 @@ public static String forwardProxyCertificateChain() {
return readPropertyHierarchically(PROPERTIES, MOCKSERVER_FORWARD_PROXY_TLS_X509_CERTIFICATE_CHAIN, "MOCKSERVER_FORWARD_PROXY_TLS_X509_CERTIFICATE_CHAIN", "");
}

// security
public static String javaScriptDeniedClasses() {
return readPropertyHierarchically(PROPERTIES, MOCKSERVER_JAVASCRIPT_CLASS_DENY, "MOCKSERVER_JAVASCRIPT_CLASS_DENY", "");
}

public static String javaScriptDeniedText() {
return readPropertyHierarchically(PROPERTIES, MOCKSERVER_JAVASCRIPT_TEXT_DENY, "MOCKSERVER_JAVASCRIPT_TEXT_DENY", "");
}

public static boolean velocityDenyClasses() {
return "true".equalsIgnoreCase(readPropertyHierarchically(PROPERTIES, MOCKSERVER_VELOCITY_CLASS_DENY, "MOCKSERVER_VELOCITY_CLASS_DENY", "false"));
}

/**
* File system path or classpath location of custom mTLS (TLS client authentication) X.509 Certificate Chain for Trusting (i.e. signature verification of) Client X.509 Certificates, the certificate chain must be a X509 PEM file.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import jdk.nashorn.api.scripting.ClassFilter;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import org.mockserver.configuration.Configuration;
import org.mockserver.log.model.LogEntry;
import org.mockserver.logging.MockServerLogger;
import org.mockserver.model.HttpRequest;
Expand All @@ -21,9 +24,12 @@
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Supplier;

import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.mockserver.configuration.Configuration.configuration;
import static org.mockserver.formatting.StringFormatter.formatLogMessage;
import static org.mockserver.formatting.StringFormatter.indentAndToString;
import static org.mockserver.log.model.LogEntry.LogMessageType.TEMPLATE_GENERATED;
Expand All @@ -39,11 +45,17 @@ public class JavaScriptTemplateEngine implements TemplateEngine {
private static ObjectMapper objectMapper;
private final MockServerLogger mockServerLogger;
private HttpTemplateOutputDeserializer httpTemplateOutputDeserializer;
private static Configuration configuration;

public JavaScriptTemplateEngine(MockServerLogger mockServerLogger) {
this(mockServerLogger, null);
}

public JavaScriptTemplateEngine(MockServerLogger mockServerLogger, Configuration configuration) {
System.setProperty("nashorn.args", "--language=es6");
this.configuration = (configuration == null) ? configuration() : configuration;
if (engine == null) {
engine = new ScriptEngineManager().getEngineByName("nashorn");
engine = new NashornScriptEngineFactory().getScriptEngine(new SecureFilter());
bhmohanr-techie marked this conversation as resolved.
Show resolved Hide resolved
}
this.mockServerLogger = mockServerLogger;
this.httpTemplateOutputDeserializer = new HttpTemplateOutputDeserializer(mockServerLogger);
Expand All @@ -57,6 +69,9 @@ public <T> T executeTemplate(String template, HttpRequest request, Class<? exten
T result = null;
String script = wrapTemplate(template);
try {
if (!validateTemplate(template)) {
throw new UnsupportedOperationException("Invalid template string specified: " + template);
}
if (engine != null) {
Compilable compilable = (Compilable) engine;
// HttpResponse handle(HttpRequest httpRequest) - ES6
Expand Down Expand Up @@ -115,4 +130,90 @@ public <T> T executeTemplate(String template, HttpRequest request, Class<? exten
static String wrapTemplate(String template) {
return "function handle(request) {" + indentAndToString(template)[0] + "}";
}

/**
* Mockserver provides option for users to execute custom javascript templates.
* However, there are possibilities where a user can inject a malicious code or access any java objects
* Mockserver sets this ClassFilter instance when an engine instance is created.
*
* Mockserver property "mockserver.javascript.text.deny" can be set to specify the list of restricted strings.
* This property takes a list of restricted text strings (use comma as separator to specify more than one restricted text).
* Ex: mockserver.javascript.text.deny=engine.factory will deny execution of the javascript if the template contains the string engine.factory
*/
boolean validateTemplate(String template) {
if (template == null) {
return true;
}

try {
String restrictedText = configuration.javaScriptDeniedText();
if ((restrictedText != null) && (restrictedText.trim().length() > 0)) {
String[] restrictedTextElements = (restrictedText.indexOf(",") > -1) ? restrictedText.split(",") : new String[] {restrictedText};
for (String restrictedTextElement : restrictedTextElements) {
if (template.indexOf(restrictedTextElement) > -1) {
return false;
}
}
return true;
}

} catch (Throwable t) {
//skip if we can't validate the template...
}
return true;
}

/**
* Class filter to be used by nashorn script engine.
* Mockserver uses nashorn script engine to run javascript.
* Mockserver sets this ClassFilter instance when an engine instance is created.
*
* Mockserver property "mockserver.javascript.class.deny" can be set to specify the list of restricted classnames.
* This property takes a list of java classnames (use comma as separator to specify more than one class).
* If this property is not set, or has the value as *... it exposes any java class to javascript
* Ex: mockserver.javascript.class.deny=java.lang.Runtime will deny exposing java.lang.Runtime class to javascript, while all other classes will be exposed.
*/
static class SecureFilter implements ClassFilter {
ArrayList<String> restrictedClassesList = null;

SecureFilter() {
init();
}

void init() {
String restrictedClasses = configuration.javaScriptDeniedClasses();
if (restrictedClassesList == null) {
if ((restrictedClasses != null) && (restrictedClasses.trim().length() > 0)) {
restrictedClassesList = new ArrayList<String>();
restrictedClassesList.addAll(Arrays.asList(restrictedClasses.split(",")));
}
}
}

@Override
/**
* Specifies whether the Java class of the specified name be exposed to javascript
* @param className is the fully qualified name of the java class being checked.
* This will not be null. Only non-array class names will be passed.
* @return true if the java class can be exposed to javascript, false otherwise
*/
public boolean exposeToScripts(String className) {
if ((restrictedClassesList == null) || (restrictedClassesList.size() < 1) || restrictedClassesList.contains("*")) {
return true;
}

if (restrictedClassesList.contains(className)) {
return false;
}

return true;
}
}

public static void clear() {
engine = null;
configuration = null;
objectMapper = null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import org.apache.velocity.tools.config.ToolConfiguration;
import org.apache.velocity.tools.config.ToolboxConfiguration;
import org.apache.velocity.tools.config.XmlFactoryConfiguration;
import org.apache.velocity.util.introspection.SecureUberspector;
import org.mockserver.configuration.Configuration;
import org.mockserver.log.model.LogEntry;
import org.mockserver.logging.MockServerLogger;
import org.mockserver.model.HttpRequest;
Expand All @@ -27,6 +29,7 @@
import java.util.Properties;

import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.mockserver.configuration.Configuration.configuration;
import static org.mockserver.formatting.StringFormatter.formatLogMessage;
import static org.mockserver.log.model.LogEntry.LogMessageType.TEMPLATE_GENERATED;
import static org.mockserver.log.model.LogEntryMessages.TEMPLATE_GENERATED_MESSAGE_FORMAT;
Expand All @@ -42,6 +45,7 @@ public class VelocityTemplateEngine implements TemplateEngine {
private static ObjectMapper objectMapper;
private final MockServerLogger mockServerLogger;
private HttpTemplateOutputDeserializer httpTemplateOutputDeserializer;
private static Configuration configuration = configuration();

static {
// See: https://velocity.apache.org/engine/2.0/configuration.html
Expand All @@ -68,6 +72,9 @@ public class VelocityTemplateEngine implements TemplateEngine {
velocityProperties.put(RuntimeConstants.RESOURCE_MANAGER_CLASS, org.apache.velocity.runtime.resource.ResourceManagerImpl.class.getName());
velocityProperties.put(RuntimeConstants.RESOURCE_MANAGER_CACHE_CLASS, org.apache.velocity.runtime.resource.ResourceCacheImpl.class.getName());
velocityProperties.put("resource.loader.file.class", org.apache.velocity.runtime.resource.loader.FileResourceLoader.class.getName());
if (configuration.velocityDenyClasses()) {
velocityProperties.put(RuntimeConstants.UBERSPECT_CLASSNAME, SecureUberspector.class.getName());
}
velocityEngine = new VelocityEngine();
velocityEngine.init(velocityProperties);

Expand Down Expand Up @@ -110,11 +117,19 @@ public class VelocityTemplateEngine implements TemplateEngine {
}

public VelocityTemplateEngine(MockServerLogger mockServerLogger) {
this(mockServerLogger, null);
}

public VelocityTemplateEngine(MockServerLogger mockServerLogger, Configuration configuration) {
this.mockServerLogger = mockServerLogger;
this.httpTemplateOutputDeserializer = new HttpTemplateOutputDeserializer(mockServerLogger);
if (objectMapper == null) {
objectMapper = ObjectMapperFactory.createObjectMapper();
bhmohanr-techie marked this conversation as resolved.
Show resolved Hide resolved
}
this.configuration = (configuration == null) ? configuration() : configuration;
bhmohanr-techie marked this conversation as resolved.
Show resolved Hide resolved
if (this.configuration.velocityDenyClasses()) {
velocityEngine.setProperty(RuntimeConstants.UBERSPECT_CLASSNAME, SecureUberspector.class.getName());
}
}

@Override
Expand Down
Loading