-
Notifications
You must be signed in to change notification settings - Fork 24
Add Java security queries: cleartext LDAP, insecure JDBC cert, credentials in URL, IDOR #151
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
Open
felickz
wants to merge
2
commits into
main
Choose a base branch
from
felickz/java-security-queries
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
26 changes: 26 additions & 0 deletions
26
java/src/security/CWE-295/InsecureJdbcCertificateValidation.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| # JDBC connection disables TLS certificate validation | ||
|
|
||
| A JDBC URL containing `trustServerCertificate=true` makes the driver accept any server certificate. Even when the URL also sets `encrypt=true`, the channel is then encrypted but unauthenticated, so a man-in-the-middle can present its own certificate, impersonate the database, and read or modify all traffic, including credentials. | ||
|
|
||
| ## Recommendation | ||
| Remove `trustServerCertificate=true` and let the driver validate the server certificate against a trusted certificate authority. If the database uses a private CA, import that CA into the client trust store rather than disabling validation. | ||
|
|
||
| ## Example | ||
| The following example builds a SQL Server JDBC URL that disables certificate validation. | ||
|
|
||
| ```java | ||
| // BAD: trustServerCertificate=true accepts any certificate (MITM risk). | ||
| private static final String JDBC_URL = | ||
| "jdbc:sqlserver://db01.corp.example.com:1433;databaseName=App;" | ||
| + "encrypt=true;trustServerCertificate=true;loginTimeout=5"; | ||
|
|
||
| try (Connection c = DriverManager.getConnection(JDBC_URL, user, password)) { | ||
| // ... | ||
| } | ||
| ``` | ||
|
|
||
| Use `encrypt=true;trustServerCertificate=false` and trust the server certificate through the client trust store. | ||
|
|
||
| ## References | ||
| * Microsoft: [Connecting with encryption (JDBC driver for SQL Server)](https://learn.microsoft.com/en-us/sql/connect/jdbc/connecting-with-ssl-encryption). | ||
| * Common Weakness Enumeration: [CWE-295](https://cwe.mitre.org/data/definitions/295.html). |
53 changes: 53 additions & 0 deletions
53
java/src/security/CWE-295/InsecureJdbcCertificateValidation.ql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| /** | ||
| * @name JDBC connection disables TLS certificate validation | ||
| * @description A JDBC URL containing `trustServerCertificate=true` makes the driver | ||
| * accept any server certificate. Even with `encrypt=true` the channel | ||
| * is then encrypted but unauthenticated, letting a man-in-the-middle | ||
| * impersonate the database. | ||
| * @kind problem | ||
| * @problem.severity warning | ||
| * @security-severity 7.5 | ||
| * @precision medium | ||
| * @id githubsecuritylab/java/jdbc-insecure-certificate | ||
| * @tags security | ||
| * external/cwe/cwe-295 | ||
| */ | ||
|
|
||
| import java | ||
|
|
||
| /** | ||
| * Holds if `e` evaluates to the constant string `v`, resolving a single field | ||
| * indirection and constant string concatenation. | ||
| */ | ||
| predicate constantStringValue(Expr e, string v) { | ||
| v = e.(CompileTimeConstantExpr).getStringValue() | ||
| or | ||
| exists(Variable var | | ||
| e = var.getAnAccess() and | ||
| v = var.getAnAssignedValue().(CompileTimeConstantExpr).getStringValue() | ||
| ) | ||
| } | ||
|
|
||
| /** A call that opens or configures a JDBC connection from a URL argument. */ | ||
| class JdbcUrlSink extends MethodCall { | ||
| Expr urlArg; | ||
|
|
||
| JdbcUrlSink() { | ||
| this.getMethod().hasName("getConnection") and | ||
| this.getMethod().getDeclaringType().hasQualifiedName("java.sql", "DriverManager") and | ||
| urlArg = this.getArgument(0) | ||
| or | ||
| this.getMethod().hasName(["setUrl", "setJdbcUrl"]) and | ||
| this.getQualifier().getType().(RefType).getName().matches(["%DataSource%", "%Config%"]) and | ||
| urlArg = this.getArgument(0) | ||
| } | ||
|
|
||
| Expr getUrlArg() { result = urlArg } | ||
| } | ||
|
|
||
| from JdbcUrlSink sink, string url | ||
| where | ||
| constantStringValue(sink.getUrlArg(), url) and | ||
| url.regexpMatch("(?i).*trustservercertificate\\s*=\\s*true.*") | ||
| select sink, | ||
| "JDBC connection uses 'trustServerCertificate=true', disabling certificate validation (MITM risk)." |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # Cleartext LDAP URL | ||
|
|
||
| Configuring an LDAP context source with an `ldap://` URL transmits bind credentials and directory data over an unencrypted channel. An attacker positioned on the network can intercept the service-account password and the contents of every directory query and response. | ||
|
|
||
| ## Recommendation | ||
| Use `ldaps://` (LDAP over TLS) or enable STARTTLS so that the connection to the directory server is encrypted and authenticated. Store the bind password outside source control (for example in a secret manager or environment variable). | ||
|
|
||
| ## Example | ||
| The following example configures a Spring `LdapContextSource` with a cleartext `ldap://` URL, so the bind credentials cross the network in the clear. | ||
|
|
||
| ```java | ||
| @Bean | ||
| public LdapContextSource ldapContextSource() { | ||
| LdapContextSource ctx = new LdapContextSource(); | ||
| // BAD: cleartext ldap:// transmits the bind password unencrypted. | ||
| ctx.setUrl("ldap://ldap.corp.example.com:389"); | ||
| ctx.setUserDn("cn=svc-app,ou=ServiceAccounts,dc=corp,dc=example,dc=com"); | ||
| ctx.setPassword(System.getenv("LDAP_PASSWORD")); | ||
| ctx.afterPropertiesSet(); | ||
| return ctx; | ||
| } | ||
| ``` | ||
|
|
||
| Use `ldaps://ldap.corp.example.com:636` instead. | ||
|
|
||
| ## References | ||
| * OWASP: [Transport Layer Protection Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html). | ||
| * Common Weakness Enumeration: [CWE-319](https://cwe.mitre.org/data/definitions/319.html). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| /** | ||
| * @name Cleartext LDAP URL | ||
| * @description Configuring an LDAP context source with an `ldap://` URL transmits | ||
| * bind credentials and directory data over an unencrypted channel, | ||
| * allowing them to be intercepted. Use `ldaps://` or STARTTLS instead. | ||
| * @kind problem | ||
| * @problem.severity warning | ||
| * @security-severity 7.5 | ||
| * @precision medium | ||
| * @id githubsecuritylab/java/cleartext-ldap-url | ||
| * @tags security | ||
| * external/cwe/cwe-319 | ||
| */ | ||
|
|
||
| import java | ||
|
|
||
| /** | ||
| * Holds if `e` evaluates to the constant string `v`, resolving a single field | ||
| * indirection (e.g. a `private static final String` constant). | ||
| */ | ||
| predicate constantStringValue(Expr e, string v) { | ||
| v = e.(CompileTimeConstantExpr).getStringValue() | ||
| or | ||
| exists(Variable var | | ||
| e = var.getAnAccess() and | ||
| v = var.getAnAssignedValue().(CompileTimeConstantExpr).getStringValue() | ||
| ) | ||
| } | ||
|
|
||
| /** A call that configures the URL of an LDAP/JNDI context source. */ | ||
| class LdapUrlSink extends MethodCall { | ||
| Expr urlArg; | ||
|
|
||
| LdapUrlSink() { | ||
| this.getMethod().hasName(["setUrl", "setProviderUrl"]) and | ||
| this.getQualifier().getType().(RefType).getName().matches("%ContextSource%") and | ||
| urlArg = this.getArgument(0) | ||
| } | ||
|
|
||
| Expr getUrlArg() { result = urlArg } | ||
| } | ||
|
|
||
| from LdapUrlSink sink, string url | ||
| where | ||
| constantStringValue(sink.getUrlArg(), url) and | ||
| url.regexpMatch("(?i)ldap://.*") | ||
| select sink, "LDAP context configured with a cleartext ldap:// URL; use ldaps:// or STARTTLS." | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # Credentials transmitted in outbound request URL | ||
|
|
||
| Embedding a password or other secret in the URL of an outbound HTTP request exposes the credential in server logs, proxy logs, and browser history. When the request is sent over `http://` the credential also crosses the network in cleartext, where it can be intercepted. | ||
|
|
||
| ## Recommendation | ||
| Send credentials in an `Authorization` header or a request body over a TLS-protected connection (`https://`), not as URL query parameters. Avoid logging full request URLs that contain secrets. | ||
|
|
||
| ## Example | ||
| The following example concatenates a password into the query string of a request issued with a Spring `RestTemplate`. | ||
|
|
||
| ```java | ||
| public String fetchReport(String reportName) { | ||
| // BAD: the password is placed in the request URL. | ||
| String url = props.getUrl() | ||
| + "/Render?report=" + reportName | ||
| + "&user=" + props.getUsername() | ||
| + "&password=" + props.getPassword(); | ||
| return restTemplate.getForObject(url, String.class); | ||
| } | ||
| ``` | ||
|
|
||
| Send the credentials in a header instead, for example via `HttpHeaders.setBasicAuth(...)` over `https://`. | ||
|
|
||
| ## References | ||
| * OWASP: [Information exposure through query strings in URL](https://owasp.org/www-community/vulnerabilities/Information_exposure_through_query_strings_in_url). | ||
| * Common Weakness Enumeration: [CWE-598](https://cwe.mitre.org/data/definitions/598.html). | ||
| * Common Weakness Enumeration: [CWE-319](https://cwe.mitre.org/data/definitions/319.html). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| /** | ||
| * @name Credentials transmitted in outbound request URL | ||
| * @description Embedding a password or secret in the URL of an outbound HTTP | ||
| * request exposes the credential in server logs, proxies and | ||
| * browser history, and over `http://` leaks it in cleartext on the | ||
| * wire. Send credentials in headers or a request body over TLS. | ||
| * @kind path-problem | ||
| * @problem.severity warning | ||
| * @security-severity 7.5 | ||
| * @precision medium | ||
| * @id githubsecuritylab/java/credentials-in-outbound-url | ||
| * @tags security | ||
| * external/cwe/cwe-598 | ||
| * external/cwe/cwe-319 | ||
| */ | ||
|
|
||
| import java | ||
| import semmle.code.java.dataflow.TaintTracking | ||
| import CredentialsInUrlFlow::PathGraph | ||
|
|
||
| /** A getter whose name suggests it returns a credential or secret. */ | ||
| class CredentialGetter extends MethodCall { | ||
| CredentialGetter() { | ||
| this.getMethod() | ||
| .getName() | ||
| .regexpMatch("(?i)get(pass(word|wd)?|secret|credential|apikey|api_?key|token).*") | ||
| } | ||
| } | ||
|
|
||
| /** The URL argument of an outbound HTTP client request. */ | ||
| class OutboundUrlArg extends Expr { | ||
| OutboundUrlArg() { | ||
| exists(MethodCall ma | | ||
| ( | ||
| ma.getMethod() | ||
| .getDeclaringType() | ||
| .getASupertype*() | ||
| .hasQualifiedName("org.springframework.web.client", "RestOperations") | ||
| or | ||
| ma.getMethod() | ||
| .hasName([ | ||
| "getForObject", "getForEntity", "postForObject", "postForEntity", "put", "delete", | ||
| "exchange", "execute" | ||
| ]) and | ||
| ma.getQualifier().getType().(RefType).getName().matches("%RestTemplate%") | ||
| ) and | ||
| this = ma.getArgument(0) | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| module CredentialsInUrlConfig implements DataFlow::ConfigSig { | ||
| predicate isSource(DataFlow::Node source) { source.asExpr() instanceof CredentialGetter } | ||
|
|
||
| predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof OutboundUrlArg } | ||
| } | ||
|
|
||
| module CredentialsInUrlFlow = TaintTracking::Global<CredentialsInUrlConfig>; | ||
|
|
||
| from CredentialsInUrlFlow::PathNode source, CredentialsInUrlFlow::PathNode sink | ||
| where CredentialsInUrlFlow::flowPath(source, sink) | ||
| select sink.getNode(), source, sink, | ||
| "Credential from $@ is concatenated into the URL of an outbound HTTP request.", source.getNode(), | ||
| "this getter" |
34 changes: 34 additions & 0 deletions
34
java/src/security/CWE-639/InsecureDirectObjectReference.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| # Insecure direct object reference | ||
|
|
||
| A web action that operates on a resource identified by user input, without checking that the current user is authorized to act on that specific resource, allows an attacker to access or modify arbitrary objects by changing the identifier. | ||
|
|
||
| This query is the Java analogue of the C# query `cs/web/insecure-direct-object-reference`. It flags a state-changing Spring controller action that takes an id-like parameter but performs no user or session check and carries no method-security annotation. | ||
|
|
||
| ## Recommendation | ||
| Add an authorization check that ties the request to the authenticated user before acting on the resource. This can be a method-security annotation such as `@PreAuthorize` or `@PostAuthorize`, an explicit ownership check against the current user or session, or a query scoped to the caller. | ||
|
|
||
| ## Example | ||
| The following example deletes a record identified by a path variable without verifying that the caller owns it. | ||
|
|
||
| ```java | ||
| @DeleteMapping("/{id}") | ||
| public void deleteStatement(@PathVariable long id) { | ||
| // BAD: no check that the current user may delete record `id`. | ||
| service.delete(id); | ||
| } | ||
| ``` | ||
|
|
||
| Restrict the action with an authorization check, for example: | ||
|
|
||
| ```java | ||
| @DeleteMapping("/{id}") | ||
| @PreAuthorize("@statementAccess.isOwner(#id, authentication.name)") | ||
| public void deleteStatement(@PathVariable long id) { | ||
| service.delete(id); | ||
| } | ||
| ``` | ||
|
|
||
| ## References | ||
| * OWASP: [Insecure Direct Object Reference Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Insecure_Direct_Object_Reference_Prevention_Cheat_Sheet.html). | ||
| * OWASP Top 10: [A01:2021 Broken Access Control](https://owasp.org/Top10/A01_2021-Broken_Access_Control/). | ||
| * Common Weakness Enumeration: [CWE-639](https://cwe.mitre.org/data/definitions/639.html). |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.