I started a new project at Spring Initializr with these parameters: - https://start.spring.io/#!type=maven-project&language=java&platformVersion=2.5.2.RELEASE&packaging=jar&jvmVersion=11&groupId=com.example&artifactId=traceforbidden1&name=traceforbidden1&description=HTTP%20TRACE%20sample&packageName=com.example.traceforbidden1&dependencies=web,actuator,security
Find the generated code here: https://github.com/juliojgd/sb-trace-http-test
Then, I created a:
@RestController
static class MyController {
@GetMapping("/trace-test")
public ResponseEntity<String> traceTest() {
return ResponseEntity.ok("Right!");
}
}
And then with no extra addition or change:
$ mvn clean install
Run the project with Actuator on the same port (8080)
$ java -Dspring.security.user.password=p1 -jar target/traceforbidden1-0.0.1-SNAPSHOT.jar
Note that in default configuration my endpoint and actuator endpoints are published in the same (8080) port.
Results issuing GET HTTP Requests
$ curl http://localhost:8080/trace-test -u user:p1
Right!
$ curl http://localhost:8080/actuator/health
{"status":"UP"}
Everything OK.
Results issuing TRACE HTTP Requests
$ curl -v http://localhost:8080/trace-test -u user:p1 -X TRACE -H "test-header: value"
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
* Server auth using Basic with user 'user'
> TRACE /trace-test HTTP/1.1
> Host: localhost:8080
> Authorization: Basic dXNlcjpwMQ==
> User-Agent: curl/7.58.0
> Accept: */*
> test-header: value
>
< HTTP/1.1 405
< Allow: HEAD, DELETE, POST, GET, OPTIONS, PUT
< Content-Type: text/html;charset=utf-8
< Content-Language: en
< Content-Length: 449
< Date: Tue, 06 Jul 2021 01:29:45 GMT
<
* Connection #0 to host localhost left intact
<!doctype html><html lang="en"><head><title>HTTP Status 405 – Method Not Allowed</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 405 – Method Not Allowed</h1></body></html>
$ curl -v http://localhost:8080/actuator/health -X TRACE -H "h: h"
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> TRACE /actuator/health HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
> h: h
>
< HTTP/1.1 405
< Allow: HEAD, DELETE, POST, GET, OPTIONS, PUT
< Content-Type: text/html;charset=utf-8
< Content-Language: en
< Content-Length: 449
< Date: Tue, 06 Jul 2021 01:33:06 GMT
<
* Connection #0 to host localhost left intact
<!doctype html><html lang="en"><head><title>HTTP Status 405 – Method Not Allowed</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 405 – Method Not Allowed</h1></body></html
Everything seems OK. TRACE HTTP Method is not allowed BOTH in my endpoint and actuator endpoint (perfect for me, it has been depicted as a security risk) and no ECHO behavior is obtained because this echo behavior is the one considered potential harm). Be aware that if the echo behavior (return the received headers) is accomplished, the security risk remains as the risky part is to return the request echo in the response (even if seems to be not alloweb (405))
Run the project with Actuator on different port (8081)
$ java -Dmanagement.server.port=8081 -Dspring.security.user.password=p1 -jar target/traceforbidden1-0.0.1-SNAPSHOT.jar
Results with GET Http Method
Same as beforehand, OK
Results issuing TRACE HTTP Requests
With my endpoint in 8080 port OK, not returning the headers as echo response because is not allowed:
$ curl -v http://localhost:8080/trace-test -u user:p1 -X TRACE -H "test-header: value"
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
* Server auth using Basic with user 'user'
> TRACE /trace-test HTTP/1.1
> Host: localhost:8080
> Authorization: Basic dXNlcjpwMQ==
> User-Agent: curl/7.58.0
> Accept: */*
> test-header: value
>
< HTTP/1.1 405
< Allow: HEAD, DELETE, POST, GET, OPTIONS, PUT
< Content-Type: text/html;charset=utf-8
< Content-Language: en
< Content-Length: 449
< Date: Tue, 06 Jul 2021 01:36:59 GMT
<
* Connection #0 to host localhost left intact
<!doctype html><html lang="en"><head><title>HTTP Status 405 – Method Not Allowed</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 405 – Method Not Allowed</h1></body></html>
But, issuing a request to Actuator endpoint in 8081 port (different):
$ curl -v http://localhost:8081/actuator/health -X TRACE -H "h: h"
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8081 (#0)
> TRACE /actuator/health HTTP/1.1
> Host: localhost:8081
> User-Agent: curl/7.58.0
> Accept: */*
> h: h
>
< HTTP/1.1 405
< Allow: HEAD, DELETE, POST, GET, OPTIONS, PUT
< Content-Type: message/http
< Content-Length: 89
< Date: Tue, 06 Jul 2021 01:38:29 GMT
<
TRACE /error HTTP/1.1
host: localhost:8081
user-agent: curl/7.58.0
accept: */*
h: h
It is returned the echo of the request! and even the potentially sensitive user-included header named "h" is included.
Clearly is working different when actuator is in the same port or it is in different port.
Is this different behavior intended? I hope not 😄
P.S. I tried to customize the setAllowTrace
creating a bean of type ManagementWebServerFactoryCustomizer<TomcatServletWebServerFactory>
and then adding a ConnectorCustomizer
but it does not work, it seems to not apply to the management (Actuator) Tomcat. It is weird as in the type name is ManagementWebServer...
Comment From: juliojgd
I implemented a workaround here: juliojgd/sb-trace-http-test#1 but I find dirty to have to create a HttpFilter in the ManagementContext only for different port situation.
The right way to do this is to get the StrictHttpFirewall
working here and forbidding the unsafe operations (TRACE Http method and others) in the same fashion it is done in main port.
Any thought on this?
Comment From: wilkinsona
A slightly more straightforward way to get consistent behaviour is to @Import
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration
in TraceHttpBlockedManagementConfiguration
and remove the two @Bean
methods. This will ensure that Spring Security's filter in the management context is configured to run on error dispatches. As a result its StrictHttpFirewall
will be in place when Tomcat tries to forward the TRACE
request to /error
and it'll be rejected with a 405.
Comment From: juliojgd
It's not working with that @Import
and removing the two beans. With such change, the call to http://localhost:8081/actuator/health with HTTP TRACE does not return the echo (OK). But the call to http://localhost:8080/trace-test with TRACE method DOES return the echo (not OK).
So your suggested solution flips the results, now the 8081 management port behavior is OK (no echo) but the 8080 main port is not OK (echo)
With this configuration class:
@ManagementContextConfiguration
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
@ConditionalOnWebApplication(type = Type.SERVLET)
@Import({org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration.class})
public class TraceHttpBlockedManagementConfiguration {
}
I get an OK result in 8081 management port (no echo):
$ curl -v localhost:8081/actuator/health -X TRACE
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8081 (#0)
> TRACE /actuator/health HTTP/1.1
> Host: localhost:8081
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 405
< Allow: HEAD, DELETE, POST, GET, OPTIONS, PUT
< Content-Type: text/html;charset=utf-8
< Content-Language: en
< Content-Length: 449
< Date: Mon, 26 Jul 2021 17:27:57 GMT
<
* Connection #0 to host localhost left intact
<!doctype html><html lang="en"><head><title>HTTP Status 405 – Method Not Allowed</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 405 – Method Not Allowed</h1></body></html>
But in main port (8080) there is echo 😢
curl -v http://localhost:8080/trace-test -u user:p1 -X TRACE -H "h: h"
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
* Server auth using Basic with user 'user'
> TRACE /trace-test HTTP/1.1
> Host: localhost:8080
> Authorization: Basic dXNlcjpwMQ==
> User-Agent: curl/7.58.0
> Accept: */*
> h: h
>
< HTTP/1.1 405
< Allow: HEAD, DELETE, POST, GET, OPTIONS, PUT
< Content-Type: message/http
< Content-Length: 124
< Date: Mon, 26 Jul 2021 17:31:45 GMT
<
TRACE /error HTTP/1.1
host: localhost:8080
authorization: Basic dXNlcjpwMQ==
user-agent: curl/7.58.0
accept: */*
h: h
* Connection #0 to host localhost left intact
Maybe we are missing some configuration property or even another needed configuration import? After debugging it seems
BTW, the sample PR juliojgd/sb-trace-http-test#1 is updated with the change you proposed and uses SB 2.5.3 also.
Comment From: wilkinsona
Sorry, I meant @ImportAutoConfiguration
rather than @Import
. You also need to place your @ManagementContextConfiguration
class in a package that isn't covered by component scanning, otherwise it affects the main context too. In the sample that could be something like com.example.actuator
.
Comment From: wilkinsona
The root cause here is that the changes made for https://github.com/spring-projects/spring-boot/issues/8289 are not effective when Spring Security's filter is in a separate management context, resulting in 1.x's behaviour instead.
Comment From: juliojgd
Sorry, I meant
@ImportAutoConfiguration
rather than@Import
. You also need to place your@ManagementContextConfiguration
class in a package that isn't covered by component scanning, otherwise it affects the main context too. In the sample that could be something likecom.example.actuator
.
It works, thanks for the clarification.
Comment From: juliojgd
The root cause here is that the changes made for #8289 are not effective when Spring Security's filter is in a separate management context, resulting in 1.x's behaviour instead.
I'll try to contribute a fix for this if you are OK with that.
Comment From: wilkinsona
Thanks for the offer, @juliojgd. As indicated by the issue being assigned to me, I was already been working on a fix which I have just pushed. Thanks anyway.