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.
169 lines
5.0 KiB
Go
169 lines
5.0 KiB
Go
/*
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
: :
|
|
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
|
: ▄█ █ █▀ · BSD 3-Clause License :
|
|
: :
|
|
: (c) 2022-2026 vmfunc, xyzeva, :
|
|
: lunchcat alumni & contributors :
|
|
: :
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
*/
|
|
|
|
package scan
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
charmlog "github.com/charmbracelet/log"
|
|
"github.com/dropalldatabases/sif/internal/httpx"
|
|
"github.com/dropalldatabases/sif/internal/logger"
|
|
"github.com/dropalldatabases/sif/internal/output"
|
|
)
|
|
|
|
// dnsURL is a var so integration tests can repoint it at a fixture.
|
|
var dnsURL = "https://raw.githubusercontent.com/dropalldatabases/sif-runtime/main/dnslist/"
|
|
|
|
// dnsTransport is a var so integration tests can route the per-host probes at a
|
|
// local server instead of resolving real DNS. nil keeps http.DefaultTransport.
|
|
var dnsTransport http.RoundTripper
|
|
|
|
const (
|
|
dnsSmallFile = "subdomains-100.txt"
|
|
dnsMediumFile = "subdomains-1000.txt"
|
|
dnsBigFile = "subdomains-10000.txt"
|
|
)
|
|
|
|
// Dnslist performs DNS subdomain enumeration on the target domain.
|
|
func Dnslist(size string, url string, timeout time.Duration, threads int, logdir string) ([]string, error) {
|
|
log := output.Module("DNS")
|
|
log.Start()
|
|
|
|
var list string
|
|
switch size {
|
|
case "small":
|
|
list = dnsURL + dnsSmallFile
|
|
case "medium":
|
|
list = dnsURL + dnsMediumFile
|
|
case "large":
|
|
list = dnsURL + dnsBigFile
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, list, http.NoBody)
|
|
if err != nil {
|
|
log.Error("Error creating request: %s", err)
|
|
return nil, err
|
|
}
|
|
resp, err := httpx.Client(timeout).Do(req)
|
|
if err != nil {
|
|
log.Error("Error downloading DNS list: %s", err)
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var dns []string
|
|
scanner := bufio.NewScanner(resp.Body)
|
|
scanner.Split(bufio.ScanLines)
|
|
for scanner.Scan() {
|
|
dns = append(dns, scanner.Text())
|
|
}
|
|
|
|
sanitizedURL := stripScheme(url)
|
|
|
|
if logdir != "" {
|
|
if err := logger.WriteHeader(sanitizedURL, logdir, size+" subdomain fuzzing"); err != nil {
|
|
log.Error("Error creating log file: %v", err)
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// per-host probe client. dnsTransport pins every dial at a fixture in
|
|
// integration tests; nil keeps the shared transport for real runs.
|
|
client := httpx.Client(timeout)
|
|
if dnsTransport != nil {
|
|
client.Transport = dnsTransport
|
|
}
|
|
|
|
progress := output.NewProgress(len(dns), "enumerating")
|
|
|
|
var wg sync.WaitGroup
|
|
var mu sync.Mutex
|
|
wg.Add(threads)
|
|
|
|
urls := make([]string, 0, 64)
|
|
for thread := 0; thread < threads; thread++ {
|
|
go func(thread int) {
|
|
defer wg.Done()
|
|
|
|
for i, domain := range dns {
|
|
if i%threads != thread {
|
|
continue
|
|
}
|
|
|
|
progress.Increment(domain)
|
|
|
|
charmlog.Debugf("Looking up: %s", domain)
|
|
|
|
// Check HTTP
|
|
httpReq, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, "http://"+domain+"."+sanitizedURL, http.NoBody)
|
|
if err != nil {
|
|
charmlog.Debugf("Error %s: %s", domain, err)
|
|
continue
|
|
}
|
|
resp, err := client.Do(httpReq)
|
|
if err != nil {
|
|
charmlog.Debugf("Error %s: %s", domain, err)
|
|
} else {
|
|
mu.Lock()
|
|
urls = append(urls, resp.Request.URL.String())
|
|
mu.Unlock()
|
|
resp.Body.Close()
|
|
|
|
progress.Pause()
|
|
log.Success("found: %s.%s [http]", output.Highlight.Render(domain), sanitizedURL)
|
|
progress.Resume()
|
|
|
|
if logdir != "" {
|
|
logger.Write(sanitizedURL, logdir, fmt.Sprintf("[http] %s.%s\n", domain, sanitizedURL))
|
|
}
|
|
}
|
|
|
|
// Check HTTPS
|
|
httpsReq, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, "https://"+domain+"."+sanitizedURL, http.NoBody)
|
|
if err != nil {
|
|
charmlog.Debugf("Error %s: %s", domain, err)
|
|
continue
|
|
}
|
|
resp, err = client.Do(httpsReq)
|
|
if err != nil {
|
|
charmlog.Debugf("Error %s: %s", domain, err)
|
|
} else {
|
|
mu.Lock()
|
|
urls = append(urls, resp.Request.URL.String())
|
|
mu.Unlock()
|
|
resp.Body.Close()
|
|
|
|
progress.Pause()
|
|
log.Success("found: %s.%s [https]", output.Highlight.Render(domain), sanitizedURL)
|
|
progress.Resume()
|
|
|
|
if logdir != "" {
|
|
_ = logger.Write(sanitizedURL, logdir, fmt.Sprintf("[https] %s.%s\n", domain, sanitizedURL))
|
|
}
|
|
}
|
|
}
|
|
}(thread)
|
|
}
|
|
wg.Wait()
|
|
progress.Done()
|
|
|
|
log.Complete(len(urls), "found")
|
|
|
|
return urls, nil
|
|
}
|