---
id: "CVE-2026-85724"
kind: "vulnerability"
aliases: ["CVE-2026-85724", "GHSA-5f42-97gr-vfhq"]
title: "Moquette: Pattern-ACL wildcard injection (cross-tenant authorization bypass) plus a remote-unauthenticated DoS cluster, a Will-message authorization bypass, and a cross-session durable-corruption bug"
severity: "critical"
cvss: 9.600000381469727
epss: 0.0027000000700354576
exploited: false
status: "open"
category: "package"
products: [{"name": "io.moquette:moquette-broker", "fixed": ["0.18.1"], "affected": "<= 0.18.0", "ecosystem": "maven"}, {"name": "moquette", "fixed": ["0.18.1"], "affected": "< 0.18.1", "ecosystem": "vendor"}]
ecosystems: ["maven", "vendor"]
tags: ["cwe-155", "cwe-863", "java", "moquette", "mqtt", "multi-tenant", "package", "self-hosted"]
not_affected_if: ["no-moquette-dependency", "no-pattern-acl", "version-0.18.1-or-later"]
published_at: "2026-09-23T16:11:18+00:00"
first_seen_at: "2026-10-23T19:11:32+00:00"
updated_at: "2026-10-23T23:44:52+00:00"
sources: [{"source": "ghsa", "url": "https://github.com/advisories/GHSA-5f42-97gr-vfhq", "first_seen_at": "2026-10-23T19:11:32+00:00"}, {"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-85724", "first_seen_at": "2026-10-23T23:44:52+00:00"}]
---

# Moquette: Pattern-ACL wildcard injection (cross-tenant authorization bypass) plus a remote-unauthenticated DoS cluster, a Will-message authorization bypass, and a cross-session durable-corruption bug

Moquette is a Java MQTT broker; versions up to 0.18.0 let any connected client (anonymous by default) bypass pattern-based ACLs by putting +/# wildcard characters in its clientId, reading and writing other tenants' topics, and also crash the whole broker with malformed shared-subscription or nested-topic input. An attacker also gets unauthorized Last-Will publishes and can corrupt another client's persistent queue state.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- maven:io.moquette:moquette-broker affected <= 0.18.0 fixed 0.18.1
- vendor:moquette affected < 0.18.1 fixed 0.18.1

## How to check

Check the Maven dependency io.moquette:moquette-broker version in the project's pom.xml or lockfile; affected if 0.18.0 or lower.
Check whether authorizator uses pattern ACL rules containing %c or %u.

## Fix

Upgrade io.moquette:moquette-broker to 0.18.1.
Until upgraded, do not rely on pattern ACLs with %c/%u for tenant isolation; unknown whether a config-only mitigation exists.

## Timeline

- 2026-10-23T19:11:32+00:00 ghsa https://github.com/advisories/GHSA-5f42-97gr-vfhq
- 2026-10-23T23:44:52+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-85724

## From the sources

moquette is reachable by untrusted MQTT clients (anonymous by default), so every byte from any client, including pre-authentication, is untrusted. This is a memory-safe JVM: the ceiling is authorization/ACL bypass + denial of service + cross-session integrity, **not RCE** (I did not find one and do not claim one). Audited at commit `da7f719a6bab9829d520b5838e13ea7b1f9be3ef`, module `broker/`.

## What a connecting client can do

1. (Critical) Bypass `pattern`-based ACLs across tenants. In `AuthorizationsCollector.canDoOperation` (AuthorizationsCollector.java:116-131, esp. line 123) the clientId/username is substituted raw into a pattern ACL rule and then wildcard-matched, and the clientId is never validated for MQTT wildcard characters +/# at CONNECT (MQTTConnection.processConnect):

Topic substitutedTopic = new Topic(auth.topic.toString().replace("%c", client).replace("%u", username));
if (topic.match(substitutedTopic)) return true;

A client that connects with clientId + turns sensor/%c/# into the filter sensor/+/#, gaining cross-tenant read AND write. (Precondition: pattern ACL rules configured — a common multi-tenant setup.)

2. (High) Crash the whole broker. SessionEventLoop (SessionEventLoop.java:40-54) catches only InterruptedException and is never restarted (SessionEventLoopGroup), so any uncaught exception on it wedges every co-located client. Trivially reachable inputs: malformed $share/grp SUBSCRIBE (SharedSubscriptionUtils.extractShareName -> StringIndexOutOfBoundsException), deeply nested topic (CTrie recursion -> StackOverflowError), and ACL NPE below. Unbounded subscriptions / retained / in-flight / topic-alias / interceptor state (BrokerInterceptor uses an unbounded queue) also allow OOM; durable stores allow disk exhaustion.

3. (High) NPE in ACL sink on clientId # (invalid filter sensor/#/# -> null tokens -> Topic.match NPE at Topic.java:173).

4. (High) Will-message authorization bypass. Last-Will topic is published (PostOffice.publishWill) without canWrite/reserved-topic checks used for normal PUBLISH.

5. (Medium) Cross-session durable corruption. H2PersistentQueue opens queue_"+clientId and queue_"+clientId+"_meta; client id sensor_meta collides with victim sensor metadata map -> corrupts head/tail.

6. (Medium) Fail-open if authenticator/authorizator class fails to load -> PermitAll/AcceptAll (Server.java:483-531).

## Proof of concept

Source-only, no network; PoCs run on JDK 17:
- PoCPatternAcl — clientId + gains cross-tenant read/write; clientId # triggers NPE
- PoCSharedSubCrash — extractShareName("$share/grp") throws StringIndexOutOfBoundsException
- PoCMapCollision — H2 MVStore collision overwrites victim metadata pointer

## Impact

Cross-tenant eavesdropping and injection, whole-broker DoS, unauthorized Will publishes, and cross-session durable corruption.

## Remediation

1. Reject clientId/username containing +/# (and / if structural) at CONNECT; expand %c/%u as literal tokens.
2. Harden SessionEventLoop (catch Throwable + restart supervision) and validate $share filters.
3. Apply authorization to Will publishes like normal PUBLISH.
4. Add resource caps (connections, queues, retained, aliases, interceptor queue) + bounded session expiry.
5. Separate H2 namespaces and fail closed on auth-class load failure.

---

_Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai_
