Skip to content

Commit

Permalink
feat(java): deserialization of user input
Browse files Browse the repository at this point in the history
  • Loading branch information
elsapet committed Feb 7, 2024
1 parent a93cb6c commit d085112
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 0 deletions.
94 changes: 94 additions & 0 deletions rules/java/lang/deserialization_of_user_input.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
imports:
- java_shared_lang_instance
- java_shared_lang_user_input
patterns:
- pattern: $<XML_DECODER_WITH_USER_INPUT>.readObject();
filters:
- variable: XML_DECODER_WITH_USER_INPUT
detection: java_lang_deserialization_of_user_input_xml_decoder_with_user_input
- pattern: $<XSTREAM>.fromXML($<INPUT>);
filters:
- variable: XSTREAM
detection: java_shared_lang_instance
scope: cursor
filters:
- variable: JAVA_SHARED_LANG_INSTANCE_TYPE
regex: \A(com\.thoughtworks\.xstream\.)?(XStream)\z
- variable: INPUT
detection: java_lang_deserialization_of_user_input_source
scope: result
- pattern: $<OBJECT_INPUT_STREAM>.read();
filters:
- variable: OBJECT_INPUT_STREAM
detection: java_lang_deserialization_of_user_input_object_input_stream_with_user_input
auxiliary:
- id: java_lang_deserialization_of_user_input_xml_decoder_with_user_input
patterns:
- pattern: new $<XML_DECODER>($<INPUT>);
filters:
- variable: XML_DECODER
regex: \A(javax\.beans\.)?XMLDecoder\z
- variable: INPUT
detection: java_lang_deserialization_of_user_input_source
scope: result
- id: java_lang_deserialization_of_user_input_object_input_stream_with_user_input
patterns:
- pattern: new $<OBJECT_INPUT_STREAM>($<INPUT>);
filters:
- variable: OBJECT_INPUT_STREAM
regex: \A(java\.io\.)?ObjectInputStream\z
- variable: INPUT
detection: java_lang_deserialization_of_user_input_source
scope: result
- id: java_lang_deserialization_of_user_input_source
patterns:
- pattern: $<USER_INPUT>;
filters:
- variable: USER_INPUT
detection: java_shared_lang_user_input
scope: result
- pattern: $<INPUT_STREAM>;
filters:
- variable: INPUT_STREAM
detection: java_shared_lang_instance
scope: cursor
filters:
- variable: JAVA_SHARED_LANG_INSTANCE_TYPE
regex: \A(java\.io\.)?(FileInputStream|InputStream)\z
- not:
variable: INPUT_STREAM
detection: java_lang_deserialization_of_user_input_known_resource
- id: java_lang_deserialization_of_user_input_known_resource
patterns:
- pattern: XmlDecodeUtil.class.getResourceAsStream()
languages:
- java
metadata:
description: Unsanitized user input in deserialization method
remediation_message: |
## Description
It is bad practice to deserialize untrusted data, such as data that comes
from params or cookies, without sufficient verification. Attackers can
transfer payloads or malicious code via serialized data, and deserializing
such data puts your application at risk.
## Remediations
❌ Do not deserialize untrusted data
❌ Avoid `XMLEncoder` and `XMLDecoder` classes, as these are not recommended
✅ Prefer pure (data-only) and language-agnostic (de)serialization formats such as JSON. Avoiding language-specific (de)serialization formats reduces the risk of attackers manipulating the deserialization process for malicious purposes.
✅ Prefer (de)serialization methods that allow you to specify the object types that are allowed to be deserialized
❌ Never permit the (de)serialization of base object types (like `Object`).
## Resources
- [OWASP XEE prevention cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#java)
- [OWASP Deserialization cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html)
cwe_id:
- 502
id: java_lang_deserialization_of_user_input
documentation_url: https://docs.bearer.com/reference/rules/java_lang_deserialization_of_user_input
18 changes: 18 additions & 0 deletions tests/java/lang/deserialization_of_user_input/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const {
createNewInvoker,
getEnvironment,
} = require("../../../helper.js")
const { ruleId, ruleFile, testBase } = getEnvironment(__dirname)

describe(ruleId, () => {
const invoke = createNewInvoker(ruleId, ruleFile, testBase)

test("deserialization_of_user_input", () => {
const testCase = "main.java"

const results = invoke(testCase)

expect(results.Missing).toEqual([])
expect(results.Extra).toEqual([])
})
})
41 changes: 41 additions & 0 deletions tests/java/lang/deserialization_of_user_input/testdata/main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Use bearer:expected java_lang_deserialization_of_user_input to flag expected findings

import java.beans.XMLDecoder;
import java.io.InputStream;
import java.io.ObjectInputStream;
import com.thoughtworks.xstream.XStream;

public class Foo {
public static void bad(InputStream in) {
XMLDecoder d = new XMLDecoder(in);
// bearer:expected java_lang_deserialization_of_user_input
Object result = d.readObject();
d.close();
}

public static void bad2(HttpServletRequest req, HttpServletResponse res) {
XMLDecoder d = new XMLDecoder(req.getInputStream());
// bearer:expected java_lang_deserialization_of_user_input
Object result = d.readObject();
d.close();
}

public Object bad3(InputStream in) {
XStream xs = new XStream();
// bearer:expected java_lang_deserialization_of_user_input
return xs.fromXML(in);
}

protected void bad4(HttpServletRequest req, HttpServletResponse res) {
ObjectInputStream s = new ObjectInputStream(req.getInputStream());
// bearer:expected java_lang_deserialization_of_user_input
Object result = s.read();
}

public static void ok(String filename) {
InputStream in = XmlDecodeUtil.class.getResourceAsStream(filename);
XMLDecoder d = new XMLDecoder(in);
Object result = d.readObject();
d.close();
}
}

0 comments on commit d085112

Please sign in to comment.