Data which is untrusted cannot be trusted to be well formed. Malformed data or unexpected data could be used to abuse application logic, deny service, or execute arbitrary code, when deserialized.
Abstract:
Deserializing user-controlled object streams at runtime can allow attackers to execute arbitrary code on the server, abuse application logic, and/or lead to denial of service.
Explanation:
Java serialization turns object graphs into byte streams that contain the objects themselves and the necessary metadata to reconstruct them from the byte stream. Developers can create custom code to aid in the process of deserializing Java objects, where they can replace the deserialized objects with different objects, or proxies. The customized deserialization process takes place during objects reconstruction, before the objects are returned to the application and cast into expected types. By the time developers try to enforce an expected type, code may have already been executed.
Custom deserialization routines are defined in the serializable classes which need to be present in the runtime classpath and cannot be injected by the attacker so the exploitability of these attacks depends on the classes available in the application environment. Unfortunately, common third party classes or even JDK classes can be abused to exhaust JVM resources, deploy malicious files, or run arbitrary code.
Example 1: An application deserializing untrusted object streams can lead to application compromise.
InputStream is = request.getInputStream();
ObjectInputStream ois = new ObjectInputStream(is);
MyObject obj = (MyObject) ois.readObject();
Recommendations:
If possible, do not deserialize untrusted data without validating the contents of the object stream. In order to validate classes being deserialized, the look-ahead deserialization pattern should be used.
The object stream will first contain the class description metadata and then the serialized bytes of their member fields. The Java serialization process enables developers to read the class description and decide whether to proceed with the deserialization of the object or abort it. In order to do so, it is necessary to subclass java.io.ObjectInputStream and provide a custom implementation of the resolveClass(ObjectStreamClass desc) method where class validation and verification should take place.
There are existing implementations of the look-ahead pattern that can be easily used, such as the Apache Commons IO (org.apache.commons.io.serialization.ValidatingObjectInputStream). Always use a strict allow list approach to only deserialize expected types. A deny list approach is not recommended since attackers may use many available gadgets to bypass the deny list. Also, keep in mind that although some classes to achieve code execution are publicly known, there may be others that are unknown or undisclosed, so an allow list approach will always be preferred. Any class allowed in the allow list should be audited to make sure it is safe to deserialize.
When deserialization takes place in a library or framework (for example, when using JMX, RMI, JMS, HTTP Invokers), the preceding recommendation is not useful since it is beyond the developer's control. In those cases, you may want to make sure that these protocols meet the following requirements:
- Not exposed publicly.
- Use authentication.
- Use integrity checks.
- Use encryption.
In addition, Fortify Runtime provides security controls to be enforced every time the application performs a deserialization from an ObjectInputStream, protecting both application code but also library and framework code from this type of attack.
Tips:
1. Due to existing flaw in ObjectInputStream implementation and the difficulties of implementing a deny list for basic classes that may be used to perform Denial Of Service (DoS) attacks, this issue will be reported even if a look-ahead ObjectInputStream is implemented but its severity will be lowered to Medium.
0 comments:
Post a Comment