mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-31 17:39:05 +00:00
MNT-21009 : Sandboxed arbitrary Code Execution (#657)
* MNT-21009 : Sandboxed arbitrary Code Execution * MNT-21009: added basic code java for javascrrict security check. * MNT-21009 : added release for MNT-21009 * [maven-release-plugin][skip ci] prepare release alfresco-repository-MNT-21009 * [maven-release-plugin][skip ci] prepare for next development iteration * MNT-21009: added sandboxNativeObject in case of calling getClass using reflection. * [maven-release-plugin][skip ci] prepare release alfresco-repository-MNT-21009 * MNT-21009 : changes after review.
This commit is contained in:
@@ -18,6 +18,7 @@ branches:
|
||||
only:
|
||||
- master
|
||||
- /support\/.*/
|
||||
- fix/MNT-21009-arbitrary-codeExecution
|
||||
|
||||
stages:
|
||||
- test
|
||||
@@ -110,7 +111,7 @@ jobs:
|
||||
script: travis_wait 20 mvn test -B -Dtest=AllDBTestsTestSuite -Ddb.name=alfresco -Ddb.url=jdbc:mariadb://localhost:3307/alfresco?useUnicode=yes\&characterEncoding=UTF-8 -Ddb.username=alfresco -Ddb.password=alfresco -Ddb.driver=org.mariadb.jdbc.Driver
|
||||
- stage: release
|
||||
name: "Push to Nexus"
|
||||
if: fork = false AND (branch = master OR branch =~ /support\/.*/) AND type != pull_request AND commit_message !~ /\[no-release\]/
|
||||
if: fork = false AND (branch = master OR branch =~ /support\/.*/ OR branch = fix/MNT-21009-arbitrary-codeExecution) AND type != pull_request AND commit_message !~ /\[no-release\]/
|
||||
before_install:
|
||||
- "cp .travis.settings.xml $HOME/.m2/settings.xml"
|
||||
script:
|
||||
@@ -119,4 +120,4 @@ jobs:
|
||||
# Add email to link commits to user
|
||||
- git config user.email "${GIT_EMAIL}"
|
||||
# Skip building of release commits
|
||||
- mvn --batch-mode -q -DscmCommentPrefix="[maven-release-plugin][skip ci] " -Dusername="${GIT_USERNAME}" -Dpassword="${GIT_PASSWORD}" -DskipTests -Darguments=-DskipTests release:clean release:prepare release:perform
|
||||
- mvn --batch-mode -q -DscmCommentPrefix="[maven-release-plugin][skip ci] " -Dusername="${GIT_USERNAME}" -Dpassword="${GIT_PASSWORD}" -DskipTests -Darguments=-DskipTests -DreleaseVersion=MNT-21009 -DdevelopmentVersion=8.22-SNAPSHOT release:clean release:prepare release:perform
|
||||
|
1
pom.xml
1
pom.xml
@@ -4,7 +4,6 @@
|
||||
<name>Alfresco Repository</name>
|
||||
<version>8.24-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-super-pom</artifactId>
|
||||
|
@@ -81,6 +81,9 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess
|
||||
/** Wrap Factory */
|
||||
private static final WrapFactory wrapFactory = new RhinoWrapFactory();
|
||||
|
||||
/** Sandbox Wrap Factory */
|
||||
private static final SandboxWrapFactory sandboxFactory = new SandboxWrapFactory();
|
||||
|
||||
/** Base Value Converter */
|
||||
private final ValueConverter valueConverter = new ValueConverter();
|
||||
|
||||
@@ -453,7 +456,7 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess
|
||||
{
|
||||
// Create a thread-specific scope from one of the shared scopes.
|
||||
// See http://www.mozilla.org/rhino/scopes.html
|
||||
cx.setWrapFactory(wrapFactory);
|
||||
cx.setWrapFactory(secure ? wrapFactory : sandboxFactory);
|
||||
Scriptable scope;
|
||||
if (this.shareSealedScopes)
|
||||
{
|
||||
@@ -461,6 +464,7 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess
|
||||
scope = cx.newObject(sharedScope);
|
||||
scope.setPrototype(sharedScope);
|
||||
scope.setParentScope(null);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -578,23 +582,44 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess
|
||||
|
||||
|
||||
/**
|
||||
* Rhino script value wraper
|
||||
* Rhino script value wrapper
|
||||
*/
|
||||
private static class RhinoWrapFactory extends WrapFactory
|
||||
{
|
||||
protected Scriptable wrapBasicJavaObject(Context cx, Scriptable scope, Object javaObject, Class<?> staticType)
|
||||
{
|
||||
return super.wrapAsJavaObject(cx, scope, javaObject, staticType);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.mozilla.javascript.WrapFactory#wrapAsJavaObject(org.mozilla.javascript.Context, org.mozilla.javascript.Scriptable, java.lang.Object, java.lang.Class)
|
||||
*/
|
||||
public Scriptable wrapAsJavaObject(Context cx, Scriptable scope, Object javaObject, Class staticType)
|
||||
public Scriptable wrapAsJavaObject(Context cx, Scriptable scope, Object javaObject, Class<?> staticType)
|
||||
{
|
||||
if (javaObject instanceof Map && !(javaObject instanceof ScriptableHashMap))
|
||||
{
|
||||
return new NativeMap(scope, (Map)javaObject);
|
||||
return new NativeMap(scope, (Map) javaObject);
|
||||
}
|
||||
return super.wrapAsJavaObject(cx, scope, javaObject, staticType);
|
||||
return wrapBasicJavaObject(cx, scope, javaObject, staticType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link WrapFactory} that ensures {@link org.mozilla.javascript.NativeJavaObject} instances are of the
|
||||
* {@link SandboxNativeJavaObject} variety.
|
||||
*/
|
||||
private static class SandboxWrapFactory extends RhinoWrapFactory
|
||||
{
|
||||
/* (non-Javadoc)
|
||||
* @see org.mozilla.javascript.WrapFactory#wrapAsJavaObject(org.mozilla.javascript.Context, org.mozilla.javascript.Scriptable, java.lang.Object, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
protected Scriptable wrapBasicJavaObject(Context cx, Scriptable scope, Object javaObject, Class<?> staticType)
|
||||
{
|
||||
return new SandboxNativeJavaObject(scope, javaObject, staticType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre initializes two scope objects (one secure and one not) with the standard objects preinitialised.
|
||||
@@ -621,7 +646,7 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess
|
||||
cx = Context.enter();
|
||||
try
|
||||
{
|
||||
cx.setWrapFactory(wrapFactory);
|
||||
cx.setWrapFactory(sandboxFactory);
|
||||
this.nonSecureScope = initScope(cx, true, true);
|
||||
}
|
||||
finally
|
||||
@@ -656,13 +681,9 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess
|
||||
else
|
||||
{
|
||||
// Initialise the secure scope
|
||||
scope = cx.initStandardObjects(null, sealed);
|
||||
// remove security issue related objects - this ensures the script may not access
|
||||
// unsecure java.* libraries or import any other classes for direct access - only
|
||||
// the configured root host objects will be available to the script writer
|
||||
scope.delete("Packages");
|
||||
scope.delete("getClass");
|
||||
scope.delete("java");
|
||||
// This sets up "scope" to have access to all the standard JavaScript classes, but does not create global objects for any top-level Java packages.
|
||||
// In addition, the "Packages," "JavaAdapter," and "JavaImporter" classes, and the "getClass" function, are not initialized.
|
||||
scope = cx.initSafeStandardObjects(null, sealed);
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.jscript;
|
||||
|
||||
import org.mozilla.javascript.NativeJavaObject;
|
||||
import org.mozilla.javascript.Scriptable;
|
||||
|
||||
|
||||
/**
|
||||
* A sandboxed {@link NativeJavaObject} that prevents using reflection to escape a sandbox.
|
||||
*/
|
||||
public class SandboxNativeJavaObject extends NativeJavaObject
|
||||
{
|
||||
public SandboxNativeJavaObject(Scriptable scope, Object javaObject, Class<?> staticType)
|
||||
{
|
||||
super(scope, javaObject, staticType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(String name, Scriptable start)
|
||||
{
|
||||
if ("getClass".equals(name))
|
||||
{
|
||||
return NOT_FOUND;
|
||||
}
|
||||
return super.get(name, start);
|
||||
}
|
||||
|
||||
}
|
@@ -25,6 +25,9 @@
|
||||
*/
|
||||
package org.alfresco.repo.jscript;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -32,12 +35,14 @@ import java.util.Map;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.dictionary.DictionaryComponent;
|
||||
import org.alfresco.repo.dictionary.DictionaryDAO;
|
||||
import org.alfresco.repo.dictionary.M2Model;
|
||||
import org.alfresco.repo.node.BaseNodeServiceTest;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
||||
import org.alfresco.repo.security.permissions.AccessDeniedException;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.service.ServiceRegistry;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
@@ -57,6 +62,7 @@ import org.mozilla.javascript.Scriptable;
|
||||
import org.mozilla.javascript.ScriptableObject;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
|
||||
/**
|
||||
* @author Kevin Roast
|
||||
*/
|
||||
@@ -360,6 +366,75 @@ public class RhinoScriptTest extends TestCase
|
||||
});
|
||||
}
|
||||
|
||||
// MNT-21009
|
||||
public void testUnsecureScriptAddedOnRepoNode()
|
||||
{
|
||||
|
||||
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Object>()
|
||||
{
|
||||
public Object execute() throws Exception
|
||||
{
|
||||
StoreRef store = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "rhino_" + System.currentTimeMillis());
|
||||
NodeRef root = nodeService.getRootNode(store);
|
||||
BaseNodeServiceTest.buildNodeGraph(nodeService, root);
|
||||
|
||||
try
|
||||
{
|
||||
Map<String, Object> model = new HashMap<String, Object>();
|
||||
model.put("out", System.out);
|
||||
|
||||
// create an Alfresco scriptable Node object
|
||||
// the Node object is a wrapper similar to the TemplateNode
|
||||
// concept
|
||||
ScriptNode rootNode = new ScriptNode(root, serviceRegistry, null);
|
||||
model.put("root", rootNode);
|
||||
|
||||
// test executing a script embedded inside Node content
|
||||
ChildAssociationRef childRef = nodeService.createNode(root, BaseNodeServiceTest.ASSOC_TYPE_QNAME_TEST_CHILDREN,
|
||||
QName.createQName(BaseNodeServiceTest.NAMESPACE, "script_content"), BaseNodeServiceTest.TYPE_QNAME_TEST_CONTENT, null);
|
||||
NodeRef contentNodeRef = childRef.getChildRef();
|
||||
ContentWriter writer = contentService.getWriter(contentNodeRef, BaseNodeServiceTest.PROP_QNAME_TEST_CONTENT, true);
|
||||
writer.setMimetype("application/x-javascript");
|
||||
writer.putContent(BASIC_JAVA);
|
||||
|
||||
try
|
||||
{
|
||||
scriptService.executeScript(contentNodeRef, BaseNodeServiceTest.PROP_QNAME_TEST_CONTENT, model);
|
||||
fail("execution of nonsecure script on nodeRef is not allowed.");
|
||||
}
|
||||
catch (AlfrescoRuntimeException ex)
|
||||
{
|
||||
// expected
|
||||
}
|
||||
|
||||
|
||||
ChildAssociationRef childRef1 = nodeService.createNode(root, BaseNodeServiceTest.ASSOC_TYPE_QNAME_TEST_CHILDREN,
|
||||
QName.createQName(BaseNodeServiceTest.NAMESPACE, "script_content"), BaseNodeServiceTest.TYPE_QNAME_TEST_CONTENT, null);
|
||||
NodeRef contentNodeRef1 = childRef1.getChildRef();
|
||||
ContentWriter writer1 = contentService.getWriter(contentNodeRef1, BaseNodeServiceTest.PROP_QNAME_TEST_CONTENT, true);
|
||||
writer1.setMimetype("application/x-javascript");
|
||||
writer1.putContent(REFLECTION_GET_CLASS);
|
||||
|
||||
try
|
||||
{
|
||||
scriptService.executeScript(contentNodeRef1, BaseNodeServiceTest.PROP_QNAME_TEST_CONTENT, model);
|
||||
fail("execution of nonsecure script on nodeRef is not allowed.");
|
||||
}
|
||||
catch (AlfrescoRuntimeException ex)
|
||||
{
|
||||
// expected
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private static final String TESTSCRIPT_CLASSPATH1 = "org/alfresco/repo/jscript/test_script1.js";
|
||||
private static final String TESTSCRIPT_CLASSPATH2 = "org/alfresco/repo/jscript/test_script2.js";
|
||||
private static final String TESTSCRIPT_CLASSPATH3 = "org/alfresco/repo/jscript/test_script3.js";
|
||||
@@ -378,4 +453,13 @@ public class RhinoScriptTest extends TestCase
|
||||
"logger.log(\"child by name path: \" + childByNameNode.name);\r\n" +
|
||||
"var xpathResults = root.childrenByXPath(\"/*\");\r\n" +
|
||||
"logger.log(\"children of root from xpath: \" + xpathResults.length);\r\n";
|
||||
|
||||
private static final String BASIC_JAVA =
|
||||
"var list = com.google.common.collect.Lists.newArrayList();\n" +
|
||||
"root.nodeRef.getClass().forName(\"java.lang.ProcessBuilder\")";
|
||||
|
||||
private static final String REFLECTION_GET_CLASS =
|
||||
"root.nodeRef.getClass().forName(\"java.lang.ProcessBuilder\")";
|
||||
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user