Skip to content

Commit

Permalink
feat(java): add SSRF rule (CWE-918) (#235)
Browse files Browse the repository at this point in the history
  • Loading branch information
elsapet authored Feb 7, 2024
1 parent 1a21afd commit a93cb6c
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 0 deletions.
86 changes: 86 additions & 0 deletions rules/java/lang/http_url_using_user_input.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
imports:
- java_shared_lang_user_input
patterns:
- pattern: |
$<USER_INPUT_URL>.$<METHOD>()
filters:
- variable: USER_INPUT_URL
detection: java_lang_http_url_using_user_input_url
scope: cursor
- variable: METHOD
values:
- connect
- GetContent
- openConnection
- openStream
- getContent
- pattern: |
new $<INET_SOCKET_ADDRESS>($<USER_INPUT>$<...>);
filters:
- variable: INET_SOCKET_ADDRESS
regex: \A(java\.net\.)?InetSocketAddress\z
- variable: USER_INPUT
detection: java_lang_http_url_using_user_input_user_input
scope: result
auxiliary:
- id: java_lang_http_url_using_user_input_url
patterns:
- pattern: new $<URI>($<USER_INPUT>);
filters:
- variable: URI
regex: \A(java\.net\.)?(URL|URI)\z
- variable: USER_INPUT
detection: java_lang_http_url_using_user_input_user_input
scope: result
- id: java_lang_http_url_using_user_input_user_input
sanitizer: java_lang_http_url_using_user_input_sanitizer
patterns:
- pattern: $<USER_INPUT>;
filters:
- variable: USER_INPUT
detection: java_shared_lang_user_input
scope: cursor
- id: java_lang_http_url_using_user_input_sanitizer
patterns:
- pattern: $<CLASS>.encode($<!>$<_>$<...>);
filters:
- variable: CLASS
regex: \A(java\.net\.)?URLEncoder\z
- pattern: $<PATH_SEGMENT_ESCAPER>.escape($<!>$<_>);
filters:
- variable: PATH_SEGMENT_ESCAPER
regex: \A((com\.google\.common\.net\.)?UrlEscapers\.)?urlPathSegmentEscaper\(\)\z
languages:
- java
metadata:
description: "Unsanitized user input in HTTP request (SSRF)"
remediation_message: |
## Description
Applications should not connect to locations formed from user input. This is bad security practice because it can lead to Server-Side-Request-Forgery (SSRF) attacks.
This rule checks for URLs containing user-supplied data.
## Remediations
❌ Avoid using user input in HTTP URLs:
```java
new URL(request.getParameter("someRandomUrl")).getContent();
```
✅ Use user input indirectly to form a URL:
```java
String url;
if (request.getParameter("selectedUrl") == "option1") {
url = "api1.com";
} else {
url = "api2.com";
}
new URL(url).getContent();
```
cwe_id:
- 918
id: java_lang_http_url_using_user_input
documentation_url: https://docs.bearer.com/reference/rules/java_lang_http_url_using_user_input
18 changes: 18 additions & 0 deletions tests/java/lang/http_url_using_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("http_url_using_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/http_url_using_user_input/testdata/main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import java.net.*;

public class Foo {
private static final int TIMEOUT_IN_SECONDS = 20;

public void bad(HttpServletRequest req, HttpServletResponse res) {
String dangerous = req.getParameter("someRandomUrl");
// bearer:expected java_lang_http_url_using_user_input
new URL(dangerous).openConnection().getInputStream();
// bearer:expected java_lang_http_url_using_user_input
new URL(dangerous).openConnection().getLastModified();
// bearer:expected java_lang_http_url_using_user_input
new URL(dangerous).openStream();
// bearer:expected java_lang_http_url_using_user_input
new URL(dangerous).getContent();
// bearer:expected java_lang_http_url_using_user_input
new URL(dangerous).getContent(new Class[0]);
URL bad = new URL(dangerous);
// bearer:expected java_lang_http_url_using_user_input
bad.openConnection().connect();
}

public void bad2(HttpServletRequest req, HttpServletResponse res) {
// bearer:expected java_lang_http_url_using_user_input
new URL("http://safe.com").openConnection(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(req.getParameter("someRandomUrl"), 8080))).connect();

int port = url.getPort();
port = port > 0 ? port : 443;
try (Socket s = ctx.getSocketFactory().createSocket()) {
// bearer:expected java_lang_http_url_using_user_input
InetSocketAddress socketAddress = new InetSocketAddress(req.getRequestURI(), port);
}
}

public static void good(HttpServletRequest req, HttpServletResponse res) {
String encodedString = URLEncoder.encode(req.getRequestURI(), StandardCharsets.UTF_8);
new URL(encodedString).openConnection();

new URL("http://safe.com").openConnection();
}
}

0 comments on commit a93cb6c

Please sign in to comment.