Skip to content

Commit

Permalink
[wip] Spring4Shell
Browse files Browse the repository at this point in the history
  • Loading branch information
jkschneider committed Mar 31, 2022
1 parent e3852db commit 530a09b
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ dependencies {
testRuntimeOnly("org.springframework:spring-beans:latest.release")
testRuntimeOnly("org.springframework:spring-context:latest.release")
testRuntimeOnly("org.springframework:spring-web:latest.release")
testRuntimeOnly("org.springframework:spring-webmvc:latest.release")
testRuntimeOnly("org.springframework:spring-test:latest.release")
testRuntimeOnly("org.springframework.boot:spring-boot-test:latest.release")
testRuntimeOnly("org.springframework.boot:spring-boot-test-autoconfigure:latest.release")
Expand Down
112 changes: 112 additions & 0 deletions src/main/java/org/openrewrite/java/spring/cve/Spring4Shell.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright 2021 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.java.spring.cve;

import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.search.FindAnnotations;
import org.openrewrite.java.search.FindTypes;
import org.openrewrite.java.search.UsesType;
import org.openrewrite.java.tree.J;

import java.util.Set;
import java.util.function.Supplier;

public class Spring4Shell extends Recipe {
@Override
public String getDisplayName() {
return "Spring4Shell fix";
}

@Override
public String getDescription() {
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.";
}

@Override
protected JavaVisitor<ExecutionContext> getSingleSourceApplicableTest() {
return new UsesType<>("org.springframework.boot.autoconfigure.SpringBootApplication");
}

// @Override
// protected TreeVisitor<?, ExecutionContext> getApplicableTest() {
// // TODO add other applicable tests around presence of dependencies below a certain version, WAR packaging, etc.
// return new JavaVisitor<ExecutionContext>() {
// @Override
// public J visitJavaSourceFile(JavaSourceFile cu, ExecutionContext ctx) {
// JavaVersion javaVersion = cu.getMarkers().findFirst(JavaVersion.class).orElse(null);
// if (javaVersion != null && javaVersion.getMajorVersion() >= 9) {
// return cu.withMarkers(cu.getMarkers().searchResult());
// }
// return cu;
// }
// };
// }

@Override
protected JavaVisitor<ExecutionContext> getVisitor() {
return new JavaIsoVisitor<ExecutionContext>() {
final Supplier<JavaParser> javaParser = () -> JavaParser.fromJavaVersion()
.classpath("spring-boot-autoconfigure", "spring-beans", "spring-web", "spring-webmvc")
.build();

final JavaTemplate mvcRegistration = JavaTemplate
.builder(this::getCursor, "" +
"@Bean " +
"public WebMvcRegistrations mvcRegistrations() {" +
" return new WebMvcRegistrations() {" +
" @Override" +
" public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {" +
" return null;" +
" }" +
" };" +
"}")
.javaParser(javaParser)
.imports(
"org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations",
"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter")
.build();

@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) {
J.ClassDeclaration c = super.visitClassDeclaration(classDecl, executionContext);
Set<J.Annotation> springBootApps = FindAnnotations.find(c, "@org.springframework.boot.autoconfigure.SpringBootApplication");
if (!springBootApps.isEmpty() && FindTypes.find(c, "org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations").isEmpty()) {
// add @Bean method
c = c.withTemplate(mvcRegistration, c.getBody().getCoordinates().addMethodDeclaration((m1, m2) -> {
if (m1.getSimpleName().equals("mvcRegistrations")) return 1;
if (m2.getSimpleName().equals("mvcRegistrations")) return -1;
return m1.getSimpleName().compareTo(m2.getSimpleName());
}));

maybeAddImport("org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations");
maybeAddImport("org.springframework.context.annotation.Bean");
maybeAddImport("org.springframework.web.bind.ServletRequestDataBinder");
maybeAddImport("org.springframework.web.context.request.NativeWebRequest");
maybeAddImport("org.springframework.web.method.annotation.InitBinderDataBinderFactory");
maybeAddImport("org.springframework.web.method.support.InvocableHandlerMethod");
maybeAddImport("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter");
maybeAddImport("org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory");
}
return c;
}
};
}
}
20 changes: 20 additions & 0 deletions src/main/java/org/openrewrite/java/spring/cve/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2021 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@NonNullApi @NonNullFields
package org.openrewrite.java.spring.cve;

import org.openrewrite.internal.lang.NonNullApi;
import org.openrewrite.internal.lang.NonNullFields;
56 changes: 56 additions & 0 deletions src/main/resources/Spring4Shell.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2021 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.annotation.InitBinderDataBinderFactory;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory;

class Fix {
@Bean
public WebMvcRegistrations mvcRegistrations() {
return new WebMvcRegistrations() {
@Override
public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
return new ExtendedRequestMappingHandlerAdapter();
}
};
}

private static class ExtendedRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {
@Override
protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> methods) {
return new ServletRequestDataBinderFactory(methods, getWebBindingInitializer()) {
@Override
protected ServletRequestDataBinder createBinderInstance(
Object target, String name, NativeWebRequest request) throws Exception {

ServletRequestDataBinder binder = super.createBinderInstance(target, name, request);
String[] fields = binder.getDisallowedFields();
List<String> fieldList = new ArrayList<>(fields != null ? Arrays.asList(fields) : Collections.emptyList());
fieldList.addAll(Arrays.asList("class.*", "Class.*", "*.class.*", "*.Class.*"));
binder.setDisallowedFields(fieldList.toArray(new String[]{}));
return binder;
}
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright 2021 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.java.spring.org.openrewrite.java.spring.cve

import org.junit.jupiter.api.Test
import org.openrewrite.Recipe
import org.openrewrite.java.JavaParser
import org.openrewrite.java.JavaRecipeTest
import org.openrewrite.java.spring.cve.Spring4Shell

class Spring4ShellTest : JavaRecipeTest {
override val parser: JavaParser
get() = JavaParser.fromJavaVersion()
.logCompilationWarningsAndErrors(true)
.classpath("spring-beans", "spring-boot", "spring-context")
.build()

override val recipe: Recipe
get() = Spring4Shell()

@Test
fun spring4Shell() = assertChanged(
before = """
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
class Test {
@Bean
String existingBean() {
return "hi";
}
}
""",
after = """
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
@SpringBootApplication
class Test {
@Bean
String existingBean() {
return "hi";
}
@Bean
public WebMvcRegistrations mvcRegistrations() {
return new WebMvcRegistrations() {
@Override
public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
return null;
}
};
}
}
""",
)
}

0 comments on commit 530a09b

Please sign in to comment.