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 java.util.Properties; 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); /** The name of the global enablement property. */ public static final String PROPERTY_UNSERIALIZER_VALIDATOR_ENABLED = "unserializer.validator.enabled"; private static final String ERR_UNEXPECTED_ERROR = "unserializer.validator.err.unexpectederror"; // Bootstrap performed? private boolean bootstrapPerformed = false; private Properties properties = null; /** * @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; } public void setProperties(Properties properties) { this.properties = properties; } 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; } private void validate() { 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."); } } private boolean isUnserializerValidatorEnabled() { return getBooleanProperty(PROPERTY_UNSERIALIZER_VALIDATOR_ENABLED, true); } private boolean getBooleanProperty(String name, boolean defaultValue) { boolean value = defaultValue; if (properties != null) { String property = properties.getProperty(name); if (property != null) { value = !property.trim().equalsIgnoreCase("false"); } } return value; } /** * Bootstrap unserializer validator. */ public void bootstrap() { if (isUnserializerValidatorEnabled()) { validate(); } else { logger.warn("Unserializer validator is disabled"); } // a bootstrap was performed bootstrapPerformed = true; } @Override protected void onBootstrap(ApplicationEvent event) { bootstrap(); } @Override protected void onShutdown(ApplicationEvent event) { // NOOP } }