diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 8bbdd6869c..deac0d0a46 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -107,6 +107,9 @@ + + + diff --git a/source/java/org/alfresco/repo/admin/UnserializerValidatorBootstrap.java b/source/java/org/alfresco/repo/admin/UnserializerValidatorBootstrap.java new file mode 100644 index 0000000000..4590bef764 --- /dev/null +++ b/source/java/org/alfresco/repo/admin/UnserializerValidatorBootstrap.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.admin; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; + +/** + * Bootstrap unserializer validator: a bootstrap bean that checks that the + * classes that would favor Java unserialize remote code execution are not + * available. Check is needed because libs could be introduced by the + * application server. + * + *

See MNT-15170 for details. + * + *

Checked conditions:
+ * org.apache.xalan.xsltc.trax.TemplatesImpl and + * org.springframework.core.SerializableTypeWrapper;
+ * org.apache.commons.collections.functors.InvokerTransformer + * org.apache.commons.collections.functors.InstantiateFactory + * org.apache.commons.collections.functors.InstantiateTransformer + * org.apache.commons.collections.functors.PrototypeCloneFactory + * org.apache.commons.collections.functors.PrototypeSerializationFactory + * org.apache.commons.collections.functors.WhileClosure + * org.apache.commons.collections.functors.CloneTransformer + * org.apache.commons.collections.functors.ForClosure + */ +public class UnserializerValidatorBootstrap extends AbstractLifecycleBean +{ + + private static Log logger = LogFactory.getLog(UnserializerValidatorBootstrap.class); + + private static final String ERR_UNEXPECTED_ERROR = "unserializer.validator.err.unexpectederror"; + + // Bootstrap performed? + private boolean bootstrapPerformed = false; + + /** + * @deprecated Was never used + */ + public void setLog(boolean logEnabled) + { + // Ignore + } + + /** + * Determine if bootstrap was performed? + * + * @return true => bootstrap was performed + */ + public boolean hasPerformedBootstrap() + { + return bootstrapPerformed; + } + + private boolean classInPath(String className) + { + try + { + Class.forName(className, false, this.getClass().getClassLoader()); + + // it exists on the classpath + return true; + } + catch (ClassNotFoundException e) + { + + // it does not exist on the classpath + return false; + } + } + + /** + * Check if Java unserialize remote code execution is already fixed on this + * commons collections version. + * + * @return + */ + private boolean isCommonsCollectionsDeserializerFixed() + { + + try + { + Class invokerTransformerClass = Class.forName( + "org.apache.commons.collections.functors.InvokerTransformer", true, this + .getClass().getClassLoader()); + + if (invokerTransformerClass != null) + { + Constructor invokerTransformerConstructor = invokerTransformerClass + .getConstructor(String.class, Class[].class, Object[].class); + + Object invokerTransformerInstance = invokerTransformerConstructor.newInstance(null, + null, null); + + ObjectOutputStream objectOut = null; + ByteArrayOutputStream byteOut = null; + try + { + // Write the object out to a byte array + byteOut = new ByteArrayOutputStream(); + objectOut = new ObjectOutputStream(byteOut); + objectOut.writeObject(invokerTransformerInstance); + objectOut.flush(); + } + catch (UnsupportedOperationException e) + { + // Expected: Serialization support is disabled for security + // reasons. + return true; + } + catch (IOException e) + { + throw new AlfrescoRuntimeException(ERR_UNEXPECTED_ERROR, e); + } + finally + { + if (objectOut != null) + { + try + { + objectOut.close(); + } + catch (Throwable e) + { + } + } + if (byteOut != null) + { + try + { + byteOut.close(); + } + catch (Throwable e) + { + } + } + } + } + } + catch (SecurityException e) + { + // This is and expected, acceptable exception that we can ignore. + } + catch (ClassNotFoundException e) + { + // This is and expected, acceptable exception that we can ignore. + } + catch (InstantiationException e) + { + // This is and expected, acceptable exception that we can ignore. + } + catch (IllegalAccessException e) + { + // This is and expected, acceptable exception that we can ignore. + } + catch (NoSuchMethodException e) + { + throw new AlfrescoRuntimeException(ERR_UNEXPECTED_ERROR, e); + } + catch (IllegalArgumentException e) + { + throw new AlfrescoRuntimeException(ERR_UNEXPECTED_ERROR, e); + } + catch (InvocationTargetException e) + { + // This is and expected, acceptable exception that we can ignore. + } + + return false; + } + + /** + * Bootstrap unserializer validator. + */ + public void bootstrap() + { + if (classInPath("org.apache.xalan.xsltc.trax.TemplatesImpl") && classInPath("org.springframework.core.SerializableTypeWrapper")) + { + throw new AlfrescoRuntimeException( + "Bootstrap failed: both org.apache.xalan.xsltc.trax.TemplatesImpl and org.springframework.core.SerializableTypeWrapper appear at the same time in classpath "); + } + + // Check if Java unserialize remote code execution is available and not + // fixed on this commons collections + if ((classInPath("org.apache.commons.collections.functors.InvokerTransformer") + || classInPath("org.apache.commons.collections.functors.InstantiateFactory") + || classInPath("org.apache.commons.collections.functors.InstantiateTransformer") + || classInPath("org.apache.commons.collections.functors.PrototypeCloneFactory") + || classInPath("org.apache.commons.collections.functors.PrototypeSerializationFactory") + || classInPath("org.apache.commons.collections.functors.WhileClosure") + || classInPath("org.apache.commons.collections.functors.CloneTransformer") || classInPath("org.apache.commons.collections.functors.ForClosure")) + && !isCommonsCollectionsDeserializerFixed()) + { + throw new AlfrescoRuntimeException( + "Bootstrap failed: org.apache.commons.collections.functors.* unsafe serialization classes found in classpath."); + } + + // a bootstrap was performed + bootstrapPerformed = true; + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + bootstrap(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // NOOP + } + +}