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 e8b752fbb30298a3a2a4e77b0396853d0ffc0d1b
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 | 56 ++++++++++++++++++++++
 .../com/opensymphony/xwork2/ognl/OgnlGuard.java    |  6 +++
 .../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, 75 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..390089f26
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlGuard.java
@@ -0,0 +1,56 @@
+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;
+
+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..043df0294
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlGuard.java
@@ -0,0 +1,6 @@
+package com.opensymphony.xwork2.ognl;
+
+public interface OgnlGuard {
+
+    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