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 486e5c2bce241dc6e060c1e129bd683d781312e6 Author: Jacques Le Roux <jacques.le.r...@les7arts.com> AuthorDate: Wed Nov 27 08:49:20 2024 +0100 Improved: Prevent URL parameters manipulation (OFBIZ-13147) We don't need hashed deniedWebShellTokens, only hashed allowedTokens, get back to simple text. Simplifies SecuredUpload: No need for Base64 handling, using deniedWebShellTokens is enough. Improves performance, remove duplicated calls inside loops or stream().allMatch Improves comments. Conflict handled by hand ControlFilter.java --- framework/security/config/security.properties | 9 +++- .../org/apache/ofbiz/security/SecuredUpload.java | 52 +++++++--------------- .../apache/ofbiz/webapp/control/ControlFilter.java | 4 +- 3 files changed, 25 insertions(+), 40 deletions(-) diff --git a/framework/security/config/security.properties b/framework/security/config/security.properties index 0e275a0e13..ed04b0091b 100644 --- a/framework/security/config/security.properties +++ b/framework/security/config/security.properties @@ -274,13 +274,18 @@ csvformat=CSVFormat.DEFAULT #-- #-- If you are sure you are safe for a token you can remove it, etc. #-- If you add a token beware that it does not content ",". It's the separator. -deniedWebShellTokens=$SHA$OFBiz$c_93W08vqLMlJHjOZ7_A6Wcaenw,$SHA$OFBiz$SigPYIfwat32A80hsAOakW0uH5A,$SHA$OFBiz$--GB0cWVhqHm-dWklW-zlGAIMkU,$SHA$OFBiz$4LL0rcLbpJHftX4g1WeF8ThuKyQ,$SHA$OFBiz$pUBkkg8Z-CiOTIYhIR1kU3DgXqY,$SHA$OFBiz$kpcFR3kDCOtNybDHn8ZPLuCVrOk,$SHA$OFBiz$zadWo3Yv2v9ArAgtj5Hdy1yjjAA,$SHA$OFBiz$gcjailglxcjBO361A7K66-4daLs,$SHA$OFBiz$5z4tXuvujvU8WlSrn3i11oUNFZo,$SHA$OFBiz$uYjP2BSE6bJ8V2QuXPWgiiwcss0,$SHA$OFBiz$fjfa3KJJBB3t7rGS5wh6outrKoY,$SHA$OFBiz$z-t-R4DxwjsPhagQBrQRCBdf3BY,$SH [...] +deniedWebShellTokens=java.,beans,freemarker,<script,javascript,<body,body ,<form,<jsp:,<c:out,taglib,<prefix,<%@ page,<?php,exec(,alert(,\ + %eval,@eval,eval(,runtime,import,passthru,shell_exec,assert,str_rot13,system,decode,include,page ,\ + chmod,mkdir,fopen,fclose,new file,upload,getfilename,download,getoutputstring,readfile,iframe,object,embed,onload,build,\ + python,perl ,/perl,ruby ,/ruby,process,function,class,InputStream,to_server,wget ,static,assign,webappPath,\ + ifconfig,route,crontab,netstat,uname ,hostname,iptables,whoami,"cmd",*cmd|,+cmd|,=cmd|,localhost,thread,require,gzdeflate,\ + execute,println,calc,touch,curl,base64,tcp,4444,base32,xxd,bash #-- 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$488OJhFI6NUQlvuqRVFHq6_KN8w +allowedTokens=$SHA$OFBiz$2naHrANKTniFcgLJk4oXr3IRQ48 allowStringConcatenationInUploadedFiles=false 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 c0524afa8b..23b54fb617 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 @@ -39,7 +39,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.ArrayList; -import java.util.Base64; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; @@ -101,7 +100,7 @@ import com.lowagie.text.pdf.PdfReader; public class SecuredUpload { - // To check if a webshell is not uploaded + // To check if a webshell is not uploaded or a reverse shell put in the query string // This can be helpful: // https://en.wikipedia.org/wiki/File_format @@ -122,52 +121,32 @@ public class SecuredUpload { private static final String FILENAMEVALIDCHARACTERS = UtilProperties.getPropertyValue("security", "fileNameValidCharacters", "[a-zA-Z0-9-_ ]"); - /** - * For the content analyze if it's a base64 string with potential code injection - * @param content - * @param allowed - * @return - * @throws IOException - */ - public static boolean isNotValidEncodedText(String content, List<String> allowed) throws IOException { - try { - return isValidText(new String(Base64.getDecoder().decode(content), StandardCharsets.UTF_8), allowed); - } catch (IllegalArgumentException e) { - // the encoded text isn't a Base64, allow it because there is no security risk - return false; - } - } - // Cover method of the same name below. Historically used with 84 references when below was created - // This is used for checking there is no web shell in an uploaded file - // A file containing a reverse shell, base64 encoded or not, will be rejected. + // check there is no web shell in the uploaded file + // A file containing a reverse shell will be rejected. public static boolean isValidText(String content, List<String> allowed) throws IOException { return isValidText(content, allowed, false); } - public static boolean isValidText(String content, List<String> allowed, boolean testEncodeContent) throws IOException { + public static boolean isValidText(String content, List<String> allowed, boolean isQuery) throws IOException { if (content == null) { return false; } - if (!testEncodeContent) { + if (!isQuery) { String contentWithoutSpaces = content.replaceAll(" ", ""); if ((contentWithoutSpaces.contains("\"+\"") || contentWithoutSpaces.contains("'+'")) && !ALLOWSTRINGCONCATENATIONINUPLOADEDFILES) { Debug.logInfo("The uploaded file contains a string concatenation. It can't be uploaded for security reason", MODULE); return false; } - } - // This is used for checking there is no reverse shell in a query string - if (testEncodeContent && isNotValidEncodedText(content, allowed)) { - return false; - } else if (testEncodeContent) { - // e.g. split parameters of an at all non encoded HTTP query string + } else { + // Check the query string is safe, notably no reverse shell List<String> queryParameters = StringUtil.split(content, "&"); - return DENIEDWEBSHELLTOKENS.stream().allMatch(token -> isValid(queryParameters, token, allowed)); + return DENIEDWEBSHELLTOKENS.stream().allMatch(token -> isValid(queryParameters, token.toLowerCase(), allowed)); } - // This is used for checking there is no web shell in an uploaded file - return DENIEDWEBSHELLTOKENS.stream().allMatch(token -> isValid(content, token.toLowerCase(), allowed)); + // Check there is no web shell in an uploaded file + return DENIEDWEBSHELLTOKENS.stream().allMatch(token -> isValid(content.toLowerCase(), token.toLowerCase(), allowed)); } public static boolean isValidFileName(String fileToCheck, Delegator delegator) throws IOException { @@ -869,21 +848,22 @@ public class SecuredUpload { return isValidText(content, allowed); } - // This is used for checking there is no web shell + // Check there is no web shell private static boolean isValid(String content, String string, List<String> allowed) { - boolean isOK = !content.toLowerCase().contains(string) || allowed.contains(string); + boolean isOK = !content.contains(string) || allowed.contains(string); if (!isOK) { Debug.logInfo("The uploaded file contains the string '" + string + "'. It can't be uploaded for security reason", MODULE); } return isOK; } - // This is used for checking there is no reverse shell + // Check there is no reverse shell in query string private static boolean isValid(List<String> queryParameters, String string, List<String> allowed) { boolean isOK = true; + for (String parameter : queryParameters) { - if (!HashCrypt.cryptBytes("SHA", "OFBiz", parameter.getBytes(StandardCharsets.UTF_8)).contains(string) - || allowed.contains(HashCrypt.cryptBytes("SHA", "OFBiz", parameter.getBytes(StandardCharsets.UTF_8)))) { + if (!parameter.contains(string) + || allowed.contains(HashCrypt.cryptBytes("SHA", "OFBiz", parameter.toLowerCase().getBytes(StandardCharsets.UTF_8)))) { continue; } else { isOK = false; 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 db3fcb0454..7018058726 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 @@ -172,8 +172,8 @@ public class ControlFilter extends HttpFilter { String queryString = req.getQueryString(); if (queryString != null) { queryString = URLDecoder.decode(queryString, "UTF-8"); - if (UtilValidate.isUrl(queryString) - || !SecuredUpload.isValidText(queryString, SecuredUpload.getallowedTokens(), true) + if (UtilValidate.isUrlInString(queryString) + || !SecuredUpload.isValidText(queryString.toLowerCase(), SecuredUpload.getallowedTokens(), true) && isSolrTest()) { Debug.logError("For security reason this URL is not accepted", MODULE); throw new RuntimeException("For security reason this URL is not accepted");