diff --git a/chaoscenter/graphql/server/pkg/chaoshub/handler/handler.go b/chaoscenter/graphql/server/pkg/chaoshub/handler/handler.go index a62fcffd18b..b1ef2997e78 100644 --- a/chaoscenter/graphql/server/pkg/chaoshub/handler/handler.go +++ b/chaoscenter/graphql/server/pkg/chaoshub/handler/handler.go @@ -155,11 +155,20 @@ func IsFileExisting(path string) (bool, error) { // DownloadRemoteHub is used to download a remote hub from the url provided by the user func DownloadRemoteHub(hubDetails model.CreateRemoteChaosHub, projectID string) error { + if projectID != sanitize.PathName(projectID) { + return fmt.Errorf("err: invalid projectID '%s', potential path injection detected", projectID) + } + dirPath := DefaultPath + projectID err := os.MkdirAll(dirPath, 0755) if err != nil { return err } + + if hubDetails.Name != sanitize.PathName(hubDetails.Name) { + return fmt.Errorf("err: invalid hub name '%s', potential path injection detected", hubDetails.Name) + } + //create the destination directory where the hub will be downloaded hubPath := dirPath + "/" + hubDetails.Name + ".zip" destDir, err := os.Create(hubPath) @@ -278,6 +287,14 @@ func CopyZipItems(file *zip.File, extractPath string, chartsPath string) error { // SyncRemoteRepo is used to sync the remote ChaosHub func SyncRemoteRepo(hubData model.CloningInput, projectID string) error { + if projectID != sanitize.PathName(projectID) { + return fmt.Errorf("err: invalid projectID '%s', potential path injection detected", projectID) + } + + if hubData.Name != sanitize.PathName(hubData.Name) { + return fmt.Errorf("err: invalid hub name '%s', potential path injection detected", hubData.Name) + } + hubPath := DefaultPath + projectID + "/" + hubData.Name err := os.RemoveAll(hubPath) if err != nil { @@ -320,6 +337,34 @@ func ChaosHubIconHandler() gin.HandlerFunc { responseStatusCode int ) + if c.Param("projectId") != sanitize.PathName(c.Param("projectId")) { + responseStatusCode = http.StatusBadRequest + log.Errorf("err: invalid projectID '%s', potential path injection detected", c.Param("projectId")) + fmt.Fprintf(c.Writer, "err: invalid projectID '%s', potential path injection detected", c.Param("projectId")) + return + } + + if c.Param("hubName") != sanitize.PathName(c.Param("hubName")) { + responseStatusCode = http.StatusBadRequest + log.Errorf("err: invalid hub name '%s', potential path injection detected", c.Param("hubName")) + fmt.Fprintf(c.Writer, "err: invalid hub name '%s', potential path injection detected", c.Param("hubName")) + return + } + + if c.Param("chartName") != sanitize.PathName(c.Param("chartName")) { + responseStatusCode = http.StatusBadRequest + log.Errorf("err: invalid chart name '%s', potential path injection detected", c.Param("chartName")) + fmt.Fprintf(c.Writer, "err: invalid chart name '%s', potential path injection detected", c.Param("chartName")) + return + } + + if c.Param("iconName") != sanitize.PathName(c.Param("iconName")) { + responseStatusCode = http.StatusBadRequest + log.Errorf("err: invalid icon name '%s', potential path injection detected", c.Param("iconName")) + fmt.Fprintf(c.Writer, "err: invalid icon name '%s', potential path injection detected", c.Param("iconName")) + return + } + if strings.ToLower(c.Param("chartName")) == "predefined" { img, err = os.Open(utils.Config.CustomChaosHubPath + c.Param("projectId") + "/" + c.Param("hubName") + "/experiments/icons/" + c.Param("iconName")) responseStatusCode = http.StatusOK @@ -354,6 +399,27 @@ func DefaultChaosHubIconHandler() gin.HandlerFunc { responseStatusCode int ) + if c.Param("hubName") != sanitize.PathName(c.Param("hubName")) { + responseStatusCode = http.StatusBadRequest + log.Errorf("err: invalid hub name '%s', potential path injection detected", c.Param("hubName")) + fmt.Fprintf(c.Writer, "err: invalid hub name '%s', potential path injection detected", c.Param("hubName")) + return + } + + if c.Param("chartName") != sanitize.PathName(c.Param("chartName")) { + responseStatusCode = http.StatusBadRequest + log.Errorf("err: invalid chart name '%s', potential path injection detected", c.Param("chartName")) + fmt.Fprintf(c.Writer, "err: invalid chart name '%s', potential path injection detected", c.Param("chartName")) + return + } + + if c.Param("iconName") != sanitize.PathName(c.Param("iconName")) { + responseStatusCode = http.StatusBadRequest + log.Errorf("err: invalid icon name '%s', potential path injection detected", c.Param("iconName")) + fmt.Fprintf(c.Writer, "err: invalid icon name '%s', potential path injection detected", c.Param("iconName")) + return + } + if strings.ToLower(c.Param("chartName")) == "predefined" { img, err = os.Open(utils.Config.DefaultChaosHubPath + c.Param("hubName") + "/experiments/icons/" + c.Param("iconName")) responseStatusCode = http.StatusOK diff --git a/chaoscenter/graphql/server/pkg/chaoshub/handler/handler_test.go b/chaoscenter/graphql/server/pkg/chaoshub/handler/handler_test.go index 1a30b1e012a..bb94c06531f 100644 --- a/chaoscenter/graphql/server/pkg/chaoshub/handler/handler_test.go +++ b/chaoscenter/graphql/server/pkg/chaoshub/handler/handler_test.go @@ -157,6 +157,15 @@ func TestDownloadRemoteHub(t *testing.T) { }, isError: true, }, + { + name: "path injection", + projectID: uuid.New().String(), + chaosHub: model.CreateRemoteChaosHub{ + Name: uuid.New().String() + "../path/injection", + RepoURL: "https://github.com/litmuschaos/chaos-charts/archive/refs/heads/master.zip", + }, + isError: true, + }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { @@ -209,6 +218,15 @@ func TestSyncRemoteRepo(t *testing.T) { }, isError: true, }, + { + name: "path injection", + projectID: uuid.New().String(), + chaosHub: model.CloningInput{ + Name: uuid.New().String() + "../path/injection", + RepoURL: "https://github.com/litmuschaos/chaos-charts/archive/refs/heads/master.zip", + }, + isError: true, + }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { diff --git a/chaoscenter/graphql/server/pkg/chaoshub/ops/gitops.go b/chaoscenter/graphql/server/pkg/chaoshub/ops/gitops.go index 9480b144e6d..2f7313eaa6d 100644 --- a/chaoscenter/graphql/server/pkg/chaoshub/ops/gitops.go +++ b/chaoscenter/graphql/server/pkg/chaoshub/ops/gitops.go @@ -7,6 +7,7 @@ import ( "github.com/litmuschaos/litmus/chaoscenter/graphql/server/graph/model" "github.com/litmuschaos/litmus/chaoscenter/graphql/server/utils" + "github.com/mrz1836/go-sanitize" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" @@ -45,9 +46,9 @@ func GetClonePath(c ChaosHubConfig) string { var repoPath string if c.IsDefault { - repoPath = "/tmp/default/" + c.HubName + repoPath = "/tmp/default/" + sanitize.PathName(c.HubName) } else { - repoPath = DefaultPath + c.ProjectID + "/" + c.HubName + repoPath = DefaultPath + sanitize.PathName(c.ProjectID) + "/" + sanitize.PathName(c.HubName) } return repoPath } diff --git a/chaoscenter/graphql/server/pkg/chaoshub/ops/gitops_test.go b/chaoscenter/graphql/server/pkg/chaoshub/ops/gitops_test.go index 531c5510ea7..74999d756fd 100644 --- a/chaoscenter/graphql/server/pkg/chaoshub/ops/gitops_test.go +++ b/chaoscenter/graphql/server/pkg/chaoshub/ops/gitops_test.go @@ -39,16 +39,52 @@ func clearCloneRepository(projectID string) { // TestGetClonePath is used to test the GetClonePath function func TestGetClonePath(t *testing.T) { - // given projectID := uuid.New().String() - chaosHubConfig := chaosHubOps.ChaosHubConfig{ - ProjectID: projectID, - HubName: "test", + + testCases := []struct { + name string + projectID string + hubName string + isDefault bool + expected string + }{ + { + name: "default hub", + projectID: projectID, + hubName: "defaultHub", + isDefault: true, + expected: "/tmp/default/defaultHub", + }, + { + name: "non-default hub", + projectID: projectID, + hubName: "testHub", + isDefault: false, + expected: "/tmp/" + projectID + "/testHub", + }, + { + name: "path injection", + projectID: projectID, + hubName: "testHub/../Path/Injection", + isDefault: false, + expected: "/tmp/" + projectID + "/testHubPathInjection", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // given + chaosHubConfig := chaosHubOps.ChaosHubConfig{ + ProjectID: tc.projectID, + HubName: tc.hubName, + IsDefault: tc.isDefault, + } + // when + path := chaosHubOps.GetClonePath(chaosHubConfig) + // then + assert.Equal(t, tc.expected, path) + }) } - // when - path := chaosHubOps.GetClonePath(chaosHubConfig) - // then - assert.Equal(t, "/tmp/"+projectID+"/test", path) } // TestGitConfigConstruct is used to test the GitConfigConstruct function diff --git a/chaoscenter/graphql/server/pkg/chaoshub/service.go b/chaoscenter/graphql/server/pkg/chaoshub/service.go index 53f46cc975f..19b89621f8b 100644 --- a/chaoscenter/graphql/server/pkg/chaoshub/service.go +++ b/chaoscenter/graphql/server/pkg/chaoshub/service.go @@ -17,6 +17,7 @@ import ( "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb" dbSchemaChaosHub "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/chaos_hub" "github.com/litmuschaos/litmus/chaoscenter/graphql/server/utils" + "github.com/mrz1836/go-sanitize" log "github.com/sirupsen/logrus" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" @@ -329,6 +330,15 @@ func (c *chaosHubService) UpdateChaosHub(ctx context.Context, chaosHub model.Upd if err != nil { return nil, err } + + if prevChaosHub.ProjectID != sanitize.PathName(prevChaosHub.ProjectID) { + return nil, fmt.Errorf("err: invalid projectID '%s', potential path injection detected", projectID) + } + + if prevChaosHub.Name != sanitize.PathName(prevChaosHub.Name) { + return nil, fmt.Errorf("err: invalid hub name '%s', potential path injection detected", chaosHub.Name) + } + clonePath := DefaultPath + prevChaosHub.ProjectID + "/" + prevChaosHub.Name if prevChaosHub.HubType == string(model.HubTypeRemote) { if prevChaosHub.Name != chaosHub.Name || prevChaosHub.RepoURL != chaosHub.RepoURL || prevChaosHub.RemoteHub != chaosHub.RemoteHub { @@ -439,6 +449,15 @@ func (c *chaosHubService) DeleteChaosHub(ctx context.Context, hubID string, proj log.Error(err) return false, err } + + if projectID != sanitize.PathName(projectID) { + return false, fmt.Errorf("err: invalid projectID '%s', potential path injection detected", projectID) + } + + if chaosHub.Name != sanitize.PathName(chaosHub.Name) { + return false, fmt.Errorf("err: invalid hub name '%s', potential path injection detected", chaosHub.Name) + } + clonePath := DefaultPath + projectID + "/" + chaosHub.Name err = os.RemoveAll(clonePath) if err != nil { @@ -733,6 +752,14 @@ func (c *chaosHubService) ListPredefinedExperiments(ctx context.Context, hubID s return nil, err } + if projectID != sanitize.PathName(projectID) { + return nil, fmt.Errorf("err: invalid projectID '%s', potential path injection detected", projectID) + } + + if hub.Name != sanitize.PathName(hub.Name) { + return nil, fmt.Errorf("err: invalid hub name '%s', potential path injection detected", hub.Name) + } + var hubPath string if hub.IsDefault { hubPath = "/tmp/default/" + hub.Name + "/experiments/" @@ -792,6 +819,15 @@ func (c *chaosHubService) GetPredefinedExperiment(ctx context.Context, hubID str if err != nil { return nil, err } + + if projectID != sanitize.PathName(projectID) { + return nil, fmt.Errorf("err: invalid projectID '%s', potential path injection detected", projectID) + } + + if hub.Name != sanitize.PathName(hub.Name) { + return nil, fmt.Errorf("err: invalid hub name '%s', potential path injection detected", hub.Name) + } + var hubPath string if hub.IsDefault { hubPath = "/tmp/default/" + hub.Name + "/experiments/" @@ -814,7 +850,7 @@ func (c *chaosHubService) getPredefinedExperimentDetails(experimentsPath string, var ( csvManifest = "" workflowManifest = "" - path = experimentsPath + experiment + "/" + experiment + ".chartserviceversion.yaml" + path = experimentsPath + sanitize.PathName(experiment) + "/" + sanitize.PathName(experiment) + ".chartserviceversion.yaml" isExist = true preDefinedWorkflow = &model.PredefinedExperimentList{} ) diff --git a/chaoscenter/graphql/server/pkg/gitops/gitops.go b/chaoscenter/graphql/server/pkg/gitops/gitops.go index 8065ace8c30..fbd7338f648 100644 --- a/chaoscenter/graphql/server/pkg/gitops/gitops.go +++ b/chaoscenter/graphql/server/pkg/gitops/gitops.go @@ -24,6 +24,7 @@ import ( "github.com/litmuschaos/litmus/chaoscenter/graphql/server/graph/model" "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/authorization" "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/gitops" + "github.com/mrz1836/go-sanitize" log "github.com/sirupsen/logrus" ssh2 "golang.org/x/crypto/ssh" ) @@ -84,7 +85,7 @@ func GetGitOpsConfig(repoData gitops.GitConfigDB) GitConfig { RepositoryURL: repoData.RepositoryURL, RemoteName: "origin", Branch: repoData.Branch, - LocalPath: DefaultPath + repoData.ProjectID, + LocalPath: DefaultPath + sanitize.PathName(repoData.ProjectID), LatestCommit: repoData.LatestCommit, UserName: repoData.UserName, Password: repoData.Password, @@ -98,6 +99,10 @@ func GetGitOpsConfig(repoData gitops.GitConfigDB) GitConfig { // setupGitRepo helps clones and sets up the repo for GitOps func (c GitConfig) setupGitRepo(user GitUser) error { + if c.ProjectID != sanitize.PathName(c.ProjectID) { + return fmt.Errorf("err: invalid projectID '%s', potential path injection detected", c.ProjectID) + } + projectPath := c.LocalPath + "/" + ProjectDataPath + "/" + c.ProjectID // clone repo diff --git a/chaoscenter/graphql/server/pkg/gitops/service.go b/chaoscenter/graphql/server/pkg/gitops/service.go index 2c19987af22..f5aecec3649 100644 --- a/chaoscenter/graphql/server/pkg/gitops/service.go +++ b/chaoscenter/graphql/server/pkg/gitops/service.go @@ -21,6 +21,7 @@ import ( "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/chaos_infrastructure" "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/gitops" "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/grpc" + "github.com/mrz1836/go-sanitize" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" @@ -145,6 +146,10 @@ func (g *gitOpsService) EnableGitOpsHandler(ctx context.Context, projectID strin // DisableGitOpsHandler disables GitOps for a specific project func (g *gitOpsService) DisableGitOpsHandler(ctx context.Context, projectID string) (bool, error) { + if projectID != sanitize.PathName(projectID) { + return false, fmt.Errorf("err: invalid projectID '%s', potential path injection detected", projectID) + } + gitLock.Lock(projectID, nil) defer gitLock.Unlock(projectID, nil) @@ -183,6 +188,11 @@ func (g *gitOpsService) UpdateGitOpsDetailsHandler(ctx context.Context, projectI gitConfig := GetGitOpsConfig(gitDB) originalPath := gitConfig.LocalPath + + if gitConfig.ProjectID != sanitize.PathName(gitConfig.ProjectID) { + return false, fmt.Errorf("err: invalid projectID '%s', potential path injection detected", gitConfig.ProjectID) + } + gitConfig.LocalPath = tempPath + gitConfig.ProjectID commit, err := SetupGitOps(GitUserFromContext(ctx), gitConfig) if err != nil { @@ -319,6 +329,14 @@ func (g *gitOpsService) DeleteExperimentFromGit(ctx context.Context, projectID s return errors.New("Sync Error | " + err.Error()) } + if gitConfig.ProjectID != sanitize.PathName(gitConfig.ProjectID) { + return fmt.Errorf("err: invalid projectID '%s', potential path injection detected", gitConfig.ProjectID) + } + + if experiment.ExperimentName != sanitize.PathName(experiment.ExperimentName) { + return fmt.Errorf("err: invalid experiment name '%s', potential path injection detected", experiment.ExperimentName) + } + experimentPath := ProjectDataPath + "/" + gitConfig.ProjectID + "/" + experiment.ExperimentName + ".yaml" exists, err := PathExists(gitConfig.LocalPath + "/" + experimentPath) if err != nil { @@ -456,6 +474,12 @@ func (g *gitOpsService) SyncDBToGit(ctx context.Context, config GitConfig) error if !strings.HasSuffix(file, ".yaml") { continue } + + if file != sanitize.PathName(file) { + log.Errorf("Invalid file name '%s', potential path injection detected", file) + continue + } + // check if file was deleted or not exists, err := PathExists(config.LocalPath + "/" + file) if err != nil {