mirror of
https://github.com/lunchcat/sif.git
synced 2026-06-12 19:11:25 -07:00
d0bdcf1690
every scanner spun up its own &http.Client, so there was no single place to apply a proxy, custom headers, a cookie or a rate limit. add an internal/httpx package that builds one configured transport at startup and hand it to every scanner via httpx.Client(timeout), keeping behavior identical when nothing is set (plain client when Configure was never called). - httpx.Configure wires -proxy (http/https/socks5), -H/--header, -cookie and -rate-limit into a package-level RoundTripper that paces via a rate.Limiter and only sets headers the caller hasn't already, so a scanner's explicit api key still wins. - route the scan/wordlist downloads that used http.DefaultClient through the shared client too; ports tcp dialing is left untouched. - clamp -threads to a floor of 1: it feeds wg.Add across the scanners, so 0 was a silent no-op and a negative value panicked the waitgroup. document the new flags in the readme, usage docs and man page.
116 lines
3.7 KiB
Go
116 lines
3.7 KiB
Go
/*
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
: :
|
|
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
|
: ▄█ █ █▀ · BSD 3-Clause License :
|
|
: :
|
|
: (c) 2022-2026 vmfunc, xyzeva, :
|
|
: lunchcat alumni & contributors :
|
|
: :
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
*/
|
|
|
|
package scan
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/charmbracelet/log"
|
|
"github.com/dropalldatabases/sif/internal/httpx"
|
|
"github.com/dropalldatabases/sif/internal/logger"
|
|
"github.com/dropalldatabases/sif/internal/styles"
|
|
)
|
|
|
|
// s3EndpointFmt is a var so integration tests can repoint it at a fixture; the
|
|
// %s is the bucket name.
|
|
var s3EndpointFmt = "https://%s.s3.amazonaws.com"
|
|
|
|
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 := stripScheme(url)
|
|
|
|
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 := httpx.Client(timeout)
|
|
|
|
potentialBuckets := extractPotentialBuckets(sanitizedURL)
|
|
|
|
var results []CloudStorageResult
|
|
|
|
for _, bucket := range potentialBuckets {
|
|
isPublic, err := checkS3Bucket(context.TODO(), 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 {
|
|
// TODO: handle non-adjacent label combos and strip the tld
|
|
parts := strings.Split(url, ".")
|
|
var buckets []string
|
|
for i, part := range parts {
|
|
buckets = append(buckets, part, part+"-s3", "s3-"+part)
|
|
|
|
if i < len(parts)-1 {
|
|
domainExtension := part + "-" + parts[i+1]
|
|
buckets = append(buckets, domainExtension, parts[i+1]+"-"+part)
|
|
}
|
|
}
|
|
return buckets
|
|
}
|
|
|
|
func checkS3Bucket(ctx context.Context, bucket string, client *http.Client) (bool, error) {
|
|
url := fmt.Sprintf(s3EndpointFmt, bucket)
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
resp, err := client.Do(req)
|
|
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
|
|
}
|