Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/pipeleak/cmd/gitea/gitea.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gitea

import (
"github.com/CompassSecurity/pipeleak/cmd/gitea/enum"
"github.com/CompassSecurity/pipeleak/cmd/gitea/runners"
"github.com/CompassSecurity/pipeleak/cmd/gitea/scan"
"github.com/CompassSecurity/pipeleak/cmd/gitea/secrets"
"github.com/CompassSecurity/pipeleak/cmd/gitea/variables"
Expand All @@ -20,6 +21,7 @@ func NewGiteaRootCmd() *cobra.Command {
giteaCmd.AddCommand(scan.NewScanCmd())
giteaCmd.AddCommand(secrets.NewSecretsCommand())
giteaCmd.AddCommand(variables.NewVariablesCommand())
giteaCmd.AddCommand(runners.NewRunnersRootCmd())

return giteaCmd
}
7 changes: 7 additions & 0 deletions src/pipeleak/cmd/gitea/gitea_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,17 @@ func TestNewGiteaRootCmd(t *testing.T) {

hasEnumCmd := false
hasScanCmd := false
hasRunnersCmd := false
for _, subCmd := range cmd.Commands() {
if subCmd.Use == "enum" {
hasEnumCmd = true
}
if subCmd.Use == "scan" {
hasScanCmd = true
}
if subCmd.Use == "runners" {
hasRunnersCmd = true
}
}

if !hasEnumCmd {
Expand All @@ -49,4 +53,7 @@ func TestNewGiteaRootCmd(t *testing.T) {
if !hasScanCmd {
t.Error("Expected 'scan' subcommand to exist")
}
if !hasRunnersCmd {
t.Error("Expected 'runners' subcommand to exist")
}
}
40 changes: 40 additions & 0 deletions src/pipeleak/cmd/gitea/runners/exploit/exploit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package exploit

import (
"github.com/CompassSecurity/pipeleak/pkg/gitea/runners/exploit"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)

func NewExploitCmd(giteaUrl *string, giteaApiToken *string) *cobra.Command {
var runnerLabels []string
var ageEncryptionPublicKey string
var repoName string
var dry bool
var shell bool

exploitCmd := &cobra.Command{
Use: "exploit",
Short: "Create repository with Gitea Actions workflow to exploit available runners",
Long: "Creates a repository, generates a workflow with jobs for available runner labels.",
Example: `
# Creates a repository with jobs for runners with ubuntu-latest and self-hosted labels. Dumps envs encrypted using Age and starts an interactive SSHX shell.
pipeleak gitea runners exploit --token xxxxx --gitea https://gitea.mydomain.com --labels ubuntu-latest,self-hosted --age-public-key age1... --repo-name my-exploit-repo --dry=false --shell=true

# Print the generated workflow YAML only, does NOT create a repository or jobs
pipeleak gitea runners exploit --token xxxxx --gitea https://gitea.mydomain.com --dry=true --shell=true
`,
Run: func(cmd *cobra.Command, args []string) {
exploit.ExploitRunners(runnerLabels, dry, shell, *giteaApiToken, *giteaUrl, ageEncryptionPublicKey, repoName)
log.Info().Msg("Done, Bye Bye 🏳️‍🌈🔥")
},
}

exploitCmd.Flags().StringSliceVarP(&runnerLabels, "labels", "", []string{}, "Jobs with the following runner labels are created")
exploitCmd.Flags().StringVarP(&ageEncryptionPublicKey, "age-public-key", "", "", "An age public key generated with ./age-keygen -o key.txt (repo: https://github.com/FiloSottile/age). Prints the encrypted environment variables in the output log.")
exploitCmd.Flags().StringVarP(&repoName, "repo-name", "", "pipeleak-runner-test", "The name for the created repository")
exploitCmd.PersistentFlags().BoolVarP(&dry, "dry", "d", false, "Only generate and print the workflow YAML, do NOT create real jobs")
exploitCmd.PersistentFlags().BoolVarP(&shell, "shell", "s", true, "Add an SSHX interactive shell to the jobs")

return exploitCmd
}
51 changes: 51 additions & 0 deletions src/pipeleak/cmd/gitea/runners/exploit/exploit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package exploit

import (
"testing"
)

func TestNewExploitCmd(t *testing.T) {
url := "https://example.com"
token := "test-token"

cmd := NewExploitCmd(&url, &token)

if cmd == nil {
t.Fatal("Expected non-nil command")
}

if cmd.Use != "exploit" {
t.Errorf("Expected Use to be 'exploit', got %q", cmd.Use)
}

if cmd.Short == "" {
t.Error("Expected non-empty Short description")
}

if cmd.Long == "" {
t.Error("Expected non-empty Long description")
}

if cmd.Example == "" {
t.Error("Expected non-empty Example")
}

flags := cmd.Flags()
if flags.Lookup("labels") == nil {
t.Error("Expected 'labels' flag to be defined")
}
if flags.Lookup("age-public-key") == nil {
t.Error("Expected 'age-public-key' flag to be defined")
}
if flags.Lookup("repo-name") == nil {
t.Error("Expected 'repo-name' flag to be defined")
}

persistentFlags := cmd.PersistentFlags()
if persistentFlags.Lookup("dry") == nil {
t.Error("Expected 'dry' persistent flag to be defined")
}
if persistentFlags.Lookup("shell") == nil {
t.Error("Expected 'shell' persistent flag to be defined")
}
}
22 changes: 22 additions & 0 deletions src/pipeleak/cmd/gitea/runners/list/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package list

import (
"github.com/CompassSecurity/pipeleak/pkg/gitea/runners/list"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)

func NewListCmd(giteaUrl *string, giteaApiToken *string) *cobra.Command {
listCmd := &cobra.Command{
Use: "list",
Short: "List available runners",
Long: "List all available Gitea Actions runners for repositories and organizations your token has access to.",
Example: `pipeleak gitea runners list --token xxxxx --gitea https://gitea.mydomain.com`,
Run: func(cmd *cobra.Command, args []string) {
list.ListAllAvailableRunners(*giteaUrl, *giteaApiToken)
log.Info().Msg("Done, Bye Bye 🏳️‍🌈🔥")
},
}

return listCmd
}
32 changes: 32 additions & 0 deletions src/pipeleak/cmd/gitea/runners/list/list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package list

import (
"testing"
)

func TestNewListCmd(t *testing.T) {
url := "https://example.com"
token := "test-token"

cmd := NewListCmd(&url, &token)

if cmd == nil {
t.Fatal("Expected non-nil command")
}

if cmd.Use != "list" {
t.Errorf("Expected Use to be 'list', got %q", cmd.Use)
}

if cmd.Short == "" {
t.Error("Expected non-empty Short description")
}

if cmd.Long == "" {
t.Error("Expected non-empty Long description")
}

if cmd.Example == "" {
t.Error("Expected non-empty Example")
}
}
37 changes: 37 additions & 0 deletions src/pipeleak/cmd/gitea/runners/runners.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package runners

import (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keep the cobra cmd definition here, do not put it into pkg

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create suvfolders for the runners subcommand named list and exploit. both need to include their cobra cmd definitions as well and not store it in the pkg.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in commit acee14a. Created cmd/gitea/runners/list/ and cmd/gitea/runners/exploit/ with their respective command definitions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in commit acee14a. Moved Cobra command definitions to cmd/gitea/runners/ with subfolders for list/ and exploit/. Business logic now resides in pkg/gitea/runners/list/ and pkg/gitea/runners/exploit/.

"github.com/CompassSecurity/pipeleak/cmd/gitea/runners/exploit"
"github.com/CompassSecurity/pipeleak/cmd/gitea/runners/list"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)

func NewRunnersRootCmd() *cobra.Command {
var giteaApiToken string
var giteaUrl string

runnersCmd := &cobra.Command{
Use: "runners",
Short: "runner related commands",
Long: "Commands to enumerate and exploit Gitea Actions runners.",
}

runnersCmd.PersistentFlags().StringVarP(&giteaUrl, "gitea", "g", "", "Gitea instance URL")
err := runnersCmd.MarkPersistentFlagRequired("gitea")
if err != nil {
log.Fatal().Stack().Err(err).Msg("Unable to require gitea flag")
}

runnersCmd.PersistentFlags().StringVarP(&giteaApiToken, "token", "t", "", "Gitea API Token")
err = runnersCmd.MarkPersistentFlagRequired("token")
if err != nil {
log.Error().Stack().Err(err).Msg("Unable to require token flag")
}
runnersCmd.MarkFlagsRequiredTogether("gitea", "token")

runnersCmd.AddCommand(list.NewListCmd(&giteaUrl, &giteaApiToken))
runnersCmd.AddCommand(exploit.NewExploitCmd(&giteaUrl, &giteaApiToken))

return runnersCmd
}
43 changes: 43 additions & 0 deletions src/pipeleak/cmd/gitea/runners/runners_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package runners

import (
"testing"
)

func TestNewRunnersRootCmd(t *testing.T) {
cmd := NewRunnersRootCmd()

if cmd == nil {
t.Fatal("Expected non-nil command")
}

if cmd.Use != "runners" {
t.Errorf("Expected Use to be 'runners', got %q", cmd.Use)
}

if cmd.Short == "" {
t.Error("Expected non-empty Short description")
}

if len(cmd.Commands()) != 2 {
t.Errorf("Expected 2 subcommands (list and exploit), got %d", len(cmd.Commands()))
}

hasListCmd := false
hasExploitCmd := false
for _, subCmd := range cmd.Commands() {
if subCmd.Use == "list" {
hasListCmd = true
}
if subCmd.Use == "exploit" {
hasExploitCmd = true
}
}

if !hasListCmd {
t.Error("Expected 'list' subcommand to exist")
}
if !hasExploitCmd {
t.Error("Expected 'exploit' subcommand to exist")
}
}
Loading
Loading