Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(java): deserialization of user input (CWE-502) #237

Merged
merged 1 commit into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
}
}
Loading