Skip to content

Instantly share code, notes, and snippets.

@mzpqnxow
Last active June 3, 2022 22:25
Show Gist options
  • Save mzpqnxow/6b3832ecea14350e449d83cb2fb7a9a3 to your computer and use it in GitHub Desktop.
Save mzpqnxow/6b3832ecea14350e449d83cb2fb7a9a3 to your computer and use it in GitHub Desktop.
Diff between decompiled class files for xwork-1.0.3-atlassian-7 and xwork-1.0.3-atlassian-10 (CVE-2022-26134)
// For context, the full "old" version of the class
// Taken from a decompiled xwork-1.0.3-atlassian-7-m08.jar
// - AG
//
package com.opensymphony.xwork;
import com.opensymphony.xwork.util.OgnlValueStack;
import com.opensymphony.xwork.util.TextParseUtil;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class ActionChainResult
implements Result
{
private static final Log log = LogFactory.getLog(ActionChainResult.class);
public static final String DEFAULT_PARAM = "actionName";
private static final String CHAIN_HISTORY = "CHAIN_HISTORY";
private ActionProxy proxy;
private String actionName;
private String namespace;
public void setActionName(String actionName) { this.actionName = actionName; }
public void setNamespace(String namespace) { this.namespace = namespace; }
public ActionProxy getProxy() { return this.proxy; }
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ActionChainResult)) {
return false;
}
ActionChainResult actionChainResult = (ActionChainResult)o;
if ((this.actionName != null) ? !this.actionName.equals(actionChainResult.actionName) : (actionChainResult.actionName != null)) {
return false;
}
return true;
}
public void execute(ActionInvocation invocation) throws Exception {
if (this.namespace == null) {
this.namespace = invocation.getProxy().getNamespace();
}
OgnlValueStack stack = ActionContext.getContext().getValueStack();
String finalNamespace = TextParseUtil.translateVariables(this.namespace, stack);
String finalActionName = TextParseUtil.translateVariables(this.actionName, stack);
if (isInChainHistory(finalNamespace, finalActionName)) {
throw new XworkException("infinite recursion detected");
}
addToHistory(finalNamespace, finalActionName);
HashMap extraContext = new HashMap();
extraContext.put("com.opensymphony.xwork.util.OgnlValueStack.ValueStack", ActionContext.getContext().getValueStack());
extraContext.put("com.opensymphony.xwork.ActionContext.parameters", ActionContext.getContext().getParameters());
extraContext.put("com.opensymphony.xwork.interceptor.component.ComponentManager", ActionContext.getContext().get("com.opensymphony.xwork.interceptor.component.ComponentManager"));
extraContext.put("CHAIN_HISTORY", ActionContext.getContext().get("CHAIN_HISTORY"));
if (log.isDebugEnabled()) {
log.debug("Chaining to action " + finalActionName);
}
this.proxy = ActionProxyFactory.getFactory().createActionProxy(finalNamespace, finalActionName, extraContext);
this.proxy.execute();
}
public int hashCode() { return (this.actionName != null) ? this.actionName.hashCode() : 0; }
private boolean isInChainHistory(String namespace, String actionName) {
Set chainHistory = (Set)ActionContext.getContext().get("CHAIN_HISTORY");
if (chainHistory == null) {
return false;
}
return chainHistory.contains(makeKey(namespace, actionName));
}
private void addToHistory(String namespace, String actionName) {
Set chainHistory = (Set)ActionContext.getContext().get("CHAIN_HISTORY");
if (chainHistory == null) {
chainHistory = new HashSet();
}
ActionContext.getContext().put("CHAIN_HISTORY", chainHistory);
chainHistory.add(makeKey(namespace, actionName));
}
private String makeKey(String namespace, String actionName) { return namespace + "/" + actionName; }
}

Notes

  • These are diffs of Java classes referenced in the Atlassian advisory for CVE-2022-26134 here
  • They are not true diffs of the full patch, they are only diffs of the hot-fix/temporary mitigations provided by Atlassian
  • Depending on the version of Confluence, changes in the webwork and xwork classes are relevant
  • ActionChanResult (part of xwork) appears to prevent a value from being treating as OGNL (makes sense)
  • SafeExpressionUtil (part of webwork) appears to add explicit logic for null checking (may be the most useful clue)
