/* ·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━· : : : █▀ █ █▀▀ · Blazing-fast pentesting suite : : ▄█ █ █▀ · BSD 3-Clause License : : : : (c) 2022-2025 vmfunc, xyzeva, : : lunchcat alumni & contributors : : : ·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━· */ package scan import ( "bufio" "context" "fmt" "net/http" "strconv" "strings" "sync" "time" charmlog "github.com/charmbracelet/log" "github.com/dropalldatabases/sif/internal/logger" "github.com/dropalldatabases/sif/internal/output" ) const ( directoryURL = "https://raw.githubusercontent.com/dropalldatabases/sif-runtime/main/dirlist/" smallFile = "directory-list-2.3-small.txt" mediumFile = "directory-list-2.3-medium.txt" bigFile = "directory-list-2.3-big.txt" ) type DirectoryResult struct { Url string `json:"url"` StatusCode int `json:"status_code"` } // Dirlist performs directory fuzzing on the target URL. func Dirlist(size string, url string, timeout time.Duration, threads int, logdir string) ([]DirectoryResult, error) { log := output.Module("DIRLIST") log.Start() sanitizedURL := strings.Split(url, "://")[1] if logdir != "" { if err := logger.WriteHeader(sanitizedURL, logdir, size+" directory fuzzing"); err != nil { log.Error("Error creating log file: %v", err) return nil, err } } var list string switch size { case "small": list = directoryURL + smallFile case "medium": list = directoryURL + mediumFile case "large": list = directoryURL + bigFile } req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, list, http.NoBody) if err != nil { log.Error("Error creating directory list request: %s", err) return nil, err } resp, err := http.DefaultClient.Do(req) if err != nil { log.Error("Error downloading directory list: %s", err) return nil, err } defer resp.Body.Close() var directories []string scanner := bufio.NewScanner(resp.Body) scanner.Split(bufio.ScanLines) for scanner.Scan() { directories = append(directories, scanner.Text()) } client := &http.Client{ Timeout: timeout, } progress := output.NewProgress(len(directories), "fuzzing") var wg sync.WaitGroup var mu sync.Mutex wg.Add(threads) results := make([]DirectoryResult, 0, 64) for thread := 0; thread < threads; thread++ { go func(thread int) { defer wg.Done() for i, directory := range directories { if i%threads != thread { continue } progress.Increment(directory) charmlog.Debugf("%s", directory) dirReq, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, url+"/"+directory, http.NoBody) if err != nil { charmlog.Debugf("Error creating request for %s: %s", directory, err) continue } resp, err := client.Do(dirReq) if err != nil { charmlog.Debugf("Error %s: %s", directory, err) continue } if resp.StatusCode != 404 && resp.StatusCode != 403 { progress.Pause() log.Success("found: %s [%s]", output.Highlight.Render(directory), output.Status.Render(strconv.Itoa(resp.StatusCode))) progress.Resume() if logdir != "" { _ = logger.Write(sanitizedURL, logdir, fmt.Sprintf("%s [%s]\n", strconv.Itoa(resp.StatusCode), directory)) } result := DirectoryResult{ Url: resp.Request.URL.String(), StatusCode: resp.StatusCode, } mu.Lock() results = append(results, result) mu.Unlock() } resp.Body.Close() } }(thread) } wg.Wait() progress.Done() log.Complete(len(results), "found") return results, nil }