mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2026-06-06 11:47:29 +02:00
Fix critical issues from follow-up security audit
A second-pass audit surfaced three severe issues missed by the previous review, each a sibling code path of a bug class that was only partially fixed before: - auth: JWT session login (jwtSession.go) registered its authenticator even when CROSS_LOGIN_JWT_HS512_KEY was unset, leaving an empty HMAC key. golang-jwt verifies any HS256/HS512 signature against an empty key, allowing unauthenticated admin token forgery. Init() now refuses to register without a key, with a defense-in-depth empty-key guard in the keyfunc. - repository: metric names from GraphQL ([String!]) were interpolated raw into json_extract(footprint, "$.<name>") SQL. SQLite parses double-quoted strings as literals, enabling SQL injection by any authenticated user. Validate metric names against ^[a-zA-Z0-9_]+$ in jobsMetricStatisticsHistogram and buildFloatJSONCondition. - metricstore: cluster/host line-protocol tags flowed unvalidated into path.Join(RootDir, cluster, host) for checkpoint/WAL files, allowing arbitrary file write outside the checkpoint root via NATS (unauthenticated) or POST /api/write. Reject path-traversal sequences in DecodeLine before the tags become path components. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: b57246993ec1
This commit is contained in:
@@ -22,6 +22,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -196,6 +197,16 @@ var decodeStatePool = sync.Pool{
|
||||
// When the checkpoint format is "wal" each successfully decoded sample is also
|
||||
// sent to WALMessages so the WAL staging goroutine can persist it durably
|
||||
// before the next binary snapshot.
|
||||
// isValidPathComponent reports whether s is safe to use as a single filesystem
|
||||
// path component (cluster or host name) when building checkpoint/WAL paths. It
|
||||
// rejects path-traversal sequences and embedded separators. An empty string is
|
||||
// allowed because a missing host tag is legitimate and does not escape the root.
|
||||
func isValidPathComponent(s string) bool {
|
||||
return !strings.Contains(s, "..") &&
|
||||
!strings.Contains(s, "/") &&
|
||||
!strings.Contains(s, "\\")
|
||||
}
|
||||
|
||||
func DecodeLine(dec *lineprotocol.Decoder,
|
||||
ms *MemoryStore,
|
||||
clusterDefault string,
|
||||
@@ -282,6 +293,17 @@ func DecodeLine(dec *lineprotocol.Decoder,
|
||||
}
|
||||
}
|
||||
|
||||
// cluster and host are taken verbatim from attacker-controllable line
|
||||
// protocol tags and are later used as filesystem path components for the
|
||||
// checkpoint/WAL files (path.Join(RootDir, cluster, host)). Reject
|
||||
// path-traversal sequences so a malicious tag cannot escape the
|
||||
// checkpoint root. Skip the offending sample; the rest of the batch is
|
||||
// still processed.
|
||||
if !isValidPathComponent(cluster) || !isValidPathComponent(host) {
|
||||
cclog.Warnf("[METRICSTORE]> rejecting metric with invalid cluster/host tag (cluster=%q host=%q)", cluster, host)
|
||||
continue
|
||||
}
|
||||
|
||||
// If the cluster or host changed, the lvl was set to nil
|
||||
if lvl == nil {
|
||||
st.selector = st.selector[:2]
|
||||
|
||||
Reference in New Issue
Block a user