Skip to content

[Security Report] mosdns: DoQ-to-UDP Backend Reuses One Source Port and Emits Sequential Backend DNS IDs #936

Description

@N0zoM1z0

Summary

This report covers mosdns reproduced from the official GitHub release asset
v5.3.4. On the reproduction date (2026-08-03), the downloaded binary
reported version v5.3.4-0-gb732318.

The issue was reproduced on the documented configuration path for:

  • a DoQ listener via quic_server
  • forwarding to a plain UDP upstream via the forward plugin

Scope is limited to the client-triggered DoQ forwarding path to the backend UDP
upstream.

On that path:

  • one live mosdns process reused one backend UDP source port across all 16
    sampled forwarded queries;
  • backend DNS IDs advanced as a straight sequence (3..18 in the base run);
  • a non-zero client-supplied DoQ DNS Message ID was accepted, and the DoQ
    response sent back to the client carried that same non-zero ID.

Four fresh reruns reproduced the same shape every time.

Attached evidence:

Related public confirmations in the same backend-state family:

RFC 9250 baseline

RFC 9250 Section 4.2.1 gives the relevant proxying rules for DoQ message IDs:

  • “When sending queries over a QUIC connection, the DNS Message ID MUST be set
    to 0.”
  • “When forwarding a DNS message from DoQ over another transport, a DNS
    Message ID MUST be generated according to the rules of the protocol that is
    in use.”

Reference: RFC 9250 §4.2.1

That section matters here for two separate reasons.

First, DoQ itself is not supposed to use the DNS Message ID as a client-chosen
matching field. In the tested path, mosdns accepted a non-zero DoQ query ID
and returned a DoQ response carrying the same non-zero ID.

Second, once a DoQ proxy forwards onto another transport, it must generate the
backend ID for that transport. This report does not claim that RFC 9250
requires random UDP IDs. The narrower point is that the generated backend state
on the tested UDP hop is highly structured: one source port for the whole run,
plus a simple sequential DNS ID.

Observed behavior

The observed behavior is consistent with connection-scoped backend forwarding
state on the UDP leg.

In the base run:

  1. the backend UDP observer saw one source port reused across all 16 forwarded
    queries;
  2. the backend DNS IDs formed the sequence 3..18;
  3. the non-zero DoQ query used client ID 16705, and the client-visible DoQ
    response carried the same ID back.

Across the shipped four-run repeatability set, that shape stayed stable:

  • sequential backend DNS-ID runs: 4/4
  • one backend source-port reused within run: 4/4
  • non-zero DoQ ID accepted: 4/4
  • client-visible non-zero ID reflected: 4/4

This is not the fixed-zero family seen in dnsproxy / AdGuard Home. The
backend DNS ID still changes per query. The problem is the combination of a
stable backend UDP source port and a simple backend ID sequence. On that hop,
the forwarded (source-port, txid) state is much more predictable than a
fresh per-query UDP forwarding tuple.

Reproduce

Prerequisites:

  • Python 3
  • curl, unzip, and openssl
  • network access to download the official mosdns release asset

This report package is self-contained. The attached wrapper downloads the
official mosdns release asset on its own, bootstraps
attachments/runtime/.venv if needed, and then runs the standalone reproducer
shipped in this directory.

To rerun the base evidence:

  1. cd attachments/scripts
  2. bash repro-g10-mosdns-backend-state.sh
  3. Inspect ../artifacts/g10/<RUN_ID>/summary.txt
  4. Inspect udp_observer.jsonl, queries.json, and summary.json

To rerun the four-sample repeatability set shipped with this report:

  1. cd attachments/scripts
  2. bash repro-g10-mosdns-repeatability.sh
  3. Inspect ../artifacts/g10/repeatability.md

The main entrypoints are:

  • attachments/scripts/repro-g10-mosdns-backend-state.sh
  • attachments/scripts/repro-g10-mosdns-repeatability.sh
  • attachments/scripts/run_g10_mosdns_backend_state.py

The exact base-run config is shipped in
attachments/artifacts/g10/20260803T093921Z-g10/mosdns.yaml. Because it was
captured from a live run, its certificate paths point into that run's temporary
directory. For a readable static equivalent, use
attachments/configs/mosdns-minimal.yaml.

Evidence package

The attachment set is enough for a vendor to verify the claim and rerun it:

  • exact binary provenance: version.txt and release-url.txt
  • exact runtime config from the base run: mosdns.yaml
  • readable static config template: attachments/configs/mosdns-minimal.yaml
  • backend packet trace: udp_observer.jsonl
  • per-query client results: zero.json, nonzero.json, sample-*.json, and
    queries.json
  • process log: server.log
  • repeatability summary across four fresh reruns: repeatability.md

The wrapper script is the primary reproducer. The bundled artifacts then let a
reviewer check the concrete base run without having to trust a prose summary.

Impact

This report does not claim a full forged-response exploit on its own. The claim
supported by the data is narrower and cleaner: on the tested DoQ-to-UDP path,
mosdns does not emit a fresh backend forwarding tuple per query. Within a
run, the backend source port stays fixed and the backend DNS ID advances as a
simple counter.

That puts mosdns in the same backend-state reduction family as the already
public RouteDNS and AdGuard Home cases, even though the exact generated ID
pattern is different. In mosdns, the backend ID is not frozen at zero; it is
still query-specific, but it is easy to model. For backend UDP spoofing
analysis, that distinction matters less than the overall shape: one stable
field and one predictable field on the forwarded UDP hop.

Attachments

attachments.zip

sha256

8122b2ad8ecf3684730230d7a2f745fcab662987be9bba296a4acda05dd26990  attachments.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions