@@ -2,126 +2,114 @@ package main
2
2
3
3
import (
4
4
"bufio"
5
+ "bytes"
5
6
"flag"
6
7
"fmt"
7
8
"os"
9
+ "os/signal"
8
10
"regexp"
11
+ "strconv"
9
12
"strings"
13
+ "sync"
14
+ "syscall"
15
+
16
+ roundChan "github.com/trickest/mkpath/round"
10
17
)
11
18
12
19
const (
13
20
fileRegex = "[^?*:;{}]+\\ .[^/?*:;{}]+"
21
+
22
+ bufferSizeMB = 100
23
+ maxWorkingThreads = 100000
24
+ numberOfFiles = 1
14
25
)
15
26
16
- func generatePaths (wordSet map [string ]bool , depth int ) []string {
17
- results := make ([]string , 0 )
18
- for i := 0 ; i < depth ; i += 1 {
19
- toMerge := results [0 :]
20
- if len (toMerge ) == 0 {
21
- for word := range wordSet {
22
- results = append (results , word )
23
- }
24
- } else {
25
- for _ , sd := range toMerge {
26
- for word := range wordSet {
27
- results = append (results , fmt .Sprintf ("%s/%s" , word , sd ))
28
- }
29
- }
30
- }
31
- }
32
- return results
33
- }
27
+ var (
28
+ domain string
29
+ inputDomains []string
30
+ domainFile string
34
31
35
- func generateFiles (paths []string , files map [string ]bool ) []string {
36
- results := make ([]string , 0 )
37
- for _ , path := range paths {
38
- for file := range files {
39
- results = append (results , path + "/" + file )
40
- }
41
- }
42
- return results
43
- }
32
+ wordlist string
33
+ toLowercase bool
34
+ regex string
35
+ dirWordSet map [string ]bool
36
+ fileWordSet map [string ]bool
44
37
45
- func generateAll (paths map [string ]bool , files map [string ]bool , depth int ) []string {
46
- var results []string
47
- results = generatePaths (paths , depth )
48
- results = append (results , generateFiles (generatePaths (paths , depth ), files )... )
49
- return results
50
- }
38
+ depth int
39
+ onlyDirs bool
40
+ onlyFiles bool
41
+ outputFileName string
42
+ silent bool
51
43
52
- func main () {
53
- domain := flag .String ("d" , "" , "Input domain" )
54
- domainFile := flag .String ("df" , "" , "Input domain file, one domain per line" )
55
- wordlist := flag .String ("w" , "" , "Wordlist file" )
56
- toLowercase := flag .Bool ("lower" , false , "Convert wordlist file content to lowercase (default false)" )
57
- r := flag .String ("r" , "" , "Regex to filter words from wordlist file" )
58
- depth := flag .Int ("l" , 1 , "URL path depth to generate (default 1)" )
59
- output := flag .String ("o" , "" , "Output file (optional)" )
60
- onlyDirs := flag .Bool ("only-dirs" , false , "Generate directories only, files are filtered out (default false)" )
61
- onlyFiles := flag .Bool ("only-files" , false , "Generate files only, file names are appended to given domains (default false)" )
62
- flag .Parse ()
44
+ workers int
45
+ workerThreadMax = make (chan struct {}, maxWorkingThreads )
46
+ done = make (chan struct {})
47
+ wg sync.WaitGroup
48
+ wgWrite sync.WaitGroup
49
+ robin roundChan.RoundRobin
50
+ )
63
51
64
- inputDomains := make ([]string , 0 )
65
- if * domain != "" {
66
- inputDomains = append (inputDomains , * domain )
52
+ func readDomainFile () {
53
+ inputFile , err := os .Open (domainFile )
54
+ if err != nil {
55
+ fmt .Println ("Could not open file to read domains:" , err )
56
+ os .Exit (1 )
67
57
}
68
- if * domainFile != "" {
69
- inputFile , err := os .Open (* domainFile )
70
- if err != nil {
71
- fmt .Println (err .Error ())
72
- os .Exit (1 )
73
- }
74
- defer inputFile .Close ()
75
- scanner := bufio .NewScanner (inputFile )
76
- for scanner .Scan () {
77
- inputDomains = append (inputDomains , scanner .Text ())
78
- }
58
+ defer inputFile .Close ()
59
+
60
+ scanner := bufio .NewScanner (inputFile )
61
+ for scanner .Scan () {
62
+ inputDomains = append (inputDomains , strings .TrimSpace (scanner .Text ()))
79
63
}
80
- if len (inputDomains ) == 0 {
81
- fmt .Println ("No input provided" )
64
+ }
65
+
66
+ func prepareDomains () {
67
+ if domain == "" && domainFile == "" {
68
+ fmt .Println ("No domain input provided!" )
82
69
os .Exit (1 )
83
70
}
84
71
85
- wordlistFile , err := os .Open (* wordlist )
86
- if err != nil {
87
- fmt .Println (err .Error ())
88
- os .Exit (1 )
72
+ inputDomains = make ([]string , 0 )
73
+ if domain != "" {
74
+ inputDomains = append (inputDomains , domain )
75
+ } else {
76
+ if domainFile != "" {
77
+ readDomainFile ()
78
+ }
89
79
}
90
- defer wordlistFile . Close ()
80
+ }
91
81
82
+ func readWordlistFile () {
92
83
var reg * regexp.Regexp
93
- if * r != "" {
94
- reg , err = regexp .Compile (* r )
84
+ var err error
85
+ if regex != "" {
86
+ reg , err = regexp .Compile (regex )
95
87
if err != nil {
96
- fmt .Println (err . Error () )
88
+ fmt .Println (err )
97
89
os .Exit (1 )
98
90
}
99
91
}
100
92
101
- var fileReg * regexp.Regexp
102
- fileReg , err = regexp .Compile (fileRegex )
93
+ wordlistFile , err := os .Open (wordlist )
103
94
if err != nil {
104
- fmt .Println (err . Error () )
95
+ fmt .Println ("Could not open file to read wordlist:" , err )
105
96
os .Exit (1 )
106
97
}
98
+ defer wordlistFile .Close ()
107
99
108
- var outputFile * os.File
109
- if * output != "" {
110
- outputFile , err = os .Create (* output )
111
- if err != nil {
112
- fmt .Println (err .Error ())
113
- os .Exit (1 )
114
- }
115
- defer outputFile .Close ()
100
+ fileReg , err := regexp .Compile (fileRegex )
101
+ if err != nil {
102
+ fmt .Println (err )
103
+ os .Exit (1 )
116
104
}
117
105
118
- dirWordSet : = make (map [string ]bool )
119
- fileWordSet : = make (map [string ]bool )
106
+ dirWordSet = make (map [string ]bool )
107
+ fileWordSet = make (map [string ]bool )
120
108
scanner := bufio .NewScanner (wordlistFile )
121
109
122
110
for scanner .Scan () {
123
111
word := scanner .Text ()
124
- if * toLowercase {
112
+ if toLowercase {
125
113
word = strings .ToLower (word )
126
114
}
127
115
word = strings .Trim (word , "/" )
@@ -138,24 +126,173 @@ func main() {
138
126
}
139
127
}
140
128
}
129
+ }
130
+
131
+ func closeWriters (number int ) {
132
+ for i := 0 ; i < number ; i ++ {
133
+ done <- struct {}{}
134
+ }
135
+ }
136
+
137
+ func spawnWriters (number int ) {
138
+ for i := 0 ; i < number ; i ++ {
139
+ var bf bytes.Buffer
140
+ ch := make (chan string , 100000 )
141
+
142
+ fileName := outputFileName
143
+ fileSplit := strings .Split (fileName , "." )
144
+ if len (fileSplit ) == 1 {
145
+ fileName += ".txt"
146
+ }
147
+ if number > 1 {
148
+ fileSplit = strings .Split (fileName , "." )
149
+ extension := "." + fileSplit [len (fileSplit )- 1 ]
150
+ fileName = strings .TrimSuffix (fileName , extension ) + "-" + strconv .Itoa (i ) + extension
151
+ }
152
+ file , err := os .Create (fileName )
153
+ if err != nil {
154
+ fmt .Println ("Couldn't open file to write output:" , err )
155
+ os .Exit (1 )
156
+ }
157
+
158
+ wgWrite .Add (1 )
159
+ go write (file , & bf , & ch )
160
+
161
+ if robin == nil {
162
+ robin = roundChan .New (& ch )
163
+ continue
164
+ }
165
+ robin .Add (& ch )
166
+ }
167
+ }
168
+
169
+ func write (file * os.File , buffer * bytes.Buffer , ch * chan string ) {
170
+ mainLoop:
171
+ for {
172
+ select {
173
+ case <- done :
174
+ for {
175
+ if ! writeOut (file , buffer , ch ) {
176
+ break
177
+ }
178
+ }
179
+ if buffer .Len () > 0 {
180
+ if file != nil {
181
+ _ , _ = file .WriteString (buffer .String ())
182
+ buffer .Reset ()
183
+ }
184
+ }
185
+ break mainLoop
186
+ default :
187
+ writeOut (file , buffer , ch )
188
+ }
189
+ }
190
+ wgWrite .Done ()
191
+ }
192
+
193
+ func writeOut (file * os.File , buffer * bytes.Buffer , outputChannel * chan string ) bool {
194
+ select {
195
+ case s := <- * outputChannel :
196
+ buffer .WriteString (s )
197
+ if buffer .Len () >= bufferSizeMB * 1024 * 1024 {
198
+ _ , _ = file .WriteString (buffer .String ())
199
+ buffer .Reset ()
200
+ }
201
+ return true
202
+ default :
203
+ return false
204
+ }
205
+ }
206
+
207
+ func combo (_comb string , level int , wg * sync.WaitGroup , wt * chan struct {}) {
208
+ defer wg .Done ()
209
+ workerThreadMax <- struct {}{}
210
+
211
+ if strings .Count (_comb , "/" ) > 0 {
212
+ processOutput (_comb , robin .Next ())
213
+ }
141
214
142
- var results []string
143
- if * onlyDirs != * onlyFiles {
144
- if * onlyDirs {
145
- results = generatePaths (dirWordSet , * depth )
146
- } else {
147
- results = generateFiles (results , fileWordSet )
215
+ var nextLevelWaitGroup sync.WaitGroup
216
+ if level > 1 {
217
+ nextLevelWt := make (chan struct {}, workers )
218
+ for dw := range dirWordSet {
219
+ nextLevelWaitGroup .Add (1 )
220
+ nextLevelWt <- struct {}{}
221
+ go combo (_comb + "/" + dw , level - 1 , & nextLevelWaitGroup , & nextLevelWt )
148
222
}
149
223
} else {
150
- results = generateAll (dirWordSet , fileWordSet , * depth )
224
+ for dw := range dirWordSet {
225
+ processOutput (_comb + "/" + dw , robin .Next ())
226
+ }
151
227
}
152
228
153
- for _ , domain := range inputDomains {
154
- for _ , subpath := range results {
155
- fmt .Println (domain + "/" + subpath )
156
- if outputFile != nil {
157
- _ , _ = outputFile .WriteString (domain + "/" + subpath + "\n " )
229
+ nextLevelWaitGroup .Wait ()
230
+ <- workerThreadMax
231
+ <- * wt
232
+ }
233
+
234
+ func processOutput (out string , outChan * chan string ) {
235
+ if onlyDirs || onlyFiles == onlyDirs {
236
+ if ! silent {
237
+ fmt .Print (out + "\n " )
238
+ }
239
+ * outChan <- out + "\n "
240
+ }
241
+
242
+ if onlyFiles || onlyFiles == onlyDirs {
243
+ for file := range fileWordSet {
244
+ if ! silent {
245
+ fmt .Print (out + "/" + file + "\n " )
158
246
}
247
+ * outChan <- out + "/" + file + "\n "
159
248
}
160
249
}
161
250
}
251
+
252
+ func main () {
253
+ flag .StringVar (& domain , "d" , "" , "Input domain" )
254
+ flag .StringVar (& domainFile , "df" , "" , "Input domain file, one domain per line" )
255
+ flag .StringVar (& wordlist , "w" , "" , "Wordlist file" )
256
+ flag .BoolVar (& toLowercase , "lower" , false , "Convert wordlist file content to lowercase (default false)" )
257
+ flag .StringVar (& regex , "r" , "" , "Regex to filter words from wordlist file" )
258
+ flag .IntVar (& depth , "l" , 1 , "URL path depth to generate" )
259
+ flag .StringVar (& outputFileName , "o" , "" , "Output file (optional)" )
260
+ flag .BoolVar (& onlyDirs , "only-dirs" , false , "Generate directories only, files are filtered out (default false)" )
261
+ flag .BoolVar (& onlyFiles , "only-files" , false , "Generate files only, file names are appended to given domains (default false)" )
262
+ flag .IntVar (& workers , "t" , 100 , "Number of threads for every path depth" )
263
+ flag .BoolVar (& silent , "silent" , true , "Skip writing generated paths to stdout (faster)" )
264
+ flag .Parse ()
265
+
266
+ go func () {
267
+ signalChannel := make (chan os.Signal , 1 )
268
+ signal .Notify (signalChannel , os .Interrupt , syscall .SIGTERM , syscall .SIGKILL )
269
+ <- signalChannel
270
+
271
+ fmt .Println ("Program interrupted, exiting..." )
272
+ os .Exit (0 )
273
+ }()
274
+
275
+ if depth <= 0 || workers <= 0 {
276
+ fmt .Println ("Path depth and number of threads must be positive integers!" )
277
+ os .Exit (0 )
278
+ }
279
+
280
+ prepareDomains ()
281
+ readWordlistFile ()
282
+ spawnWriters (numberOfFiles )
283
+
284
+ if outputFileName == "" {
285
+ silent = false
286
+ }
287
+
288
+ for _ , d := range inputDomains {
289
+ wg .Add (1 )
290
+ wt := make (chan struct {}, 1 )
291
+ wt <- struct {}{}
292
+ go combo (d , depth , & wg , & wt )
293
+ }
294
+
295
+ wg .Wait ()
296
+ closeWriters (numberOfFiles )
297
+ wgWrite .Wait ()
298
+ }
0 commit comments