diff --git a/README.md b/README.md index 76a1cf4..1203c0f 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ _(__ )_ / _ __/ - 💘 Shodan integration - 📦 CMS detection - 🔍 HTTP Header Analysis +- ☁️ C3 Misconfiguration Scanner ## Contributing and support diff --git a/pkg/config/config.go b/pkg/config/config.go index 972155e..73ffe0a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -27,6 +27,7 @@ type Settings struct { Template string CMS bool Headers bool + CloudStorage bool } const ( @@ -67,6 +68,7 @@ func Parse() *Settings { flagSet.BoolVar(&settings.JavaScript, "js", false, "Enable JavaScript scans"), flagSet.BoolVar(&settings.CMS, "cms", false, "Enable CMS detection"), flagSet.BoolVar(&settings.Headers, "headers", false, "Enable HTTP Header Analysis"), + flagSet.BoolVar(&settings.CloudStorage, "c3", false, "Enable C3 Misconfiguration Scan"), ) flagSet.CreateGroup("runtime", "Runtime", diff --git a/pkg/scan/cloudstorage.go b/pkg/scan/cloudstorage.go new file mode 100644 index 0000000..d0bc3b8 --- /dev/null +++ b/pkg/scan/cloudstorage.go @@ -0,0 +1,100 @@ +package scan + +import ( + "fmt" + "net/http" + "strings" + "time" + "os" + + "github.com/charmbracelet/log" + "github.com/dropalldatabases/sif/internal/styles" + "github.com/dropalldatabases/sif/pkg/logger" +) + +type CloudStorageResult struct { + BucketName string `json:"bucket_name"` + IsPublic bool `json:"is_public"` +} + +func CloudStorage(url string, timeout time.Duration, logdir string) ([]CloudStorageResult, error) { + fmt.Println(styles.Separator.Render("☁️ Starting " + styles.Status.Render("Cloud Storage Misconfiguration Scan") + "...")) + + sanitizedURL := strings.Split(url, "://")[1] + + if logdir != "" { + if err := logger.WriteHeader(sanitizedURL, logdir, "Cloud Storage Misconfiguration Scan"); err != nil { + log.Errorf("Error creating log file: %v", err) + return nil, err + } + } + + cloudlog := log.NewWithOptions(os.Stderr, log.Options{ + Prefix: "C3 ☁️", + }).With("url", url) + + client := &http.Client{ + Timeout: timeout, + } + + potentialBuckets := extractPotentialBuckets(sanitizedURL) + + var results []CloudStorageResult + + for _, bucket := range potentialBuckets { + isPublic, err := checkS3Bucket(bucket, client) + if err != nil { + cloudlog.Errorf("Error checking S3 bucket %s: %v", bucket, err) + continue + } + + result := CloudStorageResult{ + BucketName: bucket, + IsPublic: isPublic, + } + results = append(results, result) + + if isPublic { + cloudlog.Warnf("Public S3 bucket found: %s", styles.Highlight.Render(bucket)) + if logdir != "" { + logger.Write(sanitizedURL, logdir, fmt.Sprintf("Public S3 bucket found: %s\n", bucket)) + } + } else { + cloudlog.Infof("S3 bucket is not public/found: %s", bucket) + } + } + + return results, nil +} + +func extractPotentialBuckets(url string) []string { + // This is a simple implementation. + // TODO: add more cases + parts := strings.Split(url, ".") + var buckets []string + for i, part := range parts { + buckets = append(buckets, part) + buckets = append(buckets, part+"-s3") + buckets = append(buckets, "s3-"+part) + + if i < len(parts)-1 { + domainExtension := part + "-" + parts[i+1] + buckets = append(buckets, domainExtension) + buckets = append(buckets, parts[i+1] + "-" + part) + } + } + return buckets +} + +func checkS3Bucket(bucket string, client *http.Client) (bool, error) { + url := fmt.Sprintf("https://%s.s3.amazonaws.com", bucket) + resp, err := client.Get(url) + if err != nil { + return false, err + } + defer resp.Body.Close() + + // If we can access the bucket listing, it's public + return resp.StatusCode == http.StatusOK, nil +} + diff --git a/sif.go b/sif.go index d9a4885..a7322ee 100644 --- a/sif.go +++ b/sif.go @@ -184,6 +184,15 @@ func (app *App) Run() error { } } + if app.settings.CloudStorage { + result, err := scan.CloudStorage(url, app.settings.Timeout, app.settings.LogDir) + if err != nil { + log.Errorf("Error while running C3 Scan: %s", err) + } else { + moduleResults = append(moduleResults, ModuleResult{"cloudstorage", result}) + } + } + if app.settings.ApiMode { result := UrlResult{ Url: url,