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 6a7f3cd Fixed: Groovy Program sandbox bypass (OFBIZ-12305) 6a7f3cd is described below commit 6a7f3cd83e61f97c4ece6283ba04c683ab84b910 Author: Jacques Le Roux <jacques.le.r...@les7arts.com> AuthorDate: Fri Sep 17 18:45:58 2021 +0200 Fixed: Groovy Program sandbox bypass (OFBIZ-12305) This much increases the current security by creating SecuredUpload::isValidText and call it from ProgramExport.groovy This said I agree with thiscodecc that a better solution would be to use a Groovy sandbox, and not only in ProgramExport.groovy. I had a quick glance at a such solution. Unfortunately as Cédric Champeau reports at https://melix.github.io/blog/2015/03/sandboxing.html and unlike thiscodecc suggest there are no "mature solutions on the market". Nevertheless I'll have a deeper look at an OFBiz specific Groovy sandbox solution. Somehow related: I'll also soon extract the list of words used in SecuredUpload::isValidText in a deniedWebShellWords property in security.properties Thanks: thiscodecc for report --- .../org/apache/ofbiz/security/SecuredUpload.java | 77 ++++++++++++---------- .../groovyScripts/entity/ProgramExport.groovy | 8 +-- 2 files changed, 45 insertions(+), 40 deletions(-) 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 682cab6..7c7f462 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 @@ -34,6 +34,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -609,47 +610,53 @@ public class SecuredUpload { return false; } String content = new String(bytesFromFile); - return isValidText(content); + ArrayList<String> allowed = new ArrayList<>(); + return isValidText(content, allowed); } - public static boolean isValidText(String content) throws IOException { - return !(content.toLowerCase().contains("freemarker") // Should be OK, should not be used in Freemarker templates, not part of the syntax. - // Else "template.utility.Execute" is a good replacement but not as much catching, who - // knows... - || content.toLowerCase().contains("import=\"java") - || content.toLowerCase().contains("runtime.getruntime().exec(") - || content.toLowerCase().contains("<%@ page") - || content.toLowerCase().contains("<script") - || content.toLowerCase().contains("<body>") - || content.toLowerCase().contains("<form") - || content.toLowerCase().contains("php") - || content.toLowerCase().contains("javascript") - || content.toLowerCase().contains("%eval") - || content.toLowerCase().contains("@eval") - || content.toLowerCase().contains("import os") // Python - || content.toLowerCase().contains("passthru") - || content.toLowerCase().contains("exec") - || content.toLowerCase().contains("shell_exec") - || content.toLowerCase().contains("assert") - || content.toLowerCase().contains("str_rot13") - || content.toLowerCase().contains("system") - || content.toLowerCase().contains("phpinfo") - || content.toLowerCase().contains("base64_decode") - || content.toLowerCase().contains("chmod") - || content.toLowerCase().contains("mkdir") - || content.toLowerCase().contains("fopen") - || content.toLowerCase().contains("fclose") - || content.toLowerCase().contains("new file") - || content.toLowerCase().contains("import") - || content.toLowerCase().contains("upload") - || content.toLowerCase().contains("getfilename") - || content.toLowerCase().contains("download") - || content.toLowerCase().contains("getoutputstring") - || content.toLowerCase().contains("readfile")); + public static boolean isValidText(String content, List<String> allowed) throws IOException { + // "freemarker" should be OK, should not be used in Freemarker templates, not part of the syntax. + // Else "template.utility.Execute" is a good replacement but not as much catching, who knows... + return isValid(content, "freemarker", allowed) + && isValid(content, "freemarker", allowed) + && isValid(content, "import=\"java", allowed) + && isValid(content, "runtime.getruntime().exec(", allowed) + && isValid(content, "<%@ page", allowed) + && isValid(content, "<script", allowed) + && isValid(content, "<body>", allowed) + && isValid(content, "<form", allowed) + && isValid(content, "php", allowed) + && isValid(content, "javascript", allowed) + && isValid(content, "%eval", allowed) + && isValid(content, "@eval", allowed) + && isValid(content, "import os", allowed) // Python + && isValid(content, "passthru", allowed) + && isValid(content, "exec", allowed) + && isValid(content, "shell_exec", allowed) + && isValid(content, "assert", allowed) + && isValid(content, "str_rot13", allowed) + && isValid(content, "system", allowed) + && isValid(content, "phpinfo", allowed) + && isValid(content, "base64_decode", allowed) + && isValid(content, "chmod", allowed) + && isValid(content, "mkdir", allowed) + && isValid(content, "fopen", allowed) + && isValid(content, "fclose", allowed) + && isValid(content, "new file", allowed) + && isValid(content, "import", allowed) + && isValid(content, "upload", allowed) + && isValid(content, "getfilename", allowed) + && isValid(content, "download", allowed) + && isValid(content, "getoutputstring", allowed) + && isValid(content, "readfile", allowed); // TODO.... to be continued with known webshell contents... a complete allow list is impossible anyway... // eg: https://www.acunetix.com/blog/articles/detection-prevention-introduction-web-shells-part-5/ } + private static boolean isValid(String content, String string, List<String> allowed) { + return !content.toLowerCase().contains(string) || allowed.contains(string); + } + private static void deleteBadFile(String fileToCheck) { Debug.logError("File :" + fileToCheck + ", can't be uploaded for security reason", MODULE); File badFile = new File(fileToCheck); diff --git a/framework/webtools/groovyScripts/entity/ProgramExport.groovy b/framework/webtools/groovyScripts/entity/ProgramExport.groovy index e06cd96..0d3a5f7 100644 --- a/framework/webtools/groovyScripts/entity/ProgramExport.groovy +++ b/framework/webtools/groovyScripts/entity/ProgramExport.groovy @@ -22,6 +22,7 @@ import org.apache.ofbiz.entity.condition.EntityCondition import org.apache.ofbiz.entity.condition.EntityOperator import org.apache.ofbiz.entity.model.ModelEntity import org.apache.ofbiz.base.util.* + import org.w3c.dom.Document import org.codehaus.groovy.control.customizers.ImportCustomizer @@ -34,7 +35,7 @@ recordValues = [] errMsgList = [] if (!parameters.groovyProgram) { - + groovyProgram = ''' // Use the List variable recordValues to fill it with GenericValue maps. // full groovy syntax is available @@ -85,10 +86,7 @@ def shell = new GroovyShell(loader, binding, configuration) if (groovyProgram) { try { - // TODO more can be added... - if (groovyProgram.contains("new File") - || groovyProgram.contains(".jsp") - || groovyProgram.contains("<%=")) { + if (!org.apache.ofbiz.security.SecuredUpload.isValidText(groovyProgram,["import"])) { request.setAttribute("_ERROR_MESSAGE_", "Not executed for security reason") return }