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
The following commit(s) were added to refs/heads/trunk by this push: new 8bb054b67f Improved: Prevent URL parameters manipulation (OFBIZ-13147) 8bb054b67f is described below commit 8bb054b67fd32cc9976a4b87e913e83a2ed2b807 Author: Jacques Le Roux <jacques.le.r...@les7arts.com> AuthorDate: Fri Nov 8 16:34:17 2024 +0100 Improved: Prevent URL parameters manipulation (OFBIZ-13147) I have refactored SecuredUpload a bit by clearly separating what is used for web shell in uploaded files and reverse shell in query strings. The idea is also to keep as much as possible securing code in SecuredUpload. Even if now more than upload is concerned. --- framework/security/config/security.properties | 13 +++--- .../org/apache/ofbiz/security/SecuredUpload.java | 50 +++++++++++++++++++--- .../apache/ofbiz/webapp/control/ControlFilter.java | 2 +- 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/framework/security/config/security.properties b/framework/security/config/security.properties index be3bdf052a..8178422912 100644 --- a/framework/security/config/security.properties +++ b/framework/security/config/security.properties @@ -273,12 +273,13 @@ csvformat=CSVFormat.DEFAULT #-- Else "template.utility.Execute" is a good replacement but not as much catching, who knows... #-- #-- If you are sure you are safe for a token you can remove it, etc. -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 +#-- 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 [...] + +#-- 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$EP-l2t4A_60cRYYnEqEaSiDjfrs,$SHA$OFBiz$JG1RWjLnFzQOpNRUqllybbbfyOE 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 3a01307bb6..ed997d36aa 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 @@ -33,6 +33,7 @@ import java.io.StringReader; import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -69,6 +70,7 @@ import org.apache.commons.imaging.formats.tiff.TiffImageParser; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.StringUtils; +import org.apache.ofbiz.base.crypto.HashCrypt; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.FileUtil; import org.apache.ofbiz.base.util.StringUtil; @@ -138,23 +140,34 @@ public class SecuredUpload { } } + // 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 public static boolean isValidText(String content, List<String> allowed) throws IOException { - return isValidText(content, allowed, true); + return isValidText(content, allowed, false); } public static boolean isValidText(String content, List<String> allowed, boolean testEncodeContent) throws IOException { if (content == null) { return false; } - String contentWithoutSpaces = content.replaceAll("\\s", ""); - 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; + if (!testEncodeContent) { + 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 && !isValidEncodedText(content, allowed)) { return false; + } else if (testEncodeContent) { + // e.g. split parameters of an at all non encoded HTTP query string + List<String> queryParameters = StringUtil.split(content, "&"); + return DENIEDWEBSHELLTOKENS.stream().allMatch(token -> isValid(queryParameters, token, 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)); } @@ -860,6 +873,7 @@ public class SecuredUpload { return isValidText(content, allowed); } + // This is used for checking 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); if (!isOK) { @@ -868,6 +882,24 @@ public class SecuredUpload { return isOK; } + // This is used for checking there is no reverse shell + 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)))) { + continue; + } else { + isOK = false; + break; + } + } + if (!isOK) { + Debug.logInfo("The HTTP query string contains the string '" + string + "'. It can't be uploaded for security reason", MODULE); + } + return isOK; + } + private static void deleteBadFile(String fileToCheck) { Debug.logError("File : " + fileToCheck + ", can't be uploaded for security reason", MODULE); File badFile = new File(fileToCheck); @@ -886,6 +918,12 @@ 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) { try { File file = new File(fileToCheck); 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 d4b077b5b1..9cc412175a 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 @@ -176,7 +176,7 @@ public class ControlFilter extends HttpFilter { if (queryString != null) { queryString = URLDecoder.decode(queryString, "UTF-8"); if (UtilValidate.isUrlInString(queryString) - || !SecuredUpload.isValidText(queryString, Collections.emptyList()) + || !SecuredUpload.isValidText(queryString, 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");