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

jleroux pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git

commit f95ab015f353ca820b3718747575898e87dd9084
Author: Jacques Le Roux <jacques.le.r...@les7arts.com>
AuthorDate: Thu Jan 16 11:28:00 2025 +0100

    Fixed: [SECURITY] (CVE-2024-36104) Path traversal leading to RCE 
(OFBIZ-13092)
    
    I've got a strange issue that seems caused by one of this Jira modification:
    On demo environment, when clicking on several  sort field links, a
    'For security reason this URL is not accepted' error is thrown.
    
    For instance, if you perform a search on find party screen and try to sort 
by
    partyId, createdDate or else, you will get this message.
    
    I tried to reproduce it in a local trunk environment without success : the  
    uRIFiltered and the initialURI are the same.
    
    jleroux: but indeed when the same is done on a domain site, for instance
    official OFBiz demos, it's reproducible in all demo instances.
    
    This fixes it, with some more: refactoring, formatting and renaming
    
    Thanks: Leïla for report and details.
---
 framework/security/config/security.properties      |  2 +-
 .../org/apache/ofbiz/security/SecuredUpload.java   |  6 ---
 .../apache/ofbiz/webapp/control/ControlFilter.java | 62 ++++++++++++++++++----
 3 files changed, 52 insertions(+), 18 deletions(-)

diff --git a/framework/security/config/security.properties 
b/framework/security/config/security.properties
index 6fbc9b9ea8..141ec7e899 100644
--- a/framework/security/config/security.properties
+++ b/framework/security/config/security.properties
@@ -288,7 +288,7 @@ 
deniedWebShellTokens=java.,beans,freemarker,<script,javascript,<body,body ,<form
 #-- SHA-1 versions of tokens containing (as String) at least one 
deniedWebShellTokens
 #-- This is notably used to allow special values in query parameters.
 #-- If you add a token beware that it does not content ",". It's the separator.
-allowedTokens=$SHA$OFBiz$2naHrANKTniFcgLJk4oXr3IRQ48
+allowedTokens=$SHA$OFBiz$2naHrANKTniFcgLJk4oXr3IRQ48,$SHA$OFBiz$evAu1vcT5d1tjVXFTeVXU-6aNz8,$SHA$OFBiz$-MaMN-Dui294v86UT1T8BkG3v8k
 
 #-- RegExp to secure groovy script execution. If the regExp match a script, it 
would be disabled and OFBiz run nothing.
 #-- In this case, you will have on log the original script with it hash. The 
hash can be added on allowedScriptletHashes
diff --git 
a/framework/security/src/main/java/org/apache/ofbiz/security/SecuredUpload.java 
b/framework/security/src/main/java/org/apache/ofbiz/security/SecuredUpload.java
index 8c88d46278..cfe6e1f9ee 100644
--- 
a/framework/security/src/main/java/org/apache/ofbiz/security/SecuredUpload.java
+++ 
b/framework/security/src/main/java/org/apache/ofbiz/security/SecuredUpload.java
@@ -910,12 +910,6 @@ public class SecuredUpload {
         return UtilValidate.isNotEmpty(deniedTokens) ? 
StringUtil.split(deniedTokens, ",") : new ArrayList<>();
     }
 
