001/* 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2026, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v2.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.core.net; 015 016import ch.qos.logback.core.Context; 017import ch.qos.logback.core.spi.ContextAwareImpl; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.InvalidClassException; 022import java.io.ObjectInputFilter; 023import java.io.ObjectInputStream; 024import java.io.ObjectStreamClass; 025import java.util.ArrayList; 026import java.util.Arrays; 027import java.util.HashMap; 028import java.util.List; 029 030/** 031 * HardenedObjectInputStream restricts the set of classes that can be 032 * deserialized to a set of explicitly whitelisted classes. This prevents 033 * certain type of attacks from being successful. 034 * 035 * <p> 036 * It is assumed that classes in the "java.lang" and "java.util" packages are 037 * always authorized. 038 * </p> 039 * 040 * @author Ceki Gülcü 041 * @since 1.2.0 042 */ 043public class HardenedObjectInputStream extends ObjectInputStream { 044 045 final private List<String> whitelistedClassNames; 046 final private static String[] JAVA_CLASSES = new String[] { "java.lang.Boolean", 047 "java.lang.Byte", 048 "java.lang.Character", 049 "java.lang.Double", 050 "java.lang.Float", 051 "java.lang.Integer", 052 "java.lang.Long", 053 "java.lang.Number", 054 "java.lang.Short", 055 "java.lang.String", 056 "java,lang.Throwable", 057 "java.util.ArrayList", 058 "java.util.Collections$EmptyMap", 059 "java.util.Collections$UnmodifiableMap", 060 "java.util.concurrent.CopyOnWriteArrayList", 061 "java.util.HashMap" 062 //"java.util.HashSet", 063 //"java.util.Hashtable", 064 065 // PASS 066 //"java.util.LinkedHashMap", 067 //"java.util.LinkedHashSet", 068 //"java.util.LinkedList", 069 //"java.util.Stack", 070 //"java.util.TreeMap", 071 //"java.util.TreeSet", 072 //"java.util.Vector" 073 }; 074 final private static int DEPTH_LIMIT = 16; 075 final private static int ARRAY_LIMIT = 10000; 076 final private static int ERROR_COUNT_LIMIT = 10; 077 078 final private ContextAwareImpl contextAware; 079 final private HashMap<String, Integer> errorMap = new HashMap<>(); 080 081 public HardenedObjectInputStream(Context context, InputStream in, String[] whitelistStrings) throws IOException { 082 this(context, in, Arrays.asList(whitelistStrings)); 083 } 084 public HardenedObjectInputStream(Context context, InputStream in, List<String> whitelist) throws IOException { 085 super(in); 086 087 if(context != null) 088 this.contextAware = new ContextAwareImpl(context, this); 089 else 090 this.contextAware = null; 091 092 this.initObjectFilter(); 093 this.whitelistedClassNames = new ArrayList<String>(); 094 this.whitelistedClassNames.addAll(whitelist); 095 } 096 097 098 private void initObjectFilter() { 099 this.setObjectInputFilter(ObjectInputFilter.Config.createFilter( 100 "maxarray=" + ARRAY_LIMIT + ";maxdepth=" + DEPTH_LIMIT + ";" 101 )); 102 } 103 104 @Override 105 protected Class<?> resolveClass(ObjectStreamClass anObjectStreamClass) throws IOException, ClassNotFoundException { 106 107 String incomingClassName = anObjectStreamClass.getName(); 108 109 if (!isWhitelisted(incomingClassName)) { 110 throw new InvalidClassException("Unauthorized deserialization attempt", anObjectStreamClass.getName()); 111 } 112 113 return super.resolveClass(anObjectStreamClass); 114 } 115 116 /** 117 * There is no reason to have proxy classes in logback deserialization, so we just 118 * throw an exception here to prevent any potential bypasses that could be achieved 119 * through proxy classes. 120 * 121 * @param interfaces the list of interface names that were 122 * deserialized in the proxy class descriptor 123 * @return 124 * @throws IOException 125 * @throws ClassNotFoundException 126 * @since 1.5.34 127 */ 128 @Override 129 protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException { 130 throw new InvalidClassException("Unauthorized deserialization attempt ", Arrays.toString(interfaces)); 131 } 132 133 private boolean isWhitelisted(String incomingClassName) { 134 for (String javaClass : JAVA_CLASSES) { 135 if (incomingClassName.equals(javaClass)) 136 return true; 137 } 138 for (String whiteListed : whitelistedClassNames) { 139 if (incomingClassName.equals(whiteListed)) 140 return true; 141 } 142 143 144 int errorCount = errorMap.getOrDefault(incomingClassName, 0) + 1; 145 errorMap.put(incomingClassName, errorCount); 146 if(contextAware != null && errorCount < ERROR_COUNT_LIMIT) { 147 contextAware.addError("Unauthorized deserialization attempt for class [" + incomingClassName+"]"); 148 contextAware.addError(("If you deem the class to be legitimate, please contact the project maintainers to have it whitelisted.")); 149 } 150 151 return false; 152 } 153 154 protected void addToWhitelist(List<String> additionalAuthorizedClasses) { 155 whitelistedClassNames.addAll(additionalAuthorizedClasses); 156 } 157}