mirror of
https://github.com/github/codeql.git
synced 2026-04-26 17:25:19 +02:00
Merge pull request #2901 from ggolawski/java-spring-boot-actuators
CodeQL query to detect open Spring Boot actuator endpoints
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class SpringBootActuators extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// BAD: Unauthenticated access to Spring Boot actuator endpoints is allowed
|
||||
http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests((requests) ->
|
||||
requests.anyRequest().permitAll());
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class ActuatorSecurity extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// GOOD: only users with ENDPOINT_ADMIN role are allowed to access the actuator endpoints
|
||||
http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests((requests) ->
|
||||
requests.anyRequest().hasRole("ENDPOINT_ADMIN"));
|
||||
http.httpBasic();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>Spring Boot includes a number of additional features called actuators that let you monitor
|
||||
and interact with your web application. Exposing unprotected actuator endpoints via JXM or HTTP
|
||||
can, however, lead to information disclosure or even to remote code execution vulnerability.</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>Since actuator endpoints may contain sensitive information, careful consideration should be
|
||||
given about when to expose them. You should take care to secure exposed HTTP endpoints in the same
|
||||
way that you would any other sensitive URL. If Spring Security is present, endpoints are secured by
|
||||
default using Spring Security’s content-negotiation strategy. If you wish to configure custom
|
||||
security for HTTP endpoints, for example, only allow users with a certain role to access them,
|
||||
Spring Boot provides some convenient <code>RequestMatcher</code> objects that can be used in
|
||||
combination with Spring Security.</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 documentation:
|
||||
<a href="https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html">Actuators</a>.
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.veracode.com/blog/research/exploiting-spring-boot-actuators">Exploiting Spring Boot Actuators</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @name Exposed Spring Boot actuators
|
||||
* @description Exposing Spring Boot actuators may lead to internal application's information leak
|
||||
* or even to remote code execution.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/spring-boot-exposed-actuators
|
||||
* @tags security
|
||||
* external/cwe/cwe-16
|
||||
*/
|
||||
|
||||
import java
|
||||
import SpringBootActuators
|
||||
|
||||
from PermitAllCall permitAllCall
|
||||
where permitAllCall.permitsSpringBootActuators()
|
||||
select permitAllCall, "Unauthenticated access to Spring Boot actuator is allowed."
|
||||
@@ -0,0 +1,141 @@
|
||||
import java
|
||||
|
||||
/** The class `org.springframework.security.config.annotation.web.builders.HttpSecurity`. */
|
||||
class TypeHttpSecurity extends Class {
|
||||
TypeHttpSecurity() {
|
||||
this
|
||||
.hasQualifiedName("org.springframework.security.config.annotation.web.builders",
|
||||
"HttpSecurity")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The class
|
||||
* `org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer`.
|
||||
*/
|
||||
class TypeAuthorizedUrl extends Class {
|
||||
TypeAuthorizedUrl() {
|
||||
this
|
||||
.hasQualifiedName("org.springframework.security.config.annotation.web.configurers",
|
||||
"ExpressionUrlAuthorizationConfigurer<HttpSecurity>$AuthorizedUrl<>")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The class
|
||||
* `org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry`.
|
||||
*/
|
||||
class TypeAbstractRequestMatcherRegistry extends Class {
|
||||
TypeAbstractRequestMatcherRegistry() {
|
||||
this
|
||||
.hasQualifiedName("org.springframework.security.config.annotation.web",
|
||||
"AbstractRequestMatcherRegistry<AuthorizedUrl<>>")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The class
|
||||
* `org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest.EndpointRequestMatcher`.
|
||||
*/
|
||||
class TypeEndpointRequestMatcher extends Class {
|
||||
TypeEndpointRequestMatcher() {
|
||||
this
|
||||
.hasQualifiedName("org.springframework.boot.actuate.autoconfigure.security.servlet",
|
||||
"EndpointRequest$EndpointRequestMatcher")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `HttpSecurity.requestMatcher` method with argument of type
|
||||
* `EndpointRequestMatcher`.
|
||||
*/
|
||||
class RequestMatcherCall extends MethodAccess {
|
||||
RequestMatcherCall() {
|
||||
getMethod().hasName("requestMatcher") and
|
||||
getMethod().getDeclaringType() instanceof TypeHttpSecurity and
|
||||
getArgument(0).getType() instanceof TypeEndpointRequestMatcher
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `HttpSecurity.requestMatchers` method with lambda argument resolving to
|
||||
* `EndpointRequestMatcher` type.
|
||||
*/
|
||||
class RequestMatchersCall extends MethodAccess {
|
||||
RequestMatchersCall() {
|
||||
getMethod().hasName("requestMatchers") and
|
||||
getMethod().getDeclaringType() instanceof TypeHttpSecurity and
|
||||
getArgument(0).(LambdaExpr).getExprBody().getType() instanceof TypeEndpointRequestMatcher
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to `HttpSecurity.authorizeRequests` method. */
|
||||
class AuthorizeRequestsCall extends MethodAccess {
|
||||
AuthorizeRequestsCall() {
|
||||
getMethod().hasName("authorizeRequests") and
|
||||
getMethod().getDeclaringType() instanceof TypeHttpSecurity
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to `AuthorizedUrl.permitAll` method. */
|
||||
class PermitAllCall extends MethodAccess {
|
||||
PermitAllCall() {
|
||||
getMethod().hasName("permitAll") and
|
||||
getMethod().getDeclaringType() instanceof TypeAuthorizedUrl
|
||||
}
|
||||
|
||||
/** Holds if `permitAll` is called on request(s) mapped to actuator endpoint(s). */
|
||||
predicate permitsSpringBootActuators() {
|
||||
exists(AuthorizeRequestsCall authorizeRequestsCall |
|
||||
// .requestMatcher(EndpointRequest).authorizeRequests([...]).[...]
|
||||
authorizeRequestsCall.getQualifier() instanceof RequestMatcherCall
|
||||
or
|
||||
// .requestMatchers(matcher -> EndpointRequest).authorizeRequests([...]).[...]
|
||||
authorizeRequestsCall.getQualifier() instanceof RequestMatchersCall
|
||||
or
|
||||
// http.authorizeRequests([...]).[...]
|
||||
authorizeRequestsCall.getQualifier() instanceof VarAccess
|
||||
|
|
||||
// [...].authorizeRequests(r -> r.anyRequest().permitAll()) or
|
||||
// [...].authorizeRequests(r -> r.requestMatchers(EndpointRequest).permitAll())
|
||||
authorizeRequestsCall.getArgument(0).(LambdaExpr).getExprBody() = this and
|
||||
(
|
||||
this.getQualifier() instanceof AnyRequestCall or
|
||||
this.getQualifier() instanceof RegistryRequestMatchersCall
|
||||
)
|
||||
or
|
||||
// [...].authorizeRequests().requestMatchers(EndpointRequest).permitAll() or
|
||||
// [...].authorizeRequests().anyRequest().permitAll()
|
||||
authorizeRequestsCall.getNumArgument() = 0 and
|
||||
exists(RegistryRequestMatchersCall registryRequestMatchersCall |
|
||||
registryRequestMatchersCall.getQualifier() = authorizeRequestsCall and
|
||||
this.getQualifier() = registryRequestMatchersCall
|
||||
)
|
||||
or
|
||||
exists(AnyRequestCall anyRequestCall |
|
||||
anyRequestCall.getQualifier() = authorizeRequestsCall and
|
||||
this.getQualifier() = anyRequestCall
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to `AbstractRequestMatcherRegistry.anyRequest` method. */
|
||||
class AnyRequestCall extends MethodAccess {
|
||||
AnyRequestCall() {
|
||||
getMethod().hasName("anyRequest") and
|
||||
getMethod().getDeclaringType() instanceof TypeAbstractRequestMatcherRegistry
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `AbstractRequestMatcherRegistry.requestMatchers` method with an argument of type
|
||||
* `EndpointRequestMatcher`.
|
||||
*/
|
||||
class RegistryRequestMatchersCall extends MethodAccess {
|
||||
RegistryRequestMatchersCall() {
|
||||
getMethod().hasName("requestMatchers") and
|
||||
getMethod().getDeclaringType() instanceof TypeAbstractRequestMatcherRegistry and
|
||||
getAnArgument().getType() instanceof TypeEndpointRequestMatcher
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user