This is an automated email from the ASF dual-hosted git repository. jleroux pushed a commit to branch release18.12 in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git
commit 8385c8e75550c779bb30c93f1075499d524578be Author: Jacques Le Roux <jacques.le.r...@les7arts.com> AuthorDate: Tue Nov 26 07:56:44 2024 +0100 Improved: Prevent URL parameters manipulation (OFBIZ-13147) Add ROT13, and improves few short deniedWebShellTokens by surrounding them by spaces. It's to avoid collisions while loading image files 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. Adds 6 new deniedWebShellTokens in security.properties This follows Leïla comment. It simplifies the SecuredUpload::isValidEncodedText content and renames it isNotValidEncodedText Also 3 new deniedWebShellTokens are added, some more to come Conflicts handled by hand in ControlFilter.java and SecuredUpload.java --- framework/security/config/security.properties | 13 +++++- .../org/apache/ofbiz/security/SecuredUpload.java | 49 ++++++++++------------ .../apache/ofbiz/webapp/control/ControlFilter.java | 2 +- .../widget/renderer/macro/MacroMenuRenderer.java | 7 +--- 4 files changed, 35 insertions(+), 36 deletions(-) diff --git a/framework/security/config/security.properties b/framework/security/config/security.properties index 65ad284b38..e018769b76 100644 --- a/framework/security/config/security.properties +++ b/framework/security/config/security.properties @@ -236,12 +236,21 @@ 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 [...] +#-- +#-- If you cross issues while loading an image file because of a token found there, you may try to surround the string by spaces, as " tr " below. +#-- Actually most of the tokens should but it's now a bit late for me. I mean to test all of them... +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, tr , 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 9e9a5bd45d..1663a0b2de 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 @@ -40,7 +40,6 @@ import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Base64; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -52,7 +51,7 @@ import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.stream.ImageInputStream; -import org.apache.batik.dom.svg.SAXSVGDocumentFactory; +import org.apache.batik.anim.dom.SAXSVGDocumentFactory; import org.apache.batik.util.XMLResourceDescriptor; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; @@ -98,7 +97,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 @@ -120,46 +119,41 @@ public class SecuredUpload { * @return * @throws IOException */ - public static boolean isValidEncodedText(String content, List<String> allowed) throws IOException { + public static boolean isNotValidEncodedText(String content, List<String> allowed) throws IOException { try { - return !isValidText(Base64.getDecoder().decode(content).toString(), allowed, false) - || !isValidText(Base64.getMimeDecoder().decode(content).toString(), allowed, false) - || !isValidText(Base64.getUrlDecoder().decode(content).toString(), allowed, false); + 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 true; + 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 + // 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 && !isValidEncodedText(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 { @@ -451,7 +445,7 @@ public class SecuredUpload { default: throw new IOException("Format of the original image " + fileName + " is not supported for write operation !"); } - imageParser.writeImage(sanitizedImage, fos, new HashMap<>()); + imageParser.writeImage(sanitizedImage, fos, null); } // Set state flag safeState = true; @@ -505,7 +499,7 @@ public class SecuredUpload { return false; } // Load stream in PDF parser - PdfReader reader = new PdfReader(file.getAbsolutePath()); + new PdfReader(file.getAbsolutePath()); return true; } catch (Exception e) { return false; @@ -802,21 +796,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 126a6be85d..4e9a35efb4 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 @@ -145,7 +145,7 @@ public class ControlFilter implements Filter { if (queryString != null) { queryString = URLDecoder.decode(queryString, "UTF-8"); if (UtilValidate.isUrl(queryString) - || !SecuredUpload.isValidText(queryString, SecuredUpload.getallowedTokens(), true) + || !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"); diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroMenuRenderer.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroMenuRenderer.java index 17998b01ce..81cbd3b6b9 100644 --- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroMenuRenderer.java +++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroMenuRenderer.java @@ -257,12 +257,7 @@ public class MacroMenuRenderer implements MenuStringRenderer { targetParameters.append(parameter.getKey()); targetParameters.append("'"); targetParameters.append(",'value':'"); - UtilCodec.SimpleEncoder simpleEncoder = (UtilCodec.SimpleEncoder) context.get("simpleEncoder"); - if (simpleEncoder != null) { - targetParameters.append(simpleEncoder.encode(parameter.getValue())); - } else { - targetParameters.append(parameter.getValue()); - } + targetParameters.append(parameter.getValue()); targetParameters.append("'}"); } targetParameters.append("]");