63 lines
2.6 KiB
Java
63 lines
2.6 KiB
Java
// 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<String, String[]> params = req.getParameterMap();
|
|
for (Map.Entry<String, String[]> 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; }
|
|
}
|
|
}
|
|
}
|
|
}
|