This is an automated email from the ASF dual-hosted git repository. jleroux pushed a commit to branch release24.09 in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git
commit a02acaeb28fe8fab5e2af74e87b2d2cb2098b194 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. Conflicts handled by and in ControlFilter.java --- framework/security/config/security.properties | 2 +- .../org/apache/ofbiz/security/SecuredUpload.java | 6 --- .../apache/ofbiz/webapp/control/ControlFilter.java | 55 +++++++++++++++++++--- 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/framework/security/config/security.properties b/framework/security/config/security.properties index 544dc16421..38b0cf0beb 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 7fa9cfa64e..c6a9409349 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 @@ -896,12 +896,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 cf47c120d4..8ed0cd9df1 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 { @@ -136,8 +144,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(); @@ -155,7 +162,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"); @@ -166,18 +173,29 @@ 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.isUrl(queryString) - || !SecuredUpload.isValidText(queryString.toLowerCase(), SecuredUpload.getallowedTokens(), true) + || !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"); } } - - if (uriWithContext != null) { // Allow tests with Mockito. ControlFilterTests send null + boolean bypass = true; + if (queryString != null) { + bypass = isAnyAllowedToken(StringUtil.split(queryString.toLowerCase(), "Y&"), ALLOWEDTOKENS); + } + if (uriWithContext != null && !bypass) { // "null" allows tests with Mockito. ControlFilterTests sends null. try { String uRIFiltered = new URI(uriWithContext) .normalize().toString() @@ -226,4 +244,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; + } }