-
Notifications
You must be signed in to change notification settings - Fork 811
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
WW-5343 Make SecurityMemberAccess an extensible bean #791
Changes from 18 commits
79ffc86
0825329
b0b80ba
9e556e9
90344b3
7e92a8d
b518635
4490d9d
8bf47b3
62988f7
ceff6cd
f87d7d7
a402e5c
d0d10d9
68f5584
05a9973
9640f5b
eba79a7
85d2c74
7929d86
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -85,6 +85,7 @@ | |
import com.opensymphony.xwork2.ognl.OgnlReflectionProvider; | ||
import com.opensymphony.xwork2.ognl.OgnlUtil; | ||
import com.opensymphony.xwork2.ognl.OgnlValueStackFactory; | ||
import com.opensymphony.xwork2.ognl.SecurityMemberAccess; | ||
import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor; | ||
import com.opensymphony.xwork2.util.CompoundRoot; | ||
import com.opensymphony.xwork2.util.OgnlTextParser; | ||
|
@@ -133,7 +134,6 @@ public class DefaultConfiguration implements Configuration { | |
Map<String, Object> constants = new HashMap<>(); | ||
constants.put(StrutsConstants.STRUTS_DEVMODE, Boolean.FALSE); | ||
constants.put(StrutsConstants.STRUTS_OGNL_LOG_MISSING_PROPERTIES, Boolean.FALSE); | ||
constants.put(StrutsConstants.STRUTS_OGNL_ENABLE_EVAL_EXPRESSION, Boolean.FALSE); | ||
constants.put(StrutsConstants.STRUTS_OGNL_ENABLE_EXPRESSION_CACHE, Boolean.TRUE); | ||
constants.put(StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD, Boolean.FALSE); | ||
constants.put(StrutsConstants.STRUTS_I18N_RELOAD, Boolean.FALSE); | ||
|
@@ -142,6 +142,7 @@ public class DefaultConfiguration implements Configuration { | |
constants.put(StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_MAXSIZE, 10000); | ||
constants.put(StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_TYPE, OgnlCacheFactory.CacheType.BASIC); | ||
constants.put(StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_MAXSIZE, 10000); | ||
constants.put(StrutsConstants.STRUTS_ALLOW_STATIC_FIELD_ACCESS, Boolean.TRUE); | ||
lukaszlenart marked this conversation as resolved.
Show resolved
Hide resolved
|
||
BOOTSTRAP_CONSTANTS = Collections.unmodifiableMap(constants); | ||
} | ||
|
||
|
@@ -385,6 +386,7 @@ protected Container createBootstrapContainer(List<ContainerProvider> providers) | |
builder.factory(ExpressionCacheFactory.class, DefaultOgnlExpressionCacheFactory.class, Scope.SINGLETON); | ||
builder.factory(BeanInfoCacheFactory.class, DefaultOgnlBeanInfoCacheFactory.class, Scope.SINGLETON); | ||
builder.factory(OgnlUtil.class, Scope.SINGLETON); | ||
builder.factory(SecurityMemberAccess.class, Scope.PROTOTYPE); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if this is a good idea, attacker could get access to the SMA just by having access to the Container There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given it's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is, the container would return a new instance, and not the one that is being used by the ValueStack There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I meant it will be possible creating a new instance via There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True, I also don't think there's anything useful an attacker can do with a fresh |
||
builder.factory(OgnlGuard.class, StrutsOgnlGuard.class, Scope.SINGLETON); | ||
|
||
builder.factory(ValueSubstitutor.class, EnvsValueSubstitutor.class, Scope.SINGLETON); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,6 @@ | |
*/ | ||
package com.opensymphony.xwork2.ognl; | ||
|
||
import com.opensymphony.xwork2.config.ConfigurationException; | ||
import com.opensymphony.xwork2.conversion.impl.XWorkConverter; | ||
import com.opensymphony.xwork2.inject.Container; | ||
import com.opensymphony.xwork2.inject.Inject; | ||
|
@@ -46,22 +45,16 @@ | |
import java.lang.reflect.Method; | ||
import java.util.Collection; | ||
import java.util.HashMap; | ||
import java.util.HashSet; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.concurrent.atomic.AtomicBoolean; | ||
import java.util.regex.Pattern; | ||
import java.util.regex.PatternSyntaxException; | ||
|
||
import static com.opensymphony.xwork2.util.TextParseUtil.commaDelimitedStringToSet; | ||
import static com.opensymphony.xwork2.util.ConfigParseUtil.toClassesSet; | ||
import static com.opensymphony.xwork2.util.ConfigParseUtil.toNewPatternsSet; | ||
import static com.opensymphony.xwork2.util.ConfigParseUtil.toPackageNamesSet; | ||
import static java.util.Collections.emptySet; | ||
import static java.util.Collections.unmodifiableSet; | ||
import static java.util.Objects.requireNonNull; | ||
import static java.util.stream.Collectors.toSet; | ||
import static org.apache.commons.lang3.StringUtils.strip; | ||
import static org.apache.struts2.StrutsConstants.STRUTS_ALLOWLIST_CLASSES; | ||
import static org.apache.struts2.StrutsConstants.STRUTS_ALLOWLIST_ENABLE; | ||
import static org.apache.struts2.StrutsConstants.STRUTS_ALLOWLIST_PACKAGE_NAMES; | ||
import static org.apache.struts2.ognl.OgnlGuard.EXPR_BLOCKED; | ||
|
||
|
||
|
@@ -84,27 +77,15 @@ public class OgnlUtil { | |
private final OgnlGuard ognlGuard; | ||
|
||
private boolean devMode; | ||
private boolean enableExpressionCache = true; | ||
private boolean enableExpressionCache; | ||
private boolean enableEvalExpression; | ||
|
||
private Set<String> excludedClasses = emptySet(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All the configuration is now injected directly into |
||
private Set<Pattern> excludedPackageNamePatterns = emptySet(); | ||
private Set<String> excludedPackageNames = emptySet(); | ||
private Set<String> excludedPackageExemptClasses = emptySet(); | ||
|
||
private boolean enforceAllowlistEnabled = false; | ||
private Set<String> allowlistClasses = emptySet(); | ||
private Set<String> allowlistPackageNames = emptySet(); | ||
|
||
private Set<String> devModeExcludedClasses = emptySet(); | ||
private Set<Pattern> devModeExcludedPackageNamePatterns = emptySet(); | ||
private Set<String> devModeExcludedPackageNames = emptySet(); | ||
private Set<String> devModeExcludedPackageExemptClasses = emptySet(); | ||
private String devModeExcludedClasses = ""; | ||
private String devModeExcludedPackageNamePatterns = ""; | ||
private String devModeExcludedPackageNames = ""; | ||
private String devModeExcludedPackageExemptClasses = ""; | ||
|
||
private Container container; | ||
private boolean allowStaticFieldAccess = true; | ||
private boolean disallowProxyMemberAccess = false; | ||
private boolean disallowDefaultPackageAccess = false; | ||
|
||
/** | ||
* Construct a new OgnlUtil instance for use with the framework | ||
|
@@ -175,166 +156,110 @@ protected void setEnableEvalExpression(String evalExpression) { | |
} | ||
} | ||
|
||
@Inject(value = StrutsConstants.STRUTS_EXCLUDED_CLASSES, required = false) | ||
/** | ||
* @deprecated since 6.4.0, no replacement. | ||
*/ | ||
@Deprecated | ||
protected void setExcludedClasses(String commaDelimitedClasses) { | ||
excludedClasses = toNewClassesSet(excludedClasses, commaDelimitedClasses); | ||
} | ||
|
||
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_CLASSES, required = false) | ||
protected void setDevModeExcludedClasses(String commaDelimitedClasses) { | ||
devModeExcludedClasses = toNewClassesSet(devModeExcludedClasses, commaDelimitedClasses); | ||
this.devModeExcludedClasses = commaDelimitedClasses; | ||
} | ||
|
||
private static Set<String> toClassesSet(String newDelimitedClasses) throws ConfigurationException { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved these utility methods into their own class |
||
Set<String> classNames = commaDelimitedStringToSet(newDelimitedClasses); | ||
validateClasses(classNames, OgnlUtil.class.getClassLoader()); | ||
return unmodifiableSet(classNames); | ||
} | ||
|
||
private static Set<String> toNewClassesSet(Set<String> oldClasses, String newDelimitedClasses) throws ConfigurationException { | ||
Set<String> classNames = commaDelimitedStringToSet(newDelimitedClasses); | ||
validateClasses(classNames, OgnlUtil.class.getClassLoader()); | ||
Set<String> excludedClasses = new HashSet<>(oldClasses); | ||
excludedClasses.addAll(classNames); | ||
return unmodifiableSet(excludedClasses); | ||
} | ||
|
||
private static void validateClasses(Set<String> classNames, ClassLoader validatingClassLoader) throws ConfigurationException { | ||
for (String className : classNames) { | ||
try { | ||
validatingClassLoader.loadClass(className); | ||
} catch (ClassNotFoundException e) { | ||
throw new ConfigurationException("Cannot load class for exclusion/exemption configuration: " + className, e); | ||
} | ||
} | ||
} | ||
|
||
@Inject(value = StrutsConstants.STRUTS_EXCLUDED_PACKAGE_NAME_PATTERNS, required = false) | ||
/** | ||
* @deprecated since 6.4.0, no replacement. | ||
*/ | ||
@Deprecated | ||
protected void setExcludedPackageNamePatterns(String commaDelimitedPackagePatterns) { | ||
excludedPackageNamePatterns = toNewPatternsSet(excludedPackageNamePatterns, commaDelimitedPackagePatterns); | ||
} | ||
|
||
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAME_PATTERNS, required = false) | ||
protected void setDevModeExcludedPackageNamePatterns(String commaDelimitedPackagePatterns) { | ||
devModeExcludedPackageNamePatterns = toNewPatternsSet(devModeExcludedPackageNamePatterns, commaDelimitedPackagePatterns); | ||
this.devModeExcludedPackageNamePatterns = commaDelimitedPackagePatterns; | ||
} | ||
|
||
private static Set<Pattern> toNewPatternsSet(Set<Pattern> oldPatterns, String newDelimitedPatterns) throws ConfigurationException { | ||
Set<String> patterns = commaDelimitedStringToSet(newDelimitedPatterns); | ||
Set<Pattern> newPatterns = new HashSet<>(oldPatterns); | ||
for (String pattern: patterns) { | ||
try { | ||
newPatterns.add(Pattern.compile(pattern)); | ||
} catch (PatternSyntaxException e) { | ||
throw new ConfigurationException("Excluded package name patterns could not be parsed due to invalid regex: " + pattern, e); | ||
} | ||
} | ||
return unmodifiableSet(newPatterns); | ||
} | ||
|
||
@Inject(value = StrutsConstants.STRUTS_EXCLUDED_PACKAGE_NAMES, required = false) | ||
/** | ||
* @deprecated since 6.4.0, no replacement. | ||
*/ | ||
@Deprecated | ||
protected void setExcludedPackageNames(String commaDelimitedPackageNames) { | ||
excludedPackageNames = toNewPackageNamesSet(excludedPackageNames, commaDelimitedPackageNames); | ||
} | ||
|
||
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAMES, required = false) | ||
protected void setDevModeExcludedPackageNames(String commaDelimitedPackageNames) { | ||
devModeExcludedPackageNames = toNewPackageNamesSet(devModeExcludedPackageNames, commaDelimitedPackageNames); | ||
} | ||
|
||
private static Set<String> toPackageNamesSet(String newDelimitedPackageNames) throws ConfigurationException { | ||
Set<String> packageNames = commaDelimitedStringToSet(newDelimitedPackageNames) | ||
.stream().map(s -> strip(s, ".")).collect(toSet()); | ||
validatePackageNames(packageNames); | ||
return unmodifiableSet(packageNames); | ||
} | ||
|
||
private static Set<String> toNewPackageNamesSet(Collection<String> oldPackageNames, String newDelimitedPackageNames) throws ConfigurationException { | ||
Set<String> packageNames = commaDelimitedStringToSet(newDelimitedPackageNames) | ||
.stream().map(s -> strip(s, ".")).collect(toSet()); | ||
validatePackageNames(packageNames); | ||
Set<String> newPackageNames = new HashSet<>(oldPackageNames); | ||
newPackageNames.addAll(packageNames); | ||
return unmodifiableSet(newPackageNames); | ||
this.devModeExcludedPackageNames = commaDelimitedPackageNames; | ||
} | ||
|
||
private static void validatePackageNames(Collection<String> packageNames) { | ||
if (packageNames.stream().anyMatch(s -> Pattern.compile("\\s").matcher(s).find())) { | ||
throw new ConfigurationException("Excluded package names could not be parsed due to erroneous whitespace characters: " + packageNames); | ||
} | ||
} | ||
|
||
@Inject(value = StrutsConstants.STRUTS_EXCLUDED_PACKAGE_EXEMPT_CLASSES, required = false) | ||
/** | ||
* @deprecated since 6.4.0, no replacement. | ||
*/ | ||
@Deprecated | ||
public void setExcludedPackageExemptClasses(String commaDelimitedClasses) { | ||
excludedPackageExemptClasses = toClassesSet(commaDelimitedClasses); | ||
} | ||
|
||
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_EXEMPT_CLASSES, required = false) | ||
public void setDevModeExcludedPackageExemptClasses(String commaDelimitedClasses) { | ||
devModeExcludedPackageExemptClasses = toClassesSet(commaDelimitedClasses); | ||
this.devModeExcludedPackageExemptClasses = commaDelimitedClasses; | ||
} | ||
|
||
/** | ||
* @deprecated since 6.4.0, no replacement. | ||
*/ | ||
@Deprecated | ||
public Set<String> getExcludedClasses() { | ||
return excludedClasses; | ||
return toClassesSet(container.getInstance(String.class, StrutsConstants.STRUTS_EXCLUDED_CLASSES)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deprecated getters are still functional for API compatibility |
||
} | ||
|
||
/** | ||
* @deprecated since 6.4.0, no replacement. | ||
*/ | ||
@Deprecated | ||
public Set<Pattern> getExcludedPackageNamePatterns() { | ||
return excludedPackageNamePatterns; | ||
return toNewPatternsSet(emptySet(), container.getInstance(String.class, StrutsConstants.STRUTS_EXCLUDED_PACKAGE_NAME_PATTERNS)); | ||
} | ||
|
||
/** | ||
* @deprecated since 6.4.0, no replacement. | ||
*/ | ||
@Deprecated | ||
public Set<String> getExcludedPackageNames() { | ||
return excludedPackageNames; | ||
return toPackageNamesSet(container.getInstance(String.class, StrutsConstants.STRUTS_EXCLUDED_PACKAGE_NAMES)); | ||
} | ||
|
||
/** | ||
* @deprecated since 6.4.0, no replacement. | ||
*/ | ||
@Deprecated | ||
public Set<String> getExcludedPackageExemptClasses() { | ||
return excludedPackageExemptClasses; | ||
} | ||
|
||
@Inject(value = STRUTS_ALLOWLIST_ENABLE, required = false) | ||
protected void setEnforceAllowlistEnabled(String enforceAllowlistEnabled) { | ||
this.enforceAllowlistEnabled = BooleanUtils.toBoolean(enforceAllowlistEnabled); | ||
} | ||
|
||
@Inject(value = STRUTS_ALLOWLIST_CLASSES, required = false) | ||
protected void setAllowlistClasses(String commaDelimitedClasses) { | ||
allowlistClasses = toClassesSet(commaDelimitedClasses); | ||
} | ||
|
||
@Inject(value = STRUTS_ALLOWLIST_PACKAGE_NAMES, required = false) | ||
protected void setAllowlistPackageNames(String commaDelimitedPackageNames) { | ||
allowlistPackageNames = toPackageNamesSet(commaDelimitedPackageNames); | ||
} | ||
|
||
public boolean isEnforceAllowlistEnabled() { | ||
return enforceAllowlistEnabled; | ||
} | ||
|
||
public Set<String> getAllowlistClasses() { | ||
return allowlistClasses; | ||
} | ||
|
||
public Set<String> getAllowlistPackageNames() { | ||
return allowlistPackageNames; | ||
return toClassesSet(container.getInstance(String.class, StrutsConstants.STRUTS_EXCLUDED_PACKAGE_EXEMPT_CLASSES)); | ||
} | ||
|
||
@Inject | ||
protected void setContainer(Container container) { | ||
this.container = container; | ||
} | ||
|
||
@Inject(value = StrutsConstants.STRUTS_ALLOW_STATIC_FIELD_ACCESS, required = false) | ||
/** | ||
* @deprecated since 6.4.0, no replacement. | ||
*/ | ||
@Deprecated | ||
protected void setAllowStaticFieldAccess(String allowStaticFieldAccess) { | ||
this.allowStaticFieldAccess = BooleanUtils.toBoolean(allowStaticFieldAccess); | ||
} | ||
|
||
@Inject(value = StrutsConstants.STRUTS_DISALLOW_PROXY_MEMBER_ACCESS, required = false) | ||
/** | ||
* @deprecated since 6.4.0, no replacement. | ||
*/ | ||
@Deprecated | ||
protected void setDisallowProxyMemberAccess(String disallowProxyMemberAccess) { | ||
this.disallowProxyMemberAccess = BooleanUtils.toBoolean(disallowProxyMemberAccess); | ||
} | ||
|
||
@Inject(value = StrutsConstants.STRUTS_DISALLOW_DEFAULT_PACKAGE_ACCESS, required = false) | ||
/** | ||
* @deprecated since 6.4.0, no replacement. | ||
*/ | ||
@Deprecated | ||
protected void setDisallowDefaultPackageAccess(String disallowDefaultPackageAccess) { | ||
this.disallowDefaultPackageAccess = BooleanUtils.toBoolean(disallowDefaultPackageAccess); | ||
} | ||
|
||
/** | ||
|
@@ -356,12 +281,20 @@ protected void applyExpressionMaxLength(String maxLength) { | |
} | ||
} | ||
|
||
/** | ||
* @deprecated since 6.4.0, no replacement. | ||
*/ | ||
@Deprecated | ||
public boolean isDisallowProxyMemberAccess() { | ||
return disallowProxyMemberAccess; | ||
return BooleanUtils.toBoolean(container.getInstance(String.class, StrutsConstants.STRUTS_DISALLOW_PROXY_MEMBER_ACCESS)); | ||
} | ||
|
||
/** | ||
* @deprecated since 6.4.0, no replacement. | ||
*/ | ||
@Deprecated | ||
public boolean isDisallowDefaultPackageAccess() { | ||
return disallowDefaultPackageAccess; | ||
return BooleanUtils.toBoolean(container.getInstance(String.class, StrutsConstants.STRUTS_DISALLOW_DEFAULT_PACKAGE_ACCESS)); | ||
} | ||
|
||
/** | ||
|
@@ -922,8 +855,7 @@ protected Map<String, Object> createDefaultContext(Object root, ClassResolver cl | |
resolver = container.getInstance(CompoundRootAccessor.class); | ||
} | ||
|
||
SecurityMemberAccess memberAccess = new SecurityMemberAccess(allowStaticFieldAccess); | ||
memberAccess.disallowProxyMemberAccess(disallowProxyMemberAccess); | ||
SecurityMemberAccess memberAccess = container.getInstance(SecurityMemberAccess.class); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a prototype bean so will be a new instance per context |
||
|
||
if (devMode) { | ||
if (!warnReported.get()) { | ||
|
@@ -934,14 +866,6 @@ protected Map<String, Object> createDefaultContext(Object root, ClassResolver cl | |
memberAccess.useExcludedPackageNamePatterns(devModeExcludedPackageNamePatterns); | ||
memberAccess.useExcludedPackageNames(devModeExcludedPackageNames); | ||
memberAccess.useExcludedPackageExemptClasses(devModeExcludedPackageExemptClasses); | ||
} else { | ||
memberAccess.useExcludedClasses(getExcludedClasses()); | ||
memberAccess.useExcludedPackageNamePatterns(getExcludedPackageNamePatterns()); | ||
memberAccess.useExcludedPackageNames(getExcludedPackageNames()); | ||
memberAccess.useExcludedPackageExemptClasses(getExcludedPackageExemptClasses()); | ||
memberAccess.useEnforceAllowlistEnabled(isEnforceAllowlistEnabled()); | ||
memberAccess.useAllowlistClasses(getAllowlistClasses()); | ||
memberAccess.useAllowlistPackageNames(getAllowlistPackageNames()); | ||
} | ||
|
||
return Ognl.createDefaultContext(root, memberAccess, resolver, defaultConverter); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This constant is not required for bootstrapping