Skip to content

Commit f1f6ac6

Browse files
authored
fix(#2615): reduce ssti vectors for thymeleaf
* fix: thymeleaf ssti * feat(ci): add semgrep scan * chore: formatting * chore: remove redundant vulnerable file
1 parent 9ae408d commit f1f6ac6

File tree

4 files changed

+132
-4
lines changed

4 files changed

+132
-4
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Vulnerability Scan
2+
3+
on:
4+
pull_request: {}
5+
workflow_dispatch: {}
6+
push:
7+
branches: ["master"]
8+
schedule:
9+
- cron: '0 7 * * *'
10+
11+
jobs:
12+
semgrep:
13+
name: semgrep/ci
14+
runs-on: ubuntu-latest
15+
16+
container:
17+
image: returntocorp/semgrep
18+
19+
if: (github.actor != 'renovate')
20+
21+
steps:
22+
- uses: actions/checkout@v3
23+
- run: semgrep ci
24+
env:
25+
SEMGREP_RULES: p/default

spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/config/AdminServerNotifierAutoConfiguration.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@
4848
import org.springframework.web.client.RestTemplate;
4949
import org.thymeleaf.TemplateEngine;
5050
import org.thymeleaf.spring6.SpringTemplateEngine;
51-
import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
5251
import org.thymeleaf.templatemode.TemplateMode;
52+
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
5353

5454
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
5555
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
@@ -179,13 +179,12 @@ public MailNotifier mailNotifier(JavaMailSender mailSender, InstanceRepository r
179179

180180
@Bean
181181
public TemplateEngine mailNotifierTemplateEngine() {
182-
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
183-
resolver.setApplicationContext(this.applicationContext);
182+
ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
184183
resolver.setTemplateMode(TemplateMode.HTML);
185184
resolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
186185

187186
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
188-
templateEngine.addTemplateResolver(resolver);
187+
templateEngine.setTemplateResolver(resolver);
189188
return templateEngine;
190189
}
191190

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2014-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package de.codecentric.boot.admin.server.notify;
18+
19+
import java.io.FileNotFoundException;
20+
import java.io.IOException;
21+
import java.net.URL;
22+
23+
import org.assertj.core.api.WithAssertions;
24+
import org.junit.jupiter.api.Test;
25+
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.boot.SpringBootConfiguration;
27+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
28+
import org.springframework.boot.test.context.SpringBootTest;
29+
import org.thymeleaf.context.Context;
30+
31+
import de.codecentric.boot.admin.server.config.EnableAdminServer;
32+
33+
@SpringBootTest(properties = { "spring.mail.host=localhost", "spring.boot.admin.notify.mail=true" })
34+
class MailNotifierIntegrationTest implements WithAssertions {
35+
36+
@Autowired
37+
MailNotifier mailNotifier;
38+
39+
@Test
40+
void fileProtocolIsNotAllowed() {
41+
assertThatThrownBy(() -> {
42+
URL resource = getClass().getClassLoader().getResource(".");
43+
mailNotifier.setTemplate("file://" + resource.getFile()
44+
+ "de/codecentric/boot/admin/server/notify/vulnerable-file.html");
45+
mailNotifier.getBody(new Context());
46+
}).hasCauseInstanceOf(FileNotFoundException.class);
47+
}
48+
49+
@Test
50+
void httpProtocolIsNotAllowed() {
51+
assertThatThrownBy(() -> {
52+
URL resource = getClass().getClassLoader().getResource(".");
53+
mailNotifier.setTemplate(
54+
"https://raw.githubusercontent.com/codecentric/spring-boot-admin/gh-pages/vulnerable-file.html");
55+
mailNotifier.getBody(new Context());
56+
}).hasCauseInstanceOf(FileNotFoundException.class);
57+
}
58+
59+
@Test
60+
void classpathProtocolIsAllowed() throws IOException {
61+
assertThatThrownBy(() -> {
62+
mailNotifier.setTemplate("/de/codecentric/boot/admin/server/notify/vulnerable-file.html");
63+
String body = mailNotifier.getBody(new Context());
64+
}).rootCause().hasMessageContaining("error=2, No such file or directory");
65+
}
66+
67+
@EnableAdminServer
68+
@EnableAutoConfiguration
69+
@SpringBootConfiguration
70+
public static class TestAdminApplication {
71+
72+
}
73+
74+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<!DOCTYPE html>
2+
<html xmlns:th="http://www.thymeleaf.org">
3+
<head>
4+
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
5+
</head>
6+
<body>
7+
8+
<tr
9+
th:with="getRuntimeMethod=${T(org.springframework.util.ReflectionUtils).findMethod(T(org.springframework.util.ClassUtils).forName('java.lang.Runtime',T(org.springframework.util.ClassUtils).getDefaultClassLoader()), 'getRuntime' )}"
10+
>
11+
<td>
12+
<a
13+
th:with="runtimeObj=${T(org.springframework.util.ReflectionUtils).invokeMethod(getRuntimeMethod, null)}"
14+
>
15+
<a
16+
th:with="exeMethod=${T(org.springframework.util.ReflectionUtils).findMethod(T(org.springframework.util.ClassUtils).forName('java.lang.Runtime',T(org.springframework.util.ClassUtils).getDefaultClassLoader()), 'exec', ''.getClass() )}"
17+
>
18+
<a
19+
th:href="${param2}"
20+
th:with="param2=${T(org.springframework.util.ReflectionUtils).invokeMethod(exeMethod, runtimeObj, 'evilSoftwareThatShouldNotRun' )
21+
}"
22+
></a>
23+
</a>
24+
25+
</a>
26+
</td>
27+
</tr>
28+
29+
</body>
30+
</html>

0 commit comments

Comments
 (0)