Skip to content

Commit

Permalink
addding secure files support (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
frjcomp authored Nov 18, 2024
1 parent f4241cd commit 368e030
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 1 deletion.
10 changes: 9 additions & 1 deletion docs/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,21 @@ pipeleak vuln -g https://leakycompany.com -t glpat-[redacted]

# Misconfigurations And Mishandling

## Enumerating CI/CD Variables
## Enumerating CI/CD Variables And Secure Files
If you already have access to projects and groups you can try to enumerate CI/CD variables and use these for potential privilege escalation/lateral movement paths.
Using Pipeleak:
```bash
pipeleak variables -g https://leakycompany.com -t glpat-[redacted]
```

And enumerating the secure files:
```bash
pipeleaksecureFiles --gitlab https://leakycompany.com --token glpat-[redacted]
2024-11-18T15:38:08Z INF Fetching project variables
2024-11-18T15:38:09Z WRN Secure file content="this is a secure file!!" downloadUrl=https://leakycompany.com/api/v4/projects/60367314/secure_files/9149327/download
2024-11-18T15:38:12Z INF Fetched all secure files
```

## Secret Detection in Source Code
Manually looking for sensitive info can be cumbersome and should be partially automated.

Expand Down
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ You can tweak `--threads`, `--max-artifact-size` and `--job-limit` to obtain a c

`variables` command: Enumerate configured project/group/instance variables

`secureFiles` command: Enumerate secure files

Setting an HTTP proxy is possible by setting the environment variable `HTTP_PROXY` e.g. to route through Burp:
```bash
HTTP_PROXY=http://127.0.0.1:8080 pipeleak scan --token glpat-xxxxxxxxxxx --gitlab https://gitlab.com
Expand Down
1 change: 1 addition & 0 deletions src/pipeleak/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func init() {
rootCmd.AddCommand(NewRegisterCmd())
rootCmd.AddCommand(NewVulnCmd())
rootCmd.AddCommand(NewVariablesCmd())
rootCmd.AddCommand(NewSecureFilesCmd())
rootCmd.PersistentFlags().BoolVarP(&JsonLogoutput, "json", "", false, "Use JSON as log output format")
}

Expand Down
95 changes: 95 additions & 0 deletions src/pipeleak/cmd/secure_files.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package cmd

import (
pgitlab "github.com/CompassSecurity/pipeleak/gitlab"
"github.com/CompassSecurity/pipeleak/helper"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/xanzy/go-gitlab"
)

func NewSecureFilesCmd() *cobra.Command {
vulnCmd := &cobra.Command{
Use: "secureFiles [no options!]",
Short: "Print CI/CD secure files",
Run: FetchSecureFiles,
}
vulnCmd.Flags().StringVarP(&gitlabUrl, "gitlab", "g", "", "GitLab instance URL")
err := vulnCmd.MarkFlagRequired("gitlab")
if err != nil {
log.Fatal().Stack().Err(err).Msg("Unable to require gitlab flag")
}

vulnCmd.Flags().StringVarP(&gitlabApiToken, "token", "t", "", "GitLab API Token")
err = vulnCmd.MarkFlagRequired("token")
if err != nil {
log.Fatal().Msg("Unable to require token flag")
}
vulnCmd.MarkFlagsRequiredTogether("gitlab", "token")

vulnCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Verbose logging")
return vulnCmd
}

func FetchSecureFiles(cmd *cobra.Command, args []string) {
if verbose {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
log.Debug().Msg("Verbose log output enabled")
}

log.Info().Msg("Fetching project variables")

git, err := helper.GetGitlabClient(gitlabApiToken, gitlabUrl)
if err != nil {
log.Fatal().Stack().Err(err).Msg("failed creating gitlab client")
}

projectOpts := &gitlab.ListProjectsOptions{
ListOptions: gitlab.ListOptions{
PerPage: 100,
Page: 1,
},
Membership: gitlab.Ptr(true),
MinAccessLevel: gitlab.Ptr(gitlab.OwnerPermissions),
OrderBy: gitlab.Ptr("last_activity_at"),
}

for {
projects, resp, err := git.Projects.ListProjects(projectOpts)
if err != nil {
log.Error().Stack().Err(err).Msg("Failed fetching projects")
break
}

for _, project := range projects {
log.Debug().Str("project", project.WebURL).Msg("Fetch project secure files")
err, fileIds := pgitlab.GetSecureFiles(project.ID, gitlabUrl, gitlabApiToken)
if err != nil {
log.Error().Stack().Err(err).Str("project", project.WebURL).Msg("Failed fetching secure files list")
continue
}

for _, id := range fileIds {
err, secureFile, downloadUrl := pgitlab.DownloadSecureFile(project.ID, id, gitlabUrl, gitlabApiToken)
if err != nil {
log.Error().Stack().Err(err).Str("project", project.WebURL).Int64("fileId", id).Msg("Failed fetching secure file")
continue
}

if len(secureFile) > 100 {
secureFile = secureFile[:100]
}

log.Warn().Str("downloadUrl", downloadUrl).Bytes("content", secureFile).Msg("Secure file")
}
}

if resp.NextPage == 0 {
break
}
projectOpts.Page = resp.NextPage
}

log.Info().Msg("Fetched all secure files")
}
81 changes: 81 additions & 0 deletions src/pipeleak/gitlab/secure_files.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package gitlab

import (
"errors"
"io"
"net/http"
"net/url"
"strconv"

"github.com/CompassSecurity/pipeleak/helper"
"github.com/tidwall/gjson"
)

func GetSecureFiles(projectId int, base string, token string) (error, []int64) {
u, err := url.Parse(base)
if err != nil {
return err, []int64{}
}

client := helper.GetNonVerifyingHTTPClient()
// https://docs.gitlab.com/ee/api/secure_files.html#download-secure-file
// pagination does not exist here
u.Path = "/api/v4/projects/" + strconv.Itoa(projectId) + "/secure_files"
s := u.String()
req, err := http.NewRequest("GET", s, nil)
if err != nil {
return err, []int64{}
}
req.Header.Add("PRIVATE-TOKEN", token)
res, err := client.Do(req)
if err != nil {
return err, []int64{}
}

body, err := io.ReadAll(res.Body)
if err != nil {
return err, []int64{}
}

fileIds := []int64{}
if res.StatusCode == 200 {
result := gjson.Get(string(body), "@this")
result.ForEach(func(key, value gjson.Result) bool {
id := value.Get("id").Int()
fileIds = append(fileIds, id)
return true
})

return nil, fileIds
}

return errors.New("unable to fetch secure files"), []int64{}
}

func DownloadSecureFile(projectId int, fileId int64, base string, token string) (error, []byte, string) {
u, err := url.Parse(base)
if err != nil {
return err, []byte{}, ""
}

client := helper.GetNonVerifyingHTTPClient()
// https://docs.gitlab.com/ee/api/secure_files.html#download-secure-file
u.Path = "/api/v4/projects/" + strconv.Itoa(projectId) + "/secure_files/" + strconv.Itoa(int(fileId)) + "/download"
s := u.String()
req, err := http.NewRequest("GET", s, nil)
if err != nil {
return err, []byte{}, ""
}
req.Header.Add("PRIVATE-TOKEN", token)
res, err := client.Do(req)
if err != nil {
return err, []byte{}, ""
}

body, err := io.ReadAll(res.Body)
if err != nil {
return err, []byte{}, ""
}

return nil, body, s
}

0 comments on commit 368e030

Please sign in to comment.