-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Java: Promote Spring Boot Actuators query from experimental #18793
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
Merged
jcogs33
merged 17 commits into
github:main
from
jcogs33:jcogs33/java/spring-boot-actuators-promo
Mar 11, 2025
+698
−350
Merged
Changes from 16 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
2ce5920
Java: copy out of experimental
978834b
Java: remove deprecations
089a491
Java: fix tests; update for non-experimental directory
5e5bc2a
Java: remove experimental files
8064e8f
Java: convert tests to inline expectations
8dfb920
Java: refactor QL, move code to libraries
b2469ff
Java: add APIs and tests for more recent Spring versions: authorizeHt…
9e51b01
Java: handle example in Spring docs
f65a5b9
Java: add test for qhelp good example
6fe7c7a
Java: some refactoring
53cb30d
Java: update metadata, move from CWE-016 to CWE-200
26e3967
Java: edit qhelp
c2e859c
Java: add change note
746f022
Java: add 'Spring' prefix to public class names
82062e2
Java: update test
0eec951
Java: update change note to mention removal from Community Packs
ad63dd9
Apply suggestions from docs review
jcogs33 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
24 changes: 24 additions & 0 deletions
24
java/ql/lib/semmle/code/java/frameworks/spring/SpringBoot.qll
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,24 @@ | ||
| /** | ||
| * Provides classes for working with Spring classes and interfaces from | ||
| * `org.springframework.boot.*`. | ||
| */ | ||
|
|
||
| import java | ||
|
|
||
| /** | ||
| * The class `org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest`. | ||
| */ | ||
| class SpringEndpointRequest extends Class { | ||
| SpringEndpointRequest() { | ||
| this.hasQualifiedName("org.springframework.boot.actuate.autoconfigure.security.servlet", | ||
| "EndpointRequest") | ||
| } | ||
| } | ||
|
|
||
| /** A call to `EndpointRequest.toAnyEndpoint` method. */ | ||
| class SpringToAnyEndpointCall extends MethodCall { | ||
| SpringToAnyEndpointCall() { | ||
| this.getMethod().hasName("toAnyEndpoint") and | ||
| this.getMethod().getDeclaringType() instanceof SpringEndpointRequest | ||
| } | ||
| } |
124 changes: 124 additions & 0 deletions
124
java/ql/lib/semmle/code/java/frameworks/spring/SpringSecurity.qll
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,124 @@ | ||
| /** | ||
| * Provides classes for working with Spring classes and interfaces from | ||
| * `org.springframework.security.*`. | ||
| */ | ||
|
|
||
| import java | ||
|
|
||
| /** The class `org.springframework.security.config.annotation.web.builders.HttpSecurity`. */ | ||
| class SpringHttpSecurity extends Class { | ||
| SpringHttpSecurity() { | ||
| this.hasQualifiedName("org.springframework.security.config.annotation.web.builders", | ||
| "HttpSecurity") | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * The class | ||
| * `org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer$AuthorizedUrl` | ||
| * or the class | ||
| * `org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer$AuthorizedUrl`. | ||
| */ | ||
| class SpringAuthorizedUrl extends Class { | ||
| SpringAuthorizedUrl() { | ||
| this.hasQualifiedName("org.springframework.security.config.annotation.web.configurers", | ||
| [ | ||
| "ExpressionUrlAuthorizationConfigurer<HttpSecurity>$AuthorizedUrl<>", | ||
| "AuthorizeHttpRequestsConfigurer<HttpSecurity>$AuthorizedUrl<>" | ||
| ]) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * The class `org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry`. | ||
| */ | ||
| class SpringAbstractRequestMatcherRegistry extends Class { | ||
| SpringAbstractRequestMatcherRegistry() { | ||
| this.hasQualifiedName("org.springframework.security.config.annotation.web", | ||
| "AbstractRequestMatcherRegistry<AuthorizedUrl<>>") | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A call to the `HttpSecurity.authorizeRequests` method. | ||
| * | ||
| * Note: this method is deprecated and scheduled for removal | ||
| * in Spring Security 7.0. | ||
| */ | ||
| class SpringAuthorizeRequestsCall extends MethodCall { | ||
| SpringAuthorizeRequestsCall() { | ||
| this.getMethod().hasName("authorizeRequests") and | ||
| this.getMethod().getDeclaringType() instanceof SpringHttpSecurity | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A call to the `HttpSecurity.authorizeHttpRequests` method. | ||
| * | ||
| * Note: the no-argument version of this method is deprecated | ||
| * and scheduled for removal in Spring Security 7.0. | ||
| */ | ||
| class SpringAuthorizeHttpRequestsCall extends MethodCall { | ||
| SpringAuthorizeHttpRequestsCall() { | ||
| this.getMethod().hasName("authorizeHttpRequests") and | ||
| this.getMethod().getDeclaringType() instanceof SpringHttpSecurity | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A call to the `HttpSecurity.requestMatcher` method. | ||
| * | ||
| * Note: this method was removed in Spring Security 6.0. | ||
| * It was replaced by `securityMatcher`. | ||
| */ | ||
| class SpringRequestMatcherCall extends MethodCall { | ||
| SpringRequestMatcherCall() { | ||
| this.getMethod().hasName("requestMatcher") and | ||
| this.getMethod().getDeclaringType() instanceof SpringHttpSecurity | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A call to the `HttpSecurity.requestMatchers` method. | ||
| * | ||
| * Note: this method was removed in Spring Security 6.0. | ||
| * It was replaced by `securityMatchers`. | ||
| */ | ||
| class SpringRequestMatchersCall extends MethodCall { | ||
| SpringRequestMatchersCall() { | ||
| this.getMethod().hasName("requestMatchers") and | ||
| this.getMethod().getDeclaringType() instanceof SpringHttpSecurity | ||
| } | ||
| } | ||
|
|
||
| /** A call to the `HttpSecurity.securityMatcher` method. */ | ||
| class SpringSecurityMatcherCall extends MethodCall { | ||
| SpringSecurityMatcherCall() { | ||
| this.getMethod().hasName("securityMatcher") and | ||
| this.getMethod().getDeclaringType() instanceof SpringHttpSecurity | ||
| } | ||
| } | ||
|
|
||
| /** A call to the `HttpSecurity.securityMatchers` method. */ | ||
| class SpringSecurityMatchersCall extends MethodCall { | ||
| SpringSecurityMatchersCall() { | ||
| this.getMethod().hasName("securityMatchers") and | ||
| this.getMethod().getDeclaringType() instanceof SpringHttpSecurity | ||
| } | ||
| } | ||
|
|
||
| /** A call to the `AuthorizedUrl.permitAll` method. */ | ||
| class SpringPermitAllCall extends MethodCall { | ||
| SpringPermitAllCall() { | ||
| this.getMethod().hasName("permitAll") and | ||
| this.getMethod().getDeclaringType() instanceof SpringAuthorizedUrl | ||
| } | ||
| } | ||
|
|
||
| /** A call to the `AbstractRequestMatcherRegistry.anyRequest` method. */ | ||
| class SpringAnyRequestCall extends MethodCall { | ||
| SpringAnyRequestCall() { | ||
| this.getMethod().hasName("anyRequest") and | ||
| this.getMethod().getDeclaringType() instanceof SpringAbstractRequestMatcherRegistry | ||
| } | ||
| } |
110 changes: 110 additions & 0 deletions
110
java/ql/lib/semmle/code/java/security/SpringBootActuatorsQuery.qll
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,110 @@ | ||
| /** Provides classes and predicates to reason about exposed actuators in Spring Boot. */ | ||
|
|
||
| import java | ||
| private import semmle.code.java.frameworks.spring.SpringSecurity | ||
| private import semmle.code.java.frameworks.spring.SpringBoot | ||
|
|
||
| /** | ||
| * A call to an `HttpSecurity` matcher method with argument | ||
| * `EndpointRequest.toAnyEndpoint()`. | ||
| */ | ||
| private class HttpSecurityMatcherCall extends MethodCall { | ||
| HttpSecurityMatcherCall() { | ||
| ( | ||
| this instanceof SpringRequestMatcherCall or | ||
| this instanceof SpringSecurityMatcherCall | ||
| ) and | ||
| this.getArgument(0) instanceof SpringToAnyEndpointCall | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A call to an `HttpSecurity` matchers method with lambda | ||
| * argument `EndpointRequest.toAnyEndpoint()`. | ||
| */ | ||
| private class HttpSecurityMatchersCall extends MethodCall { | ||
| HttpSecurityMatchersCall() { | ||
| ( | ||
| this instanceof SpringRequestMatchersCall or | ||
| this instanceof SpringSecurityMatchersCall | ||
| ) and | ||
| this.getArgument(0).(LambdaExpr).getExprBody() instanceof SpringToAnyEndpointCall | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A call to an `AbstractRequestMatcherRegistry.requestMatchers` method with | ||
| * argument `EndpointRequest.toAnyEndpoint()`. | ||
| */ | ||
| private class RegistryRequestMatchersCall extends MethodCall { | ||
| RegistryRequestMatchersCall() { | ||
| this.getMethod().hasName("requestMatchers") and | ||
| this.getMethod().getDeclaringType() instanceof SpringAbstractRequestMatcherRegistry and | ||
| this.getAnArgument() instanceof SpringToAnyEndpointCall | ||
| } | ||
| } | ||
|
|
||
| /** A call to an `HttpSecurity` method that authorizes requests. */ | ||
| private class AuthorizeCall extends MethodCall { | ||
| AuthorizeCall() { | ||
| this instanceof SpringAuthorizeRequestsCall or | ||
| this instanceof SpringAuthorizeHttpRequestsCall | ||
| } | ||
| } | ||
|
|
||
| /** Holds if `permitAllCall` is called on request(s) mapped to actuator endpoint(s). */ | ||
| predicate permitsSpringBootActuators(SpringPermitAllCall permitAllCall) { | ||
| exists(AuthorizeCall authorizeCall | | ||
| // .requestMatcher(EndpointRequest).authorizeRequests([...]).[...] | ||
| authorizeCall.getQualifier() instanceof HttpSecurityMatcherCall | ||
| or | ||
| // .requestMatchers(matcher -> EndpointRequest).authorizeRequests([...]).[...] | ||
| authorizeCall.getQualifier() instanceof HttpSecurityMatchersCall | ||
| | | ||
| // [...].authorizeRequests(r -> r.anyRequest().permitAll()) or | ||
| // [...].authorizeRequests(r -> r.requestMatchers(EndpointRequest).permitAll()) | ||
| authorizeCall.getArgument(0).(LambdaExpr).getExprBody() = permitAllCall and | ||
| ( | ||
| permitAllCall.getQualifier() instanceof SpringAnyRequestCall or | ||
| permitAllCall.getQualifier() instanceof RegistryRequestMatchersCall | ||
| ) | ||
| or | ||
| // [...].authorizeRequests().requestMatchers(EndpointRequest).permitAll() or | ||
| // [...].authorizeRequests().anyRequest().permitAll() | ||
| authorizeCall.getNumArgument() = 0 and | ||
| exists(RegistryRequestMatchersCall registryRequestMatchersCall | | ||
| registryRequestMatchersCall.getQualifier() = authorizeCall and | ||
| permitAllCall.getQualifier() = registryRequestMatchersCall | ||
| ) | ||
| or | ||
| exists(SpringAnyRequestCall anyRequestCall | | ||
| anyRequestCall.getQualifier() = authorizeCall and | ||
| permitAllCall.getQualifier() = anyRequestCall | ||
| ) | ||
| ) | ||
| or | ||
| exists(AuthorizeCall authorizeCall | | ||
| // http.authorizeRequests([...]).[...] | ||
| authorizeCall.getQualifier() instanceof VarAccess | ||
| | | ||
| // [...].authorizeRequests(r -> r.requestMatchers(EndpointRequest).permitAll()) | ||
| authorizeCall.getArgument(0).(LambdaExpr).getExprBody() = permitAllCall and | ||
| permitAllCall.getQualifier() instanceof RegistryRequestMatchersCall | ||
| or | ||
| // [...].authorizeRequests().requestMatchers(EndpointRequest).permitAll() or | ||
| authorizeCall.getNumArgument() = 0 and | ||
| exists(RegistryRequestMatchersCall registryRequestMatchersCall | | ||
| registryRequestMatchersCall.getQualifier() = authorizeCall and | ||
| permitAllCall.getQualifier() = registryRequestMatchersCall | ||
| ) | ||
| or | ||
| exists(Variable v, HttpSecurityMatcherCall matcherCall | | ||
| // http.securityMatcher(EndpointRequest.toAnyEndpoint()); | ||
| // http.authorizeRequests([...].permitAll()) | ||
| v.getAnAccess() = authorizeCall.getQualifier() and | ||
| v.getAnAccess() = matcherCall.getQualifier() and | ||
| authorizeCall.getArgument(0).(LambdaExpr).getExprBody() = permitAllCall and | ||
| permitAllCall.getQualifier() instanceof SpringAnyRequestCall | ||
| ) | ||
| ) | ||
| } |
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,25 @@ | ||
| @Configuration(proxyBeanMethods = false) | ||
| public class CustomSecurityConfiguration { | ||
|
|
||
| @Bean | ||
| public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { | ||
| // BAD: Unauthenticated access to Spring Boot actuator endpoints is allowed | ||
| http.securityMatcher(EndpointRequest.toAnyEndpoint()); | ||
| http.authorizeHttpRequests((requests) -> requests.anyRequest().permitAll()); | ||
| return http.build(); | ||
| } | ||
|
|
||
| } | ||
|
|
||
| @Configuration(proxyBeanMethods = false) | ||
| public class CustomSecurityConfiguration { | ||
|
|
||
| @Bean | ||
| public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { | ||
| // GOOD: only users with ENDPOINT_ADMIN role are allowed to access the actuator endpoints | ||
| http.securityMatcher(EndpointRequest.toAnyEndpoint()); | ||
| http.authorizeHttpRequests((requests) -> requests.anyRequest().hasRole("ENDPOINT_ADMIN")); | ||
| return http.build(); | ||
| } | ||
|
|
||
| } |
36 changes: 36 additions & 0 deletions
36
java/ql/src/Security/CWE/CWE-200/SpringBootActuators.qhelp
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,36 @@ | ||
| <!DOCTYPE qhelp PUBLIC | ||
| "-//Semmle//qhelp//EN" | ||
| "qhelp.dtd"> | ||
| <qhelp> | ||
| <overview> | ||
| <p>Spring Boot includes features called actuators that let you monitor and interact with your | ||
| web application. Exposing unprotected actuator endpoints can lead to information disclosure or | ||
| even to remote code execution.</p> | ||
| </overview> | ||
|
|
||
| <recommendation> | ||
| <p>Since actuator endpoints may contain sensitive information, carefully consider when to expose them, | ||
| and secure them as you would any sensitive URL. Actuators are secured by default when using Spring | ||
| Security without a custom configuration. If you wish to define a custom security configuration, | ||
| consider only allowing users with certain roles access to the endpoints. | ||
| </p> | ||
|
|
||
| </recommendation> | ||
|
|
||
| <example> | ||
| <p>In the first example, the custom security configuration allows unauthenticated access to all | ||
| actuator endpoints. This may lead to sensitive information disclosure and should be avoided.</p> | ||
|
|
||
| <p>In the second example, only users with <code>ENDPOINT_ADMIN</code> role are allowed to access | ||
| the actuator endpoints.</p> | ||
|
|
||
| <sample src="SpringBootActuators.java" /> | ||
| </example> | ||
|
|
||
| <references> | ||
| <li> | ||
| Spring Boot Reference Documentation: | ||
| <a href="https://docs.spring.io/spring-boot/reference/actuator/endpoints.html">Endpoints</a>. | ||
| </li> | ||
| </references> | ||
| </qhelp> | ||
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,20 @@ | ||
| /** | ||
| * @name Exposed Spring Boot actuators | ||
| * @description Exposing Spring Boot actuators may lead to internal application's information leak | ||
jcogs33 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * or even to remote code execution. | ||
| * @kind problem | ||
| * @problem.severity error | ||
| * @security-severity 6.5 | ||
| * @precision high | ||
| * @id java/spring-boot-exposed-actuators | ||
| * @tags security | ||
| * external/cwe/cwe-200 | ||
| */ | ||
|
|
||
| import java | ||
| import semmle.code.java.frameworks.spring.SpringSecurity | ||
| import semmle.code.java.security.SpringBootActuatorsQuery | ||
|
|
||
| from SpringPermitAllCall permitAllCall | ||
| where permitsSpringBootActuators(permitAllCall) | ||
| select permitAllCall, "Unauthenticated access to Spring Boot actuator is allowed." | ||
4 changes: 4 additions & 0 deletions
4
java/ql/src/change-notes/2025-02-24-spring-boot-actuators-promo.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,4 @@ | ||
| --- | ||
| category: newQuery | ||
| --- | ||
| * The query `java/spring-boot-exposed-actuators` has been promoted from experimental to the main query pack. Its results will now appear by default, and it will be removed from the [CodeQL Community Packs](https://github.com/GitHubSecurityLab/CodeQL-Community-Packs). This query was originally submitted as an experimental query [by @ggolawski](https://github.com/github/codeql/pull/2901). | ||
jcogs33 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
22 changes: 0 additions & 22 deletions
22
java/ql/src/experimental/Security/CWE/CWE-016/SpringBootActuators.java
This file was deleted.
Oops, something went wrong.
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.