Skip to content

Commit ea9b046

Browse files
authored
Merge pull request #18793 from jcogs33/jcogs33/java/spring-boot-actuators-promo
Java: Promote Spring Boot Actuators query from experimental
2 parents da720b8 + ad63dd9 commit ea9b046

File tree

21 files changed

+698
-350
lines changed

21 files changed

+698
-350
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Provides classes for working with Spring classes and interfaces from
3+
* `org.springframework.boot.*`.
4+
*/
5+
6+
import java
7+
8+
/**
9+
* The class `org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest`.
10+
*/
11+
class SpringEndpointRequest extends Class {
12+
SpringEndpointRequest() {
13+
this.hasQualifiedName("org.springframework.boot.actuate.autoconfigure.security.servlet",
14+
"EndpointRequest")
15+
}
16+
}
17+
18+
/** A call to `EndpointRequest.toAnyEndpoint` method. */
19+
class SpringToAnyEndpointCall extends MethodCall {
20+
SpringToAnyEndpointCall() {
21+
this.getMethod().hasName("toAnyEndpoint") and
22+
this.getMethod().getDeclaringType() instanceof SpringEndpointRequest
23+
}
24+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/**
2+
* Provides classes for working with Spring classes and interfaces from
3+
* `org.springframework.security.*`.
4+
*/
5+
6+
import java
7+
8+
/** The class `org.springframework.security.config.annotation.web.builders.HttpSecurity`. */
9+
class SpringHttpSecurity extends Class {
10+
SpringHttpSecurity() {
11+
this.hasQualifiedName("org.springframework.security.config.annotation.web.builders",
12+
"HttpSecurity")
13+
}
14+
}
15+
16+
/**
17+
* The class
18+
* `org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer$AuthorizedUrl`
19+
* or the class
20+
* `org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer$AuthorizedUrl`.
21+
*/
22+
class SpringAuthorizedUrl extends Class {
23+
SpringAuthorizedUrl() {
24+
this.hasQualifiedName("org.springframework.security.config.annotation.web.configurers",
25+
[
26+
"ExpressionUrlAuthorizationConfigurer<HttpSecurity>$AuthorizedUrl<>",
27+
"AuthorizeHttpRequestsConfigurer<HttpSecurity>$AuthorizedUrl<>"
28+
])
29+
}
30+
}
31+
32+
/**
33+
* The class `org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry`.
34+
*/
35+
class SpringAbstractRequestMatcherRegistry extends Class {
36+
SpringAbstractRequestMatcherRegistry() {
37+
this.hasQualifiedName("org.springframework.security.config.annotation.web",
38+
"AbstractRequestMatcherRegistry<AuthorizedUrl<>>")
39+
}
40+
}
41+
42+
/**
43+
* A call to the `HttpSecurity.authorizeRequests` method.
44+
*
45+
* Note: this method is deprecated and scheduled for removal
46+
* in Spring Security 7.0.
47+
*/
48+
class SpringAuthorizeRequestsCall extends MethodCall {
49+
SpringAuthorizeRequestsCall() {
50+
this.getMethod().hasName("authorizeRequests") and
51+
this.getMethod().getDeclaringType() instanceof SpringHttpSecurity
52+
}
53+
}
54+
55+
/**
56+
* A call to the `HttpSecurity.authorizeHttpRequests` method.
57+
*
58+
* Note: the no-argument version of this method is deprecated
59+
* and scheduled for removal in Spring Security 7.0.
60+
*/
61+
class SpringAuthorizeHttpRequestsCall extends MethodCall {
62+
SpringAuthorizeHttpRequestsCall() {
63+
this.getMethod().hasName("authorizeHttpRequests") and
64+
this.getMethod().getDeclaringType() instanceof SpringHttpSecurity
65+
}
66+
}
67+
68+
/**
69+
* A call to the `HttpSecurity.requestMatcher` method.
70+
*
71+
* Note: this method was removed in Spring Security 6.0.
72+
* It was replaced by `securityMatcher`.
73+
*/
74+
class SpringRequestMatcherCall extends MethodCall {
75+
SpringRequestMatcherCall() {
76+
this.getMethod().hasName("requestMatcher") and
77+
this.getMethod().getDeclaringType() instanceof SpringHttpSecurity
78+
}
79+
}
80+
81+
/**
82+
* A call to the `HttpSecurity.requestMatchers` method.
83+
*
84+
* Note: this method was removed in Spring Security 6.0.
85+
* It was replaced by `securityMatchers`.
86+
*/
87+
class SpringRequestMatchersCall extends MethodCall {
88+
SpringRequestMatchersCall() {
89+
this.getMethod().hasName("requestMatchers") and
90+
this.getMethod().getDeclaringType() instanceof SpringHttpSecurity
91+
}
92+
}
93+
94+
/** A call to the `HttpSecurity.securityMatcher` method. */
95+
class SpringSecurityMatcherCall extends MethodCall {
96+
SpringSecurityMatcherCall() {
97+
this.getMethod().hasName("securityMatcher") and
98+
this.getMethod().getDeclaringType() instanceof SpringHttpSecurity
99+
}
100+
}
101+
102+
/** A call to the `HttpSecurity.securityMatchers` method. */
103+
class SpringSecurityMatchersCall extends MethodCall {
104+
SpringSecurityMatchersCall() {
105+
this.getMethod().hasName("securityMatchers") and
106+
this.getMethod().getDeclaringType() instanceof SpringHttpSecurity
107+
}
108+
}
109+
110+
/** A call to the `AuthorizedUrl.permitAll` method. */
111+
class SpringPermitAllCall extends MethodCall {
112+
SpringPermitAllCall() {
113+
this.getMethod().hasName("permitAll") and
114+
this.getMethod().getDeclaringType() instanceof SpringAuthorizedUrl
115+
}
116+
}
117+
118+
/** A call to the `AbstractRequestMatcherRegistry.anyRequest` method. */
119+
class SpringAnyRequestCall extends MethodCall {
120+
SpringAnyRequestCall() {
121+
this.getMethod().hasName("anyRequest") and
122+
this.getMethod().getDeclaringType() instanceof SpringAbstractRequestMatcherRegistry
123+
}
124+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/** Provides classes and predicates to reason about exposed actuators in Spring Boot. */
2+
3+
import java
4+
private import semmle.code.java.frameworks.spring.SpringSecurity
5+
private import semmle.code.java.frameworks.spring.SpringBoot
6+
7+
/**
8+
* A call to an `HttpSecurity` matcher method with argument
9+
* `EndpointRequest.toAnyEndpoint()`.
10+
*/
11+
private class HttpSecurityMatcherCall extends MethodCall {
12+
HttpSecurityMatcherCall() {
13+
(
14+
this instanceof SpringRequestMatcherCall or
15+
this instanceof SpringSecurityMatcherCall
16+
) and
17+
this.getArgument(0) instanceof SpringToAnyEndpointCall
18+
}
19+
}
20+
21+
/**
22+
* A call to an `HttpSecurity` matchers method with lambda
23+
* argument `EndpointRequest.toAnyEndpoint()`.
24+
*/
25+
private class HttpSecurityMatchersCall extends MethodCall {
26+
HttpSecurityMatchersCall() {
27+
(
28+
this instanceof SpringRequestMatchersCall or
29+
this instanceof SpringSecurityMatchersCall
30+
) and
31+
this.getArgument(0).(LambdaExpr).getExprBody() instanceof SpringToAnyEndpointCall
32+
}
33+
}
34+
35+
/**
36+
* A call to an `AbstractRequestMatcherRegistry.requestMatchers` method with
37+
* argument `EndpointRequest.toAnyEndpoint()`.
38+
*/
39+
private class RegistryRequestMatchersCall extends MethodCall {
40+
RegistryRequestMatchersCall() {
41+
this.getMethod().hasName("requestMatchers") and
42+
this.getMethod().getDeclaringType() instanceof SpringAbstractRequestMatcherRegistry and
43+
this.getAnArgument() instanceof SpringToAnyEndpointCall
44+
}
45+
}
46+
47+
/** A call to an `HttpSecurity` method that authorizes requests. */
48+
private class AuthorizeCall extends MethodCall {
49+
AuthorizeCall() {
50+
this instanceof SpringAuthorizeRequestsCall or
51+
this instanceof SpringAuthorizeHttpRequestsCall
52+
}
53+
}
54+
55+
/** Holds if `permitAllCall` is called on request(s) mapped to actuator endpoint(s). */
56+
predicate permitsSpringBootActuators(SpringPermitAllCall permitAllCall) {
57+
exists(AuthorizeCall authorizeCall |
58+
// .requestMatcher(EndpointRequest).authorizeRequests([...]).[...]
59+
authorizeCall.getQualifier() instanceof HttpSecurityMatcherCall
60+
or
61+
// .requestMatchers(matcher -> EndpointRequest).authorizeRequests([...]).[...]
62+
authorizeCall.getQualifier() instanceof HttpSecurityMatchersCall
63+
|
64+
// [...].authorizeRequests(r -> r.anyRequest().permitAll()) or
65+
// [...].authorizeRequests(r -> r.requestMatchers(EndpointRequest).permitAll())
66+
authorizeCall.getArgument(0).(LambdaExpr).getExprBody() = permitAllCall and
67+
(
68+
permitAllCall.getQualifier() instanceof SpringAnyRequestCall or
69+
permitAllCall.getQualifier() instanceof RegistryRequestMatchersCall
70+
)
71+
or
72+
// [...].authorizeRequests().requestMatchers(EndpointRequest).permitAll() or
73+
// [...].authorizeRequests().anyRequest().permitAll()
74+
authorizeCall.getNumArgument() = 0 and
75+
exists(RegistryRequestMatchersCall registryRequestMatchersCall |
76+
registryRequestMatchersCall.getQualifier() = authorizeCall and
77+
permitAllCall.getQualifier() = registryRequestMatchersCall
78+
)
79+
or
80+
exists(SpringAnyRequestCall anyRequestCall |
81+
anyRequestCall.getQualifier() = authorizeCall and
82+
permitAllCall.getQualifier() = anyRequestCall
83+
)
84+
)
85+
or
86+
exists(AuthorizeCall authorizeCall |
87+
// http.authorizeRequests([...]).[...]
88+
authorizeCall.getQualifier() instanceof VarAccess
89+
|
90+
// [...].authorizeRequests(r -> r.requestMatchers(EndpointRequest).permitAll())
91+
authorizeCall.getArgument(0).(LambdaExpr).getExprBody() = permitAllCall and
92+
permitAllCall.getQualifier() instanceof RegistryRequestMatchersCall
93+
or
94+
// [...].authorizeRequests().requestMatchers(EndpointRequest).permitAll() or
95+
authorizeCall.getNumArgument() = 0 and
96+
exists(RegistryRequestMatchersCall registryRequestMatchersCall |
97+
registryRequestMatchersCall.getQualifier() = authorizeCall and
98+
permitAllCall.getQualifier() = registryRequestMatchersCall
99+
)
100+
or
101+
exists(Variable v, HttpSecurityMatcherCall matcherCall |
102+
// http.securityMatcher(EndpointRequest.toAnyEndpoint());
103+
// http.authorizeRequests([...].permitAll())
104+
v.getAnAccess() = authorizeCall.getQualifier() and
105+
v.getAnAccess() = matcherCall.getQualifier() and
106+
authorizeCall.getArgument(0).(LambdaExpr).getExprBody() = permitAllCall and
107+
permitAllCall.getQualifier() instanceof SpringAnyRequestCall
108+
)
109+
)
110+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
@Configuration(proxyBeanMethods = false)
2+
public class CustomSecurityConfiguration {
3+
4+
@Bean
5+
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
6+
// BAD: Unauthenticated access to Spring Boot actuator endpoints is allowed
7+
http.securityMatcher(EndpointRequest.toAnyEndpoint());
8+
http.authorizeHttpRequests((requests) -> requests.anyRequest().permitAll());
9+
return http.build();
10+
}
11+
12+
}
13+
14+
@Configuration(proxyBeanMethods = false)
15+
public class CustomSecurityConfiguration {
16+
17+
@Bean
18+
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
19+
// GOOD: only users with ENDPOINT_ADMIN role are allowed to access the actuator endpoints
20+
http.securityMatcher(EndpointRequest.toAnyEndpoint());
21+
http.authorizeHttpRequests((requests) -> requests.anyRequest().hasRole("ENDPOINT_ADMIN"));
22+
return http.build();
23+
}
24+
25+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>Spring Boot includes features called actuators that let you monitor and interact with your
7+
web application. Exposing unprotected actuator endpoints can lead to information disclosure or
8+
even to remote code execution.</p>
9+
</overview>
10+
11+
<recommendation>
12+
<p>Since actuator endpoints may contain sensitive information, carefully consider when to expose them,
13+
and secure them as you would any sensitive URL. Actuators are secured by default when using Spring
14+
Security without a custom configuration. If you wish to define a custom security configuration,
15+
consider only allowing users with certain roles to access these endpoints.
16+
</p>
17+
18+
</recommendation>
19+
20+
<example>
21+
<p>In the first example, the custom security configuration allows unauthenticated access to all
22+
actuator endpoints. This may lead to sensitive information disclosure and should be avoided.</p>
23+
24+
<p>In the second example, only users with <code>ENDPOINT_ADMIN</code> role are allowed to access
25+
the actuator endpoints.</p>
26+
27+
<sample src="SpringBootActuators.java" />
28+
</example>
29+
30+
<references>
31+
<li>
32+
Spring Boot Reference Documentation:
33+
<a href="https://docs.spring.io/spring-boot/reference/actuator/endpoints.html">Endpoints</a>.
34+
</li>
35+
</references>
36+
</qhelp>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* @name Exposed Spring Boot actuators
3+
* @description Exposing Spring Boot actuators may lead to information leak from the internal application,
4+
* or even to remote code execution.
5+
* @kind problem
6+
* @problem.severity error
7+
* @security-severity 6.5
8+
* @precision high
9+
* @id java/spring-boot-exposed-actuators
10+
* @tags security
11+
* external/cwe/cwe-200
12+
*/
13+
14+
import java
15+
import semmle.code.java.frameworks.spring.SpringSecurity
16+
import semmle.code.java.security.SpringBootActuatorsQuery
17+
18+
from SpringPermitAllCall permitAllCall
19+
where permitsSpringBootActuators(permitAllCall)
20+
select permitAllCall, "Unauthenticated access to Spring Boot actuator is allowed."
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: newQuery
3+
---
4+
* 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 the query itself 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).

java/ql/src/experimental/Security/CWE/CWE-016/SpringBootActuators.java

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)