yellow.git


yellow

~ opinionated web scanning pipeline for pentesters in a hurry
git clone https://github.com/djnnvx/yellow.git
log | files | refs | archive | README | LICENSE

alterx.go (2807B)

      1 package osint
      2 
      3 import (
      4 	"context"
      5 	"fmt"
      6 	"net"
      7 	"sync"
      8 	"time"
      9 
     10 	"evil.djnn.sh/djnn/yellow/core"
     11 	"github.com/projectdiscovery/alterx"
     12 	"github.com/projectdiscovery/dnsx/libs/dnsx"
     13 )
     14 
     15 const (
     16 	alterxLimit   = 5000
     17 	alterxWorkers = 40
     18 	alterxBudget  = 5 * time.Minute
     19 )
     20 
     21 type Alterx struct{}
     22 
     23 func (*Alterx) Name() string { return "alterx" }
     24 
     25 func (*Alterx) Run(ctx *core.Context) error {
     26 	fmt.Println("[+] Running alterx on", ctx.Domain)
     27 	if ctx.DryRun {
     28 		return nil
     29 	}
     30 
     31 	var inputs []string
     32 	for _, d := range ctx.Domains {
     33 		if d != "" && net.ParseIP(d) == nil {
     34 			inputs = append(inputs, d)
     35 		}
     36 	}
     37 	if len(inputs) == 0 {
     38 		fmt.Println("[!] alterx: no subdomains to permute, skipping")
     39 		return nil
     40 	}
     41 
     42 	mutator, err := alterx.New(&alterx.Options{Domains: inputs, Enrich: true})
     43 	if err != nil {
     44 		fmt.Printf("[!] alterx: %v\n", err)
     45 		return nil
     46 	}
     47 
     48 	// Execute ignores Options.Limit; cap here, draining fully so the producer can't leak
     49 	var candidates []string
     50 	for c := range mutator.Execute(context.Background()) {
     51 		if len(candidates) < alterxLimit {
     52 			candidates = append(candidates, c)
     53 		}
     54 	}
     55 	if len(candidates) == 0 {
     56 		fmt.Println("[!] alterx: generated no candidates, skipping")
     57 		return nil
     58 	}
     59 
     60 	live := resolveLive(candidates)
     61 
     62 	existing := map[string]struct{}{}
     63 	for _, d := range ctx.Domains {
     64 		existing[d] = struct{}{}
     65 	}
     66 	var added []string
     67 	for _, h := range live {
     68 		if _, ok := existing[h]; ok {
     69 			continue
     70 		}
     71 		existing[h] = struct{}{}
     72 		added = append(added, h)
     73 	}
     74 	ctx.Domains = append(ctx.Domains, added...)
     75 
     76 	outfile := fmt.Sprintf("%s/alterx.txt", ctx.ScanPath)
     77 	if err := core.WriteLines(outfile, added); err != nil {
     78 		fmt.Printf("[!] alterx: could not write %s: %v\n", outfile, err)
     79 	}
     80 
     81 	fmt.Printf("[OSINT %s] alterx: %d candidates, %d live, %d new in %s\n", ctx.Domain, len(candidates), len(live), len(added), outfile)
     82 	return nil
     83 }
     84 
     85 func resolveLive(candidates []string) []string {
     86 	opts := dnsx.DefaultOptions
     87 	opts.MaxRetries = 2
     88 	client, err := dnsx.New(opts)
     89 	if err != nil {
     90 		fmt.Printf("[!] alterx: dns resolver init failed: %v\n", err)
     91 		return nil
     92 	}
     93 
     94 	ctx, cancel := context.WithTimeout(context.Background(), alterxBudget)
     95 	defer cancel()
     96 
     97 	jobs := make(chan string)
     98 	var (
     99 		mu   sync.Mutex
    100 		live []string
    101 		wg   sync.WaitGroup
    102 	)
    103 	for i := 0; i < alterxWorkers; i++ {
    104 		wg.Add(1)
    105 		go func() {
    106 			defer wg.Done()
    107 			for {
    108 				select {
    109 				case <-ctx.Done():
    110 					return
    111 				case name, ok := <-jobs:
    112 					if !ok {
    113 						return
    114 					}
    115 					if ips, err := client.Lookup(name); err == nil && len(ips) > 0 {
    116 						mu.Lock()
    117 						live = append(live, name)
    118 						mu.Unlock()
    119 					}
    120 				}
    121 			}
    122 		}()
    123 	}
    124 dispatch:
    125 	for _, c := range candidates {
    126 		select {
    127 		case <-ctx.Done():
    128 			break dispatch
    129 		case jobs <- c:
    130 		}
    131 	}
    132 	close(jobs)
    133 	wg.Wait()
    134 	return live
    135 }