1
2
3
4
5
6
7
8
9
10
11
12
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
32
33
34
35
36
37
38
39
40
41
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
63
64
65
66
67
68
69
70
71
72
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
118
119
120
121
122
123
124
125
126
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 }