commit 8d99b717d03e982b7ef07d5ad76b8a4766a96cac Author: delorean Date: Thu Nov 21 00:16:20 2024 -0600 initial diff --git a/README.md b/README.md new file mode 100644 index 0000000..a5ce10b --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +

+ +

+ +# maraudir + +> rapid & comprehensive open directory aggregator + + +## usage +``` +maraudir + -l + -r + -t [50] + -timeout [500] + -delay [200] + -s [false] + +results are written to stdout and can be piped accordingly, +verbosity is directed to stderr when silent is not set +``` diff --git a/cmd/maraudir/main.go b/cmd/maraudir/main.go new file mode 100644 index 0000000..e6241cb --- /dev/null +++ b/cmd/maraudir/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "maraudir/common" +) + +func main() { + common.LoadParams() + common.Takeoff() +} diff --git a/common/args.go b/common/args.go new file mode 100644 index 0000000..8baa3f8 --- /dev/null +++ b/common/args.go @@ -0,0 +1,37 @@ +package common + +import ( + "flag" +) + +type Params struct { + List string + Cidr string + Threads int + Tmout int + Delay int + Silent bool +} + +var ( + list = flag.String("l", "", "") + cidr = flag.String("r", "", "") + threads = flag.Int("t", 50, "") + tmout = flag.Int("timeout", 500, "") + delay = flag.Int("delay", 200, "") + silent = flag.Bool("s", false, "") + Conf Params +) + +func LoadParams() { + flag.Usage = Usage + flag.Parse() + Conf = Params{ + List: *list, + Cidr: *cidr, + Threads: *threads, + Tmout: *tmout, + Delay: *delay, + Silent: *silent, + } +} diff --git a/common/exec.go b/common/exec.go new file mode 100644 index 0000000..2d5e0c9 --- /dev/null +++ b/common/exec.go @@ -0,0 +1,63 @@ +package common + +import ( + "fmt" + "log/slog" + "os" +) + +func thread(l *slog.Logger, dests <-chan string, tab chan<- interface{}) { + c := MkClient() + for dest := range dests { + uriloop: + for _, uri := range Paths { + url := fmt.Sprintf("https://%s%s", dest, uri) + fallback := true + for { + if doc, err := Hit(c, url); err == nil { + if doc != nil { + if Checktitle(doc) { + entries := Entries(doc) + l.Info("opendir", "url", url, "entries", entries) + } + } + break + } else { + if !fallback { + break uriloop + } + url = fmt.Sprintf("http://%s%s", dest, uri) + fallback = false + } + } + } + } + tab <- "H A C K T H E P L A N E T" +} + +func Takeoff() { + dests := make(chan string) + tab := make(chan interface{}) + logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) + + for i := 0; i < Conf.Threads; i++ { + go thread(logger, dests, tab) + } + + if Conf.List != "" { + Readfile(Conf.List, dests) + } else if Conf.Cidr != "" { + if !ValidRange(Conf.Cidr) { + Fatal("invalid cidr range") + } + LCG(Conf.Cidr, dests) + } else { + Fatal("provide either a list (-l) or cidr range (-r)") + } + close(dests) + + for x := 0; x < Conf.Threads; x++ { + <-tab + } + close(tab) +} diff --git a/common/io.go b/common/io.go new file mode 100644 index 0000000..c567abc --- /dev/null +++ b/common/io.go @@ -0,0 +1,28 @@ +package common + +import ( + "fmt" + "os" +) + +func Fatal(msg string) { + fmt.Fprintf(os.Stderr, "fatal: %s\n", msg) + os.Exit(-1) +} + +func Usage() { + fmt.Fprintf(os.Stderr, ` +maraudir + -l + -r + -t [50] + -timeout [500] + -delay [200] + -s [false] + +results are written to stdout and can be piped accordingly, +verbosity is directed to stderr when silent is not set + +`) + os.Exit(-1) +} diff --git a/common/lcg.go b/common/lcg.go new file mode 100644 index 0000000..19835bf --- /dev/null +++ b/common/lcg.go @@ -0,0 +1,42 @@ +package common + +import ( + "fmt" + "math/big" + "net" +) + +func iptouint(ip net.IP) uint32 { + ip = ip.To4() + return uint32(ip[0])<<24 | uint32(ip[1])<<16 | uint32(ip[2])<<8 | uint32(ip[3]) +} + +func toip(num uint32) string { + return fmt.Sprintf("%d.%d.%d.%d", + (num>>24)&255, + (num>>16)&255, + (num>>8)&255, + num&255) +} + +// per-cidr linear congruential generator for efficient randomized target ip ordering, ty claude +func LCG(cidr string, out chan<- string) { + // lcg constants + const a uint64 = 1664525 + const c uint64 = 1013904223 + + _, ipnet, _ := net.ParseCIDR(cidr) + start := iptouint(ipnet.IP) + + ones, bits := ipnet.Mask.Size() + addrcount := new(big.Int).Lsh(big.NewInt(1), uint(bits-ones)) + + x := uint64(start) + m := uint64(addrcount.Uint64()) + + for i := uint64(0); i < m; i++ { + x = (a*x + c) % (1 << 32) // mod of full 32bit addr count + ip := toip(uint32((x % m) + uint64(start))) + out <- ip + } +} diff --git a/common/net.go b/common/net.go new file mode 100644 index 0000000..03c6151 --- /dev/null +++ b/common/net.go @@ -0,0 +1,108 @@ +package common + +import ( + "net" + "strings" + "time" + + "github.com/PuerkitoBio/goquery" + "github.com/valyala/fasthttp" +) + +var ( + // constants + Paths = []string{"/", "/files/", "/ftp/", "/backup/", "/backups/", "/config/", "/logs/", "/data/", "/uploads/", "/temp/", "/tmp/", "/static/"} + Patterns = []string{"index of", "directory listing for"} + Ignore = []string{"..", ".", "../", "./", "parent directory", "last modified", "name", "size", "description"} + UserAgent = "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" +) + +func ValidRange(r string) bool { + if _, _, err := net.ParseCIDR(r); err != nil { + return false + } + return true +} + +func Entries(doc *goquery.Document) []string { + var list []string + doc.Find("a").Each(func(i int, s *goquery.Selection) { + if len(list) >= 100 { + return + } + + text := strings.TrimSpace(s.Text()) + + for _, ig := range Ignore { + if text == ig { + return + } + } + + list = append(list, text) + }) + + return list +} + +func Checktitle(doc *goquery.Document) bool { + if title := doc.Find("title").Text(); len(title) > 0 { + for _, pattern := range Patterns { + if strings.Contains(title, pattern) { + return true + } + } + } + return false +} + +func MkClient() *fasthttp.Client { + tmout := time.Duration(Conf.Tmout) * time.Millisecond + dialer := &fasthttp.TCPDialer{ + Concurrency: 0, + } + + return &fasthttp.Client{ + MaxResponseBodySize: 10 * 1024 * 1024, // 10mb + ReadTimeout: 5 * time.Second, + WriteTimeout: tmout, + MaxIdleConnDuration: 5 * time.Second, + MaxConnsPerHost: Conf.Threads, + NoDefaultUserAgentHeader: true, + DisableHeaderNamesNormalizing: true, + DisablePathNormalizing: true, + DialTimeout: func(addr string, timeout time.Duration) (net.Conn, error) { + return dialer.DialTimeout(addr, tmout) + }, + } +} + +func Hit(c *fasthttp.Client, url string) (*goquery.Document, error) { + req := fasthttp.AcquireRequest() + req.SetRequestURI(url) + req.Header.SetMethod(fasthttp.MethodGet) + req.Header.SetUserAgent(UserAgent) + defer fasthttp.ReleaseRequest(req) + + resp := fasthttp.AcquireResponse() + defer fasthttp.ReleaseResponse(resp) + + // goes to stderr for verbosity, not included when stdout is piped + if !Conf.Silent { + println("->", url) + } + + var err error + if err = c.DoTimeout(req, resp, time.Duration(Conf.Tmout)*time.Millisecond); err == nil { + if body := strings.ToLower(string(resp.Body())); len(body) > 0 { + doc, err := goquery.NewDocumentFromReader(strings.NewReader(body)) + if err != nil { + return nil, nil + } + + return doc, nil + } + } + + return nil, err +} diff --git a/common/read.go b/common/read.go new file mode 100644 index 0000000..bd80aed --- /dev/null +++ b/common/read.go @@ -0,0 +1,23 @@ +package common + +import ( + "bufio" + "os" + "strings" +) + +func Readfile(filename string, out chan<- string) error { + file, err := os.Open(filename) + if err != nil { + return err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + out <- line + } + + return scanner.Err() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d01604d --- /dev/null +++ b/go.mod @@ -0,0 +1,18 @@ +module maraudir + +go 1.23 + +toolchain go1.23.3 + +require ( + github.com/PuerkitoBio/goquery v1.10.0 + github.com/valyala/fasthttp v1.57.0 +) + +require ( + github.com/andybalholm/brotli v1.1.1 // indirect + github.com/andybalholm/cascadia v1.3.2 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + golang.org/x/net v0.30.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..047c4ba --- /dev/null +++ b/go.sum @@ -0,0 +1,50 @@ +github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4= +github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= +github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.57.0 h1:Xw8SjWGEP/+wAAgyy5XTvgrWlOD1+TxbbvNADYCm1Tg= +github.com/valyala/fasthttp v1.57.0/go.mod h1:h6ZBaPRlzpZ6O3H5t2gEk1Qi33+TmLvfwgLLp0t9CpE= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=