diff --git a/backend/internal/api/handlers/crowdsec_files_test.go b/backend/internal/api/handlers/crowdsec_files_test.go index db25481d0..0c98cd3fa 100644 --- a/backend/internal/api/handlers/crowdsec_files_test.go +++ b/backend/internal/api/handlers/crowdsec_files_test.go @@ -578,6 +578,12 @@ func TestPathWithin(t *testing.T) { assert.False(t, pathWithin("/a/b", "/a")) assert.False(t, pathWithin("/a/b", "/x/y")) assert.False(t, pathWithin("/a/b", "relative")) + // A name that merely starts with two dots is inside the folder; real parent traversal is not. + assert.True(t, pathWithin("/a/b", "/a/b/..foo")) + assert.True(t, pathWithin("/a/b", "/a/b/..foo/bar")) + assert.False(t, pathWithin("/a/b", "/a/b/../c")) + assert.False(t, pathWithin("/a/b", "/a/b/../../etc/passwd")) + assert.False(t, pathWithin("/a/b", "/a/b_sibling/config.yaml")) } func TestCrowdsecFiles_Read_UnreadableFileIs500(t *testing.T) { diff --git a/backend/internal/api/handlers/crowdsec_handler.go b/backend/internal/api/handlers/crowdsec_handler.go index 378609aeb..64671ff8b 100644 --- a/backend/internal/api/handlers/crowdsec_handler.go +++ b/backend/internal/api/handlers/crowdsec_handler.go @@ -2216,7 +2216,7 @@ func (h *CrowdsecHandler) DiagnosticsConfig(c *gin.Context) { if configPath := crowdsec.FindConfigFile(h.DataDir); configPath != "" { // Path traversal protection: ensure path is within DataDir cleanConfigPath = filepath.Clean(configPath) - if !strings.HasPrefix(cleanConfigPath, filepath.Clean(h.DataDir)) { + if !pathWithin(filepath.Clean(h.DataDir), cleanConfigPath) { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid config path"}) return } @@ -2262,7 +2262,7 @@ func (h *CrowdsecHandler) DiagnosticsConfig(c *gin.Context) { cleanDataDir := filepath.Clean(h.DataDir) // Path traversal protection cleanAcquisPath := filepath.Clean(acquisPath) - if !strings.HasPrefix(cleanAcquisPath, cleanDataDir) { + if !pathWithin(cleanDataDir, cleanAcquisPath) { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid acquis path"}) return }