Codebase list ffuf / upstream/latest
New upstream version 1.3.0 Sophie Brun 3 years ago
33 changed file(s) with 752 addition(s) and 300 deletion(s). Raw diff Collapse all Expand all
0 github: [joohoi]
+0
-71
.github/workflows/codeql-analysis.yml less more
0 # For most projects, this workflow file will not need changing; you simply need
1 # to commit it to your repository.
2 #
3 # You may wish to alter this file to override the set of languages analyzed,
4 # or to provide custom queries or build logic.
5 name: "CodeQL"
6
7 on:
8 push:
9 branches: [master]
10 pull_request:
11 # The branches below must be a subset of the branches above
12 branches: [master]
13 schedule:
14 - cron: '0 9 * * 3'
15
16 jobs:
17 analyze:
18 name: Analyze
19 runs-on: ubuntu-latest
20
21 strategy:
22 fail-fast: false
23 matrix:
24 # Override automatic language detection by changing the below list
25 # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
26 language: ['go']
27 # Learn more...
28 # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
29
30 steps:
31 - name: Checkout repository
32 uses: actions/checkout@v2
33 with:
34 # We must fetch at least the immediate parents so that if this is
35 # a pull request then we can checkout the head.
36 fetch-depth: 2
37
38 # If this run was triggered by a pull request event, then checkout
39 # the head of the pull request instead of the merge commit.
40 - run: git checkout HEAD^2
41 if: ${{ github.event_name == 'pull_request' }}
42
43 # Initializes the CodeQL tools for scanning.
44 - name: Initialize CodeQL
45 uses: github/codeql-action/init@v1
46 with:
47 languages: ${{ matrix.language }}
48 # If you wish to specify custom queries, you can do so here or in a config file.
49 # By default, queries listed here will override any specified in a config file.
50 # Prefix the list here with "+" to use these queries and those in the config file.
51 # queries: ./path/to/local/query, your-org/your-repo/queries@main
52
53 # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54 # If this step fails, then you should remove it and run the build manually (see below)
55 - name: Autobuild
56 uses: github/codeql-action/autobuild@v1
57
58 # ℹī¸ Command-line programs to run using the OS shell.
59 # 📚 https://git.io/JvXDl
60
61 # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines
62 # and modify them (or add more) to build your code if your project
63 # uses a compiled language
64
65 #- run: |
66 # make bootstrap
67 # make release
68
69 - name: Perform CodeQL Analysis
70 uses: github/codeql-action/analyze@v1
99 gcflags:
1010 - all=-trimpath={{.Env.GOPATH}}
1111 ldflags: |
12 -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -extldflags '-static'
12 -s -w -X github.com/ffuf/ffuf/pkg/ffuf.VERSION_APPENDIX= -extldflags '-static'
1313 goos:
1414 - linux
1515 - windows
11 - master
22 - New
33 - Changed
4
5 - v1.3.0
6 - New
7 - All output file formats now include the `Content-Type`.
8 - New CLI flag `-recursion-strategy` that allows adding new queued recursion jobs for non-redirect responses.
9 - Ability to enter interactive mode by pressing `ENTER` during the ffuf execution. The interactive mode allows
10 user to change filters, manage recursion queue, save snapshot of matches to a file etc.
11 - Changed
12 - Fix a badchar in progress output
13
14 - v1.2.1
15 - Changed
16 - Fixed a build breaking bug in `input-shell` parameter
417
518 - v1.2.0
619 - New
2222 * [Kiblyn11](https://github.com/Kiblyn11)
2323 * [lc](https://github.com/lc)
2424 * [nnwakelam](https://twitter.com/nnwakelam)
25 * [noraj](https://pwn.by/noraj)
2526 * [oh6hay](https://github.com/oh6hay)
2627 * [putsi](https://github.com/putsi)
2728 * [SakiiR](https://github.com/SakiiR)
2829 * [seblw](https://github.com/seblw)
2930 * [Shaked](https://github.com/Shaked)
3031 * [SolomonSklash](https://github.com/SolomonSklash)
32 * [l4yton](https://github.com/l4yton)
99 # ffuf - Fuzz Faster U Fool
1010
1111 A fast web fuzzer written in Go.
12
13 - [Installation](https://github.com/ffuf/ffuf#installation)
14 - [Example usage](https://github.com/ffuf/ffuf#example-usage)
15 - [Content discovery](https://github.com/ffuf/ffuf#typical-directory-discovery)
16 - [Vhost discovery](https://github.com/ffuf/ffuf#virtual-host-discovery-without-dns-records)
17 - [Parameter fuzzing](https://github.com/ffuf/ffuf#get-parameter-fuzzing)
18 - [POST data fuzzing](https://github.com/ffuf/ffuf#post-data-fuzzing)
19 - [Using external mutator](https://github.com/ffuf/ffuf#using-external-mutator-to-produce-test-cases)
20 - [Configuration files](https://github.com/ffuf/ffuf#configuration-files)
21 - [Help](https://github.com/ffuf/ffuf#usage)
22 - [Interactive mode](https://github.com/ffuf/ffuf#interactive-mode)
23 - [Sponsorware?](https://github.com/ffuf/ffuf#sponsorware)
24
25 ## Sponsors
26 [![Offensive Security](_img/offsec-logo.png)](https://www.offensive-security.com/)
27
28 ## Official Discord Channel
29
30 ffuf has a channel at Porchetta Industries Discord server alongside of channels for many other tools.
31
32 Come to hang out & to discuss about ffuf, it's usage and development!
33
34 [![Porchetta Industries](https://discordapp.com/api/guilds/736724457258745996/widget.png?style=banner2)](https://discord.gg/VWcdZCUsQP)
35
36
37
1238
1339 ## Installation
1440
134160 Fuzz Faster U Fool - v1.2.0-git
135161
136162 HTTP OPTIONS:
137 -H Header `"Name: Value"`, separated by colon. Multiple -H flags are accepted.
138 -X HTTP method to use (default: GET)
139 -b Cookie data `"NAME1=VALUE1; NAME2=VALUE2"` for copy as curl functionality.
140 -d POST data
141 -ignore-body Do not fetch the response content. (default: false)
142 -r Follow redirects (default: false)
143 -recursion Scan recursively. Only FUZZ keyword is supported, and URL (-u) has to end in it. (default: false)
144 -recursion-depth Maximum recursion depth. (default: 0)
145 -replay-proxy Replay matched requests using this proxy.
146 -timeout HTTP request timeout in seconds. (default: 10)
147 -u Target URL
148 -x HTTP Proxy URL
163 -H Header `"Name: Value"`, separated by colon. Multiple -H flags are accepted.
164 -X HTTP method to use
165 -b Cookie data `"NAME1=VALUE1; NAME2=VALUE2"` for copy as curl functionality.
166 -d POST data
167 -ignore-body Do not fetch the response content. (default: false)
168 -r Follow redirects (default: false)
169 -recursion Scan recursively. Only FUZZ keyword is supported, and URL (-u) has to end in it. (default: false)
170 -recursion-depth Maximum recursion depth. (default: 0)
171 -recursion-strategy Recursion strategy: "default" for a redirect based, and "greedy" to recurse on all matches (default: default)
172 -replay-proxy Replay matched requests using this proxy.
173 -timeout HTTP request timeout in seconds. (default: 10)
174 -u Target URL
175 -x Proxy URL (SOCKS5 or HTTP). For example: http://127.0.0.1:8080 or socks5://127.0.0.1:8080
149176
150177 GENERAL OPTIONS:
151178 -V Show version information. (default: false)
215242
216243 ```
217244
245 ### Interactive mode
246
247 By pressing `ENTER` during ffuf execution, the process is paused and user is dropped to a shell-like interactive mode:
248 ```
249 entering interactive mode
250 type "help" for a list of commands, or ENTER to resume.
251 > help
252
253 available commands:
254 fc [value] - (re)configure status code filter
255 fl [value] - (re)configure line count filter
256 fw [value] - (re)configure word count filter
257 fs [value] - (re)configure size filter
258 queueshow - show recursive job queue
259 queuedel [number] - delete a recursion job in the queue
260 queueskip - advance to the next queued recursion job
261 restart - restart and resume the current ffuf job
262 resume - resume current ffuf job (or: ENTER)
263 show - show results
264 savejson [filename] - save current matches to a file
265 help - you are looking at it
266 >
267 ```
268
269 in this mode, filters can be reconfigured, queue managed and the current state saved to disk.
270
271 When (re)configuring the filters, they get applied posthumously and all the false positive matches from memory that
272 would have been filtered out by the newly added filters get deleted.
273
274 The new state of matches can be printed out with a command `show` that will print out all the matches as like they
275 would have been found by `ffuf`.
276
277 As "negative" matches are not stored to memory, relaxing the filters cannot unfortunately bring back the lost matches.
278 For this kind of scenario, the user is able to use the command `restart`, which resets the state and starts the current
279 job from the beginning.
280
281
282 ## Sponsorware
283
284 `ffuf` employs a sponsorware model. This means that all new features developed by its author are initially exclusively
285 available for their sponsors. 30 days after the exclusive release, all the new features will be released at the freely
286 available open source repository at https://github.com/ffuf/ffuf .
287
288 This model enables me to provide concrete benefits for the generous individuals and companies that enable me to work on
289 `ffuf`. The different sponsorship tiers can be seen [here](https://github.com/sponsors/joohoi).
290
291 All the community contributions are and will be available directly in the freely available open source repository. The
292 exclusive version benefits only include new features created by [@joohoi](https://github.com/joohoi)
293
294 ### Access the sponsorware through code contributions
295
296 People that create significant contributions to the `ffuf` project itself should and will have access to the sponsorware
297 as well. If you are planning to create such a contribution, please contact [@joohoi](https://github.com/joohoi)
298 first to ensure that there aren't other people working on the same feature.
299
218300 ## Helper scripts and advanced payloads
219301
220302 See [ffuf-scripts](https://github.com/ffuf/ffuf-scripts) repository for helper scripts and payload generators
Binary diff not shown
1414 method = "GET"
1515 proxyurl = "http://127.0.0.1:8080"
1616 recursion = false
17 recursiondepth = 0
17 recursion_depth = 0
18 recursion_strategy = "default"
1819 replayproxyurl = "http://127.0.0.1:8080"
1920 timeout = 10
2021 url = "https://example.org/FUZZ"
5353 Description: "Options controlling the HTTP request and its parts.",
5454 Flags: make([]UsageFlag, 0),
5555 Hidden: false,
56 ExpectedFlags: []string{"H", "X", "b", "d", "r", "u", "recursion", "recursion-depth", "replay-proxy", "timeout", "ignore-body", "x"},
56 ExpectedFlags: []string{"H", "X", "b", "d", "r", "u", "recursion", "recursion-depth", "recursion-strategy", "replay-proxy", "timeout", "ignore-body", "x"},
5757 }
5858 u_general := UsageSection{
5959 Name: "GENERAL OPTIONS",
122122 }
123123 })
124124
125 fmt.Printf("Fuzz Faster U Fool - v%s\n\n", ffuf.VERSION)
125 fmt.Printf("Fuzz Faster U Fool - v%s\n\n", ffuf.Version())
126126
127127 // Print out the sections
128128 for _, section := range sections {
33 "context"
44 "flag"
55 "fmt"
6 "github.com/ffuf/ffuf/pkg/ffuf"
7 "github.com/ffuf/ffuf/pkg/filter"
8 "github.com/ffuf/ffuf/pkg/input"
9 "github.com/ffuf/ffuf/pkg/interactive"
10 "github.com/ffuf/ffuf/pkg/output"
11 "github.com/ffuf/ffuf/pkg/runner"
612 "io/ioutil"
713 "log"
814 "os"
915 "strings"
10
11 "github.com/ffuf/ffuf/pkg/ffuf"
12 "github.com/ffuf/ffuf/pkg/filter"
13 "github.com/ffuf/ffuf/pkg/input"
14 "github.com/ffuf/ffuf/pkg/output"
15 "github.com/ffuf/ffuf/pkg/runner"
1616 )
1717
1818 type multiStringFlag []string
9090 flag.StringVar(&opts.HTTP.Data, "data-ascii", opts.HTTP.Data, "POST data (alias of -d)")
9191 flag.StringVar(&opts.HTTP.Data, "data-binary", opts.HTTP.Data, "POST data (alias of -d)")
9292 flag.StringVar(&opts.HTTP.Method, "X", opts.HTTP.Method, "HTTP method to use")
93 flag.StringVar(&opts.HTTP.ProxyURL, "x", opts.HTTP.ProxyURL, "HTTP Proxy URL")
93 flag.StringVar(&opts.HTTP.ProxyURL, "x", opts.HTTP.ProxyURL, "Proxy URL (SOCKS5 or HTTP). For example: http://127.0.0.1:8080 or socks5://127.0.0.1:8080")
9494 flag.StringVar(&opts.HTTP.ReplayProxyURL, "replay-proxy", opts.HTTP.ReplayProxyURL, "Replay matched requests using this proxy.")
95 flag.StringVar(&opts.HTTP.RecursionStrategy, "recursion-strategy", opts.HTTP.RecursionStrategy, "Recursion strategy: \"default\" for a redirect based, and \"greedy\" to recurse on all matches")
9596 flag.StringVar(&opts.HTTP.URL, "u", opts.HTTP.URL, "Target URL")
9697 flag.StringVar(&opts.Input.Extensions, "e", opts.Input.Extensions, "Comma separated list of extensions. Extends FUZZ keyword.")
9798 flag.StringVar(&opts.Input.InputMode, "mode", opts.Input.InputMode, "Multi-wordlist operation mode. Available modes: clusterbomb, pitchfork")
135136 opts = ParseFlags(opts)
136137
137138 if opts.General.ShowVersion {
138 fmt.Printf("ffuf version: %s\n", ffuf.VERSION)
139 fmt.Printf("ffuf version: %s\n", ffuf.Version())
139140 os.Exit(0)
140141 }
141142 if len(opts.Output.DebugLog) != 0 {
196197 fmt.Fprintf(os.Stderr, "Error in autocalibration, exiting: %s\n", err)
197198 os.Exit(1)
198199 }
200 go func() {
201 err := interactive.Handle(job)
202 if err != nil {
203 log.Printf("Error while trying to initialize interactive session: %s", err)
204 }
205 }()
199206
200207 // Job handles waiting for goroutines to complete itself
201208 job.Start()
3232 OutputDirectory string `json:"outputdirectory"`
3333 OutputFile string `json:"outputfile"`
3434 OutputFormat string `json:"outputformat"`
35 OutputCreateEmptyFile bool `json:"OutputCreateEmptyFile"`
35 OutputCreateEmptyFile bool `json:"OutputCreateEmptyFile"`
3636 ProgressFrequency int `json:"-"`
3737 ProxyURL string `json:"proxyurl"`
3838 Quiet bool `json:"quiet"`
3939 Rate int64 `json:"rate"`
4040 Recursion bool `json:"recursion"`
4141 RecursionDepth int `json:"recursion_depth"`
42 RecursionStrategy string `json:"recursion_strategy"`
4243 ReplayProxyURL string `json:"replayproxyurl"`
4344 StopOn403 bool `json:"stop_403"`
4445 StopOnAll bool `json:"stop_all"`
8384 conf.Rate = 0
8485 conf.Recursion = false
8586 conf.RecursionDepth = 0
87 conf.RecursionStrategy = "default"
8688 conf.StopOn403 = false
8789 conf.StopOnAll = false
8890 conf.StopOnErrors = false
+0
-6
pkg/ffuf/const.go less more
0 package ffuf
1
2 const (
3 //VERSION holds the current version number
4 VERSION = "1.2.1"
5 )
33 type FilterProvider interface {
44 Filter(response *Response) (bool, error)
55 Repr() string
6 ReprVerbose() string
67 }
78
89 //RunnerProvider is an interface for request executors
3940 Progress(status Progress)
4041 Info(infostring string)
4142 Error(errstring string)
43 Raw(output string)
4244 Warning(warnstring string)
4345 Result(resp Response)
46 PrintResult(res Result)
47 SaveFile(filename, format string) error
48 GetResults() []Result
49 SetResults(results []Result)
50 Reset()
4451 }
52
53 type Result struct {
54 Input map[string][]byte `json:"input"`
55 Position int `json:"position"`
56 StatusCode int64 `json:"status"`
57 ContentLength int64 `json:"length"`
58 ContentWords int64 `json:"words"`
59 ContentLines int64 `json:"lines"`
60 ContentType string `json:"content-type"`
61 RedirectLocation string `json:"redirectlocation"`
62 Url string `json:"url"`
63 ResultFile string `json:"resultfile"`
64 Host string `json:"host"`
65 HTMLColor string `json:"-"`
66 }
2424 Total int
2525 Running bool
2626 RunningJob bool
27 Paused bool
2728 Count403 int
2829 Count429 int
2930 Error string
3233 startTimeJob time.Time
3334 queuejobs []QueueJob
3435 queuepos int
36 skipQueue bool
3537 currentDepth int
38 pauseWg sync.WaitGroup
3639 }
3740
3841 type QueueJob struct {
4851 j.SpuriousErrorCounter = 0
4952 j.Running = false
5053 j.RunningJob = false
54 j.Paused = false
5155 j.queuepos = 0
5256 j.queuejobs = make([]QueueJob, 0)
5357 j.currentDepth = 0
5458 j.Rate = NewRateThrottle(conf)
59 j.skipQueue = false
5560 return &j
5661 }
5762
8489 j.SpuriousErrorCounter = 0
8590 }
8691
92 //DeleteQueueItem deletes a recursion job from the queue by its index in the slice
93 func (j *Job) DeleteQueueItem(index int) {
94 index = j.queuepos + index - 1
95 j.queuejobs = append(j.queuejobs[:index], j.queuejobs[index+1:]...)
96 }
97
98 //QueuedJobs returns the slice of queued recursive jobs
99 func (j *Job) QueuedJobs() []QueueJob {
100 return j.queuejobs[j.queuepos-1:]
101 }
102
87103 //Start the execution of the Job
88104 func (j *Job) Start() {
89105 if j.startTime.IsZero() {
106122 j.interruptMonitor()
107123 for j.jobsInQueue() {
108124 j.prepareQueueJob()
109
110 if j.queuepos > 1 && !j.RunningJob {
111 // Print info for queued recursive jobs
112 j.Output.Info(fmt.Sprintf("Scanning: %s", j.Config.Url))
113 }
114 j.Input.Reset()
115 j.startTimeJob = time.Now()
125 j.Reset()
116126 j.RunningJob = true
117 j.Counter = 0
118127 j.startExecution()
119128 }
120129
122131 if err != nil {
123132 j.Output.Error(err.Error())
124133 }
134 }
135
136 // Reset resets the counters and wordlist position for a job
137 func (j *Job) Reset() {
138 j.Input.Reset()
139 j.Counter = 0
140 j.skipQueue = false
141 j.startTimeJob = time.Now()
142 j.Output.Reset()
125143 }
126144
127145 func (j *Job) jobsInQueue() bool {
132150 j.Config.Url = j.queuejobs[j.queuepos].Url
133151 j.currentDepth = j.queuejobs[j.queuepos].depth
134152 j.queuepos += 1
153 }
154
155 //SkipQueue allows to skip the current job and advance to the next queued recursion job
156 func (j *Job) SkipQueue() {
157 j.skipQueue = true
135158 }
136159
137160 func (j *Job) sleepIfNeeded() {
152175 }
153176 }
154177
178 // Pause pauses the job process
179 func (j *Job) Pause() {
180 if !j.Paused {
181 j.Paused = true
182 j.pauseWg.Add(1)
183 j.Output.Info("------ PAUSING ------")
184 }
185 }
186
187 // Resume resumes the job process
188 func (j *Job) Resume() {
189 if j.Paused {
190 j.Paused = false
191 j.Output.Info("------ RESUMING -----")
192 j.pauseWg.Done()
193 }
194 }
195
155196 func (j *Job) startExecution() {
156197 var wg sync.WaitGroup
157198 wg.Add(1)
158199 go j.runBackgroundTasks(&wg)
200
201 // Print the base URL when starting a new recursion queue job
202 if j.queuepos > 1 {
203 j.Output.Info(fmt.Sprintf("Starting queued job on target: %s", j.Config.Url))
204 }
205
159206 //Limiter blocks after reaching the buffer, ensuring limited concurrency
160207 limiter := make(chan bool, j.Config.Threads)
161208
162 for j.Input.Next() {
209 for j.Input.Next() && !j.skipQueue {
163210 // Check if we should stop the process
164211 j.CheckStop()
165212
167214 defer j.Output.Warning(j.Error)
168215 break
169216 }
217 j.pauseWg.Wait()
170218 limiter <- true
171219 nextInput := j.Input.Value()
172220 nextPosition := j.Input.Position()
199247 go func() {
200248 for range sigChan {
201249 j.Error = "Caught keyboard interrupt (Ctrl-C)\n"
250 // resume if paused
251 if j.Paused {
252 j.pauseWg.Done()
253 }
254 // Stop the job
202255 j.Stop()
203256 }
204257 }()
207260 func (j *Job) runBackgroundTasks(wg *sync.WaitGroup) {
208261 defer wg.Done()
209262 totalProgress := j.Input.Total()
210 for j.Counter <= totalProgress {
211
263 for j.Counter <= totalProgress && !j.skipQueue {
264 j.pauseWg.Wait()
212265 if !j.Running {
213266 break
214267 }
314367 j.Output.Result(resp)
315368 // Refresh the progress indicator as we printed something out
316369 j.updateProgress()
317 }
318
319 if j.Config.Recursion && len(resp.GetRedirectLocation(false)) > 0 {
320 j.handleRecursionJob(resp)
321 }
322 }
323
324 //handleRecursionJob adds a new recursion job to the job queue if a new directory is found
325 func (j *Job) handleRecursionJob(resp Response) {
370 if j.Config.Recursion && j.Config.RecursionStrategy == "greedy" {
371 j.handleGreedyRecursionJob(resp)
372 }
373 }
374
375 if j.Config.Recursion && j.Config.RecursionStrategy == "default" && len(resp.GetRedirectLocation(false)) > 0 {
376 j.handleDefaultRecursionJob(resp)
377 }
378 }
379
380 //handleGreedyRecursionJob adds a recursion job to the queue if the maximum depth has not been reached
381 func (j *Job) handleGreedyRecursionJob(resp Response) {
382 // Handle greedy recursion strategy. Match has been determined before calling handleRecursionJob
383 if j.Config.RecursionDepth == 0 || j.currentDepth < j.Config.RecursionDepth {
384 recUrl := resp.Request.Url + "/" + "FUZZ"
385 newJob := QueueJob{Url: recUrl, depth: j.currentDepth + 1}
386 j.queuejobs = append(j.queuejobs, newJob)
387 j.Output.Info(fmt.Sprintf("Adding a new job to the queue: %s", recUrl))
388 } else {
389 j.Output.Warning(fmt.Sprintf("Maximum recursion depth reached. Ignoring: %s", resp.Request.Url))
390 }
391 }
392
393 //handleDefaultRecursionJob adds a new recursion job to the job queue if a new directory is found and maximum depth has
394 //not been reached
395 func (j *Job) handleDefaultRecursionJob(resp Response) {
396 recUrl := resp.Request.Url + "/" + "FUZZ"
326397 if (resp.Request.Url + "/") != resp.GetRedirectLocation(true) {
327398 // Not a directory, return early
328399 return
329400 }
330401 if j.Config.RecursionDepth == 0 || j.currentDepth < j.Config.RecursionDepth {
331402 // We have yet to reach the maximum recursion depth
332 recUrl := resp.Request.Url + "/" + "FUZZ"
333403 newJob := QueueJob{Url: recUrl, depth: j.currentDepth + 1}
334404 j.queuejobs = append(j.queuejobs, newJob)
335405 j.Output.Info(fmt.Sprintf("Adding a new job to the queue: %s", recUrl))
2525 }
2626
2727 type HTTPOptions struct {
28 Cookies []string
29 Data string
30 FollowRedirects bool
31 Headers []string
32 IgnoreBody bool
33 Method string
34 ProxyURL string
35 Recursion bool
36 RecursionDepth int
37 ReplayProxyURL string
38 Timeout int
39 URL string
28 Cookies []string
29 Data string
30 FollowRedirects bool
31 Headers []string
32 IgnoreBody bool
33 Method string
34 ProxyURL string
35 Recursion bool
36 RecursionDepth int
37 RecursionStrategy string
38 ReplayProxyURL string
39 Timeout int
40 URL string
4041 }
4142
4243 type GeneralOptions struct {
7172 }
7273
7374 type OutputOptions struct {
74 DebugLog string
75 OutputDirectory string
76 OutputFile string
77 OutputFormat string
78 OutputCreateEmptyFile bool
75 DebugLog string
76 OutputDirectory string
77 OutputFile string
78 OutputFormat string
79 OutputCreateEmptyFile bool
7980 }
8081
8182 type FilterOptions struct {
122123 c.HTTP.ProxyURL = ""
123124 c.HTTP.Recursion = false
124125 c.HTTP.RecursionDepth = 0
126 c.HTTP.RecursionStrategy = "default"
125127 c.HTTP.ReplayProxyURL = ""
126128 c.HTTP.Timeout = 10
127129 c.HTTP.URL = ""
386388 conf.FollowRedirects = parseOpts.HTTP.FollowRedirects
387389 conf.Recursion = parseOpts.HTTP.Recursion
388390 conf.RecursionDepth = parseOpts.HTTP.RecursionDepth
391 conf.RecursionStrategy = parseOpts.HTTP.RecursionStrategy
389392 conf.AutoCalibration = parseOpts.General.AutoCalibration
390393 conf.Threads = parseOpts.General.Threads
391394 conf.Timeout = parseOpts.HTTP.Timeout
1212 ContentLength int64
1313 ContentWords int64
1414 ContentLines int64
15 ContentType string
1516 Cancelled bool
1617 Request *Request
1718 Raw string
4950 var resp Response
5051 resp.Request = req
5152 resp.StatusCode = int64(httpresp.StatusCode)
53 resp.ContentType = httpresp.Header.Get("Content-Type")
5254 resp.Headers = httpresp.Header
5355 resp.Cancelled = false
5456 resp.Raw = ""
00 package ffuf
11
22 import (
3 "fmt"
34 "math/rand"
45 "os"
56 )
4041
4142 return !md.IsDir()
4243 }
44
45 //Version returns the ffuf version string
46 func Version() string {
47 return fmt.Sprintf("%s%s", VERSION, VERSION_APPENDIX)
48 }
0 package ffuf
1
2 var (
3 //VERSION holds the current version number
4 VERSION = "1.3.0"
5 //VERSION_APPENDIX holds additional version definition
6 VERSION_APPENDIX = "-exclusive-dev"
7 )
2929
3030 //AddFilter adds a new filter to Config
3131 func AddFilter(conf *ffuf.Config, name string, option string) error {
32 newf, err := NewFilterByName(name, option)
33 if err == nil {
34 // valid filter create or append
35 if conf.Filters[name] == nil {
36 conf.Filters[name] = newf
37 } else {
38 currentfilter := conf.Filters[name].Repr()
39 newoption := strings.TrimSpace(strings.Split(currentfilter, ":")[1]) + "," + option
40 newerf, err := NewFilterByName(name, newoption)
41 if err == nil {
42 conf.Filters[name] = newerf
43 }
44 }
45 }
46 return err
32 newf, err := NewFilterByName(name, option)
33 if err == nil {
34 // valid filter create or append
35 if conf.Filters[name] == nil {
36 conf.Filters[name] = newf
37 } else {
38 newoption := conf.Filters[name].Repr() + "," + option
39 newerf, err := NewFilterByName(name, newoption)
40 if err == nil {
41 conf.Filters[name] = newerf
42 }
43 }
44 }
45 return err
46 }
47
48 //RemoveFilter removes a filter of a given type
49 func RemoveFilter(conf *ffuf.Config, name string) {
50 delete(conf.Filters, name)
4751 }
4852
4953 //AddMatcher adds a new matcher to Config
5959 strval = append(strval, strconv.Itoa(int(iv.Min))+"-"+strconv.Itoa(int(iv.Max)))
6060 }
6161 }
62 return fmt.Sprintf("Response lines: %s", strings.Join(strval, ","))
62 return strings.Join(strval, ",")
6363 }
64
65 func (f *LineFilter) ReprVerbose() string {
66 return fmt.Sprintf("Response lines: %s", f.Repr())
67 }
5050 }
5151
5252 func (f *RegexpFilter) Repr() string {
53 return f.valueRaw
54 }
55
56 func (f *RegexpFilter) ReprVerbose() string {
5357 return fmt.Sprintf("Regexp: %s", f.valueRaw)
5458 }
5959 strval = append(strval, strconv.Itoa(int(iv.Min))+"-"+strconv.Itoa(int(iv.Max)))
6060 }
6161 }
62 return fmt.Sprintf("Response size: %s", strings.Join(strval, ","))
62 return strings.Join(strval, ",")
6363 }
64
65 func (f *SizeFilter) ReprVerbose() string {
66 return fmt.Sprintf("Response size: %s", f.Repr())
67 }
7474 strval = append(strval, strconv.Itoa(int(iv.Min))+"-"+strconv.Itoa(int(iv.Max)))
7575 }
7676 }
77 return fmt.Sprintf("Response status: %s", strings.Join(strval, ","))
77 return strings.Join(strval, ",")
7878 }
79
80 func (f *StatusFilter) ReprVerbose() string {
81 return fmt.Sprintf("Response status: %s", f.Repr())
82 }
5959 strval = append(strval, strconv.Itoa(int(iv.Min))+"-"+strconv.Itoa(int(iv.Max)))
6060 }
6161 }
62 return fmt.Sprintf("Response words: %s", strings.Join(strval, ","))
62 return strings.Join(strval, ",")
6363 }
64
65 func (f *WordFilter) ReprVerbose() string {
66 return fmt.Sprintf("Response words: %s", f.Repr())
67 }
0 // +build !windows
1
2 package interactive
3
4 import "os"
5
6 func termHandle() (*os.File, error) {
7 return os.Open("/dev/tty")
8 }
0 package interactive
1
2 import (
3 "bufio"
4 "fmt"
5 "github.com/ffuf/ffuf/pkg/ffuf"
6 "github.com/ffuf/ffuf/pkg/filter"
7 "strconv"
8 "strings"
9 "time"
10 )
11
12 type interactive struct {
13 Job *ffuf.Job
14 paused bool
15 }
16
17 func Handle(job *ffuf.Job) error {
18 i := interactive{job, false}
19 tty, err := termHandle()
20 if err != nil {
21 return err
22 }
23 defer tty.Close()
24 inreader := bufio.NewScanner(tty)
25 inreader.Split(bufio.ScanLines)
26 for inreader.Scan() {
27 i.handleInput(inreader.Bytes())
28 }
29 return nil
30 }
31
32 func (i *interactive) handleInput(in []byte) {
33 instr := string(in)
34 args := strings.Split(strings.TrimSpace(instr), " ")
35 if len(args) == 1 && args[0] == "" {
36 // Enter pressed - toggle interactive state
37 i.paused = !i.paused
38 if i.paused {
39 i.Job.Pause()
40 time.Sleep(500 * time.Millisecond)
41 i.printBanner()
42 } else {
43 i.Job.Resume()
44 }
45 } else {
46 switch args[0] {
47 case "?":
48 i.printHelp()
49 case "help":
50 i.printHelp()
51 case "resume":
52 i.paused = false
53 i.Job.Resume()
54 case "restart":
55 i.Job.Reset()
56 i.paused = false
57 i.Job.Output.Info("Restarting the current ffuf job!")
58 i.Job.Resume()
59 case "show":
60 for _, r := range i.Job.Output.GetResults() {
61 i.Job.Output.PrintResult(r)
62 }
63 case "savejson":
64 if len(args) < 2 {
65 i.Job.Output.Error("Please define the filename")
66 } else if len(args) > 2 {
67 i.Job.Output.Error("Too many arguments for \"savejson\"")
68 } else {
69 err := i.Job.Output.SaveFile(args[1], "json")
70 if err != nil {
71 i.Job.Output.Error(fmt.Sprintf("%s", err))
72 } else {
73 i.Job.Output.Info("Output file successfully saved!")
74 }
75 }
76 case "fc":
77 if len(args) < 2 {
78 i.Job.Output.Error("Please define a value for status code filter, or \"none\" for removing it")
79 } else if len(args) > 2 {
80 i.Job.Output.Error("Too many arguments for \"fc\"")
81 } else {
82 i.updateFilter("status", args[1])
83 i.Job.Output.Info("New status code filter value set")
84 }
85 case "fl":
86 if len(args) < 2 {
87 i.Job.Output.Error("Please define a value for line count filter, or \"none\" for removing it")
88 } else if len(args) > 2 {
89 i.Job.Output.Error("Too many arguments for \"fl\"")
90 } else {
91 i.updateFilter("line", args[1])
92 i.Job.Output.Info("New line count filter value set")
93 }
94 case "fw":
95 if len(args) < 2 {
96 i.Job.Output.Error("Please define a value for word count filter, or \"none\" for removing it")
97 } else if len(args) > 2 {
98 i.Job.Output.Error("Too many arguments for \"fw\"")
99 } else {
100 i.updateFilter("word", args[1])
101 i.Job.Output.Info("New word count filter value set")
102 }
103 case "fs":
104 if len(args) < 2 {
105 i.Job.Output.Error("Please define a value for response size filter, or \"none\" for removing it")
106 } else if len(args) > 2 {
107 i.Job.Output.Error("Too many arguments for \"fs\"")
108 } else {
109 i.updateFilter("size", args[1])
110 i.Job.Output.Info("New response size filter value set")
111 }
112 case "queueshow":
113 i.printQueue()
114 case "queuedel":
115 if len(args) < 2 {
116 i.Job.Output.Error("Please define the index of a queued job to remove. Use \"queueshow\" for listing of jobs.")
117 } else if len(args) > 2 {
118 i.Job.Output.Error("Too many arguments for \"queuedel\"")
119 } else {
120 i.deleteQueue(args[1])
121 }
122 case "queueskip":
123 i.Job.SkipQueue()
124 i.Job.Output.Info("Skipping to the next queued job")
125 default:
126 if i.paused {
127 i.Job.Output.Warning(fmt.Sprintf("Unknown command: \"%s\". Enter \"help\" for a list of available commands", args[0]))
128 } else {
129 i.Job.Output.Error("NOPE")
130 }
131 }
132 }
133
134 if i.paused {
135 i.printPrompt()
136 }
137 }
138
139 func (i *interactive) updateFilter(name, value string) {
140 if value == "none" {
141 filter.RemoveFilter(i.Job.Config, name)
142 } else {
143 newFc, err := filter.NewFilterByName(name, value)
144 if err != nil {
145 i.Job.Output.Error(fmt.Sprintf("Error while setting new filter value: %s", err))
146 return
147 } else {
148 i.Job.Config.Filters[name] = newFc
149 }
150
151 results := make([]ffuf.Result, 0)
152 for _, res := range i.Job.Output.GetResults() {
153 fakeResp := &ffuf.Response{
154 StatusCode: res.StatusCode,
155 ContentLines: res.ContentLength,
156 ContentWords: res.ContentWords,
157 ContentLength: res.ContentLength,
158 }
159 filterOut, _ := newFc.Filter(fakeResp)
160 if !filterOut {
161 results = append(results, res)
162 }
163 }
164 i.Job.Output.SetResults(results)
165 }
166 }
167
168 func (i *interactive) printQueue() {
169 if len(i.Job.QueuedJobs()) > 0 {
170 i.Job.Output.Raw("Queued recursion jobs:\n")
171 for index, job := range i.Job.QueuedJobs() {
172 postfix := ""
173 if index == 0 {
174 postfix = " (active job)"
175 }
176 i.Job.Output.Raw(fmt.Sprintf(" [%d] : %s%s\n", index, job.Url, postfix))
177 }
178 } else {
179 i.Job.Output.Info("Recursion job queue is empty")
180 }
181 }
182
183 func (i *interactive) deleteQueue(in string) {
184 index, err := strconv.Atoi(in)
185 if err != nil {
186 i.Job.Output.Warning(fmt.Sprintf("Not a number: %s", in))
187 } else {
188 if index < 0 || index > len(i.Job.QueuedJobs())-1 {
189 i.Job.Output.Warning("No such queued job. Use \"queueshow\" to list the jobs in queue")
190 } else if index == 0 {
191 i.Job.Output.Warning("Cannot delete the currently running job. Use \"queueskip\" to advance to the next one")
192 } else {
193 i.Job.DeleteQueueItem(index)
194 i.Job.Output.Info("Recursion job successfully deleted!")
195 }
196 }
197 }
198 func (i *interactive) printBanner() {
199 i.Job.Output.Raw("entering interactive mode\ntype \"help\" for a list of commands, or ENTER to resume.\n")
200 }
201
202 func (i *interactive) printPrompt() {
203 i.Job.Output.Raw("> ")
204 }
205
206 func (i *interactive) printHelp() {
207 var fc, fl, fs, fw string
208 for name, filter := range i.Job.Config.Filters {
209 switch name {
210 case "status":
211 fc = "(active: " + filter.Repr() + ")"
212 case "line":
213 fl = "(active: " + filter.Repr() + ")"
214 case "word":
215 fw = "(active: " + filter.Repr() + ")"
216 case "size":
217 fs = "(active: " + filter.Repr() + ")"
218 }
219 }
220 help := `
221 available commands:
222 fc [value] - (re)configure status code filter %s
223 fl [value] - (re)configure line count filter %s
224 fw [value] - (re)configure word count filter %s
225 fs [value] - (re)configure size filter %s
226 queueshow - show recursive job queue
227 queuedel [number] - delete a recursion job in the queue
228 queueskip - advance to the next queued recursion job
229 restart - restart and resume the current ffuf job
230 resume - resume current ffuf job (or: ENTER)
231 show - show results
232 savejson [filename] - save current matches to a file
233 help - you are looking at it
234 `
235 i.Job.Output.Raw(fmt.Sprintf(help, fc, fl, fw, fs))
236 }
0 // +build windows
1
2 package interactive
3
4 import (
5 "os"
6 "syscall"
7 )
8
9 func termHandle() (*os.File, error) {
10 var tty *os.File
11 _, err := syscall.Open("CONIN$", syscall.O_RDWR, 0)
12 if err != nil {
13 return tty, err
14 }
15 tty, err = os.Open("CONIN$")
16 if err != nil {
17 return tty, err
18 }
19 return tty, nil
20 }
88 "github.com/ffuf/ffuf/pkg/ffuf"
99 )
1010
11 var staticheaders = []string{"url", "redirectlocation", "position", "status_code", "content_length", "content_words", "content_lines", "resultfile"}
11 var staticheaders = []string{"url", "redirectlocation", "position", "status_code", "content_length", "content_words", "content_lines", "content_type", "resultfile"}
1212
13 func writeCSV(config *ffuf.Config, res []Result, encode bool) error {
14
15 if(config.OutputCreateEmptyFile && (len(res) == 0)){
13 func writeCSV(filename string, config *ffuf.Config, res []ffuf.Result, encode bool) error {
14
15 if config.OutputCreateEmptyFile && (len(res) == 0) {
1616 return nil
1717 }
18
18
1919 header := make([]string, 0)
20 f, err := os.Create(config.OutputFile)
20 f, err := os.Create(filename)
2121 if err != nil {
2222 return err
2323 }
5555 return base64.StdEncoding.EncodeToString(in)
5656 }
5757
58 func toCSV(r Result) []string {
58 func toCSV(r ffuf.Result) []string {
5959 res := make([]string, 0)
6060 for _, v := range r.Input {
6161 res = append(res, string(v))
6767 res = append(res, strconv.FormatInt(r.ContentLength, 10))
6868 res = append(res, strconv.FormatInt(r.ContentWords, 10))
6969 res = append(res, strconv.FormatInt(r.ContentLines, 10))
70 res = append(res, r.ContentType)
7071 res = append(res, r.ResultFile)
7172 return res
7273 }
1111 CommandLine string
1212 Time string
1313 Keys []string
14 Results []Result
14 Results []ffuf.Result
1515 }
1616
1717 const (
7575 <th>Position</th>
7676 <th>Length</th>
7777 <th>Words</th>
78 <th>Lines</th>
78 <th>Lines</th>
79 <th>Type</th>
7980 <th>Resultfile</th>
8081 </tr>
8182 </thead>
8384 <tbody>
8485 {{range $result := .Results}}
8586 <div style="display:none">
86 |result_raw|{{ $result.StatusCode }}{{ range $keyword, $value := $result.Input }}|{{ $value | printf "%s" }}{{ end }}|{{ $result.Url }}|{{ $result.RedirectLocation }}|{{ $result.Position }}|{{ $result.ContentLength }}|{{ $result.ContentWords }}|{{ $result.ContentLines }}|
87 |result_raw|{{ $result.StatusCode }}{{ range $keyword, $value := $result.Input }}|{{ $value | printf "%s" }}{{ end }}|{{ $result.Url }}|{{ $result.RedirectLocation }}|{{ $result.Position }}|{{ $result.ContentLength }}|{{ $result.ContentWords }}|{{ $result.ContentLines }}|{{ $result.ContentType }}|
8788 </div>
8889 <tr class="result-{{ $result.StatusCode }}" style="background-color: {{$result.HTMLColor}};">
8990 <td><font color="black" class="status-code">{{ $result.StatusCode }}</font></td>
9596 <td>{{ $result.Position }}</td>
9697 <td>{{ $result.ContentLength }}</td>
9798 <td>{{ $result.ContentWords }}</td>
98 <td>{{ $result.ContentLines }}</td>
99 <td>{{ $result.ContentLines }}</td>
100 <td>{{ $result.ContentType }}</td>
99101 <td>{{ $result.ResultFile }}</td>
100102 </tr>
101103 {{ end }}
142144 )
143145
144146 // colorizeResults returns a new slice with HTMLColor attribute
145 func colorizeResults(results []Result) []Result {
146 newResults := make([]Result, 0)
147 func colorizeResults(results []ffuf.Result) []ffuf.Result {
148 newResults := make([]ffuf.Result, 0)
147149
148150 for _, r := range results {
149151 result := r
173175 return newResults
174176 }
175177
176 func writeHTML(config *ffuf.Config, results []Result) error {
177
178 if(config.OutputCreateEmptyFile && (len(results) == 0)){
178 func writeHTML(filename string, config *ffuf.Config, results []ffuf.Result) error {
179
180 if config.OutputCreateEmptyFile && (len(results) == 0) {
179181 return nil
180 }
181
182 }
183
182184 results = colorizeResults(results)
183185
184186 ti := time.Now()
195197 Keys: keywords,
196198 }
197199
198 f, err := os.Create(config.OutputFile)
200 f, err := os.Create(filename)
199201 if err != nil {
200202 return err
201203 }
88 )
99
1010 type ejsonFileOutput struct {
11 CommandLine string `json:"commandline"`
12 Time string `json:"time"`
13 Results []Result `json:"results"`
14 Config *ffuf.Config `json:"config"`
11 CommandLine string `json:"commandline"`
12 Time string `json:"time"`
13 Results []ffuf.Result `json:"results"`
14 Config *ffuf.Config `json:"config"`
1515 }
1616
1717 type JsonResult struct {
2121 ContentLength int64 `json:"length"`
2222 ContentWords int64 `json:"words"`
2323 ContentLines int64 `json:"lines"`
24 ContentType string `json:"content-type"`
2425 RedirectLocation string `json:"redirectlocation"`
2526 ResultFile string `json:"resultfile"`
2627 Url string `json:"url"`
3435 Config *ffuf.Config `json:"config"`
3536 }
3637
37 func writeEJSON(config *ffuf.Config, res []Result) error {
38
39 if(config.OutputCreateEmptyFile && (len(res) == 0)){
38 func writeEJSON(filename string, config *ffuf.Config, res []ffuf.Result) error {
39
40 if config.OutputCreateEmptyFile && (len(res) == 0) {
4041 return nil
4142 }
42
43
4344 t := time.Now()
4445 outJSON := ejsonFileOutput{
4546 CommandLine: config.CommandLine,
5152 if err != nil {
5253 return err
5354 }
54 err = ioutil.WriteFile(config.OutputFile, outBytes, 0644)
55 err = ioutil.WriteFile(filename, outBytes, 0644)
5556 if err != nil {
5657 return err
5758 }
5859 return nil
5960 }
6061
61 func writeJSON(config *ffuf.Config, res []Result) error {
62 func writeJSON(filename string, config *ffuf.Config, res []ffuf.Result) error {
6263 t := time.Now()
6364 jsonRes := make([]JsonResult, 0)
6465 for _, r := range res {
7374 ContentLength: r.ContentLength,
7475 ContentWords: r.ContentWords,
7576 ContentLines: r.ContentLines,
77 ContentType: r.ContentType,
7678 RedirectLocation: r.RedirectLocation,
7779 ResultFile: r.ResultFile,
7880 Url: r.Url,
8991 if err != nil {
9092 return err
9193 }
92 err = ioutil.WriteFile(config.OutputFile, outBytes, 0644)
94 err = ioutil.WriteFile(filename, outBytes, 0644)
9395 if err != nil {
9496 return err
9597 }
1313 Command line : ` + "`{{.CommandLine}}`" + `
1414 Time: ` + "{{ .Time }}" + `
1515
16 {{ range .Keys }}| {{ . }} {{ end }}| URL | Redirectlocation | Position | Status Code | Content Length | Content Words | Content Lines | ResultFile |
17 {{ range .Keys }}| :- {{ end }}| :-- | :--------------- | :---- | :------- | :---------- | :------------- | :------------ | :--------- |
18 {{range .Results}}{{ range $keyword, $value := .Input }}| {{ $value | printf "%s" }} {{ end }}| {{ .Url }} | {{ .RedirectLocation }} | {{ .Position }} | {{ .StatusCode }} | {{ .ContentLength }} | {{ .ContentWords }} | {{ .ContentLines }} | {{ .ResultFile }} |
16 {{ range .Keys }}| {{ . }} {{ end }}| URL | Redirectlocation | Position | Status Code | Content Length | Content Words | Content Lines | Content Type | ResultFile |
17 {{ range .Keys }}| :- {{ end }}| :-- | :--------------- | :---- | :------- | :---------- | :------------- | :------------ | :--------- | :----------- |
18 {{range .Results}}{{ range $keyword, $value := .Input }}| {{ $value | printf "%s" }} {{ end }}| {{ .Url }} | {{ .RedirectLocation }} | {{ .Position }} | {{ .StatusCode }} | {{ .ContentLength }} | {{ .ContentWords }} | {{ .ContentLines }} | {{ .ContentType }} | {{ .ResultFile }} |
1919 {{end}}` // The template format is not pretty but follows the markdown guide
2020 )
2121
22 func writeMarkdown(config *ffuf.Config, res []Result) error {
22 func writeMarkdown(filename string, config *ffuf.Config, res []ffuf.Result) error {
2323
24 if(config.OutputCreateEmptyFile && (len(res) == 0)){
24 if config.OutputCreateEmptyFile && (len(res) == 0) {
2525 return nil
2626 }
2727
3939 Keys: keywords,
4040 }
4141
42 f, err := os.Create(config.OutputFile)
42 f, err := os.Create(filename)
4343 if err != nil {
4444 return err
4545 }
66 "os"
77 "path"
88 "strconv"
9 "strings"
910 "time"
1011
1112 "github.com/ffuf/ffuf/pkg/ffuf"
2526
2627 type Stdoutput struct {
2728 config *ffuf.Config
28 Results []Result
29 }
30
31 type Result struct {
32 Input map[string][]byte `json:"input"`
33 Position int `json:"position"`
34 StatusCode int64 `json:"status"`
35 ContentLength int64 `json:"length"`
36 ContentWords int64 `json:"words"`
37 ContentLines int64 `json:"lines"`
38 RedirectLocation string `json:"redirectlocation"`
39 Url string `json:"url"`
40 ResultFile string `json:"resultfile"`
41 Host string `json:"host"`
42 HTMLColor string `json:"-"`
29 Results []ffuf.Result
4330 }
4431
4532 func NewStdoutput(conf *ffuf.Config) *Stdoutput {
4633 var outp Stdoutput
4734 outp.config = conf
48 outp.Results = []Result{}
35 outp.Results = []ffuf.Result{}
4936 return &outp
5037 }
5138
5239 func (s *Stdoutput) Banner() {
53 fmt.Fprintf(os.Stderr, "%s\n v%s\n%s\n\n", BANNER_HEADER, ffuf.VERSION, BANNER_SEP)
40 version := strings.ReplaceAll(ffuf.Version(), "<3", fmt.Sprintf("%s<3%s", ANSI_RED, ANSI_CLEAR))
41 fmt.Fprintf(os.Stderr, "%s\n v%s\n%s\n\n", BANNER_HEADER, version, BANNER_SEP)
5442 printOption([]byte("Method"), []byte(s.config.Method))
5543 printOption([]byte("URL"), []byte(s.config.Url))
5644
133121
134122 // Print matchers
135123 for _, f := range s.config.Matchers {
136 printOption([]byte("Matcher"), []byte(f.Repr()))
124 printOption([]byte("Matcher"), []byte(f.ReprVerbose()))
137125 }
138126 // Print filters
139127 for _, f := range s.config.Filters {
140 printOption([]byte("Filter"), []byte(f.Repr()))
128 printOption([]byte("Filter"), []byte(f.ReprVerbose()))
141129 }
142130 fmt.Fprintf(os.Stderr, "%s\n\n", BANNER_SEP)
131 }
132
133 // Reset resets the result slice
134 func (s *Stdoutput) Reset() {
135 s.Results = make([]ffuf.Result, 0)
136 }
137
138 // GetResults returns the result slice
139 func (s *Stdoutput) GetResults() []ffuf.Result {
140 return s.Results
141 }
142
143 // SetResults sets the result slice
144 func (s *Stdoutput) SetResults(results []ffuf.Result) {
145 s.Results = results
143146 }
144147
145148 func (s *Stdoutput) Progress(status ffuf.Progress) {
163166 dur -= mins * time.Minute
164167 secs := dur / time.Second
165168
166 fmt.Fprintf(os.Stderr, "%s:: Progress: [%d/%d] :: Job [%d/%d] :: %d req/sec :: Duration: [%d:%02d:%02d] :: Errors: %d ::", TERMINAL_CLEAR_LINE, status.ReqCount, status.ReqTotal, status.QueuePos, status.QueueTotal, reqRate, hours, mins, secs, status.ErrorCount)
169 fmt.Fprintf(os.Stderr, "%s:: Progress: [%d/%d] :: Job [%d/%d] :: %d req/sec :: Duration: [%d:%02d:%02d] :: Errors: %d ::", TERMINAL_CLEAR_LINE, status.ReqCount, status.ReqTotal, status.QueuePos, status.QueueTotal, reqRate, hours, mins, secs, status.ErrorCount)
167170 }
168171
169172 func (s *Stdoutput) Info(infostring string) {
171174 fmt.Fprintf(os.Stderr, "%s", infostring)
172175 } else {
173176 if !s.config.Colors {
174 fmt.Fprintf(os.Stderr, "%s[INFO] %s\n", TERMINAL_CLEAR_LINE, infostring)
175 } else {
176 fmt.Fprintf(os.Stderr, "%s[%sINFO%s] %s\n", TERMINAL_CLEAR_LINE, ANSI_BLUE, ANSI_CLEAR, infostring)
177 fmt.Fprintf(os.Stderr, "%s[INFO] %s\n\n", TERMINAL_CLEAR_LINE, infostring)
178 } else {
179 fmt.Fprintf(os.Stderr, "%s[%sINFO%s] %s\n\n", TERMINAL_CLEAR_LINE, ANSI_BLUE, ANSI_CLEAR, infostring)
177180 }
178181 }
179182 }
195198 fmt.Fprintf(os.Stderr, "%s", warnstring)
196199 } else {
197200 if !s.config.Colors {
198 fmt.Fprintf(os.Stderr, "%s[WARN] %s", TERMINAL_CLEAR_LINE, warnstring)
201 fmt.Fprintf(os.Stderr, "%s[WARN] %s\n", TERMINAL_CLEAR_LINE, warnstring)
199202 } else {
200203 fmt.Fprintf(os.Stderr, "%s[%sWARN%s] %s\n", TERMINAL_CLEAR_LINE, ANSI_RED, ANSI_CLEAR, warnstring)
201204 }
202205 }
203206 }
204207
205 func (s *Stdoutput) writeToAll(config *ffuf.Config, res []Result) error {
208 func (s *Stdoutput) Raw(output string) {
209 fmt.Fprintf(os.Stderr, "%s%s", TERMINAL_CLEAR_LINE, output)
210 }
211
212 func (s *Stdoutput) writeToAll(filename string, config *ffuf.Config, res []ffuf.Result) error {
206213 var err error
207214 var BaseFilename string = s.config.OutputFile
208215
209216 // Go through each type of write, adding
210217 // the suffix to each output file.
211218
212 if(config.OutputCreateEmptyFile && (len(res) == 0)){
219 if config.OutputCreateEmptyFile && (len(res) == 0) {
213220 return nil
214 }
221 }
215222
216223 s.config.OutputFile = BaseFilename + ".json"
217 err = writeJSON(s.config, s.Results)
224 err = writeJSON(filename, s.config, s.Results)
218225 if err != nil {
219226 s.Error(err.Error())
220227 }
221228
222229 s.config.OutputFile = BaseFilename + ".ejson"
223 err = writeEJSON(s.config, s.Results)
230 err = writeEJSON(filename, s.config, s.Results)
224231 if err != nil {
225232 s.Error(err.Error())
226233 }
227234
228235 s.config.OutputFile = BaseFilename + ".html"
229 err = writeHTML(s.config, s.Results)
236 err = writeHTML(filename, s.config, s.Results)
230237 if err != nil {
231238 s.Error(err.Error())
232239 }
233240
234241 s.config.OutputFile = BaseFilename + ".md"
235 err = writeMarkdown(s.config, s.Results)
242 err = writeMarkdown(filename, s.config, s.Results)
236243 if err != nil {
237244 s.Error(err.Error())
238245 }
239246
240247 s.config.OutputFile = BaseFilename + ".csv"
241 err = writeCSV(s.config, s.Results, false)
248 err = writeCSV(filename, s.config, s.Results, false)
242249 if err != nil {
243250 s.Error(err.Error())
244251 }
245252
246253 s.config.OutputFile = BaseFilename + ".ecsv"
247 err = writeCSV(s.config, s.Results, true)
254 err = writeCSV(filename, s.config, s.Results, true)
248255 if err != nil {
249256 s.Error(err.Error())
250257 }
253260
254261 }
255262
263 // SaveFile saves the current results to a file of a given type
264 func (s *Stdoutput) SaveFile(filename, format string) error {
265 var err error
266 switch format {
267 case "all":
268 err = s.writeToAll(filename, s.config, s.Results)
269 case "json":
270 err = writeJSON(filename, s.config, s.Results)
271 case "ejson":
272 err = writeEJSON(filename, s.config, s.Results)
273 case "html":
274 err = writeHTML(filename, s.config, s.Results)
275 case "md":
276 err = writeMarkdown(filename, s.config, s.Results)
277 case "csv":
278 err = writeCSV(filename, s.config, s.Results, false)
279 case "ecsv":
280 err = writeCSV(filename, s.config, s.Results, true)
281 }
282 return err
283 }
284
285 // Finalize gets run after all the ffuf jobs are completed
256286 func (s *Stdoutput) Finalize() error {
257287 var err error
258288 if s.config.OutputFile != "" {
259 if s.config.OutputFormat == "all" {
260 err = s.writeToAll(s.config, s.Results)
261 } else if s.config.OutputFormat == "json" {
262 err = writeJSON(s.config, s.Results)
263 } else if s.config.OutputFormat == "ejson" {
264 err = writeEJSON(s.config, s.Results)
265 } else if s.config.OutputFormat == "html" {
266 err = writeHTML(s.config, s.Results)
267 } else if s.config.OutputFormat == "md" {
268 err = writeMarkdown(s.config, s.Results)
269 } else if s.config.OutputFormat == "csv" {
270 err = writeCSV(s.config, s.Results, false)
271 } else if s.config.OutputFormat == "ecsv" {
272 err = writeCSV(s.config, s.Results, true)
273 }
289 err = s.SaveFile(s.config.OutputFile, s.config.OutputFormat)
274290 if err != nil {
275291 s.Error(err.Error())
276292 }
284300 if len(s.config.OutputDirectory) > 0 {
285301 resp.ResultFile = s.writeResultToFile(resp)
286302 }
303
304 inputs := make(map[string][]byte, len(resp.Request.Input))
305 for k, v := range resp.Request.Input {
306 inputs[k] = v
307 }
308 sResult := ffuf.Result{
309 Input: inputs,
310 Position: resp.Request.Position,
311 StatusCode: resp.StatusCode,
312 ContentLength: resp.ContentLength,
313 ContentWords: resp.ContentWords,
314 ContentLines: resp.ContentLines,
315 ContentType: resp.ContentType,
316 RedirectLocation: resp.GetRedirectLocation(false),
317 Url: resp.Request.Url,
318 ResultFile: resp.ResultFile,
319 Host: resp.Request.Host,
320 }
321 s.Results = append(s.Results, sResult)
287322 // Output the result
288 s.printResult(resp)
289 // Check if we need the data later
290 if s.config.OutputFile != "" {
291 // No need to store results if we're not going to use them later
292 inputs := make(map[string][]byte, len(resp.Request.Input))
293 for k, v := range resp.Request.Input {
294 inputs[k] = v
295 }
296 sResult := Result{
297 Input: inputs,
298 Position: resp.Request.Position,
299 StatusCode: resp.StatusCode,
300 ContentLength: resp.ContentLength,
301 ContentWords: resp.ContentWords,
302 ContentLines: resp.ContentLines,
303 RedirectLocation: resp.GetRedirectLocation(false),
304 Url: resp.Request.Url,
305 ResultFile: resp.ResultFile,
306 Host: resp.Request.Host,
307 }
308 s.Results = append(s.Results, sResult)
309 }
323 s.PrintResult(sResult)
310324 }
311325
312326 func (s *Stdoutput) writeResultToFile(resp ffuf.Response) string {
334348 return fileName
335349 }
336350
337 func (s *Stdoutput) printResult(resp ffuf.Response) {
351 func (s *Stdoutput) PrintResult(res ffuf.Result) {
338352 if s.config.Quiet {
339 s.resultQuiet(resp)
340 } else {
341 if len(resp.Request.Input) > 1 || s.config.Verbose || len(s.config.OutputDirectory) > 0 {
353 s.resultQuiet(res)
354 } else {
355 if len(res.Input) > 1 || s.config.Verbose || len(s.config.OutputDirectory) > 0 {
342356 // Print a multi-line result (when using multiple input keywords and wordlists)
343 s.resultMultiline(resp)
344 } else {
345 s.resultNormal(resp)
346 }
347 }
348 }
349
350 func (s *Stdoutput) prepareInputsOneLine(resp ffuf.Response) string {
357 s.resultMultiline(res)
358 } else {
359 s.resultNormal(res)
360 }
361 }
362 }
363
364 func (s *Stdoutput) prepareInputsOneLine(res ffuf.Result) string {
351365 inputs := ""
352 if len(resp.Request.Input) > 1 {
353 for k, v := range resp.Request.Input {
366 if len(res.Input) > 1 {
367 for k, v := range res.Input {
354368 if inSlice(k, s.config.CommandKeywords) {
355369 // If we're using external command for input, display the position instead of input
356 inputs = fmt.Sprintf("%s%s : %s ", inputs, k, strconv.Itoa(resp.Request.Position))
370 inputs = fmt.Sprintf("%s%s : %s ", inputs, k, strconv.Itoa(res.Position))
357371 } else {
358372 inputs = fmt.Sprintf("%s%s : %s ", inputs, k, v)
359373 }
360374 }
361375 } else {
362 for k, v := range resp.Request.Input {
376 for k, v := range res.Input {
363377 if inSlice(k, s.config.CommandKeywords) {
364378 // If we're using external command for input, display the position instead of input
365 inputs = strconv.Itoa(resp.Request.Position)
379 inputs = strconv.Itoa(res.Position)
366380 } else {
367381 inputs = string(v)
368382 }
371385 return inputs
372386 }
373387
374 func (s *Stdoutput) resultQuiet(resp ffuf.Response) {
375 fmt.Println(s.prepareInputsOneLine(resp))
376 }
377
378 func (s *Stdoutput) resultMultiline(resp ffuf.Response) {
388 func (s *Stdoutput) resultQuiet(res ffuf.Result) {
389 fmt.Println(s.prepareInputsOneLine(res))
390 }
391
392 func (s *Stdoutput) resultMultiline(res ffuf.Result) {
379393 var res_hdr, res_str string
380394 res_str = "%s%s * %s: %s\n"
381 res_hdr = fmt.Sprintf("%s[Status: %d, Size: %d, Words: %d, Lines: %d]", TERMINAL_CLEAR_LINE, resp.StatusCode, resp.ContentLength, resp.ContentWords, resp.ContentLines)
382 res_hdr = s.colorize(res_hdr, resp.StatusCode)
395 res_hdr = fmt.Sprintf("%s[Status: %d, Size: %d, Words: %d, Lines: %d]", TERMINAL_CLEAR_LINE, res.StatusCode, res.ContentLength, res.ContentWords, res.ContentLines)
396 res_hdr = s.colorize(res_hdr, res.StatusCode)
383397 reslines := ""
384398 if s.config.Verbose {
385 reslines = fmt.Sprintf("%s%s| URL | %s\n", reslines, TERMINAL_CLEAR_LINE, resp.Request.Url)
386 redirectLocation := resp.GetRedirectLocation(false)
399 reslines = fmt.Sprintf("%s%s| URL | %s\n", reslines, TERMINAL_CLEAR_LINE, res.Url)
400 redirectLocation := res.RedirectLocation
387401 if redirectLocation != "" {
388402 reslines = fmt.Sprintf("%s%s| --> | %s\n", reslines, TERMINAL_CLEAR_LINE, redirectLocation)
389403 }
390404 }
391 if resp.ResultFile != "" {
392 reslines = fmt.Sprintf("%s%s| RES | %s\n", reslines, TERMINAL_CLEAR_LINE, resp.ResultFile)
393 }
394 for k, v := range resp.Request.Input {
405 if res.ResultFile != "" {
406 reslines = fmt.Sprintf("%s%s| RES | %s\n", reslines, TERMINAL_CLEAR_LINE, res.ResultFile)
407 }
408 for k, v := range res.Input {
395409 if inSlice(k, s.config.CommandKeywords) {
396410 // If we're using external command for input, display the position instead of input
397 reslines = fmt.Sprintf(res_str, reslines, TERMINAL_CLEAR_LINE, k, strconv.Itoa(resp.Request.Position))
411 reslines = fmt.Sprintf(res_str, reslines, TERMINAL_CLEAR_LINE, k, strconv.Itoa(res.Position))
398412 } else {
399413 // Wordlist input
400414 reslines = fmt.Sprintf(res_str, reslines, TERMINAL_CLEAR_LINE, k, v)
403417 fmt.Printf("%s\n%s\n", res_hdr, reslines)
404418 }
405419
406 func (s *Stdoutput) resultNormal(resp ffuf.Response) {
407 res := fmt.Sprintf("%s%-23s [Status: %s, Size: %d, Words: %d, Lines: %d]", TERMINAL_CLEAR_LINE, s.prepareInputsOneLine(resp), s.colorize(fmt.Sprintf("%d", resp.StatusCode), resp.StatusCode), resp.ContentLength, resp.ContentWords, resp.ContentLines)
408 fmt.Println(res)
420 func (s *Stdoutput) resultNormal(res ffuf.Result) {
421 resnormal := fmt.Sprintf("%s%-23s [Status: %s, Size: %d, Words: %d, Lines: %d]", TERMINAL_CLEAR_LINE, s.prepareInputsOneLine(res), s.colorize(fmt.Sprintf("%d", res.StatusCode), res.StatusCode), res.ContentLength, res.ContentWords, res.ContentLines)
422 fmt.Println(resnormal)
409423 }
410424
411425 func (s *Stdoutput) colorize(input string, status int64) string {
103103
104104 // set default User-Agent header if not present
105105 if _, ok := req.Headers["User-Agent"]; !ok {
106 req.Headers["User-Agent"] = fmt.Sprintf("%s v%s", "Fuzz Faster U Fool", ffuf.VERSION)
106 req.Headers["User-Agent"] = fmt.Sprintf("%s v%s", "Fuzz Faster U Fool", ffuf.Version())
107107 }
108108
109109 // Handle Go http.Request special cases