-    public static List<String> getallowedTokens() {
-        String allowedTokens = UtilProperties.getPropertyValue("security", 
"allowedTokens");
-        return UtilValidate.isNotEmpty(allowedTokens) ? 
StringUtil.split(allowedTokens, ",") : new ArrayList<>();
-    }
-
-
     private static boolean checkMaxLinesLength(String fileToCheck) {
         if (MAXLINELENGTH == 0) {
             return true;
diff --git 
a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlFilter.java
 
b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlFilter.java
index ca8d2cca69..2c97b0c9f2 100644
--- 
a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlFilter.java
+++ 
b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlFilter.java
@@ -22,8 +22,11 @@ import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -38,7 +41,10 @@ import javax.servlet.http.HttpSession;
 import org.apache.commons.lang.BooleanUtils;
 import org.apache.commons.validator.routines.UrlValidator;
 import org.apache.logging.log4j.ThreadContext;
+import org.apache.ofbiz.base.crypto.HashCrypt;
 import org.apache.ofbiz.base.util.Debug;
+import org.apache.ofbiz.base.util.StringUtil;
+import org.apache.ofbiz.base.util.UtilProperties;
 import org.apache.ofbiz.base.util.UtilValidate;
 import org.apache.ofbiz.entity.GenericValue;
 import org.apache.ofbiz.security.SecuredFreemarker;
@@ -85,6 +91,8 @@ public class ControlFilter extends HttpFilter {
     private int errorCode;
     /** The list of all path prefixes that are allowed. */
     private Set<String> allowedPaths;
+    private static final List<String> ALLOWEDTOKENS = getAllowedTokens();
+
 
     @Override
     public void init(FilterConfig conf) throws ServletException {
@@ -139,8 +147,7 @@ public class ControlFilter extends HttpFilter {
      * Makes allowed paths pass through while redirecting the others to a fix 
location.
      */
     @Override
-    public void doFilter(HttpServletRequest req, HttpServletResponse resp, 
FilterChain chain)
-            throws IOException, ServletException {
+    public void doFilter(HttpServletRequest req, HttpServletResponse resp, 
FilterChain chain) throws IOException, ServletException {
         String context = req.getContextPath();
         HttpSession session = req.getSession();
 
@@ -158,7 +165,7 @@ public class ControlFilter extends HttpFilter {
         } else if (req.getAttribute(FORWARDED_FROM_SERVLET) == null
                 && !allowedPaths.isEmpty()) {
             // Get the request URI without the webapp mount point.
-            String uriWithContext = req.getRequestURI();
+            String uriWithContext = URLDecoder.decode(req.getRequestURI(), 
"UTF-8");
             String uri = uriWithContext.substring(context.length());
 
             GenericValue userLogin = (GenericValue) 
session.getAttribute("userLogin");
@@ -169,25 +176,35 @@ public class ControlFilter extends HttpFilter {
             }
 
             // Reject wrong URLs
-            String queryString = req.getQueryString();
+            String queryString = null;
+            try {
+                queryString = new URI(uriWithContext).getQuery();
+
+            } catch (URISyntaxException e) {
+                Debug.logError("Weird URI: " + e, MODULE);
+                throw new RuntimeException(e);
+            }
+
             if (queryString != null) {
                 queryString = URLDecoder.decode(queryString, "UTF-8");
                 if (UtilValidate.isUrlInString(queryString)
-                        || 
!SecuredUpload.isValidText(queryString.toLowerCase(), 
SecuredUpload.getallowedTokens(), true)
-                        && isSolrTest()) {
+                        || 
!SecuredUpload.isValidText(queryString.toLowerCase(), ALLOWEDTOKENS, true)
+                                && isSolrTest()) {
                     Debug.logError("For security reason this URL is not 
accepted", MODULE);
                     throw new RuntimeException("For security reason this URL 
is not accepted");
                 }
             }
-
-            String initialURI = req.getRequestURI();
-            if (initialURI != null) { // Allow tests with Mockito. 
ControlFilterTests send null
+            boolean bypass = true;
+            if (queryString != null) {
+                bypass = 
isAnyAllowedToken(StringUtil.split(queryString.toLowerCase(), "Y&amp;"), 
ALLOWEDTOKENS);
+            }
+            if (uriWithContext != null && !bypass) { // "null" allows tests 
with Mockito. ControlFilterTests sends null.
                 try {
-                    String uRIFiltered = new URI(initialURI)
+                    String uRIFiltered = new URI(uriWithContext)
                             .normalize().toString()
                             .replaceAll(";", "")
                             .replaceAll("(?i)%2e", "");
-                    if (!initialURI.equals(uRIFiltered)) {
+                    if (!uriWithContext.equals(uRIFiltered)) {
                         Debug.logError("For security reason this URL is not 
accepted", MODULE);
                         throw new RuntimeException("For security reason this 
URL is not accepted");
                     }
@@ -230,4 +247,27 @@ public class ControlFilter extends HttpFilter {
     private void redirect(HttpServletResponse resp, String contextPath) throws 
IOException {
         resp.sendRedirect(redirectPathIsUrl ? redirectPath : (contextPath + 
redirectPath));
     }
+
+    private static List<String> getAllowedTokens() {
+        String allowedTokens = UtilProperties.getPropertyValue("security", 
"allowedTokens");
+        return UtilValidate.isNotEmpty(allowedTokens) ? 
StringUtil.split(allowedTokens, ",") : new ArrayList<>();
+    }
+
+
+
+
+    // Check there is any allowedToken in URL
+    private static boolean isAnyAllowedToken(List<String> queryParameters, 
List<String> allowed) {
+        boolean isOK = false;
+        for (String parameter : queryParameters) {
+            parameter = parameter.substring(0, parameter.indexOf("=") + 1);
+            if (allowed.contains(HashCrypt.cryptBytes("SHA", "OFBiz", 
parameter.getBytes(StandardCharsets.UTF_8)))) {
+                isOK = true;
+                break;
+            } else {
+                continue;
+            }
+        }
+        return isOK;
+    }
 }

Reply via email to