// Spring DataBinder + JDK9+ property navigation — pre-CVE-2022-22965 (Spring4Shell). // PropertyAccessor follows arbitrary nested setter chains from HTTP form params. // "class.module.classLoader.resources.context.parent.pipeline.first.pattern" // reaches Tomcat AccessLogValve.pattern → attacker-controlled JSP path → RCE. package org.springframework.beans; import javax.servlet.http.HttpServletRequest; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.util.Map; public final class DataBinderUnsafe { public void bind(Object target, HttpServletRequest req) throws Exception { Map params = req.getParameterMap(); for (Map.Entry e : params.entrySet()) { String path = e.getKey(); /* attacker-controlled */ String value = e.getValue()[0]; /* attacker-controlled */ setNestedProperty(target, path, value); } } /** * BUG: traverses dotted paths through ANY introspectable getter chain * without filtering "class"/"classLoader"/sensitive bean traversals. * Under JDK9+ where Class.module is exposed, an attacker can navigate * from any bean → getClass() → module → classLoader → ... → AccessLogValve * → set pattern to an attacker-chosen log path that writes JSP. */ private void setNestedProperty(Object root, String dottedPath, String value) throws Exception { String[] parts = dottedPath.split("\\."); Object cur = root; for (int i = 0; i < parts.length - 1; ++i) { cur = invokeGetter(cur, parts[i]); if (cur == null) return; } invokeSetter(cur, parts[parts.length - 1], value); } private Object invokeGetter(Object obj, String prop) throws Exception { PropertyDescriptor[] descs = Introspector.getBeanInfo(obj.getClass()).getPropertyDescriptors(); for (PropertyDescriptor d : descs) { if (d.getName().equals(prop)) { Method m = d.getReadMethod(); if (m != null) return m.invoke(obj); } } return null; } private void invokeSetter(Object obj, String prop, String value) throws Exception { PropertyDescriptor[] descs = Introspector.getBeanInfo(obj.getClass()).getPropertyDescriptors(); for (PropertyDescriptor d : descs) { if (d.getName().equals(prop)) { Method m = d.getWriteMethod(); if (m != null) { m.invoke(obj, value); return; } } } } }