Skip to content

Commit 530a09b

Browse files
committed
[wip] Spring4Shell
1 parent e3852db commit 530a09b

File tree

5 files changed

+262
-0
lines changed

5 files changed

+262
-0
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ dependencies {
205205
testRuntimeOnly("org.springframework:spring-beans:latest.release")
206206
testRuntimeOnly("org.springframework:spring-context:latest.release")
207207
testRuntimeOnly("org.springframework:spring-web:latest.release")
208+
testRuntimeOnly("org.springframework:spring-webmvc:latest.release")
208209
testRuntimeOnly("org.springframework:spring-test:latest.release")
209210
testRuntimeOnly("org.springframework.boot:spring-boot-test:latest.release")
210211
testRuntimeOnly("org.springframework.boot:spring-boot-test-autoconfigure:latest.release")
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
* <p>
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+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
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+
package org.openrewrite.java.spring.cve;
17+
18+
import org.openrewrite.ExecutionContext;
19+
import org.openrewrite.Recipe;
20+
import org.openrewrite.java.JavaIsoVisitor;
21+
import org.openrewrite.java.JavaParser;
22+
import org.openrewrite.java.JavaTemplate;
23+
import org.openrewrite.java.JavaVisitor;
24+
import org.openrewrite.java.search.FindAnnotations;
25+
import org.openrewrite.java.search.FindTypes;
26+
import org.openrewrite.java.search.UsesType;
27+
import org.openrewrite.java.tree.J;
28+
29+
import java.util.Set;
30+
import java.util.function.Supplier;
31+
32+
public class Spring4Shell extends Recipe {
33+
@Override
34+
public String getDisplayName() {
35+
return "Spring4Shell fix";
36+
}
37+
38+
@Override
39+
public String getDescription() {
40+
return "See the [blog post](https://spring.io/blog/2022/03/31/spring-framework-rce-early-announcement#status) on the issue. This recipe can be further refined as more information becomes available.";
41+
}
42+
43+
@Override
44+
protected JavaVisitor<ExecutionContext> getSingleSourceApplicableTest() {
45+
return new UsesType<>("org.springframework.boot.autoconfigure.SpringBootApplication");
46+
}
47+
48+
// @Override
49+
// protected TreeVisitor<?, ExecutionContext> getApplicableTest() {
50+
// // TODO add other applicable tests around presence of dependencies below a certain version, WAR packaging, etc.
51+
// return new JavaVisitor<ExecutionContext>() {
52+
// @Override
53+
// public J visitJavaSourceFile(JavaSourceFile cu, ExecutionContext ctx) {
54+
// JavaVersion javaVersion = cu.getMarkers().findFirst(JavaVersion.class).orElse(null);
55+
// if (javaVersion != null && javaVersion.getMajorVersion() >= 9) {
56+
// return cu.withMarkers(cu.getMarkers().searchResult());
57+
// }
58+
// return cu;
59+
// }
60+
// };
61+
// }
62+
63+
@Override
64+
protected JavaVisitor<ExecutionContext> getVisitor() {
65+
return new JavaIsoVisitor<ExecutionContext>() {
66+
final Supplier<JavaParser> javaParser = () -> JavaParser.fromJavaVersion()
67+
.classpath("spring-boot-autoconfigure", "spring-beans", "spring-web", "spring-webmvc")
68+
.build();
69+
70+
final JavaTemplate mvcRegistration = JavaTemplate
71+
.builder(this::getCursor, "" +
72+
"@Bean " +
73+
"public WebMvcRegistrations mvcRegistrations() {" +
74+
" return new WebMvcRegistrations() {" +
75+
" @Override" +
76+
" public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {" +
77+
" return null;" +
78+
" }" +
79+
" };" +
80+
"}")
81+
.javaParser(javaParser)
82+
.imports(
83+
"org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations",
84+
"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter")
85+
.build();
86+
87+
@Override
88+
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) {
89+
J.ClassDeclaration c = super.visitClassDeclaration(classDecl, executionContext);
90+
Set<J.Annotation> springBootApps = FindAnnotations.find(c, "@org.springframework.boot.autoconfigure.SpringBootApplication");
91+
if (!springBootApps.isEmpty() && FindTypes.find(c, "org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations").isEmpty()) {
92+
// add @Bean method
93+
c = c.withTemplate(mvcRegistration, c.getBody().getCoordinates().addMethodDeclaration((m1, m2) -> {
94+
if (m1.getSimpleName().equals("mvcRegistrations")) return 1;
95+
if (m2.getSimpleName().equals("mvcRegistrations")) return -1;
96+
return m1.getSimpleName().compareTo(m2.getSimpleName());
97+
}));
98+
99+
maybeAddImport("org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations");
100+
maybeAddImport("org.springframework.context.annotation.Bean");
101+
maybeAddImport("org.springframework.web.bind.ServletRequestDataBinder");
102+
maybeAddImport("org.springframework.web.context.request.NativeWebRequest");
103+
maybeAddImport("org.springframework.web.method.annotation.InitBinderDataBinderFactory");
104+
maybeAddImport("org.springframework.web.method.support.InvocableHandlerMethod");
105+
maybeAddImport("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter");
106+
maybeAddImport("org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory");
107+
}
108+
return c;
109+
}
110+
};
111+
}
112+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
* <p>
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+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
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+
@NonNullApi @NonNullFields
17+
package org.openrewrite.java.spring.cve;
18+
19+
import org.openrewrite.internal.lang.NonNullApi;
20+
import org.openrewrite.internal.lang.NonNullFields;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
* <p>
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+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
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+
import org.springframework.boot.SpringApplication;
17+
import org.springframework.boot.autoconfigure.SpringBootApplication;
18+
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
19+
import org.springframework.context.annotation.Bean;
20+
import org.springframework.web.bind.ServletRequestDataBinder;
21+
import org.springframework.web.context.request.NativeWebRequest;
22+
import org.springframework.web.method.annotation.InitBinderDataBinderFactory;
23+
import org.springframework.web.method.support.InvocableHandlerMethod;
24+
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
25+
import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory;
26+
27+
class Fix {
28+
@Bean
29+
public WebMvcRegistrations mvcRegistrations() {
30+
return new WebMvcRegistrations() {
31+
@Override
32+
public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
33+
return new ExtendedRequestMappingHandlerAdapter();
34+
}
35+
};
36+
}
37+
38+
private static class ExtendedRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {
39+
@Override
40+
protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> methods) {
41+
return new ServletRequestDataBinderFactory(methods, getWebBindingInitializer()) {
42+
@Override
43+
protected ServletRequestDataBinder createBinderInstance(
44+
Object target, String name, NativeWebRequest request) throws Exception {
45+
46+
ServletRequestDataBinder binder = super.createBinderInstance(target, name, request);
47+
String[] fields = binder.getDisallowedFields();
48+
List<String> fieldList = new ArrayList<>(fields != null ? Arrays.asList(fields) : Collections.emptyList());
49+
fieldList.addAll(Arrays.asList("class.*", "Class.*", "*.class.*", "*.Class.*"));
50+
binder.setDisallowedFields(fieldList.toArray(new String[]{}));
51+
return binder;
52+
}
53+
};
54+
}
55+
}
56+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
* <p>
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+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
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+
package org.openrewrite.java.spring.org.openrewrite.java.spring.cve
17+
18+
import org.junit.jupiter.api.Test
19+
import org.openrewrite.Recipe
20+
import org.openrewrite.java.JavaParser
21+
import org.openrewrite.java.JavaRecipeTest
22+
import org.openrewrite.java.spring.cve.Spring4Shell
23+
24+
class Spring4ShellTest : JavaRecipeTest {
25+
override val parser: JavaParser
26+
get() = JavaParser.fromJavaVersion()
27+
.logCompilationWarningsAndErrors(true)
28+
.classpath("spring-beans", "spring-boot", "spring-context")
29+
.build()
30+
31+
override val recipe: Recipe
32+
get() = Spring4Shell()
33+
34+
@Test
35+
fun spring4Shell() = assertChanged(
36+
before = """
37+
import org.springframework.boot.autoconfigure.SpringBootApplication;
38+
import org.springframework.context.annotation.Bean;
39+
40+
@SpringBootApplication
41+
class Test {
42+
@Bean
43+
String existingBean() {
44+
return "hi";
45+
}
46+
}
47+
""",
48+
after = """
49+
import org.springframework.boot.autoconfigure.SpringBootApplication;
50+
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
51+
import org.springframework.context.annotation.Bean;
52+
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
53+
54+
@SpringBootApplication
55+
class Test {
56+
@Bean
57+
String existingBean() {
58+
return "hi";
59+
}
60+
61+
@Bean
62+
public WebMvcRegistrations mvcRegistrations() {
63+
return new WebMvcRegistrations() {
64+
@Override
65+
public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
66+
return null;
67+
}
68+
};
69+
}
70+
}
71+
""",
72+
)
73+
}

0 commit comments

Comments
 (0)