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