Skip to content

Commit fda4ab1

Browse files
committed
CodeQL query to detect open Spring Boot actuator endpoints
1 parent 0628625 commit fda4ab1

File tree

6 files changed

+260
-0
lines changed

6 files changed

+260
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
@Configuration(proxyBeanMethods = false)
2+
public class ActuatorSecurity extends WebSecurityConfigurerAdapter {
3+
4+
@Override
5+
protected void configure(HttpSecurity http) throws Exception {
6+
// BAD: Unauthenticated access to Spring Boot actuator endpoints is allowed
7+
http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests((requests) ->
8+
requests.anyRequest().permitAll());
9+
}
10+
}
11+
12+
@Configuration(proxyBeanMethods = false)
13+
public class ActuatorSecurity extends WebSecurityConfigurerAdapter {
14+
15+
@Override
16+
protected void configure(HttpSecurity http) throws Exception {
17+
// GOOD: only users with ENDPOINT_ADMIN role are allowed to access the actuator endpoints
18+
http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests((requests) ->
19+
requests.anyRequest().hasRole("ENDPOINT_ADMIN"));
20+
http.httpBasic();
21+
}
22+
}
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 a number of additional features called actuators that let you monitor
7+
and interact with your web application. Exposing unprotected actuator endpoints via JXM or HTTP
8+
can, however, lead to information disclosure or even to remote code execution vulnerability.</p>
9+
</overview>
10+
11+
<recommendation>
12+
<p>Since actuator endpoints may contain sensitive information, careful consideration should be
13+
given about when to expose them. You should take care to secure exposed HTTP endpoints in the same
14+
way that you would any other sensitive URL. If Spring Security is present, endpoints are secured by
15+
default using Spring Security’s content-negotiation strategy. If you wish to configure custom
16+
security for HTTP endpoints, for example, only allow users with a certain role to access them,
17+
Spring Boot provides some convenient <code>RequestMatcher</code> objects that can be used in
18+
combination with Spring Security.</p>
19+
</recommendation>
20+
21+
<example>
22+
<p>In the first example, the custom security configuration allows unauthenticated access to all
23+
actuator endpoints. This may lead to sensitive information disclosure and should be avoided.</p>
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 documentation:
33+
<a href="https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html">Actuators</a>.
34+
</li>
35+
</references>
36+
</qhelp>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* @name Exposed Spring Boot actuators
3+
* @description Exposing Spring Boot actuators may lead to internal application's information leak
4+
* or even to remote code execution.
5+
* @kind problem
6+
* @problem.severity error
7+
* @precision high
8+
* @id java/spring-boot-exposed-actuators
9+
* @tags security
10+
* external/cwe/cwe-16
11+
*/
12+
13+
import java
14+
import SpringBootActuators
15+
16+
from PermitAllCall permitAllCall
17+
where permitAllCall.permitsSpringBootActuators()
18+
select permitAllCall, "Unauthenticated access to Spring Boot actuator is allowed."
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import java
2+
3+
/** The class `org.springframework.security.config.annotation.web.builders.HttpSecurity`. */
4+
class TypeHttpSecurity extends Class {
5+
TypeHttpSecurity() {
6+
this
7+
.hasQualifiedName("org.springframework.security.config.annotation.web.builders",
8+
"HttpSecurity")
9+
}
10+
}
11+
12+
/**
13+
* The class
14+
* `org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer`.
15+
*/
16+
class TypeAuthorizedUrl extends Class {
17+
TypeAuthorizedUrl() {
18+
this
19+
.hasQualifiedName("org.springframework.security.config.annotation.web.configurers",
20+
"ExpressionUrlAuthorizationConfigurer<HttpSecurity>$AuthorizedUrl<>")
21+
}
22+
}
23+
24+
/**
25+
* The class
26+
* `org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry`.
27+
*/
28+
class TypeAbstractRequestMatcherRegistry extends Class {
29+
TypeAbstractRequestMatcherRegistry() {
30+
this
31+
.hasQualifiedName("org.springframework.security.config.annotation.web",
32+
"AbstractRequestMatcherRegistry<AuthorizedUrl<>>")
33+
}
34+
}
35+
36+
/**
37+
* The class
38+
* `org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest.EndpointRequestMatcher`.
39+
*/
40+
class TypeEndpointRequestMatcher extends Class {
41+
TypeEndpointRequestMatcher() {
42+
this
43+
.hasQualifiedName("org.springframework.boot.actuate.autoconfigure.security.servlet",
44+
"EndpointRequest$EndpointRequestMatcher")
45+
}
46+
}
47+
48+
/**
49+
* A call to `HttpSecurity.requestMatcher` method with argument of type
50+
* `EndpointRequestMatcher`.
51+
*/
52+
class RequestMatcherCall extends MethodAccess {
53+
RequestMatcherCall() {
54+
getMethod().hasName("requestMatcher") and
55+
getMethod().getDeclaringType() instanceof TypeHttpSecurity and
56+
getArgument(0).getType() instanceof TypeEndpointRequestMatcher
57+
}
58+
}
59+
60+
/**
61+
* A call to `HttpSecurity.requestMatchers` method with lambda argument resolving to
62+
* `EndpointRequestMatcher` type.
63+
*/
64+
class RequestMatchersCall extends MethodAccess {
65+
RequestMatchersCall() {
66+
getMethod().hasName("requestMatchers") and
67+
getMethod().getDeclaringType() instanceof TypeHttpSecurity and
68+
getArgument(0).(LambdaExpr).getExprBody().getType() instanceof TypeEndpointRequestMatcher
69+
}
70+
}
71+
72+
/** A call to `HttpSecurity.authorizeRequests` method. */
73+
class AuthorizeRequestsCall extends MethodAccess {
74+
AuthorizeRequestsCall() {
75+
getMethod().hasName("authorizeRequests") and
76+
getMethod().getDeclaringType() instanceof TypeHttpSecurity
77+
}
78+
}
79+
80+
/** A call to `AuthorizedUrl.permitAll` method. */
81+
class PermitAllCall extends MethodAccess {
82+
PermitAllCall() {
83+
getMethod().hasName("permitAll") and
84+
getMethod().getDeclaringType() instanceof TypeAuthorizedUrl
85+
}
86+
87+
/** Holds if `permitAll` is called on request(s) mapped to actuator endpoint(s). */
88+
predicate permitsSpringBootActuators() {
89+
exists(
90+
RequestMatcherCall requestMatcherCall, RequestMatchersCall requestMatchersCall,
91+
RegistryRequestMatchersCall registryRequestMatchersCall,
92+
AuthorizeRequestsCall authorizeRequestsCall, AnyRequestCall anyRequestCall
93+
|
94+
// .requestMatcher(EndpointRequest).authorizeRequests([...]).[...]
95+
authorizeRequestsCall.getQualifier() = requestMatcherCall
96+
or
97+
// .requestMatchers(matcher -> EndpointRequest).authorizeRequests([...]).[...]
98+
authorizeRequestsCall.getQualifier() = requestMatchersCall
99+
or
100+
// http.authorizeRequests([...]).[...]
101+
authorizeRequestsCall.getQualifier() instanceof VarAccess
102+
|
103+
// [...].authorizeRequests(r -> r.anyRequest().permitAll()) or
104+
// [...].authorizeRequests(r -> r.requestMatchers(EndpointRequest).permitAll())
105+
authorizeRequestsCall.getArgument(0).(LambdaExpr).getExprBody() = this and
106+
(
107+
this.getQualifier() = anyRequestCall or
108+
this.getQualifier() = registryRequestMatchersCall
109+
)
110+
or
111+
// [...].authorizeRequests().requestMatchers(EndpointRequest).permitAll() or
112+
// [...].authorizeRequests().anyRequest().permitAll()
113+
authorizeRequestsCall.getNumArgument() = 0 and
114+
(
115+
registryRequestMatchersCall.getQualifier() = authorizeRequestsCall and
116+
this.getQualifier() = registryRequestMatchersCall
117+
)
118+
or
119+
anyRequestCall.getQualifier() = authorizeRequestsCall and
120+
this.getQualifier() = anyRequestCall
121+
)
122+
}
123+
}
124+
125+
/** A call to `AbstractRequestMatcherRegistry.anyRequest` method. */
126+
class AnyRequestCall extends MethodAccess {
127+
AnyRequestCall() {
128+
getMethod().hasName("anyRequest") and
129+
getMethod().getDeclaringType() instanceof TypeAbstractRequestMatcherRegistry
130+
}
131+
}
132+
133+
/**
134+
* A call to `AbstractRequestMatcherRegistry.requestMatchers` method with an argument of type
135+
* `EndpointRequestMatcher`.
136+
*/
137+
class RegistryRequestMatchersCall extends MethodAccess {
138+
RegistryRequestMatchersCall() {
139+
getMethod().hasName("requestMatchers") and
140+
getMethod().getDeclaringType() instanceof TypeAbstractRequestMatcherRegistry and
141+
getAnArgument().getType() instanceof TypeEndpointRequestMatcher
142+
}
143+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
2+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3+
4+
public class ActuatorSecurityConfig {
5+
protected void configure(HttpSecurity http) throws Exception {
6+
http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests(requests -> requests.anyRequest().permitAll());
7+
}
8+
9+
protected void configure2(HttpSecurity http) throws Exception {
10+
http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll();
11+
}
12+
13+
protected void configure3(HttpSecurity http) throws Exception {
14+
http.requestMatchers(matcher -> EndpointRequest.toAnyEndpoint()).authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll();
15+
}
16+
17+
protected void configure4(HttpSecurity http) throws Exception {
18+
http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests().anyRequest().permitAll();
19+
}
20+
21+
protected void configure5(HttpSecurity http) throws Exception {
22+
http.authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll();
23+
}
24+
25+
protected void configure6(HttpSecurity http) throws Exception {
26+
http.authorizeRequests(requests -> requests.requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll());
27+
}
28+
29+
protected void configure7(HttpSecurity http) throws Exception {
30+
http.requestMatchers(matcher -> EndpointRequest.toAnyEndpoint()).authorizeRequests().anyRequest().permitAll();
31+
}
32+
33+
protected void configureOk1(HttpSecurity http) throws Exception {
34+
http.requestMatcher(EndpointRequest.toAnyEndpoint());
35+
}
36+
37+
protected void configureOk2(HttpSecurity http) throws Exception {
38+
http.requestMatchers().requestMatchers(EndpointRequest.toAnyEndpoint());
39+
}
40+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE/CWE-016/SpringBootActuators.ql

0 commit comments

Comments
 (0)