--- ParametersInterceptor-old.java 2022-06-03 18:24:26.358589585 -0400
+++ ParametersInterceptor-new.java 2022-06-03 18:24:43.586589640 -0400
@@ -38,11 +38,19 @@
-
public class ParametersInterceptor
extends AroundInterceptor
{
+ protected boolean acceptableName(String name) {
+ if (name.indexOf('=') != -1 || name.indexOf(',') != -1 || name.indexOf('#') != -1 || name.indexOf(':') != -1 || name.indexOf("\\u0023") != -1) {
+ return false;
+ }
+ return true;
+ }
+
+
protected void after(ActionInvocation dispatcher, String result) throws Exception {}
+
protected void before(ActionInvocation invocation) throws Exception {
if (!(invocation.getAction() instanceof NoParameters)) {
@@ -52,7 +60,7 @@
this.log.debug("Setting params " + parameters);
}
- ActionContext invocationContext = invocation.getInvocationContext();
+ invocationContext = invocation.getInvocationContext();
try {
invocationContext.put("xwork.NullHandler.createNullObjects", Boolean.TRUE);
@@ -65,7 +73,11 @@
Iterator iterator = parameters.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry)iterator.next();
- stack.setValue(entry.getKey().toString(), entry.getValue());
+ String name = entry.getKey().toString();
+
+ if (acceptableName(name)) {
+ stack.setValue(name, entry.getValue());
+ }
}
}
} finally {
--- SafeExpressionUtil.java 2022-06-03 17:18:39.126576907 -0400
+++ SafeExpressionUtil-new.java 2022-06-03 17:19:02.690576982 -0400
@@ -8,6 +8,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import javax.lang.model.SourceVersion;
@@ -33,7 +34,6 @@
-
class SafeExpressionUtil
{
private static final Set<String> UNSAFE_VARIABLE_NAMES;
@@ -247,7 +247,7 @@
}
if (SourceVersion.isName(trimmedClassName)) {
List<String> parentPackageNames = populateParentPackages(trimmedClassName, new ArrayList());
- return parentPackageNames.stream().anyMatch(this.unsafePackageNames::contains);
+ Objects.requireNonNull(this.unsafePackageNames); return parentPackageNames.stream().anyMatch(this.unsafePackageNames::contains);
}
return false;
}
--- TextParseUtil.old.java 2022-06-03 18:05:35.574585953 -0400
+++ TextParseUtil.new.java 2022-06-03 18:05:50.630586001 -0400
@@ -1,5 +1,8 @@
package com.opensymphony.xwork.util;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
@@ -24,26 +27,26 @@
public class TextParseUtil
{
public static String translateVariables(String expression, OgnlValueStack stack) {
- while (true) {
- int x = expression.indexOf("${");
- int y = expression.indexOf("}", x);
+ StringBuilder sb = new StringBuilder();
+ Pattern p = Pattern.compile("\\$\\{([^}]*)\\}");
+ Matcher m = p.matcher(expression);
+ int previous = 0;
+ while (m.find()) {
+ String value, g = m.group(1);
+ int start = m.start();
- if (x != -1 && y != -1) {
- String var = expression.substring(x + 2, y);
-
- Object o = stack.findValue(var);
-
- if (o != null) {
- expression = expression.substring(0, x) + o + expression.substring(y + 1);
- continue;
- }
- expression = expression.substring(0, x) + expression.substring(y + 1);
-
- continue;
+ try {
+ Object o = stack.findValue(g);
+ value = (o == null) ? "" : o.toString();
+ } catch (Exception ignored) {
+ value = "";
}
-
- break;
+ sb.append(expression.substring(previous, start)).append(value);
+ previous = m.end();
}
- return expression;
+ if (previous < expression.length()) {
+ sb.append(expression.substring(previous));
+ }
+ return sb.toString();
}
}
--- XmlConfigurationProvider-old.java 2022-06-03 17:52:30.786583432 -0400
+++ XmlConfigurationProvider-new.java 2022-06-03 17:53:10.814583561 -0400
@@ -97,17 +97,19 @@
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
- db.setEntityResolver(new EntityResolver(this) { private final XmlConfigurationProvider this$0;
+ db.setEntityResolver(new EntityResolver() {
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
+ if ("-//OpenSymphony Group//XWork 1.0.1//EN".equals(publicId))
+ return new InputSource(ClassLoaderUtil.getResourceAsStream("xwork-1.0.1.dtd", XmlConfigurationProvider.class));
if ("-//OpenSymphony Group//XWork 1.0//EN".equals(publicId)) {
- return new InputSource(ClassLoaderUtil.getResourceAsStream("xwork-1.0.dtd", (XmlConfigurationProvider.class$com$opensymphony$xwork$config$providers$XmlConfigurationProvider == null) ? (XmlConfigurationProvider.class$com$opensymphony$xwork$config$providers$XmlConfigurationProvider = XmlConfigurationProvider.class$("com.opensymphony.xwork.config.providers.XmlConfigurationProvider")) : XmlConfigurationProvider.class$com$opensymphony$xwork$config$providers$XmlConfigurationProvider));
+ return new InputSource(ClassLoaderUtil.getResourceAsStream("xwork-1.0.dtd", XmlConfigurationProvider.class));
}
return null;
- } }
- );
- db.setErrorHandler(new ErrorHandler(this) { private final XmlConfigurationProvider this$0;
-
+ }
+ });
+ db.setErrorHandler(new ErrorHandler()
+ {
public void warning(SAXParseException exception) throws SAXException {}
public void error(SAXParseException exception) throws SAXException {
@@ -118,8 +120,8 @@
public void fatalError(SAXParseException exception) throws SAXException {
LOG.fatal(exception.getMessage() + " at (" + exception.getLineNumber() + ":" + exception.getColumnNumber() + ")");
throw exception;
- } }
- );
+ }
+ });
loadConfigurationFile(this.configFileName, db);
} catch (Exception e) {
LOG.fatal("Could not load XWork configuration file, failing", e);
@@ -393,7 +395,7 @@
try {
String paramName = (String)resultClass.getField("DEFAULT_PARAM").get(null);
params.put(paramName, resultElement.getChildNodes().item(0).getNodeValue());
- } catch (Throwable t) {}
+ } catch (Throwable throwable) {}
}
}
@@ -509,7 +511,7 @@
this.includedFileNames.add(fileName);
Document doc = null;
- InputStream is = null;
+ is = null;
try {
is = getInputStream(fileName);
@@ -550,6 +552,8 @@
} else if (nodeName.equals("include")) {
String includeFileName = child.getAttribute("file");
loadConfigurationFile(includeFileName, db);
+ } else if (nodeName.equals("constant")) {
+ addConstant(child);
}
}
}
@@ -559,6 +563,21 @@
}
}
}
+
+ private void addConstant(Element packageElement) throws ConfigurationException {
+ String name = TextUtils.noNull(packageElement.getAttribute("name"));
+ String value = TextUtils.noNull(packageElement.getAttribute("value"));
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(String.format("Adding %s: \"%s\"", new Object[] { name, value }));
+ }
+ if ("xwork.excludedClasses".equals(name)) {
+ this.configuration.addExcludedClasses(commaDelimitedStringToSet(value));
+ } else if ("xwork.excludedPackageNames".equals(name)) {
+ this.configuration.addExcludedPackageNames(commaDelimitedStringToSet(value));
+ } else if ("xwork.allowedClasses".equals(name)) {
+ this.configuration.addAllowedClasses(commaDelimitedStringToSet(value));
+ }
+ }
@@ -573,6 +592,22 @@
return InterceptorBuilder.constructInterceptorReference(context, refName, refParams);
}
+
+
+
+
+
+
+ private Set<String> commaDelimitedStringToSet(String s) {
+ Set<String> set = new HashSet<String>();
+ String[] split = s.split(",", -1);
+ for (String aSplit : split) {
+ String trimmed = aSplit.trim();
+ if (trimmed.length() > 0)
+ set.add(trimmed);
+ }
+ return set;
+ }
public XmlConfigurationProvider() {}
}
--- ActionChainResult.java.old 2022-06-03 16:58:32.690573031 -0400
+++ ActionChainResult.java.new 2022-06-03 16:59:14.582573166 -0400
@@ -1,5 +1,3 @@
-import com.opensymphony.xwork.util.OgnlValueStack;
-import com.opensymphony.xwork.util.TextParseUtil;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
@@ -24,6 +22,8 @@
public class ActionChainResult
implements Result
{
@@ -88,10 +88,11 @@
if (this.namespace == null) {
this.namespace = invocation.getProxy().getNamespace();
}
- OgnlValueStack stack = ActionContext.getContext().getValueStack();
- String finalNamespace = TextParseUtil.translateVariables(this.namespace, stack);
- String finalActionName = TextParseUtil.translateVariables(this.actionName, stack);
+ String finalNamespace = this.namespace;
+ String finalActionName = this.actionName;
if (isInChainHistory(finalNamespace, finalActionName)) {
throw new XworkException("infinite recursion detected");
--- OLD-xwork-1.0.3-atlassian-10.jar/xwork-default.xml 2022-06-03 17:44:39.000000000 -0400
+++ NEW-xwork-1.0.3-atlassian-10.jar/xwork-default.xml 2022-06-03 17:44:23.000000000 -0400
@@ -1,4 +1,4 @@
-<!DOCTYPE xwork PUBLIC "-//OpenSymphony Group//XWork 1.0//EN" "http://www.opensymphony.com/xwork/xwork-1.0.dtd">
+<!DOCTYPE xwork PUBLIC "-//OpenSymphony Group//XWork 1.0.1//EN" "http://www.opensymphony.com/xwork/xwork-1.0.1.dtd">
<xwork>
<package name="xwork-default">
@@ -31,4 +31,39 @@
</interceptor-stack>
</interceptors>
</package>
-</xwork>
\ No newline at end of file
+
+ <constant name="xwork.excludedClasses"
+ value="
+ java.lang.Object,
+ java.lang.Runtime,
+ java.lang.System,
+ java.lang.Class,
+ java.lang.ClassLoader,
+ java.lang.Shutdown,
+ java.lang.ProcessBuilder,
+ java.lang.Thread,
+ sun.misc.Unsafe,
+ com.opensymphony.xwork2.ActionContext" />
+
+ <constant name="xwork.excludedPackageNames"
+ value="
+ ognl,
+ java.io,
+ java.net,
+ java.nio,
+ javax,
+ freemarker.core,
+ freemarker.template,
+ freemarker.ext.jsp,
+ freemarker.ext.rhino,
+ sun.misc,
+ sun.reflect,
+ javassist,
+ org.apache.velocity,
+ org.objectweb.asm,
+ org.springframework.context,
+ com.opensymphony.xwork.util,
+ org.apache.tomcat,
+ org.apache.catalina.core,
+ org.wildfly.extension.undertow.deployment" />
+</xwork>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment