This is an automated email from the ASF dual-hosted git repository.

jleroux pushed a commit to branch release17.12
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git


The following commit(s) were added to refs/heads/release17.12 by this push:
     new 72e2eac  Fixed: Groovy Program sandbox bypass (OFBIZ-12305)
72e2eac is described below

commit 72e2eacef814cf679837fb46e666005bfdca9289
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
    
    Conflicts handled by hand
      framework/webtools/groovyScripts/entity/ProgramExport.groovy
---
 .../org/apache/ofbiz/security/SecuredUpload.java   | 77 ++++++++++++----------
 .../groovyScripts/entity/ProgramExport.groovy      |  9 +--
 2 files changed, 45 insertions(+), 41 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 e3fb163..4dc79d7 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;
@@ -615,47 +616,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 41d29e6..12b8293 100644
--- a/framework/webtools/groovyScripts/entity/ProgramExport.groovy
+++ b/framework/webtools/groovyScripts/entity/ProgramExport.groovy
@@ -20,6 +20,7 @@ import org.apache.ofbiz.entity.Delegator
 import org.apache.ofbiz.entity.GenericValue
 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
@@ -31,8 +32,7 @@ String groovyProgram = null
 recordValues = []
 errMsgList = []
 
-if (UtilValidate.isEmpty(parameters.groovyProgram)) {
-    
+if (!parameters.groovyProgram) {
     groovyProgram = '''
 // Use the List variable recordValues to fill it with GenericValue maps.
 // full groovy syntaxt is available
@@ -73,10 +73,7 @@ def shell = new GroovyShell(loader, binding, configuration)
 
 if (UtilValidate.isNotEmpty(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
         }

Reply via email to