Thanks to visit codestin.com
Credit goes to github.com

Skip to content

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
merged 17 commits into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions java/ql/lib/semmle/code/java/frameworks/spring/SpringBoot.qll
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 java/ql/lib/semmle/code/java/frameworks/spring/SpringSecurity.qll
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 java/ql/lib/semmle/code/java/security/SpringBootActuatorsQuery.qll
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
)
)
}
25 changes: 25 additions & 0 deletions java/ql/src/Security/CWE/CWE-200/SpringBootActuators.java
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 java/ql/src/Security/CWE/CWE-200/SpringBootActuators.qhelp
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 to access these 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>
20 changes: 20 additions & 0 deletions java/ql/src/Security/CWE/CWE-200/SpringBootActuators.ql
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 information leak from the internal application,
* 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."
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 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).

This file was deleted.

Loading