From c914aa8aa70e2b9fcba36d29365e531295e9227b Mon Sep 17 00:00:00 2001 From: h0tak88r Date: Wed, 4 Dec 2024 18:51:55 +0200 Subject: [PATCH 1/6] new flag for unique results --- README.md | 1 + b29325839bbc8a11ff17152201234d16 | 3 + main.go | 10 + pkg/ffuf/config.go | 1 + pkg/ffuf/optionsparser.go | 2 + pkg/filter/filter.go | 3 + pkg/filter/uniquesize.go | 54 ++ pkg/output/stdout.go | 41 +- pkg/output/uniqueresults.go | 20 + quick_fuzz.txt | 928 +++++++++++++++++++++++++++++++ 10 files changed, 1044 insertions(+), 19 deletions(-) create mode 100644 b29325839bbc8a11ff17152201234d16 create mode 100644 pkg/filter/uniquesize.go create mode 100644 pkg/output/uniqueresults.go create mode 100644 quick_fuzz.txt diff --git a/README.md b/README.md index e9259c7e..602bacc8 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,7 @@ GENERAL OPTIONS: -search Search for a FFUFHASH payload from ffuf history -sf Stop when > 95% of responses return 403 Forbidden (default: false) -t Number of concurrent threads. (default: 40) + -unique Only show the first occurrence of responses with same size (default: false) -v Verbose output, printing full URL and redirect location (if any) with the results. (default: false) MATCHER OPTIONS: diff --git a/b29325839bbc8a11ff17152201234d16 b/b29325839bbc8a11ff17152201234d16 new file mode 100644 index 00000000..5094bcee --- /dev/null +++ b/b29325839bbc8a11ff17152201234d16 @@ -0,0 +1,3 @@ + +---- ↑ Request ---- Response ↓ ---- + diff --git a/main.go b/main.go index 64b4711f..4e0fecce 100644 --- a/main.go +++ b/main.go @@ -76,6 +76,7 @@ func ParseFlags(opts *ffuf.ConfigOptions) *ffuf.ConfigOptions { flag.BoolVar(&opts.General.StopOnAll, "sa", opts.General.StopOnAll, "Stop on all error cases. Implies -sf and -se.") flag.BoolVar(&opts.General.StopOnErrors, "se", opts.General.StopOnErrors, "Stop on spurious errors") flag.BoolVar(&opts.General.Verbose, "v", opts.General.Verbose, "Verbose output, printing full URL and redirect location (if any) with the results.") + flag.BoolVar(&opts.General.UniqueSizes, "unique", opts.General.UniqueSizes, "Only show unique response sizes in output") flag.BoolVar(&opts.HTTP.FollowRedirects, "r", opts.HTTP.FollowRedirects, "Follow redirects") flag.BoolVar(&opts.HTTP.IgnoreBody, "ignore-body", opts.HTTP.IgnoreBody, "Do not fetch the response content.") flag.BoolVar(&opts.HTTP.Raw, "raw", opts.HTTP.Raw, "Do not encode URI") @@ -299,6 +300,15 @@ func prepareJob(conf *ffuf.Config) (*ffuf.Job, error) { func SetupFilters(parseOpts *ffuf.ConfigOptions, conf *ffuf.Config) error { errs := ffuf.NewMultierror() conf.MatcherManager = filter.NewMatcherManager() + + // If -unique flag is set, add the unique size filter + if parseOpts.General.UniqueSizes { + err := conf.MatcherManager.AddFilter("uniquesize", "", false) + if err != nil { + return fmt.Errorf("could not setup unique size filter: %s", err) + } + } + // If any other matcher is set, ignore -mc default value matcherSet := false statusSet := false diff --git a/pkg/ffuf/config.go b/pkg/ffuf/config.go index 5680e5cc..12eb97ce 100644 --- a/pkg/ffuf/config.go +++ b/pkg/ffuf/config.go @@ -62,6 +62,7 @@ type Config struct { Threads int `json:"threads"` Timeout int `json:"timeout"` Url string `json:"url"` + UniqueSizes bool `json:"unique_sizes"` Verbose bool `json:"verbose"` Wordlists []string `json:"wordlists"` Http2 bool `json:"http2"` diff --git a/pkg/ffuf/optionsparser.go b/pkg/ffuf/optionsparser.go index e4a34e2a..9175d6fe 100644 --- a/pkg/ffuf/optionsparser.go +++ b/pkg/ffuf/optionsparser.go @@ -69,6 +69,7 @@ type GeneralOptions struct { StopOnAll bool `json:"stop_on_all"` StopOnErrors bool `json:"stop_on_errors"` Threads int `json:"threads"` + UniqueSizes bool `json:"unique_sizes"` Verbose bool `json:"verbose"` } @@ -143,6 +144,7 @@ func NewConfigOptions() *ConfigOptions { c.General.StopOnAll = false c.General.StopOnErrors = false c.General.Threads = 40 + c.General.UniqueSizes = false c.General.Verbose = false c.HTTP.Data = "" c.HTTP.FollowRedirects = false diff --git a/pkg/filter/filter.go b/pkg/filter/filter.go index 8b7f29fb..17a45287 100644 --- a/pkg/filter/filter.go +++ b/pkg/filter/filter.go @@ -59,6 +59,9 @@ func NewFilterByName(name string, value string) (ffuf.FilterProvider, error) { if name == "size" { return NewSizeFilter(value) } + if name == "uniquesize" { + return NewUniqueSizeFilter(), nil + } if name == "word" { return NewWordFilter(value) } diff --git a/pkg/filter/uniquesize.go b/pkg/filter/uniquesize.go new file mode 100644 index 00000000..02df7fe6 --- /dev/null +++ b/pkg/filter/uniquesize.go @@ -0,0 +1,54 @@ +package filter + +import ( + "sync" + + "github.com/ffuf/ffuf/v2/pkg/ffuf" +) + +type UniqueSizeFilter struct { + seenSizes map[int64]bool + mutex sync.Mutex + firstOccurrence map[int64]bool +} + +func NewUniqueSizeFilter() ffuf.FilterProvider { + return &UniqueSizeFilter{ + seenSizes: make(map[int64]bool), + firstOccurrence: make(map[int64]bool), + } +} + +func (f *UniqueSizeFilter) Filter(response *ffuf.Response) (bool, error) { + f.mutex.Lock() + defer f.mutex.Unlock() + + size := response.ContentLength + + if !f.seenSizes[size] { + // First time seeing this size + f.seenSizes[size] = true + f.firstOccurrence[size] = true + return false, nil + } + + // If we've seen this size before, only allow it through if it's the first occurrence + if f.firstOccurrence[size] { + f.firstOccurrence[size] = false + return false, nil + } + + return true, nil +} + +func (f *UniqueSizeFilter) Repr() string { + return "Unique response sizes only" +} + +func (f *UniqueSizeFilter) ReprVerbose() string { + return "Unique response sizes only" +} + +func (f *UniqueSizeFilter) MarshalJSON() ([]byte, error) { + return []byte(`{"type":"uniquesize"}`), nil +} diff --git a/pkg/output/stdout.go b/pkg/output/stdout.go index 44dc9010..421f9cb9 100644 --- a/pkg/output/stdout.go +++ b/pkg/output/stdout.go @@ -147,8 +147,12 @@ func (s *Stdoutput) Reset() { // Cycle moves the CurrentResults to Results and resets the results slice func (s *Stdoutput) Cycle() { - s.Results = append(s.Results, s.CurrentResults...) - s.Reset() + if s.config.UniqueSizes { + s.Results = append(s.Results, FilterUniqueResults(s.CurrentResults)...) + } else { + s.Results = append(s.Results, s.CurrentResults...) + } + s.CurrentResults = make([]ffuf.Result, 0) } // GetResults returns the result slice @@ -202,7 +206,7 @@ func (s *Stdoutput) Error(errstring string) { fmt.Fprintf(os.Stderr, "%s", errstring) } else { if !s.config.Colors { - fmt.Fprintf(os.Stderr, "%s[ERR] %s\n", TERMINAL_CLEAR_LINE, errstring) + fmt.Fprintf(os.Stderr, "%s[ERR] %ss\n", TERMINAL_CLEAR_LINE, errstring) } else { fmt.Fprintf(os.Stderr, "%s[%sERR%s] %s\n", TERMINAL_CLEAR_LINE, ANSI_RED, ANSI_CLEAR, errstring) } @@ -314,17 +318,8 @@ func (s *Stdoutput) Finalize() error { } func (s *Stdoutput) Result(resp ffuf.Response) { - // Do we want to write request and response to a file - if len(s.config.OutputDirectory) > 0 { - resp.ResultFile = s.writeResultToFile(resp) - } - - inputs := make(map[string][]byte, len(resp.Request.Input)) - for k, v := range resp.Request.Input { - inputs[k] = v - } - sResult := ffuf.Result{ - Input: inputs, + res := ffuf.Result{ + Input: resp.Request.Input, Position: resp.Request.Position, StatusCode: resp.StatusCode, ContentLength: resp.ContentLength, @@ -332,15 +327,23 @@ func (s *Stdoutput) Result(resp ffuf.Response) { ContentLines: resp.ContentLines, ContentType: resp.ContentType, RedirectLocation: resp.GetRedirectLocation(false), - ScraperData: resp.ScraperData, Url: resp.Request.Url, Duration: resp.Time, - ResultFile: resp.ResultFile, + ResultFile: s.writeResultToFile(resp), Host: resp.Request.Host, } - s.CurrentResults = append(s.CurrentResults, sResult) - // Output the result - s.PrintResult(sResult) + + if s.config.UniqueSizes { + // For unique sizes, we'll print immediately if it's a new size + newResult := FilterUniqueResults(append(s.CurrentResults, res)) + if len(newResult) > len(s.CurrentResults) { + // This is a new unique size, print it + s.PrintResult(res) + } + } else { + s.PrintResult(res) + } + s.CurrentResults = append(s.CurrentResults, res) } func (s *Stdoutput) writeResultToFile(resp ffuf.Response) string { diff --git a/pkg/output/uniqueresults.go b/pkg/output/uniqueresults.go new file mode 100644 index 00000000..993efb82 --- /dev/null +++ b/pkg/output/uniqueresults.go @@ -0,0 +1,20 @@ +package output + +import ( + "github.com/ffuf/ffuf/v2/pkg/ffuf" +) + +// FilterUniqueResults filters out results with duplicate sizes, keeping only the first occurrence +func FilterUniqueResults(results []ffuf.Result) []ffuf.Result { + seenSizes := make(map[int64]bool) + uniqueResults := make([]ffuf.Result, 0) + + for _, result := range results { + if !seenSizes[result.ContentLength] { + seenSizes[result.ContentLength] = true + uniqueResults = append(uniqueResults, result) + } + } + + return uniqueResults +} diff --git a/quick_fuzz.txt b/quick_fuzz.txt new file mode 100644 index 00000000..8340b7f2 --- /dev/null +++ b/quick_fuzz.txt @@ -0,0 +1,928 @@ +.bash_history +.bashrc +.cache +.config +.cvs +.cvsignore +.forward +.geschiedenis +.history +.hta +.htaccess +.htpasswd +.lijst +.listing +.listings +.mysql_history +.passwd +.perf +.profiel +.profile +.rhosts +.sh_history +.ssh +.subversie +.subversion +.svn +.swf +.vooruit +.web +.7z +.AppleDB +.AppleDesktop +.AppleDouble +.CSV +.CVS +.DS_Store +.FBCIndex +.HTF +.JustCode +.LOCAL +.LSOverride +.Python +.RData +.Rapp.history +.Rhistory +.Rproj.user +.SyncID +.SyncIgnore +.Trash +.Trashes +.access +.addressbook +.adm +.admin +.adminer.php.swp +.apdisk +.apt_generated +.architect +.asmx +.aws +.axoCover +.babelrc +.backup +.bak +.bash_logout +.bash_profile +.bower-cache +.bower-cachez +.bower-registry +.bower-tmp +.bower.json +.build +.buildpacks +.buildpath +.builds +.bundle +.byebug_history +.bz2 +.bzr +.c9 +.c9revisions +.cabal-sandbox +.canna +.capistrano +.cask +.cc-ban.txt +.cc-ban.txt.bak +.cfg +.cfignore +.checkstyle +.circleci +.classpath +.cobalt +.codeclimate.yml +.codeintel +.codekit-cache +.codio +.coffee_history +.compile +.composer +.composer/composer.json +.concrete +.conf +.config.php.swp +.configuration.php.swp +.consulo +.contracts +.coq-native +.core +.coverage +.coveralls.yml +.cpan +.cpanel +.cpcache +.cproject +.cr +.csdp.cache +.cshrc +.csv +.dart_tool +.dat +.db +.db.xml +.db.yaml +.deployignore +.dev +.directory +.dockerignore +.drone.yml +.dub +.dump +.eclipse +.editorconfig +.eggs +.elasticbeanstalk +.elb +.elc +.emacs +.emacs.desktop +.emacs.desktop.lock +.empty-folder +.env +.env-example +.env.bak +.env.dev +.env.dev.local +.env.development.local +.env.development.sample +.env.docker +.env.docker.dev +.env.example +.env.live +.env.local +.env.php +.env.prod +.env.prod.local +.env.production.local +.env.sample.php +.env.stage +.env.test.sample +.environment +.env~ +.error_log +.esformatter +.eslintcache +.eslintignore +.eslintrc +.espressostorage +.eunit +.external +.externalNativeBuild +.externalToolBuilders +.fake +.fetch +.fhp +.filemgr-tmp +.filezilla +.fishsrv.pl +.flac +.flowconfig +.fontconfig +.fontcustom-manifest.json +.ftp +.ftp-access +.ftppass +.ftpquota +.gem +.gfclient +.git +.git-credentials +.git-rewrite +.git2 +.git_release +.gitattributes +.gitconfig +.gitignore +.gitignore.swp +.gitignore.swpf +.gitignore_global +.gitignore~ +.gitk +.gitkeep +.gitlab +.gitlab-ci.yml +.gitmodules +.gitreview +.gradle +.gradletasknamecache +.grunt +.gtkrc +.guile_history +.gwt +.gwt-tmp +.gz +.hash +.hg +.hgignore +.hgignore.global +.hgrc +.histfile +.hpc +.hsenv +.ht_wsr.txt +.htaccess-dev +.htaccess-local +.htaccess-marco +.htaccess.BAK +.htaccess.bak +.htaccess.bak1 +.htaccess.old +.htaccess.orig +.htaccess.sample +.htaccess.save +.htaccess.txt +.htaccessBAK +.htaccessOLD +.htaccessOLD2 +.htaccess_extra +.htaccess_orig +.htaccess_sc +.htaccess~ +.htgroup +.htpasswd-old +.htpasswd_test +.htpasswds +.httr-oauth +.htusers +.hypothesis +.idea +.idea0 +.idea_modules +.ignore +.ignored +.import +.influx_history +.ini +.inst +.install +.installed.cfg +.ipynb_checkpoints +.jekyll-cache +.jekyll-metadata +.jestrc +.joe_state +.jpilot +.jrubyrc +.jscsrc +.jsfmtrc +.jshintignore +.jshintrc +.jsp +.kde +.keep +.key +.keys.yml +.kitchen +.kitchen.local.yml +.kitchen.yml +.komodotools +.ksh_history +.last_cover_stats +.lein-deps-sum +.lein-failures +.lein-plugins +.lein-repl-history +.lesshst +.lia.cache +.libs +.lighttpd.conf +.loadpath +.local +.localcache +.localeapp +.localsettings.php.swp +.lock-wscript +.log +.log.txt +.login +.login_conf +.lynx_cookies +.lz +.magentointel-cache +.mail_aliases +.mailrc +.maintenance +.maintenance2 +.mc +.members +.memdump +.mergesources.yml +.merlin +.meta +.metadata +.metrics +.modgit +.modman +.modules +.mono +.mr.developer.cfg +.msi +.mtj.tmp +.mvn +.mweval_history +.mwsql_history +.mypy_cache +.nano_history +.navigation +.nbproject +.netrc +.netrwhist +.next +.nia.cache +.nlia.cache +.node_repl_history +.nodelete +.npm +.npmignore +.npmrc +.nra.cache +.nrepl-port +.nsconfig +.nsf +.ntvs_analysis.dat +.nuget +.nyc_output +.old +.oldsnippets +.oldstatic +.org-id-locations +.ost +.packages +.paket +.pass +.passes +.password +.passwords +.passwrd +.patches +.pdf +.pem +.pgadmin3 +.pgpass +.pgsql_history +.photon +.php-ini +.php-version +.php_history +.phperr.log +.phpintel +.phpstorm.meta.php +.phptidy-cache +.phpversion +.pki +.placeholder +.playground +.powenv +.procmailrc +.project +.project.xml +.projectOptions +.properties +.psci +.psci_modules +.psql_history +.psqlrc +.pst +.pub +.pwd +.pydevproject +.pytest_cache +.python-eggs +.python-history +.python-version +.qmake.cache +.qmake.stash +.qqestore +.rar +.raw +.rbtp +.rdsTempFiles +.rebar +.rediscli_history +.redmine-cli +.reek +.remote-sync.json +.repl_history +.revision +.rhost +.robots.txt +.rocketeer +.ropeproject +.rspec +.rsync-filter +.rsync_cache +.rtlcssrc +.rubocop.yml +.rubocop_todo.yml +.ruby-gemset +.ruby-version +.rvmrc +.s3backupstatus +.sass-cache +.scala_history +.sconsign.dblite +.scrapy +.scrutinizer.yml +.selected_editor +.sencha +.settings +.settings.php.swp +.settings/rules.json?auth=FIREBASE_SECRET +.sh +.shrc +.simplecov +.sln +.smileys +.smushit-status +.spamassassin +.spyderproject +.spyproject +.sql +.sql.7z +.sql.bz2 +.sql.gz +.sql.lz +.sql.rar +.sql.tar.gz +.sql.tar.z +.sql.xz +.sql.z +.sql.zip +.sqlite +.sqlite_history +.sqlitedb +.src +.ssh.asp +.ssh.php +.ssh/id_dsa +.ssh/id_rsa +.st_cache +.stack-work +.stylelintrc +.sublime-gulp.cache +.sublime-project +.sublime-workspace +.sucuriquarantine +.sunw +.svn/entries +.svn/prop-base/ +.svn/text-base/ +.svnignore +.sw +.swo +.swp +.synthquota +.system +.tags +.tags_sorted_by_file +.tar +.tar.bz2 +.tar.gz +.tar.z +.tconn +.temp +.texpadtmp +.tfignore +.tgitconfig +.thumbs +.tmp +.tmp_versions +.tmproj +.tox +.transients_purge.log +.travis.yml +.tx +.txt +.user.ini +.users +.vacation.cache +.vagrant +.venv +.version +.vgextensions +.viminfo +.vimrc +.vs +.web-server-pid +.webassets-cache +.well-known/acme-challenge/%3C%3fxml%20version=%221.0%22%3f%3E%3Cx:script%20xmlns:x=%22http://www.w3.org/1999/xhtml%22%3Ealert%28document.domain%26%23x29%3B%3C/x:script%3E +.well-known/openid-configuration +.well-known/security.txt +.workspace +.wp-config.php.swp +.wsdl +.www_acl +.wwwacl +.xz +.yardoc +.yardopts +.yarn-integrity +.yo-rc.json +.z +.zeus.sock +.zfs +.zip +.zsh_history +.zshrc +.tmux.conf +.inputrc +.dircolors +.screenrc +.bash_aliases +.nanorc +.wgetrc +.curlrc +.xinitrc +.xsession +.xsession-errors +.fonts.conf +.gtkrc-2.0 +.gtkrc-3.0 +.tern-project +.ackrc +.yarnrc +.gemrc +.irbrc +.pryrc +.railsrc +.slate +.tern-config +.tm_properties +.kermrc +.mtoolsrc +.xmodmap +.xscreensaver +.fehbg +.mpdconf +.ncmpcpp +.conkyrc +.asoundrc +.lesskey +.msmtprc +/admin +/admin/config +/admin/database +/admin/debug +/admin/settings +/admin/users +/admin/login +/admin/logout +/admin/register +/config +/config/database +/config/settings +/debug +/debug/config +/debug/settings +/debug/info +/debug/logs +/logs +/logs/debug +/logs/errors +/logs/access +/backup +/backup/database +/backup/files +/backup/config +/backup/logs +/private +/private/config +/private/files +/private/data +/private/logs +/secret +/secret/config +/secret/data +/secret/files +/secret/logs +/test +/test/config +/test/database +/test/logs +/test/users +/api/keys +/api/credentials +/api/config +/api/debug +/api/test +/tmp +/temp +/uploads +/files +/api/v1/users +/api/v1/users/:id +/api/v1/users/:id/posts +/api/v1/users/:id/comments +/api/v1/auth/login +/api/v1/auth/logout +/api/v1/auth/register +/api/v1/auth/refresh +/api/v1/posts +/api/v1/posts/:id +/api/v1/posts/:id/comments +/api/v1/comments +/api/v1/comments/:id +/api/v1/profiles +/api/v1/profiles/:id +/api/v1/profiles/:id/followers +/api/v1/profiles/:id/following +/api/v1/notifications +/api/v1/notifications/:id +/api/v1/settings +/api/v1/settings/privacy +/api/v1/settings/security +/api/v1/files +/api/v1/files/:id +/api/v1/uploads +/api/v1/uploads/:id +/api/v1/messages +/api/v1/messages/:id +/api/v1/chats +/api/v1/chats/:id +/api/v1/chats/:id/messages +/api/v1/products +/api/v1/products/:id +/api/v1/categories +/api/v1/categories/:id +/api/v1/orders +/api/v1/orders/:id +/api/v1/carts +/api/v1/carts/:id +/api/v1/payments +/api/v1/payments/:id +/api/v1/shipping +/api/v1/shipping/:id +/api/v1/inventory +/api/v1/inventory/:id +/api/v1/reviews +/api/v1/reviews/:id +/api/v1/tags +/api/v1/tags/:id +/api/v1/search +/api/v1/admin/config +/api/v1/admin/database +/api/v1/admin/logs +/api/v1/admin/users +/api/v1/admin/stats +/api/v1/debug/config +/api/v1/debug/info +/api/v1/debug/logs +/api/v1/config +/api/v1/config/database +/api/v1/config/settings +/api/v1/config/keys +/api/v1/config/secrets +/api/v1/internal +/api/v1/internal/config +/api/v1/internal/database +/api/v1/internal/logs +/api/v1/internal/users +/api/v1/private/config +/api/v1/private/database +/api/v1/private/logs +/api/v1/private/users +/api/v1/private/data +/api/v1/secret +/api/v1/secret/config +/api/v1/secret/database +/api/v1/secret/logs +/api/v1/secret/users +/api/v1/test/config +/api/v1/test/database +/api/v1/test/logs +/api/v1/test/users +/api/v1/backup +/api/v1/backup/config +/api/v1/backup/database +/api/v1/backup/logs +/api/v1/backup/files +/api/v1/logs/debug +/api/v1/logs/errors +/api/v1/logs/access +/api/v1/logs/system +/api/v1/env +/api/v1/env/settings +/api/v1/env/variables +/api/v1/tmp +/api/v1/temp +/api/v1/keys +/api/v1/credentials +/admin/logs +/admin/backup +/config/database.yml +/config/settings.yml +/config/secrets.yml +/config/initializers +/config/environment.rb +/logs/development.log +/logs/production.log +/logs/error.log +/logs/access.log +/backup/db_backup.sql +/backup/site_backup.zip +/tmp/cache +/tmp/sessions +/tmp/uploads +/private/backup +/private/database +/secret/database +/secret/backup +/internal/config +/internal/logs +/internal/backup +/internal/database +/debug/vars +/env +/env/settings +/env/variables +/api/secrets +/api/logs +/api/backup +/.env +/.htaccess +/.git +/docker-compose.yml +/Dockerfile +/config/secrets +/config/config.yml +/config/config.json +/logs/debug.log +/logs/system.log +/logs/server.log +/backup/config_backup.tar.gz +/internal +/api +/.git/config +/.gitignore +/web.config +/META-INF +/WEB-INF +/phpinfo.php +/server-status +/server-info +/config.php +/config.inc.php +/config.json +/config.xml +/wp-config.php +/app/config +/app/logs +/app/secrets +/app/database +/app/backup +/app/config/config.yml +/app/config/config.json +/app/config/database.yml +/app/config/settings.yml +/app/config/secrets.yml +/admin.php +/admin/index.php +/administrator +/administrator/index.php +/administrator/admin.php +/backup.tar.gz +/backup.zip +/config.bak +/admin_panel +/admin_console +/admin_dashboard +/admin_area +/admin_tools +/admin_scripts +/admin_controls +/config_admin +/config_backup +/config_cache +/config_db +/config_dev +/config_env +/config_local +/config_prod +/config_prod.yml +/config_prod.json +/config_dev.yml +/config_dev.json +/config_test +/config_test.yml +/config_test.json +/log +/log/development +/log/production +/log/error +/log/access +/log/debug +/log/system +/log/server +/log/application +/backup_files +/backup_logs +/backup_db +/backup_sql +/tmp_files +/tmp_logs +/tmp_cache +/tmp_sessions +/private_files +/private_logs +/private_backup +/private_db +/secret_files +/secret_logs +/secret_backup +/secret_db +/internal_files +/internal_logs +/internal_backup +/internal_db +/debug_files +/debug_logs +/debug_backup +/debug_db +/env_settings +/env_variables +/env_config +/env_backup +/env_secrets +/api_keys +/api_tokens +/api_secrets +/api_config +/api_logs +/api_backup +/api_env +/.bash_history +/.bash_profile +/.bash_logout +/.profile +/.zshrc +/.zshenv +/.cshrc +/.tcshrc +/.login +/.Xauthority +/.Xresources +/.xsession +/.xinitrc +/.npmrc +/.yarnrc +/.config +/.config/Code +/.config/code-oss +/.config/google-chrome +/.config/chromium +/dockerfile_dev +/dockerfile_prod +/dockerfile_test +/docker/docker-compose.prod.yml +/docker/docker-compose.dev.yml +/docker/docker-compose.override.yml +/docker/docker-compose.test.yml +/docker/.env +/META-INF/context.xml +/WEB-INF/web.xml +/WEB-INF/classes +/WEB-INF/lib +/.eslintignore +/admin/upgrade.txt +/download?MaterialID=tietoturvaopas +/settings/admin +/schema_diff/previous_release.graphqls +/fixtures/schema_diff/previous_release.graphqls +/webapi/tests/fixtures/schema_diff/previous_release.graphqls +/totara/webapi/tests/fixtures/schema_diff/previous_release.graphqls +/admin/cron.php +/php_info.php +ui/v2/appvue.js +/OA_HTML/bin/sqlnet.log +/gateway/assets/ +/sandbox/wbtstatuscheck/application.log +/sandbox/ip.php +/database.sql +/dump.sql +/archive.zip +/playground.xcworkspace +/api/notifications +/api/payments +/_wpeprivate/config.json +/config/login-config.js +login-config.js +/.env.example +database.create.json +/SiteAdmin +release.tar.gz +local_settings.py +defaults.env +password_control +.github/workflows/puch.yml +workflows/puch.yml +puch.yml +info.php +/MafData/ +Pagos.tar.gz +/WEB-INF/classes/argo.properties +classes/argo.properties +argo.properties +/package.json +/package-lock.json +/wp-login.php?action=register \ No newline at end of file From 8f76a58382ccd63290d760fb5f61ec9a45912d0d Mon Sep 17 00:00:00 2001 From: h0tak88r Date: Wed, 4 Dec 2024 19:22:32 +0200 Subject: [PATCH 2/6] Fix module path in go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 390031ec..2689cb35 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/ffuf/ffuf/v2 +module github.com/cyinnove/ffuf go 1.17 From 68876a02142c941034ba8ee55aeb368d1159f9c0 Mon Sep 17 00:00:00 2001 From: h0tak88r Date: Wed, 4 Dec 2024 19:37:06 +0200 Subject: [PATCH 3/6] fixed the issue in go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 2689cb35..390031ec 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/cyinnove/ffuf +module github.com/ffuf/ffuf/v2 go 1.17 From e4762f75ef73da02cfad8969ec63c8757cef3fe7 Mon Sep 17 00:00:00 2001 From: h0tak88r Date: Wed, 4 Dec 2024 19:43:57 +0200 Subject: [PATCH 4/6] docs: update README with unique flag feature and fork information --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 602bacc8..d27b7fc4 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,9 @@ A fast web fuzzer written in Go. - [Download](https://github.com/ffuf/ffuf/releases/latest) a prebuilt binary from [releases page](https://github.com/ffuf/ffuf/releases/latest), unpack and run! + _or_ +- To use the enhanced version with unique response filtering feature, download from [cyinnove's fork](https://github.com/cyinnove/ffuf/releases/tag/v1.0.0) + _or_ - If you are on macOS with [homebrew](https://brew.sh), ffuf can be installed with: `brew install ffuf` @@ -43,6 +46,16 @@ Michael Skelton ([@codingo](https://github.com/codingo)). You can also practise your ffuf scans against a live host with different lessons and use cases either locally by using the docker container https://github.com/adamtlangley/ffufme or against the live hosted version at http://ffuf.me created by Adam Langley [@adamtlangley](https://twitter.com/adamtlangley). +### Filtering Duplicate Response Sizes + +Using the `-unique` flag, you can filter out responses with duplicate sizes, showing only the first occurrence of each unique size. This is useful for reducing noise and identifying distinct responses: + +```bash +ffuf -w wordlist.txt -u https://example.org/FUZZ -unique +``` + +This will only show responses with unique content lengths, helping you focus on potentially interesting endpoints. + ### Typical directory discovery [![asciicast](https://asciinema.org/a/211350.png)](https://asciinema.org/a/211350) From cb545452b25abec1db94e4841202fba777efa1af Mon Sep 17 00:00:00 2001 From: h0tak88r Date: Thu, 5 Dec 2024 03:02:05 +0200 Subject: [PATCH 5/6] Fix -unique flag to show first URL for each response size Previously, the -unique flag would filter out all URLs with duplicate response sizes, showing no results when all responses had the same size. Now it keeps the first URL encountered for each unique response size, ensuring at least one example is shown for each size. --- help.go | 2 +- pkg/filter/uniquesize.go | 19 +++++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/help.go b/help.go index a2f0c2a8..47e820c0 100644 --- a/help.go +++ b/help.go @@ -61,7 +61,7 @@ func Usage() { Description: "", Flags: make([]UsageFlag, 0), Hidden: false, - ExpectedFlags: []string{"ac", "acc", "ack", "ach", "acs", "c", "config", "json", "maxtime", "maxtime-job", "noninteractive", "p", "rate", "scraperfile", "scrapers", "search", "s", "sa", "se", "sf", "t", "v", "V"}, + ExpectedFlags: []string{"ac", "acc", "ack", "ach", "acs", "c", "config", "json", "maxtime", "maxtime-job", "noninteractive", "p", "rate", "scraperfile", "scrapers", "search", "s", "sa", "se", "sf", "t", "unique", "v", "V"}, } u_compat := UsageSection{ Name: "COMPATIBILITY OPTIONS", diff --git a/pkg/filter/uniquesize.go b/pkg/filter/uniquesize.go index 02df7fe6..e85bcb39 100644 --- a/pkg/filter/uniquesize.go +++ b/pkg/filter/uniquesize.go @@ -7,15 +7,13 @@ import ( ) type UniqueSizeFilter struct { - seenSizes map[int64]bool + seenSizes map[int64]string // maps size to first URL with that size mutex sync.Mutex - firstOccurrence map[int64]bool } func NewUniqueSizeFilter() ffuf.FilterProvider { return &UniqueSizeFilter{ - seenSizes: make(map[int64]bool), - firstOccurrence: make(map[int64]bool), + seenSizes: make(map[int64]string), } } @@ -25,19 +23,16 @@ func (f *UniqueSizeFilter) Filter(response *ffuf.Response) (bool, error) { size := response.ContentLength - if !f.seenSizes[size] { + if firstURL, seen := f.seenSizes[size]; !seen { // First time seeing this size - f.seenSizes[size] = true - f.firstOccurrence[size] = true + f.seenSizes[size] = response.Request.Url return false, nil - } - - // If we've seen this size before, only allow it through if it's the first occurrence - if f.firstOccurrence[size] { - f.firstOccurrence[size] = false + } else if firstURL == response.Request.Url { + // This is the first URL we saw with this size, keep it return false, nil } + // Not the first URL with this size, filter it out return true, nil } From dff8c53f934e971efe6239cd1eba769bb4241bde Mon Sep 17 00:00:00 2001 From: h0tak88r Date: Thu, 5 Dec 2024 03:03:01 +0200 Subject: [PATCH 6/6] Bump version to 2.1.1 --- pkg/ffuf/constants.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/ffuf/constants.go b/pkg/ffuf/constants.go index 459726fa..097f0897 100644 --- a/pkg/ffuf/constants.go +++ b/pkg/ffuf/constants.go @@ -7,7 +7,7 @@ import ( var ( //VERSION holds the current version number - VERSION = "2.1.0" + VERSION = "2.1.1" //VERSION_APPENDIX holds additional version definition VERSION_APPENDIX = "-dev" CONFIGDIR = filepath.Join(xdg.ConfigHome, "ffuf")