• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 Code Intelligence GmbH
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 package jaz;
16 
17 import com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh;
18 import com.code_intelligence.jazzer.api.Jazzer;
19 import java.io.*;
20 import java.util.*;
21 import java.util.concurrent.Callable;
22 import java.util.function.Function;
23 
24 /**
25  * A honeypot class that reports a finding on initialization.
26  *
27  * Class loading based on externally controlled data could lead to RCE
28  * depending on available classes on the classpath. Even if no applicable
29  * gadget class is available, allowing input to control class loading is a bad
30  * idea and should be prevented. A finding is generated whenever the class
31  * is loaded and initialized, regardless of its further use.
32  * <p>
33  * This class needs to implement {@link Serializable} to be considered in
34  * deserialization scenarios. It also implements common constructors, getter
35  * and setter and common interfaces to increase chances of passing
36  * deserialization checks.
37  * <p>
38  * <b>Note</b>: Jackson provides a nice list of "nasty classes" at
39  * <a
40  * href=https://github.com/FasterXML/jackson-databind/blob/2.14/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/SubTypeValidator.java>SubTypeValidator</a>.
41  * <p>
42  * <b>Note</b>: This class must not be referenced in any way by the rest of the code, not even
43  * statically. When referring to it, always use its hardcoded class name {@code jaz.Zer}.
44  */
45 @SuppressWarnings({"rawtypes", "unused"})
46 public class Zer
47     implements Serializable, Cloneable, Comparable<Zer>, Comparator, Closeable, Flushable, Iterable,
48                Iterator, Runnable, Callable, Function, Collection, List {
49   static final long serialVersionUID = 42L;
50 
51   // serialized size is 41 bytes
52   private static final byte REFLECTIVE_CALL_SANITIZER_ID = 0;
53   private static final byte DESERIALIZATION_SANITIZER_ID = 1;
54   private static final byte EXPRESSION_LANGUAGE_SANITIZER_ID = 2;
55 
56   // A byte representing the relevant sanitizer for a given jaz.Zer instance. It is used to check
57   // whether the corresponding sanitizer is disabled and jaz.Zer will not report a finding in this
58   // case. Each sanitizer which relies on this class must set this byte accordingly. We choose a
59   // single byte to represent the sanitizer in order to keep the serialized version of jaz.Zer
60   // objects small (currently 41 bytes) so that it fits in the 64 byte limit of the words that can
61   // be used with Jazzer's methods that guide the fuzzer towards generating inputs that contain or
62   // are equal to target strings. This limit comes from the corresponding libFuzzer hooks that
63   // Jazzer uses under the hood.
64   private byte sanitizer = REFLECTIVE_CALL_SANITIZER_ID;
65 
66   // Common constructors
Zer()67   public Zer() {
68     reportFindingIfEnabled();
69   }
70 
Zer(String arg1)71   public Zer(String arg1) {
72     reportFindingIfEnabled();
73   }
74 
Zer(String arg1, Throwable arg2)75   public Zer(String arg1, Throwable arg2) {
76     reportFindingIfEnabled();
77   }
78 
Zer(byte sanitizer)79   public Zer(byte sanitizer) {
80     this.sanitizer = sanitizer;
81     reportFindingIfEnabled();
82   }
83 
84   // A special static method that is called by the expression language injection sanitizer. We
85   // choose a parameterless method to keep the string that the sanitizer guides the fuzzer to
86   // generate within the 64-byte boundary required by the corresponding guiding methods.
el()87   public static void el() {
88     if (isSanitizerEnabled(EXPRESSION_LANGUAGE_SANITIZER_ID)) {
89       reportFinding();
90     }
91   }
92 
reportFindingIfEnabled()93   private void reportFindingIfEnabled() {
94     if (isSanitizerEnabled(sanitizer)) {
95       reportFinding();
96     }
97   }
98 
reportFinding()99   private static void reportFinding() {
100     Jazzer.reportFindingFromHook(new FuzzerSecurityIssueHigh("Remote Code Execution\n"
101         + "Unrestricted class/object creation based on externally controlled data may allow\n"
102         + "remote code execution depending on available classes on the classpath."));
103   }
104 
isSanitizerEnabled(byte sanitizerId)105   private static boolean isSanitizerEnabled(byte sanitizerId) {
106     String allDisabledHooks = System.getProperty("jazzer.disabled_hooks");
107     if (allDisabledHooks == null || allDisabledHooks.equals("")) {
108       return true;
109     }
110 
111     String sanitizer;
112     switch (sanitizerId) {
113       case DESERIALIZATION_SANITIZER_ID:
114         sanitizer = "com.code_intelligence.jazzer.sanitizers.Deserialization";
115         break;
116       case EXPRESSION_LANGUAGE_SANITIZER_ID:
117         sanitizer = "com.code_intelligence.jazzer.sanitizers.ExpressionLanguageInjection";
118         break;
119       default:
120         sanitizer = "com.code_intelligence.jazzer.sanitizers.ReflectiveCall";
121     }
122     return Arrays.stream(allDisabledHooks.split(",")).noneMatch(sanitizer::equals);
123   }
124 
125   // Getter/Setter
126 
getJaz()127   public Object getJaz() {
128     reportFindingIfEnabled();
129     return this;
130   }
131 
setJaz(String jaz)132   public void setJaz(String jaz) {
133     reportFindingIfEnabled();
134   }
135 
136   @Override
hashCode()137   public int hashCode() {
138     reportFindingIfEnabled();
139     return super.hashCode();
140   }
141 
142   @Override
equals(Object obj)143   public boolean equals(Object obj) {
144     reportFindingIfEnabled();
145     return super.equals(obj);
146   }
147 
148   @Override
toString()149   public String toString() {
150     reportFindingIfEnabled();
151     return super.toString();
152   }
153 
154   // Common interface stubs
155 
156   @Override
close()157   public void close() {
158     reportFindingIfEnabled();
159   }
160 
161   @Override
flush()162   public void flush() {
163     reportFindingIfEnabled();
164   }
165 
166   @Override
compareTo(Zer o)167   public int compareTo(Zer o) {
168     reportFindingIfEnabled();
169     return 0;
170   }
171 
172   @Override
compare(Object o1, Object o2)173   public int compare(Object o1, Object o2) {
174     reportFindingIfEnabled();
175     return 0;
176   }
177 
178   @Override
size()179   public int size() {
180     reportFindingIfEnabled();
181     return 0;
182   }
183 
184   @Override
isEmpty()185   public boolean isEmpty() {
186     reportFindingIfEnabled();
187     return false;
188   }
189 
190   @Override
contains(Object o)191   public boolean contains(Object o) {
192     reportFindingIfEnabled();
193     return false;
194   }
195 
196   @Override
toArray()197   public Object[] toArray() {
198     reportFindingIfEnabled();
199     return new Object[0];
200   }
201 
202   @Override
add(Object o)203   public boolean add(Object o) {
204     reportFindingIfEnabled();
205     return false;
206   }
207 
208   @Override
remove(Object o)209   public boolean remove(Object o) {
210     reportFindingIfEnabled();
211     return false;
212   }
213 
214   @Override
addAll(Collection c)215   public boolean addAll(Collection c) {
216     reportFindingIfEnabled();
217     return false;
218   }
219 
220   @Override
addAll(int index, Collection c)221   public boolean addAll(int index, Collection c) {
222     reportFindingIfEnabled();
223     return false;
224   }
225 
226   @Override
clear()227   public void clear() {
228     reportFindingIfEnabled();
229   }
230 
231   @Override
get(int index)232   public Object get(int index) {
233     reportFindingIfEnabled();
234     return this;
235   }
236 
237   @Override
set(int index, Object element)238   public Object set(int index, Object element) {
239     reportFindingIfEnabled();
240     return this;
241   }
242 
243   @Override
add(int index, Object element)244   public void add(int index, Object element) {
245     reportFindingIfEnabled();
246   }
247 
248   @Override
remove(int index)249   public Object remove(int index) {
250     reportFindingIfEnabled();
251     return this;
252   }
253 
254   @Override
indexOf(Object o)255   public int indexOf(Object o) {
256     reportFindingIfEnabled();
257     return 0;
258   }
259 
260   @Override
lastIndexOf(Object o)261   public int lastIndexOf(Object o) {
262     reportFindingIfEnabled();
263     return 0;
264   }
265 
266   @Override
267   @SuppressWarnings("ConstantConditions")
listIterator()268   public ListIterator listIterator() {
269     reportFindingIfEnabled();
270     return null;
271   }
272 
273   @Override
274   @SuppressWarnings("ConstantConditions")
listIterator(int index)275   public ListIterator listIterator(int index) {
276     reportFindingIfEnabled();
277     return null;
278   }
279 
280   @Override
subList(int fromIndex, int toIndex)281   public List subList(int fromIndex, int toIndex) {
282     reportFindingIfEnabled();
283     return this;
284   }
285 
286   @Override
retainAll(Collection c)287   public boolean retainAll(Collection c) {
288     reportFindingIfEnabled();
289     return false;
290   }
291 
292   @Override
removeAll(Collection c)293   public boolean removeAll(Collection c) {
294     reportFindingIfEnabled();
295     return false;
296   }
297 
298   @Override
containsAll(Collection c)299   public boolean containsAll(Collection c) {
300     reportFindingIfEnabled();
301     return false;
302   }
303 
304   @Override
toArray(Object[] a)305   public Object[] toArray(Object[] a) {
306     reportFindingIfEnabled();
307     return new Object[0];
308   }
309 
310   @Override
iterator()311   public Iterator iterator() {
312     reportFindingIfEnabled();
313     return this;
314   }
315 
316   @Override
run()317   public void run() {
318     reportFindingIfEnabled();
319   }
320 
321   @Override
hasNext()322   public boolean hasNext() {
323     reportFindingIfEnabled();
324     return false;
325   }
326 
327   @Override
next()328   public Object next() {
329     reportFindingIfEnabled();
330     return this;
331   }
332 
333   @Override
call()334   public Object call() throws Exception {
335     reportFindingIfEnabled();
336     return this;
337   }
338 
339   @Override
apply(Object o)340   public Object apply(Object o) {
341     reportFindingIfEnabled();
342     return this;
343   }
344 
345   @Override
346   @SuppressWarnings("MethodDoesntCallSuperMethod")
clone()347   public Object clone() {
348     reportFindingIfEnabled();
349     return this;
350   }
351 
352   // readObject calls can directly result in RCE, see https://github.com/frohoff/ysoserial for
353   // examples. Since deserialization doesn't call constructors (see
354   // https://docs.oracle.com/javase/7/docs/platform/serialization/spec/input.html#2971), we emit a
355   // finding right in the readObject method.
readObject(ObjectInputStream stream)356   private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
357     // Need to read in ourselves to initialize the sanitizer field.
358     stream.defaultReadObject();
359     reportFindingIfEnabled();
360   }
361 }
362