View Javadoc
1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v2.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.net;
15  
16  import ch.qos.logback.core.Context;
17  import ch.qos.logback.core.spi.ContextAwareImpl;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.InvalidClassException;
22  import java.io.ObjectInputFilter;
23  import java.io.ObjectInputStream;
24  import java.io.ObjectStreamClass;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.HashMap;
28  import java.util.List;
29  
30  /**
31   * HardenedObjectInputStream restricts the set of classes that can be
32   * deserialized to a set of explicitly whitelisted classes. This prevents
33   * certain type of attacks from being successful.
34   * 
35   * <p>
36   * It is assumed that classes in the "java.lang" and "java.util" packages are
37   * always authorized.
38   * </p>
39   * 
40   * @author Ceki G&uuml;lc&uuml;
41   * @since 1.2.0
42   */
43  public class HardenedObjectInputStream extends ObjectInputStream {
44  
45      final private List<String> whitelistedClassNames;
46      final private static String[] JAVA_CLASSES = new String[] { "java.lang.Boolean",
47              "java.lang.Byte",
48              "java.lang.Character",
49              "java.lang.Double",
50              "java.lang.Float",
51              "java.lang.Integer",
52              "java.lang.Long",
53              "java.lang.Number",
54              "java.lang.Short",
55              "java.lang.String",
56              "java.lang.Throwable",
57              "java.util.ArrayList",
58              "java.util.Collections$EmptyMap",
59              "java.util.Collections$UnmodifiableMap",
60              "java.util.concurrent.CopyOnWriteArrayList",
61              "java.util.HashMap"
62              //"java.util.HashSet",
63              //"java.util.Hashtable",
64  
65              // PASS
66              //"java.util.LinkedHashMap",
67              //"java.util.LinkedHashSet",
68              //"java.util.LinkedList",
69              //"java.util.Stack",
70              //"java.util.TreeMap",
71              //"java.util.TreeSet",
72              //"java.util.Vector"
73      };
74      final private static int DEPTH_LIMIT = 16;
75      final private static int ARRAY_LIMIT = 10000;
76      final private static int ERROR_COUNT_LIMIT = 10;
77  
78      final private ContextAwareImpl contextAware;
79      final private HashMap<String, Integer> errorMap = new HashMap<>();
80  
81      public HardenedObjectInputStream(Context context, InputStream in, String[] whitelistStrings) throws IOException {
82        this(context, in, Arrays.asList(whitelistStrings));
83      }
84      public HardenedObjectInputStream(Context context, InputStream in, List<String> whitelist) throws IOException {
85          super(in);
86  
87          if(context != null)
88              this.contextAware = new ContextAwareImpl(context, this);
89           else
90              this.contextAware = null;
91  
92          this.initObjectFilter();
93          this.whitelistedClassNames = new ArrayList<String>();
94          this.whitelistedClassNames.addAll(whitelist);
95      }
96  
97  
98      private void initObjectFilter() {
99          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 }