[ 
https://issues.apache.org/jira/browse/GROOVY-8163?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=15990743#comment-15990743
 ] 

Jochen Theodorou commented on GROOVY-8163:
------------------------------------------

I think the idea of the patch is good and we should think about integrating it. 
Since it won´t do anything without a security manager being set it should be 
not causing trouble for example for testing code. But I also think the patch 
will not solve all the attack vectors. For example if a subclass of ClassLoader 
overwrites defineClass, your patch will not catch that. Your code will also 
catch a lot less if invokedynamic is enabled.

But anyway, I still think this would be a good start..

btw Dimitry, it would be even better if you provided this as pull request on 
github

> Groovy scripts can disable java security manager and escape sandbox
> -------------------------------------------------------------------
>
>                 Key: GROOVY-8163
>                 URL: https://issues.apache.org/jira/browse/GROOVY-8163
>             Project: Groovy
>          Issue Type: Bug
>    Affects Versions: 2.5.0-alpha-1, 2.4.9, 2.4.10
>            Reporter: Dimitry Polivaev
>
> Consider following test
> {code}
> package groovytest;
> import groovy.util.Eval;
> import org.junit.*;
> import java.net.URL;
> import java.security.AccessController;
> import java.security.PrivilegedAction;
> public class GroovySecurityTest {
>       public static final String 
> RESTRICTED_PERMISSIONS_FOR_SCRIPT_ONLY_POLICY = 
> "/restrictedPermissionsForScriptOnlyPolicy.txt";
>       public static final String POLICY = 
> RESTRICTED_PERMISSIONS_FOR_SCRIPT_ONLY_POLICY;
>       @BeforeClass
>       public static void setPolicy() throws Exception {
>               final String dirTest = 
> GroovySecurityTest.class.getProtectionDomain().getCodeSource().getLocation().toString();
>               final String dirGroovy = 
> Eval.class.getProtectionDomain().getCodeSource().getLocation().toString();
>               System.setProperty("dir.test",dirTest + "-");
>               System.setProperty("dir.groovy",dirGroovy);
>               final URL policy = GroovySecurityTest.class.getResource(POLICY);
>               System.setProperty("java.security.policy", policy.toString());
>       }
>       
>       
>       @Before
>       public void setSecurityManager() throws Exception {
>                       System.setSecurityManager(new SecurityManager());
>       }
>       @After
>       public void removeSecurityManager() throws Exception {
>               AccessController.doPrivileged(new PrivilegedAction<Void>() {
>                       @Override
>                       public Void run() {
>                               System.setSecurityManager(null);
>                               return null;
>                       }
>               });
>       }
>       @Test
>       public void doesNotChangeScriptPermissionsUsungPrivateFieldAccess() 
> throws Exception {
>               try {
>                       AccessController.doPrivileged(new 
> PrivilegedAction<Void>() {
>                 @Override
>                 public Void run() {
>                     Eval.me("getClass().protectionDomain0.hasAllPerm = true;"
>                                     + "System.setSecurityManager(null);"
>                                     + "1");
>                     return null;
>                 }
>             });
>               } catch (Exception e) {
>               }
>               Assert.assertNotNull(System.getSecurityManager());
>       }
> }
> {code}
> with following policy file restrictedPermissionsForScriptOnlyPolicy.txt
> {code}
> grant codeBase "${dir.test}" {
>       permission java.security.AllPermission;
> };
> grant codeBase "${dir.groovy}" {
>       permission java.security.AllPermission;
> };
> grant {
> };
> {code}
> It fails: security manager is not set any more when the test assertion is 
> checked.
> It happens because CachedField from org.codehaus.groovy.reflection is created 
> withing trusted code base (groovy jar) and gives access to the field to 
> untrusted scripts without any security checks. The same problem relates to 
> CachedMethod which would allow any script to access protected method 
> java.lang.ClassLoader#defineClass(java.lang.String, byte[], int, int, 
> java.security.ProtectionDomain) that can be misused to manipulate code 
> sources of classes loaded from script to give them all permissions.
> It also appears that if I remove permissions from groovy.jar using more 
> restrictive policy using following policy file restrictedPermissionsPolicy.txt
> {code}
> grant  codeBase "${dir.test}" {
>     permission java.lang.RuntimePermission "*";
>     permission java.security.SecurityPermission "*";
>      permission java.io.FilePermission "<<ALL FILES>>", "read";
>     permission java.util.PropertyPermission "*", "read";
>     permission groovy.security.GroovyCodeSourcePermission "*";
> };
> grant  codeBase "${dir.groovy}" {
>     permission java.lang.RuntimePermission "*";
>     permission java.security.SecurityPermission "*";
>     permission java.io.FilePermission "<<ALL FILES>>", "read";
>     permission java.util.PropertyPermission "*", "read";
>     permission groovy.security.GroovyCodeSourcePermission "*";
> };
> grant {
>     permission java.lang.RuntimePermission "accessDeclaredMembers";
> };
> {code}
> it has a consequence that groovy can not access even some public methods on 
> bean properties as shown in the following test
> {code}
> package groovytest;
> import groovy.util.Eval;
> import org.junit.*;
> import java.net.URL;
> import java.security.AccessController;
> import java.security.PrivilegedAction;
> public class GroovyBeanTest {
>       public static final String RESTRICTED_PERMISSIONS_POLICY = 
> "/restrictedPermissionsPolicy.txt";
>       public static final String POLICY = RESTRICTED_PERMISSIONS_POLICY;
>       @BeforeClass
>       public static void setPolicy() throws Exception {
>               final String dirTest = 
> GroovyBeanTest.class.getProtectionDomain().getCodeSource().getLocation().toString();
>               final String dirGroovy = 
> Eval.class.getProtectionDomain().getCodeSource().getLocation().toString();
>               System.setProperty("dir.test",dirTest + "-");
>               System.setProperty("dir.groovy",dirGroovy);
>               final URL policy = GroovyBeanTest.class.getResource(POLICY);
>               System.setProperty("java.security.policy", policy.toString());
>       }
>       
>       
>       @Before
>       public void setSecurityManager() throws Exception {
>                       System.setSecurityManager(new SecurityManager());
>       }
>       @After
>       public void removeSecurityManager() throws Exception {
>               AccessController.doPrivileged(new PrivilegedAction<Void>() {
>                       @Override
>                       public Void run() {
>                               System.setSecurityManager(null);
>                               return null;
>                       }
>               });
>       }
>       
>       public interface BeanInterface{
>               public String getName();
>       }
>       private class Bean implements BeanInterface{
>               private String _name = "bean";
>               public String getName(){
>                       return _name;
>               }
>       }
>       @Test
>       public void returnsBeanPropertyValue() throws Exception {
>               AccessController.doPrivileged(new PrivilegedAction<Void>() {
>                       @Override
>                       public Void run() {
>                               Assert.assertEquals("bean", Eval.x(new Bean(), 
> "x.name"));
>                               return null;
>                       }
>               });
>       }
>       static class BeanClass {
>               private String name = "bean";
>       }
>       @Test
>       public void returnsBeanClassName() throws Exception {
>               AccessController.doPrivileged(new PrivilegedAction<Void>() {
>                       @Override
>                       public Void run() {
>                               Assert.assertEquals(BeanClass.class.getName(), 
> Eval.x(new BeanClass(), "x.class.name"));
>                               return null;
>                       }
>               });
>       }
> }
> {code}
> The both tests fail as the bean properties can not be found by groovy.
> It turned out that I can not run the both tests in one process, make sure you 
> run them separately.
> In order to fix this issue for my open source project Freeplane I have to 
> patch groovy code. It turned out to be possible. 
> So I want to share the fix with you and ask you to integrate it.
> The fix is based on groovy version 2.4.9 and I think that it can be applied 
> to any Groovy version.
> You only need to check for access permissions at the relevant places to make 
> sure that they are not leaked from groovy (which needs them to work properly) 
> to the untrusted script
> {code}
> package org.codehaus.groovy.reflection;
> import java.lang.reflect.Field;
> import java.lang.reflect.Method;
> import java.lang.reflect.Modifier;
> import java.lang.reflect.ReflectPermission;
> import groovy.lang.GroovyObject;
> class AccessPermissionChecker {
>       private static final ReflectPermission REFLECT_PERMISSION = new 
> ReflectPermission("suppressAccessChecks");
>       static private void checkAccessPermission(Class<?> declaringClass, 
> final int modifiers, boolean isAccessible) {
>               final SecurityManager securityManager = 
> System.getSecurityManager();
>               if (isAccessible && securityManager != null) {
>                               if ((modifiers & (Modifier.PUBLIC | 
> Modifier.PROTECTED)) == 0
>                                               && 
> !GroovyObject.class.isAssignableFrom(declaringClass)) {
>                         securityManager.checkPermission(REFLECT_PERMISSION);
>                 }
>                 else if ((modifiers & (Modifier.PUBLIC)) == 0
>                                       && 
> declaringClass.equals(ClassLoader.class)){
>                                       
> securityManager.checkCreateClassLoader();
>                               }
>               }
>       }
>       static public void checkAccessPermission(Method method) {
>               checkAccessPermission(method.getDeclaringClass(), 
> method.getModifiers(), method.isAccessible()
>               );
>       }
>       public static void checkAccessPermission(Field field) {
>               checkAccessPermission(field.getDeclaringClass(), 
> field.getModifiers(), field.isAccessible()
>               );
>       }
> }
> {code}
> patching your classes as follows:
> CachedField.java:
> {code}
>     /**
>      * @return the property of the given object
>      * @throws RuntimeException if the property could not be evaluated
>      */
>     public Object getProperty(final Object object) {
>         try {
>             AccessPermissionChecker.checkAccessPermission(field);
>         }
>         catch (AccessControlException ex) {
>             throw new IllegalArgumentException("Illegal access to field" + " 
> " + field.getName());
>         }
>         try {
>             return field.get(object);
>         } catch (IllegalAccessException e) {
>             throw new GroovyRuntimeException("Cannot get the property '" + 
> name + "'.", e);
>         }
>     }
>     /**
>      * Sets the property on the given object to the new value
>      *
>      * @param object on which to set the property
>      * @param newValue the new value of the property
>      * @throws RuntimeException if the property could not be set
>      */
>     public void setProperty(final Object object, Object newValue) {
>         try {
>             AccessPermissionChecker.checkAccessPermission(field);
>         }
>         catch (AccessControlException ex) {
>             throw new IllegalArgumentException("Illegal access to field" + " 
> " + field.getName());
>         }
>         final Object goalValue = 
> DefaultTypeTransformation.castToType(newValue, field.getType());
>         if (isFinal()) {
>             throw new GroovyRuntimeException("Cannot set the property '" + 
> name + "' because the backing field is final.");
>         }
>         try {
>             field.set(object, goalValue);
>         } catch (IllegalAccessException ex) {
>             throw new GroovyRuntimeException("Cannot set the property '" + 
> name + "'.", ex);
>         }
>     }
> {code}
> CachedMethod.java:
> {code}
>     public final Object invoke(Object object, Object[] arguments) {
>         try {
>             AccessPermissionChecker.checkAccessPermission(cachedMethod);
>         }
>         catch (AccessControlException ex) {
>             throw new InvokerInvocationException(new 
> IllegalArgumentException("Illegal access to method" + 
> cachedMethod.getName()));
>         }
>         try {
>             return cachedMethod.invoke(object, arguments);
>         } catch (IllegalArgumentException e) {
>             throw new InvokerInvocationException(e);
>         } catch (IllegalAccessException e) {
>             throw new InvokerInvocationException(e);
>         } catch (InvocationTargetException e) {
>             Throwable cause = e.getCause();
>             throw (cause instanceof RuntimeException && !(cause instanceof 
> MissingMethodException)) ?
>                     (RuntimeException) cause : new 
> InvokerInvocationException(e);
>         }
>     }
>     public final Method setAccessible() {
>         try {
>             AccessPermissionChecker.checkAccessPermission(cachedMethod);
>         }
>         catch (AccessControlException ex) {
>             throw new IllegalArgumentException("Illegal access to method" + 
> cachedMethod.getName());
>         }
> //        if (queuedToCompile.compareAndSet(false,true)) {
> //            if (isCompilable())
> //              CompileThread.addMethod(this);
> //        }
>         return cachedMethod;
>     }
>     public Method getCachedMethod() {
>         try {
>             AccessPermissionChecker.checkAccessPermission(cachedMethod);
>         }
>         catch (AccessControlException ex) {
>             throw new IllegalArgumentException("Illegal access to method" + 
> cachedMethod.getName());
>         }
>         return cachedMethod;
>     }
> }
> {code}
> In order to apply the patch in Freeplane I created a separate project 
> https://github.com/dpolivaev/securegroovy which generates the patch at 
> runtime using bytebuddy. 
> I would appreciate if you could integrate the patch in groovy code.
> There is one subtle issue with AccessPermissionChecker: as you see it allows 
> access to all protected methods except for ClassLoader without any further 
> checks.
> But for the protected methods from ClassLoader is makes one additional check: 
> Otherwise a script could access a class loader by getClass().getClassLoader() 
> and misuse its defineClass method to let malicious code appear trusted. 



--
This message was sent by Atlassian JIRA
(v6.3.15#6346)

Reply via email to