001/** 002 * Copyright (C) 2006-2025 Talend Inc. - www.talend.com 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.talend.sdk.component.runtime.serialization; 017 018import static java.util.Optional.ofNullable; 019import static java.util.stream.Collectors.toList; 020import static java.util.stream.Collectors.toSet; 021import static lombok.AccessLevel.PRIVATE; 022 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.ObjectInputStream; 026import java.io.ObjectStreamClass; 027import java.lang.reflect.Proxy; 028import java.util.Collection; 029import java.util.function.Predicate; 030import java.util.stream.Stream; 031 032import lombok.NoArgsConstructor; 033import lombok.extern.slf4j.Slf4j; 034 035public class EnhancedObjectInputStream extends ObjectInputStream { 036 037 private final ClassLoader loader; 038 039 private final Predicate<String> classesWhitelist; 040 041 protected EnhancedObjectInputStream(final InputStream in, final ClassLoader loader, final Predicate<String> filter) 042 throws IOException { 043 super(in); 044 this.loader = loader; 045 this.classesWhitelist = filter; 046 } 047 048 public EnhancedObjectInputStream(final InputStream in, final ClassLoader loader) throws IOException { 049 this(in, loader, Defaults.SECURITY_FILTER_WHITELIST); 050 } 051 052 @Override 053 protected Class<?> resolveClass(final ObjectStreamClass desc) throws ClassNotFoundException { 054 final String name = desc.getName(); 055 if (name.equals("boolean")) { 056 return boolean.class; 057 } 058 if (name.equals("byte")) { 059 return byte.class; 060 } 061 if (name.equals("char")) { 062 return char.class; 063 } 064 if (name.equals("short")) { 065 return short.class; 066 } 067 if (name.equals("int")) { 068 return int.class; 069 } 070 if (name.equals("long")) { 071 return long.class; 072 } 073 if (name.equals("float")) { 074 return float.class; 075 } 076 if (name.equals("double")) { 077 return double.class; 078 } 079 080 doSecurityCheck(name); 081 082 try { 083 return Class.forName(name, false, loader); 084 } catch (final ClassNotFoundException e) { 085 // try again from beam classloader for complex classloader graphs, 086 // really a fallback mode 087 return Class.forName(name, false, getClass().getClassLoader()); 088 } 089 } 090 091 @Override 092 protected Class<?> resolveProxyClass(final String[] interfaces) throws ClassNotFoundException { 093 final Class[] interfaceTypes = new Class[interfaces.length]; 094 for (int i = 0; i < interfaces.length; i++) { 095 doSecurityCheck(interfaces[i]); 096 interfaceTypes[i] = Class.forName(interfaces[i], false, loader); 097 } 098 099 try { 100 return Proxy.getProxyClass(loader, interfaceTypes); 101 } catch (final IllegalArgumentException e) { 102 throw new ClassNotFoundException(null, e); 103 } 104 } 105 106 private void doSecurityCheck(final String name) { 107 if (!classesWhitelist.test(processForWhiteListing(name))) { 108 throw new SecurityException("'" + name + "' not supported, add it in " 109 + "-Dtalend.component.runtme.serialization.java" + ".inputstream.whitelist if needed"); 110 } 111 } 112 113 private String processForWhiteListing(final String name) { 114 if (name.startsWith("[L") && name.endsWith(";")) { 115 return name.substring(2, name.length() - 1); 116 } 117 return name; 118 } 119 120 @Slf4j 121 @NoArgsConstructor(access = PRIVATE) 122 static class Defaults { 123 124 // IMPORTANT: this MUST come from the JVM and not from a SPI which could enable to break that! 125 static final Predicate<String> SECURITY_FILTER_WHITELIST = 126 ofNullable(System.getProperty("talend.component.runtime.serialization.java.inputstream.whitelist")) 127 .map(s -> Stream 128 .of(s.split(",")) 129 .map(String::trim) 130 .filter(it -> !it.isEmpty()) 131 .collect(toList())) 132 .map(l -> (Predicate<String>) name -> l.stream().anyMatch(name::startsWith)) 133 .orElseGet(() -> { 134 final Collection<String> blacklist = Stream 135 .of("org.codehaus.groovy.runtime.", "org.apache.commons.collections.functors.", 136 "org.apache.commons.collections4.functors.", "org.apache.xalan", 137 "java.lang.Process", "java.util.logging.", "java.rmi.server.", 138 "com.sun.org.apache.xalan.internal.xsltc.trax.", "com.sun.rowset.", 139 "org.springframework.beans.factory.config.", 140 "org.apache.tomcat.dbcp.dbcp2.", "com.sun.org.apache.bcel.internal.util.", 141 "org.hibernate.jmx.", "org.apache.ibatis.", "jodd.db.connection.", 142 "oracle.jdbc.", "org.slf4j.ext.", "flex.messaging.util.concurrent.", 143 "com.sun.deploy.security.ruleset.", "org.apache.axis2.jaxws.spi.handler.", 144 "org.apache.axis2.transport.jms.", "org.jboss.util.propertyeditor.", 145 "org.apache.openjpa.ee.") 146 .collect(toSet()); 147 log 148 .warn("talend.component.runtime.serialization.java.inputstream.whitelist " 149 + "system property not set, " 150 + "will use default blacklist but this is not considered as a secure setup. " 151 + "Blacklisted packages: {}", blacklist); 152 return name -> blacklist.stream().noneMatch(name::startsWith); 153 }); 154 } 155}