This is an automated email from the ASF dual-hosted git repository.

kusal pushed a commit to branch WW-5340-ognl-guard
in repository https://gitbox.apache.org/repos/asf/struts.git

commit 7106acf599ef426a3b8230f527ea18831f775af7
Author: Kusal Kithul-Godage <g...@kusal.io>
AuthorDate: Thu Aug 31 19:21:51 2023 +1000

    WW-5340 Introducing OGNL Guard
---
 .../opensymphony/xwork2/ognl/DefaultOgnlGuard.java | 79 ++++++++++++++++++++++
 .../com/opensymphony/xwork2/ognl/OgnlGuard.java    | 37 ++++++++++
 .../com/opensymphony/xwork2/ognl/OgnlUtil.java     |  9 +++
 .../java/org/apache/struts2/StrutsConstants.java   |  3 +
 core/src/main/resources/struts-beans.xml           |  1 +
 5 files changed, 129 insertions(+)

diff --git 
a/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlGuard.java 
b/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlGuard.java
new file mode 100644
index 000000000..0d70d515c
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlGuard.java
@@ -0,0 +1,79 @@
+/*
+ * 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 com.opensymphony.xwork2.ognl;
+
+import com.opensymphony.xwork2.inject.Inject;
+import ognl.Node;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.StrutsConstants;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static 
com.opensymphony.xwork2.util.TextParseUtil.commaDelimitedStringToSet;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.unmodifiableSet;
+
+/**
+ * The default implementation of {@link OgnlGuard}.
+ *
+ * @since 6.4.0
+ */
+public class DefaultOgnlGuard implements OgnlGuard {
+
+    private static final Logger LOG = 
LogManager.getLogger(DefaultOgnlGuard.class);
+
+    private Set<String> excludedNodeTypes = emptySet();
+
+    @Inject(value = StrutsConstants.STRUTS_OGNL_EXCLUDED_NODE_TYPES, required 
= false)
+    public void useExcludedNodeTypes(String excludedNodeTypes) {
+        Set<String> incomingExcludedNodeTypes = 
commaDelimitedStringToSet(excludedNodeTypes);
+        Set<String> newExcludeNodeTypes = new 
HashSet<>(this.excludedNodeTypes);
+        newExcludeNodeTypes.addAll(incomingExcludedNodeTypes);
+        this.excludedNodeTypes = unmodifiableSet(newExcludeNodeTypes);
+    }
+
+    @Override
+    public boolean isBlocked(String expr, Object tree) {
+        return containsExcludedNodeType(tree);
+    }
+
+    protected boolean containsExcludedNodeType(Object tree) {
+        if (!(tree instanceof Node) || excludedNodeTypes.isEmpty()) {
+            return false;
+        }
+        return recurseExcludedNodeType((Node) tree);
+    }
+
+    protected boolean recurseExcludedNodeType(Node node) {
+        String nodeClassName = node.getClass().getName();
+        if (excludedNodeTypes.contains(nodeClassName)) {
+            LOG.warn("Expression contains blocked node type [{}]", 
nodeClassName);
+            return true;
+        } else {
+            for (int i = 0; i < node.jjtGetNumChildren(); i++) {
+                if (containsExcludedNodeType(node.jjtGetChild(i))) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+}
diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlGuard.java 
b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlGuard.java
new file mode 100644
index 000000000..b1bcb409e
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlGuard.java
@@ -0,0 +1,37 @@
+/*
+ * 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 com.opensymphony.xwork2.ognl;
+
+/**
+ * Guards all expressions parsed by Struts Core. It is evaluated by {@link 
OgnlUtil} immediately after parsing any
+ * expression.
+ *
+ * @since 6.4.0
+ */
+public interface OgnlGuard {
+
+    /**
+     * It is imperative that the parsed tree matches the expression.
+     *
+     * @param expr OGNL expression
+     * @param tree parsed tree of expression
+     * @return whether the expression should be blocked
+     */
+    boolean isBlocked(String expr, Object tree);
+}
diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java 
b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java
index 8edac0a95..b9ae31455 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java
@@ -73,6 +73,7 @@ public class OgnlUtil {
     private final OgnlCache<String, Object> expressionCache;
     private final OgnlCache<Class<?>, BeanInfo> beanInfoCache;
     private TypeConverter defaultConverter;
+    private OgnlGuard ognlGuard;
 
     private boolean devMode;
     private boolean enableExpressionCache = true;
@@ -140,6 +141,11 @@ public class OgnlUtil {
         this.beanInfoCache = ognlBeanInfoCacheFactory.buildOgnlCache();
     }
 
+    @Inject
+    protected void setOgnlGuard(OgnlGuard ognlGuard) {
+        this.ognlGuard = ognlGuard;
+    }
+
     @Inject
     protected void setXWorkConverter(XWorkConverter conv) {
         this.defaultConverter = new OgnlTypeConverterWrapper(conv);
@@ -624,6 +630,9 @@ public class OgnlUtil {
                 expressionCache.put(expr, tree);
             }
         }
+        if (ognlGuard.isBlocked(expr, tree)) {
+            throw new OgnlException("Expression blocked by OgnlGuard: " + 
expr);
+        }
         return tree;
     }
 
diff --git a/core/src/main/java/org/apache/struts2/StrutsConstants.java 
b/core/src/main/java/org/apache/struts2/StrutsConstants.java
index 29dfca4b9..143b8c832 100644
--- a/core/src/main/java/org/apache/struts2/StrutsConstants.java
+++ b/core/src/main/java/org/apache/struts2/StrutsConstants.java
@@ -364,6 +364,9 @@ public final class StrutsConstants {
     /** The maximum length of an expression (OGNL) */
     public static final String STRUTS_OGNL_EXPRESSION_MAX_LENGTH = 
"struts.ognl.expressionMaxLength";
 
+    /** Parsed OGNL expressions which contain these node types will be blocked 
*/
+    public static final String STRUTS_OGNL_EXCLUDED_NODE_TYPES = 
"struts.ognl.excludedNodeTypes";
+
     /** Disables {@link org.apache.struts2.dispatcher.StrutsRequestWrapper} 
request attribute value stack lookup (JSTL accessibility) */
     public static final String 
STRUTS_DISABLE_REQUEST_ATTRIBUTE_VALUE_STACK_LOOKUP = 
"struts.disableRequestAttributeValueStackLookup";
 
diff --git a/core/src/main/resources/struts-beans.xml 
b/core/src/main/resources/struts-beans.xml
index fc1fb2ee7..89acda7c6 100644
--- a/core/src/main/resources/struts-beans.xml
+++ b/core/src/main/resources/struts-beans.xml
@@ -166,6 +166,7 @@
           
class="com.opensymphony.xwork2.validator.DefaultValidatorFileParser"/>
 
     <bean class="com.opensymphony.xwork2.ognl.OgnlUtil"/>
+    <bean type="com.opensymphony.xwork2.ognl.OgnlGuard" 
class="com.opensymphony.xwork2.ognl.DefaultOgnlGuard"/>
 
     <bean type="com.opensymphony.xwork2.util.TextParser" name="struts"
           class="com.opensymphony.xwork2.util.OgnlTextParser" 
scope="singleton"/>

Reply via email to