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