Compare commits

..

111 Commits

Author SHA1 Message Date
allcontributors[bot]
70ae679b50 docs: add swordfish0x0 as a contributor for ideas (#1168)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-06-16 15:56:56 -04:00
epi
01da38fa6d Merge branch 'main' of github.com:epi052/feroxbuster 2024-06-16 09:32:44 -04:00
epi
22586f3835 updated regex filter help 2024-06-16 09:32:40 -04:00
allcontributors[bot]
0510cb91aa docs: add sa7mon as a contributor for ideas (#1167)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-06-16 09:28:44 -04:00
allcontributors[bot]
4663ec4cea docs: add L1-0 as a contributor for bug (#1166)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-06-16 09:28:22 -04:00
allcontributors[bot]
e8a98a54d8 docs: add wikamp-collaborator as a contributor for ideas, and infra (#1165)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-06-16 09:28:02 -04:00
allcontributors[bot]
fa42c72ac5 docs: add JulianGR as a contributor for infra, doc, and ideas (#1164)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-06-16 09:27:34 -04:00
allcontributors[bot]
4ce77b5012 docs: add sitiom as a contributor for infra, and doc (#1163)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-06-16 09:27:11 -04:00
epi
72c09854fc upgrade deps 2024-06-16 09:19:01 -04:00
Ryan
17a3d8af9f ci: add winget releaser workflow (#1155)
* ci: add winget releaser workflow
* docs(readme): add winget installation reference
2024-06-16 09:10:28 -04:00
Julián Gómez
b67f1399b3 fixes #1151 (#1152)
fixes issue https://github.com/epi052/feroxbuster/issues/1151
2024-06-16 08:59:44 -04:00
epi
57db4adb69 added headers to regex filtering (#1142)
* added headers to regex filtering

* added regex header test

* added pipeline for mac arm

* bumped version; updated deps; updated .cargo/config to .toml

* -b more robust

* fixed overall prog bar showing 0 eta too early

* fixed ssl error test

* added time estimate to SMM
2024-06-16 08:59:17 -04:00
allcontributors[bot]
87b6589f51 docs: add soutzis as a contributor for bug (#1133)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-04-26 20:18:37 -04:00
allcontributors[bot]
f36897431e docs: add JulianGR as a contributor for ideas (#1132)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-04-26 20:18:03 -04:00
allcontributors[bot]
3c89721f54 docs: add Spidle as a contributor for ideas (#1131)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-04-26 20:17:38 -04:00
allcontributors[bot]
9193614f3c docs: add deadloot as a contributor for ideas (#1130)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-04-26 20:17:01 -04:00
epi
8eb41f40a0 1105 - improve json logs for post processing (#1114)
* added timestamp field to responses
* added targets field to stats object
* clippy
* write config to output file
* --data implies post request if no other method provided
* fixed issue with multiple leading spaces
* ignoring flaky test
* upgraded deps
* bumped version to 2.10.3
2024-04-26 20:15:00 -04:00
epi
f3d6d185cd default to silent when parallel used 2024-03-01 07:38:53 -05:00
allcontributors[bot]
df7b6ab6f9 docs: add rew1nter as a contributor for ideas (#1093)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-02-28 07:11:16 -05:00
allcontributors[bot]
22bed3c9e7 docs: add tritoke as a contributor for code (#1092)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-02-28 07:07:14 -05:00
Sam Leonard
fe0f7d6f3c query fontconfig to determine if Noto Color Emoji is installed (#1083) 2024-02-28 07:06:50 -05:00
allcontributors[bot]
3b0d787ca7 docs: add NotoriousRebel as a contributor for ideas (#1091)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-02-28 07:05:03 -05:00
allcontributors[bot]
eba35b205e docs: add FieldOfRice as a contributor for infra (#1090)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-02-28 07:04:16 -05:00
allcontributors[bot]
ecdd1bce81 docs: add dirhamgithub as a contributor for bug (#1089)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-02-28 07:03:11 -05:00
allcontributors[bot]
0771407939 docs: add amiremami as a contributor for bug (#1088)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-02-28 07:02:28 -05:00
allcontributors[bot]
2ea6b97c86 docs: add n0kovo as a contributor for bug (#1087)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-02-28 07:01:16 -05:00
epi
9ff0253deb parallel time limit enforced individually instead of main thread (#1072)
* parallel time limit enforced individually instead of main thread
* added ability to select silent/quiet when using --parallel
* fixed robots.txt error -> hang
* build pipeline back to main
* fixed up tests
* removed retries from nextest
* clippy
2024-02-28 06:59:43 -05:00
allcontributors[bot]
423889b142 docs: add ArthurMuraro as a contributor for bug (#1069)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-01-24 06:55:30 -05:00
epi
595665cc04 fixed issue where --silent included too much info on found dir (#1067) 2024-01-24 06:50:27 -05:00
allcontributors[bot]
a583e2ff38 docs: add manugramm as a contributor for bug (#1061)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-01-20 16:23:47 -05:00
epi
539851e3e8 headers with commas from cli should parse correctly 2024-01-20 16:19:23 -05:00
allcontributors[bot]
c1e7c5ff59 docs: add Mister7F as a contributor for ideas (#1036)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2023-11-25 09:51:50 -05:00
epi
38a1ed3f63 custom backup extension list (#1035)
* --collect-backups flag changed to accept values
* added default set of backup extensions constant
* tweaked cli parsing to account for changes to --collect-backups
* changed backup collection to use new cli values; fixed bug that could hang ferox on exit
* fmt
* bumped version to 2.10.2
* underped cli logic
2023-11-25 09:51:07 -05:00
allcontributors[bot]
0d55fe2502 docs: add stuhlmann as a contributor for bug (#1034)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2023-11-25 06:36:44 -05:00
epi
a714825d09 remove scan info from update check (#1033)
* removed scan info from github update check
* added build-debug pipeline job
2023-11-25 06:35:20 -05:00
epi
d805e46474 fixed bug with url parsing; re: devx00 2023-11-13 08:44:27 -05:00
allcontributors[bot]
fe71f288e3 docs: add RavySena as a contributor for ideas (#1025)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2023-11-08 21:24:35 -05:00
allcontributors[bot]
a38a0444fe docs: add ocervell as a contributor for ideas (#1024)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2023-11-08 21:23:26 -05:00
allcontributors[bot]
2938094c73 docs: add devx00 as a contributor for bug (#1023)
* docs: update README.md [skip ci]
* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2023-11-08 21:22:40 -05:00
epi
55c67358d6 allow --json in conjunction with --silent (#1022)
* updated parser to allow --silent with --json
* updated config parsing with new silentjson output variant
* added new silentjson output variant
* updated outputlevel usage to include new variant
2023-11-08 21:18:53 -05:00
epi
c3c6fc6753 add http/2 support (#1020)
* added http/2 support
* updated deps
2023-11-08 06:22:00 -05:00
epi
a28ff857ca added test for robots/--dont-extract-links 2023-11-03 06:38:55 -04:00
epi
6c0fe90909 fixed collect backups filtering (#1016)
* fixed collect backups filtering and clippy
* added test for filtered backups
2023-11-03 06:28:09 -04:00
epi
bc486ac8d3 nitpickery; added success msg 2023-10-04 21:43:17 -04:00
epi
fa9d42554f Merge pull request #1001 from epi052/all-contributors/add-N0ur5
docs: add N0ur5 as a contributor for bug
2023-10-04 21:45:11 -04:00
allcontributors[bot]
b78dbe6cc4 docs: update .all-contributorsrc [skip ci] 2023-10-05 01:45:01 +00:00
allcontributors[bot]
29f616f51a docs: update README.md [skip ci] 2023-10-05 01:45:00 +00:00
epi
c1ba5cf942 fixed unwritable cwd bug 2023-10-04 20:53:49 -04:00
epi
e3ec3aee3a Merge pull request #980 from epi052/all-contributors/add-sawmj
docs: add sawmj as a contributor for bug
2023-09-11 21:18:35 -04:00
allcontributors[bot]
52db396aa9 docs: update .all-contributorsrc [skip ci] 2023-09-12 01:17:22 +00:00
allcontributors[bot]
e1066cd5c7 docs: update README.md [skip ci] 2023-09-12 01:17:21 +00:00
epi
d90ee38aad added error message in response to issue #977 2023-09-11 21:10:42 -04:00
epi
a3501ac494 clippy 2023-09-11 08:02:35 -04:00
epi
23827a1d45 fmt 2023-09-11 07:57:54 -04:00
epi
a2b04b2b5e Merge pull request #978 from epi052/all-contributors/add-andreademurtas
docs: add andreademurtas as a contributor for code
2023-09-11 08:00:45 -04:00
epi
362633bc63 Merge pull request #976 from andreademurtas/extensions-from-file
Enable reading extensions from file
2023-09-11 08:00:26 -04:00
allcontributors[bot]
08c5b2bf67 docs: update .all-contributorsrc [skip ci] 2023-09-11 12:00:21 +00:00
allcontributors[bot]
ccef4fd713 docs: update README.md [skip ci] 2023-09-11 12:00:20 +00:00
Andrea De Murtas
4afe0cf95c Update src/config/container.rs
Co-authored-by: epi <43392618+epi052@users.noreply.github.com>
2023-09-10 18:28:48 +02:00
Andrea De Murtas
564686bc5a Update src/config/container.rs
Co-authored-by: epi <43392618+epi052@users.noreply.github.com>
2023-09-10 18:28:42 +02:00
Andrea De Murtas
83f90529e9 Update src/config/container.rs
Co-authored-by: epi <43392618+epi052@users.noreply.github.com>
2023-09-10 18:28:35 +02:00
Andrea De Murtas
ad49320968 enable reading extensions from file 2023-09-07 19:27:31 +02:00
epi
70946ad916 Merge pull request #938 from epi052/all-contributors/add-ktecv2000
docs: add ktecv2000 as a contributor for bug
2023-07-11 05:35:21 -05:00
allcontributors[bot]
fd5c5af5fa docs: update .all-contributorsrc [skip ci] 2023-07-11 10:35:12 +00:00
allcontributors[bot]
ff32aba1db docs: update README.md [skip ci] 2023-07-11 10:35:11 +00:00
epi
cbf028a8ac Merge pull request #937 from epi052/all-contributors/add-sectroyer
docs: add sectroyer as a contributor for bug, and ideas
2023-07-11 05:34:39 -05:00
allcontributors[bot]
8bf80f4eda docs: update .all-contributorsrc [skip ci] 2023-07-11 10:34:19 +00:00
allcontributors[bot]
7c2d09cc22 docs: update README.md [skip ci] 2023-07-11 10:34:18 +00:00
epi
0fb682c121 Merge pull request #936 from epi052/935-fix-scan-menu-range-issue
935 fix scan menu range issue
2023-07-11 05:32:59 -05:00
epi
bcfd8b6eef fixed unwrap in nlp::document 2023-07-11 06:23:18 -04:00
epi
1c9235a56b dont show cancelled scans in scan menu 2023-07-11 06:04:34 -04:00
epi
4d787f08d0 kept indicatif pinned to 17.3 due to bug crashing scan menu 2023-07-11 06:04:09 -04:00
epi
0c7520f5ee clippy 2023-07-11 05:25:53 -04:00
epi
83b55aaf10 updated deps 2023-07-11 05:22:11 -04:00
epi
aea64324f7 cancel scans ignores scantypes of file 2023-07-11 05:14:33 -04:00
epi
8d0614b1a5 Merge pull request #898 from epi052/all-contributors/add-AkechiShiro
docs: add AkechiShiro as a contributor for ideas
2023-05-06 06:47:16 -05:00
allcontributors[bot]
d19cf58af3 docs: update .all-contributorsrc [skip ci] 2023-05-06 11:46:58 +00:00
allcontributors[bot]
bd44bacf95 docs: update README.md [skip ci] 2023-05-06 11:46:57 +00:00
epi
2bf5dc5e6f updated deps 2023-05-06 06:44:18 -05:00
epi
e5fadde073 Merge pull request #892 from lavafroth/client_ssl_cert
Adds certificate management options (unknown servers & mTLS)
2023-05-06 06:38:32 -05:00
epi
ac3fdb1975 added mutual auth testing server and cert generating script 2023-05-06 06:21:08 -05:00
epi
ff40549140 added a little more context to connection errors 2023-05-06 05:54:05 -05:00
epi
0cd25eedfc added client init tests; removed extension requirement 2023-05-06 05:47:22 -05:00
epi
328d1d2ec9 bumped version to 2.10.0 2023-05-06 04:54:17 -05:00
epi
68cc6bc748 nitpickery 2023-05-06 04:53:28 -05:00
epi
f44f320a49 added banner tests 2023-05-06 04:53:10 -05:00
Himadri Bhattacharjee
0965379b9a cargo fmt --all 2023-05-06 09:24:31 +05:30
Himadri Bhattacharjee
4afbf77631 do not use option for server_certs since next() on an empty iterator yields None 2023-05-06 09:14:19 +05:30
epi
5385ce5e99 bumped version to 2.9.6 2023-05-05 18:48:58 -05:00
epi
c8a50d9c0c added new config values to ferox-config example 2023-05-05 18:47:56 -05:00
epi
a3c887f2d7 added config tests for new values 2023-05-05 18:47:01 -05:00
epi
e094dab4a4 key requires cert; accept multiple server certs 2023-05-05 07:07:06 -05:00
epi
c307e6d56d fixed missing client cert issues 2023-05-05 06:32:46 -05:00
Himadri Bhattacharjee
d27cb57d66 simplify certificate and key parsing logic 2023-05-04 08:52:19 +05:30
Himadri Bhattacharjee
372f7c5cd4 add client_key flag to separated PEM key file for identity from_pkcs8_pem 2023-05-04 08:35:25 +05:30
Himadri Bhattacharjee
4986ebdaae cargo clippy 2023-05-02 12:47:47 +05:30
Himadri Bhattacharjee
4198a019d3 added functionality to use SSL key as client identity for mutual authentication 2023-05-02 12:43:45 +05:30
Himadri Bhattacharjee
3b8c6f6ba9 added doc comments for certificate field in Configuration struct 2023-04-30 08:36:47 +05:30
Himadri Bhattacharjee
39f8259f31 documentation for changes in client 2023-04-30 08:36:40 +05:30
Himadri Bhattacharjee
3f5ff1ad3e create Some or None variants for proxy and certificate ahead of time to avoid multiple initializations. 2023-04-30 08:30:17 +05:30
Himadri Bhattacharjee
12206e668f added basic functionality to add root cert to client 2023-04-29 21:07:08 +05:30
Himadri Bhattacharjee
1796e4eeb2 added cert flag 2023-04-29 18:20:04 +05:30
epi
70a8d0f5df Merge pull request #889 from epi052/all-contributors/add-lavafroth
docs: add lavafroth as a contributor for code, and ideas
2023-04-26 19:35:58 -05:00
allcontributors[bot]
11831a3ab5 docs: update .all-contributorsrc [skip ci] 2023-04-27 00:35:40 +00:00
allcontributors[bot]
9356b058eb docs: update README.md [skip ci] 2023-04-27 00:35:39 +00:00
epi
df490f6224 update 2023-04-26 19:35:28 -05:00
epi
4079551c96 update 2023-04-26 19:34:18 -05:00
epi
6d0658a635 update 2023-04-26 19:33:14 -05:00
epi
890519f39c Merge pull request #888 from epi052/all-contributors/add-aroly
docs: add aroly as a contributor for ideas, and code
2023-04-26 19:32:09 -05:00
allcontributors[bot]
abf18b0481 docs: update .all-contributorsrc [skip ci] 2023-04-27 00:29:40 +00:00
allcontributors[bot]
409844ed05 docs: update README.md [skip ci] 2023-04-27 00:29:39 +00:00
65 changed files with 3565 additions and 1394 deletions

View File

@@ -198,7 +198,8 @@
"avatar_url": "https://avatars.githubusercontent.com/u/24260009?v=4",
"profile": "https://github.com/N0ur5",
"contributions": [
"ideas"
"ideas",
"bug"
]
},
{
@@ -299,7 +300,8 @@
"avatar_url": "https://avatars.githubusercontent.com/u/16690056?v=4",
"profile": "https://github.com/n0kovo",
"contributions": [
"ideas"
"ideas",
"bug"
]
},
{
@@ -580,6 +582,273 @@
"contributions": [
"bug"
]
},
{
"login": "aroly",
"name": "Antoine Roly",
"avatar_url": "https://avatars.githubusercontent.com/u/1257705?v=4",
"profile": "https://github.com/aroly",
"contributions": [
"ideas"
]
},
{
"login": "lavafroth",
"name": "Himadri Bhattacharjee",
"avatar_url": "https://avatars.githubusercontent.com/u/107522312?v=4",
"profile": "http://lavafroth.is-a.dev",
"contributions": [
"code",
"ideas"
]
},
{
"login": "AkechiShiro",
"name": "Samy Lahfa",
"avatar_url": "https://avatars.githubusercontent.com/u/14914796?v=4",
"profile": "https://github.com/AkechiShiro",
"contributions": [
"ideas"
]
},
{
"login": "sectroyer",
"name": "sectroyer",
"avatar_url": "https://avatars.githubusercontent.com/u/6706818?v=4",
"profile": "https://github.com/sectroyer",
"contributions": [
"bug",
"ideas"
]
},
{
"login": "ktecv2000",
"name": "ktecv2000",
"avatar_url": "https://avatars.githubusercontent.com/u/19836003?v=4",
"profile": "https://medium.com/@b3rm1nG",
"contributions": [
"bug"
]
},
{
"login": "andreademurtas",
"name": "Andrea De Murtas",
"avatar_url": "https://avatars.githubusercontent.com/u/56048157?v=4",
"profile": "http://untrue.me",
"contributions": [
"code"
]
},
{
"login": "sawmj",
"name": "sawmj",
"avatar_url": "https://avatars.githubusercontent.com/u/30024085?v=4",
"profile": "https://github.com/sawmj",
"contributions": [
"bug"
]
},
{
"login": "devx00",
"name": "Zach Hanson",
"avatar_url": "https://avatars.githubusercontent.com/u/6897405?v=4",
"profile": "https://github.com/devx00",
"contributions": [
"bug"
]
},
{
"login": "ocervell",
"name": "Olivier Cervello",
"avatar_url": "https://avatars.githubusercontent.com/u/9629314?v=4",
"profile": "https://github.com/ocervell",
"contributions": [
"ideas"
]
},
{
"login": "RavySena",
"name": "RavySena",
"avatar_url": "https://avatars.githubusercontent.com/u/67729597?v=4",
"profile": "https://github.com/RavySena",
"contributions": [
"ideas"
]
},
{
"login": "stuhlmann",
"name": "Florian Stuhlmann",
"avatar_url": "https://avatars.githubusercontent.com/u/11061864?v=4",
"profile": "https://github.com/stuhlmann",
"contributions": [
"bug"
]
},
{
"login": "Mister7F",
"name": "Mister7F",
"avatar_url": "https://avatars.githubusercontent.com/u/35213773?v=4",
"profile": "https://github.com/Mister7F",
"contributions": [
"ideas"
]
},
{
"login": "manugramm",
"name": "manugramm",
"avatar_url": "https://avatars.githubusercontent.com/u/145961515?v=4",
"profile": "https://github.com/manugramm",
"contributions": [
"bug"
]
},
{
"login": "ArthurMuraro",
"name": "ArthurMuraro",
"avatar_url": "https://avatars.githubusercontent.com/u/73059809?v=4",
"profile": "https://github.com/ArthurMuraro",
"contributions": [
"bug"
]
},
{
"login": "amiremami",
"name": "Shadow",
"avatar_url": "https://avatars.githubusercontent.com/u/15929497?v=4",
"profile": "https://github.com/amiremami",
"contributions": [
"bug"
]
},
{
"login": "dirhamgithub",
"name": "dirhamgithub",
"avatar_url": "https://avatars.githubusercontent.com/u/115349974?v=4",
"profile": "https://github.com/dirhamgithub",
"contributions": [
"bug"
]
},
{
"login": "FieldOfRice",
"name": "FieldOfRice",
"avatar_url": "https://avatars.githubusercontent.com/u/85353?v=4",
"profile": "https://github.com/FieldOfRice",
"contributions": [
"infra"
]
},
{
"login": "NotoriousRebel",
"name": "Matt",
"avatar_url": "https://avatars.githubusercontent.com/u/36310667?v=4",
"profile": "https://github.com/NotoriousRebel",
"contributions": [
"ideas"
]
},
{
"login": "tritoke",
"name": "Sam Leonard",
"avatar_url": "https://avatars.githubusercontent.com/u/34941249?v=4",
"profile": "https://github.com/tritoke",
"contributions": [
"code"
]
},
{
"login": "rew1nter",
"name": "Rewinter",
"avatar_url": "https://avatars.githubusercontent.com/u/64508791?v=4",
"profile": "https://github.com/rew1nter",
"contributions": [
"ideas"
]
},
{
"login": "deadloot",
"name": "deadloot",
"avatar_url": "https://avatars.githubusercontent.com/u/92878901?v=4",
"profile": "https://github.com/deadloot",
"contributions": [
"ideas"
]
},
{
"login": "Spidle",
"name": "Spidle",
"avatar_url": "https://avatars.githubusercontent.com/u/90011249?v=4",
"profile": "https://github.com/Spidle",
"contributions": [
"ideas"
]
},
{
"login": "JulianGR",
"name": "Julián Gómez",
"avatar_url": "https://avatars.githubusercontent.com/u/53094530?v=4",
"profile": "https://github.com/JulianGR",
"contributions": [
"ideas",
"infra",
"doc"
]
},
{
"login": "soutzis",
"name": "Petros",
"avatar_url": "https://avatars.githubusercontent.com/u/25797286?v=4",
"profile": "https://github.com/soutzis",
"contributions": [
"bug"
]
},
{
"login": "sitiom",
"name": "Ryan",
"avatar_url": "https://avatars.githubusercontent.com/u/56180050?v=4",
"profile": "https://github.com/sitiom",
"contributions": [
"infra",
"doc"
]
},
{
"login": "wikamp-collaborator",
"name": "wikamp-collaborator",
"avatar_url": "https://avatars.githubusercontent.com/u/147445097?v=4",
"profile": "https://github.com/wikamp-collaborator",
"contributions": [
"ideas",
"infra"
]
},
{
"login": "L1-0",
"name": "Lino",
"avatar_url": "https://avatars.githubusercontent.com/u/123986259?v=4",
"profile": "http://lino.codes",
"contributions": [
"bug"
]
},
{
"login": "sa7mon",
"name": "Dan Salmon",
"avatar_url": "https://avatars.githubusercontent.com/u/3712226?v=4",
"profile": "https://danthesalmon.com",
"contributions": [
"ideas"
]
},
{
"login": "swordfish0x0",
"name": "swordfish0x0",
"avatar_url": "https://avatars.githubusercontent.com/u/21209130?v=4",
"profile": "https://github.com/swordfish0x0",
"contributions": [
"ideas"
]
}
],
"contributorsPerLine": 7,
@@ -588,5 +857,6 @@
"repoType": "github",
"repoHost": "https://github.com",
"skipCi": true,
"commitConvention": "angular"
"commitConvention": "angular",
"commitType": "docs"
}

View File

@@ -73,22 +73,57 @@ jobs:
name: ${{ matrix.name }}.tar.gz
path: ${{ matrix.name }}.tar.gz
# build-deb:
# needs: [build-nix]
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@master
# - name: Install cargo-deb
# run: cargo install -f cargo-deb
# - name: Install musl toolchain
# run: rustup target add x86_64-unknown-linux-musl
# - name: Deb Build
# run: cargo deb --target=x86_64-unknown-linux-musl
# - name: Upload Deb Artifact
# uses: actions/upload-artifact@v2
# with:
# name: feroxbuster_amd64.deb
# path: ./target/x86_64-unknown-linux-musl/debian/*
build-debug:
env:
IN_PIPELINE: true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install System Dependencies
run: |
env
sudo apt-get update
sudo apt-get install -y --no-install-recommends libssl-dev pkg-config
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: x86_64-unknown-linux-musl
override: true
- uses: actions-rs/cargo@v1
env:
PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig
OPENSSL_DIR: /usr/lib/ssl
with:
use-cross: true
command: build
args: --target=x86_64-unknown-linux-musl
- uses: actions/upload-artifact@v2
with:
name: x86_64-linux-debug-feroxbuster
path: target/x86_64-unknown-linux-musl/debug/feroxbuster
build-deb:
needs: [build-nix]
runs-on: ubuntu-latest
env:
IN_PIPELINE: true
steps:
- uses: actions/checkout@master
- name: Install cargo-deb
run: cargo install -f cargo-deb
- uses: awalsh128/cache-apt-pkgs-action@v1
with:
packages: musl-tools # provides musl-gcc
version: 1.0
- name: Install musl toolchain
run: rustup target add x86_64-unknown-linux-musl
- name: Deb Build
run: cargo deb --target=x86_64-unknown-linux-musl
- name: Upload Deb Artifact
uses: actions/upload-artifact@v2
with:
name: feroxbuster_amd64.deb
path: ./target/x86_64-unknown-linux-musl/debian/*
build-macos:
env:
@@ -121,6 +156,38 @@ jobs:
with:
name: x86_64-macos-feroxbuster.tar.gz
path: x86_64-macos-feroxbuster.tar.gz
build-macos-aarch64:
env:
IN_PIPELINE: true
runs-on: macos-latest
# if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: aarch64-apple-darwin
override: true
- uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --release --target=aarch64-apple-darwin
- name: Strip symbols from binary
run: |
strip -u -r target/aarch64-apple-darwin/release/feroxbuster
- name: Build tar.gz for homebrew installs
run: |
tar czf aarch64-macos-feroxbuster.tar.gz -C target/aarch64-apple-darwin/release feroxbuster
- uses: actions/upload-artifact@v2
with:
name: aarch64-macos-feroxbuster
path: target/aarch64-apple-darwin/release/feroxbuster
- uses: actions/upload-artifact@v2
with:
name: aarch64-macos-feroxbuster.tar.gz
path: aarch64-macos-feroxbuster.tar.gz
build-windows:
env:

View File

@@ -23,7 +23,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: nextest
args: run --all-features --all-targets --retries 10
args: run --all-features --all-targets
fmt:
name: Rust fmt

14
.github/workflows/winget.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
name: Publish to Winget
on:
release:
types: [released]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: vedantmgoyal2009/winget-releaser@v2
with:
identifier: epi052.feroxbuster
installers-regex: '-windows-feroxbuster\.exe\.zip$'
token: ${{ secrets.WINGET_TOKEN }}

2525
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "feroxbuster"
version = "2.9.5"
version = "2.10.4"
authors = ["Ben 'epi' Risher (@epi052)"]
license = "MIT"
edition = "2021"
@@ -22,41 +22,46 @@ build = "build.rs"
maintenance = { status = "actively-developed" }
[build-dependencies]
clap = { version = "4.2", features = ["wrap_help", "cargo"] }
clap_complete = "4.1"
regex = "1.5"
clap = { version = "4.5", features = ["wrap_help", "cargo"] }
clap_complete = "4.5"
regex = "1.10"
lazy_static = "1.4"
dirs = "5.0"
[dependencies]
scraper = "0.16"
scraper = "0.19"
futures = "0.3"
tokio = { version = "1.26", features = ["full"] }
tokio = { version = "1.38", features = ["full"] }
tokio-util = { version = "0.7", features = ["codec"] }
log = "0.4"
env_logger = "0.10"
reqwest = { version = "0.11", features = ["socks"] }
env_logger = "0.11"
reqwest = { version = "0.12", features = ["socks", "native-tls-alpn"] }
# uses feature unification to add 'serde' to reqwest::Url
url = { version = "2.2", features = ["serde"] }
url = { version = "2.5", features = ["serde"] }
serde_regex = "1.1"
clap = { version = "4.2", features = ["wrap_help", "cargo"] }
clap = { version = "4.5", features = ["wrap_help", "cargo"] }
lazy_static = "1.4"
toml = "0.7"
toml = "0.8"
serde = { version = "1.0", features = ["derive", "rc"] }
serde_json = "1.0"
uuid = { version = "1.3", features = ["v4"] }
indicatif = "0.17"
uuid = { version = "1.8", features = ["v4"] }
# last known working version of indicatif; 0.17.5 has a bug that causes the
# scan menu to fail spectacularly
indicatif = { version = "0.17.8" }
console = "0.15"
openssl = { version = "0.10", features = ["vendored"] }
dirs = "5.0"
regex = "1.5"
crossterm = "0.26"
rlimit = "0.9"
ctrlc = "3.2"
regex = "1.10"
crossterm = "0.27"
rlimit = "0.10"
ctrlc = "3.4"
anyhow = "1.0"
leaky-bucket = "0.12"
gaoya = "0.1"
self_update = { version = "0.36", features = [
leaky-bucket = "1.1"
gaoya = "0.2"
# 0.37+ relies on the broken version of indicatif and forces
# the broken version to be used regardless of the version
# specified above
self_update = { version = "0.40", features = [
"archive-tar",
"compression-flate2",
"archive-zip",
@@ -64,10 +69,10 @@ self_update = { version = "0.36", features = [
] }
[dev-dependencies]
tempfile = "3.3"
httpmock = "0.6"
tempfile = "3.10"
httpmock = "0.7"
assert_cmd = "2.0"
predicates = "3.0"
predicates = "3.1"
[profile.release]
lto = true

View File

@@ -2,6 +2,9 @@
[tasks.upgrade]
dependencies = ["upgrade-deps", "update"]
[tasks.check]
dependencies = ["fmt", "clippy", "test"]
# cleaning
[tasks.clean-state]
script = """
@@ -11,7 +14,7 @@ rm ferox-*.state
# dependency management
[tasks.upgrade-deps]
command = "cargo"
args = ["upgrade"]
args = ["upgrade", "--exclude", "indicatif, self_update"]
[tasks.update]
command = "cargo"
@@ -24,9 +27,15 @@ script = """
cargo clippy --all-targets --all-features -- -D warnings
"""
[tasks.fmt]
clear = true
script = """
cargo fmt --all
"""
# tests
[tasks.test]
clear = true
script = """
cargo nextest run --all-features --all-targets --retries 10
cargo nextest run --all-features --all-targets
"""

View File

@@ -121,6 +121,12 @@ Expand-Archive .\feroxbuster.zip
.\feroxbuster\feroxbuster.exe -V
```
#### Windows via Winget
```
winget install epi052.feroxbuster
```
#### Windows via Chocolatey
```
@@ -232,7 +238,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sbrun"><img src="https://avatars.githubusercontent.com/u/7712154?v=4?s=100" width="100px;" alt="Sophie Brun"/><br /><sub><b>Sophie Brun</b></sub></a><br /><a href="#infra-sbrun" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/black-A"><img src="https://avatars.githubusercontent.com/u/30686803?v=4?s=100" width="100px;" alt="black-A"/><br /><sub><b>black-A</b></sub></a><br /><a href="#ideas-black-A" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dinosn"><img src="https://avatars.githubusercontent.com/u/3851678?v=4?s=100" width="100px;" alt="Nicolas Krassas"/><br /><sub><b>Nicolas Krassas</b></sub></a><br /><a href="#ideas-dinosn" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/N0ur5"><img src="https://avatars.githubusercontent.com/u/24260009?v=4?s=100" width="100px;" alt="N0ur5"/><br /><sub><b>N0ur5</b></sub></a><br /><a href="#ideas-N0ur5" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/N0ur5"><img src="https://avatars.githubusercontent.com/u/24260009?v=4?s=100" width="100px;" alt="N0ur5"/><br /><sub><b>N0ur5</b></sub></a><br /><a href="#ideas-N0ur5" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/epi052/feroxbuster/issues?q=author%3AN0ur5" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/moscowchill"><img src="https://avatars.githubusercontent.com/u/72578879?v=4?s=100" width="100px;" alt="mchill"/><br /><sub><b>mchill</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Amoscowchill" title="Bug reports">🐛</a></td>
@@ -247,7 +253,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hunter0x8"><img src="https://avatars.githubusercontent.com/u/46222314?v=4?s=100" width="100px;" alt="Muhammad Ahsan"/><br /><sub><b>Muhammad Ahsan</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Ahunter0x8" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cortantief"><img src="https://avatars.githubusercontent.com/u/34527333?v=4?s=100" width="100px;" alt="cortantief"/><br /><sub><b>cortantief</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Acortantief" title="Bug reports">🐛</a> <a href="https://github.com/epi052/feroxbuster/commits?author=cortantief" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dsaxton"><img src="https://avatars.githubusercontent.com/u/2658661?v=4?s=100" width="100px;" alt="Daniel Saxton"/><br /><sub><b>Daniel Saxton</b></sub></a><br /><a href="#ideas-dsaxton" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/epi052/feroxbuster/commits?author=dsaxton" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/n0kovo"><img src="https://avatars.githubusercontent.com/u/16690056?v=4?s=100" width="100px;" alt="n0kovo"/><br /><sub><b>n0kovo</b></sub></a><br /><a href="#ideas-n0kovo" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/n0kovo"><img src="https://avatars.githubusercontent.com/u/16690056?v=4?s=100" width="100px;" alt="n0kovo"/><br /><sub><b>n0kovo</b></sub></a><br /><a href="#ideas-n0kovo" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/epi052/feroxbuster/issues?q=author%3An0kovo" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://ring0.lol"><img src="https://avatars.githubusercontent.com/u/1893909?v=4?s=100" width="100px;" alt="Justin Steven"/><br /><sub><b>Justin Steven</b></sub></a><br /><a href="#ideas-justinsteven" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/7047payloads"><img src="https://avatars.githubusercontent.com/u/95562424?v=4?s=100" width="100px;" alt="7047payloads"/><br /><sub><b>7047payloads</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/commits?author=7047payloads" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/unkn0wnsyst3m"><img src="https://avatars.githubusercontent.com/u/21272239?v=4?s=100" width="100px;" alt="unkn0wnsyst3m"/><br /><sub><b>unkn0wnsyst3m</b></sub></a><br /><a href="#ideas-unkn0wnsyst3m" title="Ideas, Planning, & Feedback">🤔</a></td>
@@ -286,6 +292,43 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center" valign="top" width="14.28%"><a href="https://github.com/imBigo"><img src="https://avatars.githubusercontent.com/u/54672433?v=4?s=100" width="100px;" alt="Simon"/><br /><sub><b>Simon</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3AimBigo" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://acut3.github.io/"><img src="https://avatars.githubusercontent.com/u/17295243?v=4?s=100" width="100px;" alt="Nicolas Christin"/><br /><sub><b>Nicolas Christin</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Aacut3" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/DrorDvash"><img src="https://avatars.githubusercontent.com/u/8413651?v=4?s=100" width="100px;" alt="DrDv"/><br /><sub><b>DrDv</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3ADrorDvash" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/aroly"><img src="https://avatars.githubusercontent.com/u/1257705?v=4?s=100" width="100px;" alt="Antoine Roly"/><br /><sub><b>Antoine Roly</b></sub></a><br /><a href="#ideas-aroly" title="Ideas, Planning, & Feedback">🤔</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://lavafroth.is-a.dev"><img src="https://avatars.githubusercontent.com/u/107522312?v=4?s=100" width="100px;" alt="Himadri Bhattacharjee"/><br /><sub><b>Himadri Bhattacharjee</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/commits?author=lavafroth" title="Code">💻</a> <a href="#ideas-lavafroth" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/AkechiShiro"><img src="https://avatars.githubusercontent.com/u/14914796?v=4?s=100" width="100px;" alt="Samy Lahfa"/><br /><sub><b>Samy Lahfa</b></sub></a><br /><a href="#ideas-AkechiShiro" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sectroyer"><img src="https://avatars.githubusercontent.com/u/6706818?v=4?s=100" width="100px;" alt="sectroyer"/><br /><sub><b>sectroyer</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Asectroyer" title="Bug reports">🐛</a> <a href="#ideas-sectroyer" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://medium.com/@b3rm1nG"><img src="https://avatars.githubusercontent.com/u/19836003?v=4?s=100" width="100px;" alt="ktecv2000"/><br /><sub><b>ktecv2000</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Aktecv2000" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://untrue.me"><img src="https://avatars.githubusercontent.com/u/56048157?v=4?s=100" width="100px;" alt="Andrea De Murtas"/><br /><sub><b>Andrea De Murtas</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/commits?author=andreademurtas" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sawmj"><img src="https://avatars.githubusercontent.com/u/30024085?v=4?s=100" width="100px;" alt="sawmj"/><br /><sub><b>sawmj</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Asawmj" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/devx00"><img src="https://avatars.githubusercontent.com/u/6897405?v=4?s=100" width="100px;" alt="Zach Hanson"/><br /><sub><b>Zach Hanson</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Adevx00" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ocervell"><img src="https://avatars.githubusercontent.com/u/9629314?v=4?s=100" width="100px;" alt="Olivier Cervello"/><br /><sub><b>Olivier Cervello</b></sub></a><br /><a href="#ideas-ocervell" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/RavySena"><img src="https://avatars.githubusercontent.com/u/67729597?v=4?s=100" width="100px;" alt="RavySena"/><br /><sub><b>RavySena</b></sub></a><br /><a href="#ideas-RavySena" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/stuhlmann"><img src="https://avatars.githubusercontent.com/u/11061864?v=4?s=100" width="100px;" alt="Florian Stuhlmann"/><br /><sub><b>Florian Stuhlmann</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Astuhlmann" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Mister7F"><img src="https://avatars.githubusercontent.com/u/35213773?v=4?s=100" width="100px;" alt="Mister7F"/><br /><sub><b>Mister7F</b></sub></a><br /><a href="#ideas-Mister7F" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/manugramm"><img src="https://avatars.githubusercontent.com/u/145961515?v=4?s=100" width="100px;" alt="manugramm"/><br /><sub><b>manugramm</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Amanugramm" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ArthurMuraro"><img src="https://avatars.githubusercontent.com/u/73059809?v=4?s=100" width="100px;" alt="ArthurMuraro"/><br /><sub><b>ArthurMuraro</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3AArthurMuraro" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/amiremami"><img src="https://avatars.githubusercontent.com/u/15929497?v=4?s=100" width="100px;" alt="Shadow"/><br /><sub><b>Shadow</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Aamiremami" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dirhamgithub"><img src="https://avatars.githubusercontent.com/u/115349974?v=4?s=100" width="100px;" alt="dirhamgithub"/><br /><sub><b>dirhamgithub</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Adirhamgithub" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/FieldOfRice"><img src="https://avatars.githubusercontent.com/u/85353?v=4?s=100" width="100px;" alt="FieldOfRice"/><br /><sub><b>FieldOfRice</b></sub></a><br /><a href="#infra-FieldOfRice" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/NotoriousRebel"><img src="https://avatars.githubusercontent.com/u/36310667?v=4?s=100" width="100px;" alt="Matt"/><br /><sub><b>Matt</b></sub></a><br /><a href="#ideas-NotoriousRebel" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tritoke"><img src="https://avatars.githubusercontent.com/u/34941249?v=4?s=100" width="100px;" alt="Sam Leonard"/><br /><sub><b>Sam Leonard</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/commits?author=tritoke" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rew1nter"><img src="https://avatars.githubusercontent.com/u/64508791?v=4?s=100" width="100px;" alt="Rewinter"/><br /><sub><b>Rewinter</b></sub></a><br /><a href="#ideas-rew1nter" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/deadloot"><img src="https://avatars.githubusercontent.com/u/92878901?v=4?s=100" width="100px;" alt="deadloot"/><br /><sub><b>deadloot</b></sub></a><br /><a href="#ideas-deadloot" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Spidle"><img src="https://avatars.githubusercontent.com/u/90011249?v=4?s=100" width="100px;" alt="Spidle"/><br /><sub><b>Spidle</b></sub></a><br /><a href="#ideas-Spidle" title="Ideas, Planning, & Feedback">🤔</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JulianGR"><img src="https://avatars.githubusercontent.com/u/53094530?v=4?s=100" width="100px;" alt="Julián Gómez"/><br /><sub><b>Julián Gómez</b></sub></a><br /><a href="#ideas-JulianGR" title="Ideas, Planning, & Feedback">🤔</a> <a href="#infra-JulianGR" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/epi052/feroxbuster/commits?author=JulianGR" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/soutzis"><img src="https://avatars.githubusercontent.com/u/25797286?v=4?s=100" width="100px;" alt="Petros"/><br /><sub><b>Petros</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Asoutzis" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sitiom"><img src="https://avatars.githubusercontent.com/u/56180050?v=4?s=100" width="100px;" alt="Ryan"/><br /><sub><b>Ryan</b></sub></a><br /><a href="#infra-sitiom" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/epi052/feroxbuster/commits?author=sitiom" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/wikamp-collaborator"><img src="https://avatars.githubusercontent.com/u/147445097?v=4?s=100" width="100px;" alt="wikamp-collaborator"/><br /><sub><b>wikamp-collaborator</b></sub></a><br /><a href="#ideas-wikamp-collaborator" title="Ideas, Planning, & Feedback">🤔</a> <a href="#infra-wikamp-collaborator" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://lino.codes"><img src="https://avatars.githubusercontent.com/u/123986259?v=4?s=100" width="100px;" alt="Lino"/><br /><sub><b>Lino</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3AL1-0" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://danthesalmon.com"><img src="https://avatars.githubusercontent.com/u/3712226?v=4?s=100" width="100px;" alt="Dan Salmon"/><br /><sub><b>Dan Salmon</b></sub></a><br /><a href="#ideas-sa7mon" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/swordfish0x0"><img src="https://avatars.githubusercontent.com/u/21209130?v=4?s=100" width="100px;" alt="swordfish0x0"/><br /><sub><b>swordfish0x0</b></sub></a><br /><a href="#ideas-swordfish0x0" title="Ideas, Planning, & Feedback">🤔</a></td>
</tr>
</tbody>
</table>

View File

@@ -54,6 +54,9 @@
# queries = [["name","value"], ["rick", "astley"]]
# save_state = false
# time_limit = "10m"
# server_certs = ["/some/cert.pem", "/some/other/cert.pem"]
# client_cert = "/some/client/cert.pem"
# client_key = "/some/client/key.pem"
# headers can be specified on multiple lines or as an inline table
#
@@ -64,6 +67,7 @@
# note: if multi-line is used, all key/value pairs under it belong to the headers table until the next table
# is found or the end of the file is reached
#
# If you want to use [headers], UNCOMMENT the line below
# [headers]
# stuff = "things"
# more = "headers"

View File

@@ -44,7 +44,7 @@ elif [[ "$(expr substr $(uname -s) 1 5)" == "Linux" ]]; then
rm "$LIN64_ZIP"
fi
if [[ -e ~/.fonts/NotoColorEmoji.ttf ]]; then
if [[ "$(fc-list NotoColorEmoji | wc -l)" -gt 0 ]]; then
echo "[=] Found Noto Emoji Font, skipping install"
else
echo "[=] Installing Noto Emoji Font"

View File

@@ -14,64 +14,69 @@ _feroxbuster() {
fi
local context curcontext="$curcontext" state line
_arguments "${_arguments_options[@]}" \
_arguments "${_arguments_options[@]}" : \
'-u+[The target URL (required, unless \[--stdin || --resume-from\] used)]:URL:_urls' \
'--url=[The target URL (required, unless \[--stdin || --resume-from\] used)]:URL:_urls' \
'(-u --url)--resume-from=[State file from which to resume a partially complete scan (ex. --resume-from ferox-1606586780.state)]:STATE_FILE:_files' \
'-p+[Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)]:PROXY:_urls' \
'--proxy=[Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)]:PROXY:_urls' \
'-p+[Proxy to use for requests (ex\: http(s)\://host\:port, socks5(h)\://host\:port)]:PROXY:_urls' \
'--proxy=[Proxy to use for requests (ex\: http(s)\://host\:port, socks5(h)\://host\:port)]:PROXY:_urls' \
'-P+[Send only unfiltered requests through a Replay Proxy, instead of all requests]:REPLAY_PROXY:_urls' \
'--replay-proxy=[Send only unfiltered requests through a Replay Proxy, instead of all requests]:REPLAY_PROXY:_urls' \
'*-R+[Status Codes to send through a Replay Proxy when found (default: --status-codes value)]:REPLAY_CODE: ' \
'*--replay-codes=[Status Codes to send through a Replay Proxy when found (default: --status-codes value)]:REPLAY_CODE: ' \
'-a+[Sets the User-Agent (default: feroxbuster/2.9.5)]:USER_AGENT: ' \
'--user-agent=[Sets the User-Agent (default: feroxbuster/2.9.5)]:USER_AGENT: ' \
'*-x+[File extension(s) to search for (ex: -x php -x pdf js)]:FILE_EXTENSION: ' \
'*--extensions=[File extension(s) to search for (ex: -x php -x pdf js)]:FILE_EXTENSION: ' \
'*-m+[Which HTTP request method(s) should be sent (default: GET)]:HTTP_METHODS: ' \
'*--methods=[Which HTTP request method(s) should be sent (default: GET)]:HTTP_METHODS: ' \
'--data=[Request'\''s Body; can read data from a file if input starts with an @ (ex: @post.bin)]:DATA: ' \
'*-H+[Specify HTTP headers to be used in each request (ex: -H Header:val -H '\''stuff: things'\'')]:HEADER: ' \
'*--headers=[Specify HTTP headers to be used in each request (ex: -H Header:val -H '\''stuff: things'\'')]:HEADER: ' \
'*-b+[Specify HTTP cookies to be used in each request (ex: -b stuff=things)]:COOKIE: ' \
'*--cookies=[Specify HTTP cookies to be used in each request (ex: -b stuff=things)]:COOKIE: ' \
'*-Q+[Request'\''s URL query parameters (ex: -Q token=stuff -Q secret=key)]:QUERY: ' \
'*--query=[Request'\''s URL query parameters (ex: -Q token=stuff -Q secret=key)]:QUERY: ' \
'*-R+[Status Codes to send through a Replay Proxy when found (default\: --status-codes value)]:REPLAY_CODE: ' \
'*--replay-codes=[Status Codes to send through a Replay Proxy when found (default\: --status-codes value)]:REPLAY_CODE: ' \
'-a+[Sets the User-Agent (default\: feroxbuster/2.10.4)]:USER_AGENT: ' \
'--user-agent=[Sets the User-Agent (default\: feroxbuster/2.10.4)]:USER_AGENT: ' \
'*-x+[File extension(s) to search for (ex\: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex\: @ext.txt)]:FILE_EXTENSION: ' \
'*--extensions=[File extension(s) to search for (ex\: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex\: @ext.txt)]:FILE_EXTENSION: ' \
'*-m+[Which HTTP request method(s) should be sent (default\: GET)]:HTTP_METHODS: ' \
'*--methods=[Which HTTP request method(s) should be sent (default\: GET)]:HTTP_METHODS: ' \
'--data=[Request'\''s Body; can read data from a file if input starts with an @ (ex\: @post.bin)]:DATA: ' \
'*-H+[Specify HTTP headers to be used in each request (ex\: -H Header\:val -H '\''stuff\: things'\'')]:HEADER: ' \
'*--headers=[Specify HTTP headers to be used in each request (ex\: -H Header\:val -H '\''stuff\: things'\'')]:HEADER: ' \
'*-b+[Specify HTTP cookies to be used in each request (ex\: -b stuff=things)]:COOKIE: ' \
'*--cookies=[Specify HTTP cookies to be used in each request (ex\: -b stuff=things)]:COOKIE: ' \
'*-Q+[Request'\''s URL query parameters (ex\: -Q token=stuff -Q secret=key)]:QUERY: ' \
'*--query=[Request'\''s URL query parameters (ex\: -Q token=stuff -Q secret=key)]:QUERY: ' \
'*--dont-scan=[URL(s) or Regex Pattern(s) to exclude from recursion/scans]:URL: ' \
'*-S+[Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)]:SIZE: ' \
'*--filter-size=[Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)]:SIZE: ' \
'*-X+[Filter out messages via regular expression matching on the response'\''s body (ex: -X '\''^ignore me$'\'')]:REGEX: ' \
'*--filter-regex=[Filter out messages via regular expression matching on the response'\''s body (ex: -X '\''^ignore me$'\'')]:REGEX: ' \
'*-W+[Filter out messages of a particular word count (ex: -W 312 -W 91,82)]:WORDS: ' \
'*--filter-words=[Filter out messages of a particular word count (ex: -W 312 -W 91,82)]:WORDS: ' \
'*-N+[Filter out messages of a particular line count (ex: -N 20 -N 31,30)]:LINES: ' \
'*--filter-lines=[Filter out messages of a particular line count (ex: -N 20 -N 31,30)]:LINES: ' \
'(-s --status-codes)*-C+[Filter out status codes (deny list) (ex: -C 200 -C 401)]:STATUS_CODE: ' \
'(-s --status-codes)*--filter-status=[Filter out status codes (deny list) (ex: -C 200 -C 401)]:STATUS_CODE: ' \
'*--filter-similar-to=[Filter out pages that are similar to the given page (ex. --filter-similar-to http://site.xyz/soft404)]:UNWANTED_PAGE:_urls' \
'*-s+[Status Codes to include (allow list) (default: All Status Codes)]:STATUS_CODE: ' \
'*--status-codes=[Status Codes to include (allow list) (default: All Status Codes)]:STATUS_CODE: ' \
'-T+[Number of seconds before a client'\''s request times out (default: 7)]:SECONDS: ' \
'--timeout=[Number of seconds before a client'\''s request times out (default: 7)]:SECONDS: ' \
'-t+[Number of concurrent threads (default: 50)]:THREADS: ' \
'--threads=[Number of concurrent threads (default: 50)]:THREADS: ' \
'-d+[Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)]:RECURSION_DEPTH: ' \
'--depth=[Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)]:RECURSION_DEPTH: ' \
'-L+[Limit total number of concurrent scans (default: 0, i.e. no limit)]:SCAN_LIMIT: ' \
'--scan-limit=[Limit total number of concurrent scans (default: 0, i.e. no limit)]:SCAN_LIMIT: ' \
'--parallel=[Run parallel feroxbuster instances (one child process per url passed via stdin)]:PARALLEL_SCANS: ' \
'(--auto-tune)--rate-limit=[Limit number of requests per second (per directory) (default: 0, i.e. no limit)]:RATE_LIMIT: ' \
'--time-limit=[Limit total run time of all scans (ex: --time-limit 10m)]:TIME_SPEC: ' \
'*-S+[Filter out messages of a particular size (ex\: -S 5120 -S 4927,1970)]:SIZE: ' \
'*--filter-size=[Filter out messages of a particular size (ex\: -S 5120 -S 4927,1970)]:SIZE: ' \
'*-X+[Filter out messages via regular expression matching on the response'\''s body/headers (ex\: -X '\''^ignore me\$'\'')]:REGEX: ' \
'*--filter-regex=[Filter out messages via regular expression matching on the response'\''s body/headers (ex\: -X '\''^ignore me\$'\'')]:REGEX: ' \
'*-W+[Filter out messages of a particular word count (ex\: -W 312 -W 91,82)]:WORDS: ' \
'*--filter-words=[Filter out messages of a particular word count (ex\: -W 312 -W 91,82)]:WORDS: ' \
'*-N+[Filter out messages of a particular line count (ex\: -N 20 -N 31,30)]:LINES: ' \
'*--filter-lines=[Filter out messages of a particular line count (ex\: -N 20 -N 31,30)]:LINES: ' \
'(-s --status-codes)*-C+[Filter out status codes (deny list) (ex\: -C 200 -C 401)]:STATUS_CODE: ' \
'(-s --status-codes)*--filter-status=[Filter out status codes (deny list) (ex\: -C 200 -C 401)]:STATUS_CODE: ' \
'*--filter-similar-to=[Filter out pages that are similar to the given page (ex. --filter-similar-to http\://site.xyz/soft404)]:UNWANTED_PAGE:_urls' \
'*-s+[Status Codes to include (allow list) (default\: All Status Codes)]:STATUS_CODE: ' \
'*--status-codes=[Status Codes to include (allow list) (default\: All Status Codes)]:STATUS_CODE: ' \
'-T+[Number of seconds before a client'\''s request times out (default\: 7)]:SECONDS: ' \
'--timeout=[Number of seconds before a client'\''s request times out (default\: 7)]:SECONDS: ' \
'--server-certs=[Add custom root certificate(s) for servers with unknown certificates]:PEM|DER:_files' \
'--client-cert=[Add a PEM encoded certificate for mutual authentication (mTLS)]:PEM:_files' \
'--client-key=[Add a PEM encoded private key for mutual authentication (mTLS)]:PEM:_files' \
'-t+[Number of concurrent threads (default\: 50)]:THREADS: ' \
'--threads=[Number of concurrent threads (default\: 50)]:THREADS: ' \
'-d+[Maximum recursion depth, a depth of 0 is infinite recursion (default\: 4)]:RECURSION_DEPTH: ' \
'--depth=[Maximum recursion depth, a depth of 0 is infinite recursion (default\: 4)]:RECURSION_DEPTH: ' \
'-L+[Limit total number of concurrent scans (default\: 0, i.e. no limit)]:SCAN_LIMIT: ' \
'--scan-limit=[Limit total number of concurrent scans (default\: 0, i.e. no limit)]:SCAN_LIMIT: ' \
'(-v --verbosity)--parallel=[Run parallel feroxbuster instances (one child process per url passed via stdin)]:PARALLEL_SCANS: ' \
'(--auto-tune)--rate-limit=[Limit number of requests per second (per directory) (default\: 0, i.e. no limit)]:RATE_LIMIT: ' \
'--time-limit=[Limit total run time of all scans (ex\: --time-limit 10m)]:TIME_SPEC: ' \
'-w+[Path or URL of the wordlist]:FILE:_files' \
'--wordlist=[Path or URL of the wordlist]:FILE:_files' \
'-B+[Automatically request likely backup extensions for "found" urls (default\: ~, .bak, .bak2, .old, .1)]' \
'--collect-backups=[Automatically request likely backup extensions for "found" urls (default\: ~, .bak, .bak2, .old, .1)]' \
'*-I+[File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)]:FILE_EXTENSION: ' \
'*--dont-collect=[File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)]:FILE_EXTENSION: ' \
'-o+[Output file to write results to (use w/ --json for JSON entries)]:FILE:_files' \
'--output=[Output file to write results to (use w/ --json for JSON entries)]:FILE:_files' \
'--debug-log=[Output file to write log entries (use w/ --json for JSON entries)]:FILE:_files' \
'(-u --url)--stdin[Read url(s) from STDIN]' \
'(-p --proxy -k --insecure --burp-replay)--burp[Set --proxy to http://127.0.0.1:8080 and set --insecure to true]' \
'(-P --replay-proxy -k --insecure)--burp-replay[Set --replay-proxy to http://127.0.0.1:8080 and set --insecure to true]' \
'(-p --proxy -k --insecure --burp-replay)--burp[Set --proxy to http\://127.0.0.1\:8080 and set --insecure to true]' \
'(-P --replay-proxy -k --insecure)--burp-replay[Set --replay-proxy to http\://127.0.0.1\:8080 and set --insecure to true]' \
'(--rate-limit --auto-bail)--smart[Set --auto-tune, --collect-words, and --collect-backups to true]' \
'(--rate-limit --auto-bail)--thorough[Use the same settings as --smart and set --collect-extensions to true]' \
'-A[Use a random User-Agent]' \
@@ -85,8 +90,8 @@ _feroxbuster() {
'-n[Do not scan recursively]' \
'--no-recursion[Do not scan recursively]' \
'(-n --no-recursion)--force-recursion[Force recursion attempts on all '\''found'\'' endpoints (still respects recursion depth)]' \
'-e[Extract links from response body (html, javascript, etc...); make new requests based on findings (default: true)]' \
'--extract-links[Extract links from response body (html, javascript, etc...); make new requests based on findings (default: true)]' \
'-e[Extract links from response body (html, javascript, etc...); make new requests based on findings (default\: true)]' \
'--extract-links[Extract links from response body (html, javascript, etc...); make new requests based on findings (default\: true)]' \
'--dont-extract-links[Don'\''t extract links from response body (html, javascript, etc...)]' \
'(--auto-bail)--auto-tune[Automatically lower scan rate when an excessive amount of errors are encountered]' \
'--auto-bail[Automatically stop scanning when an excessive amount of errors are encountered]' \
@@ -94,13 +99,11 @@ _feroxbuster() {
'--dont-filter[Don'\''t auto-filter wildcard responses]' \
'-E[Automatically discover extensions and add them to --extensions (unless they'\''re in --dont-collect)]' \
'--collect-extensions[Automatically discover extensions and add them to --extensions (unless they'\''re in --dont-collect)]' \
'-B[Automatically request likely backup extensions for "found" urls]' \
'--collect-backups[Automatically request likely backup extensions for "found" urls]' \
'-g[Automatically discover important words from within responses and add them to the wordlist]' \
'--collect-words[Automatically discover important words from within responses and add them to the wordlist]' \
'(--silent)*-v[Increase verbosity level (use -vv or more for greater effect. \[CAUTION\] 4 -v'\''s is probably too much)]' \
'(--silent)*--verbosity[Increase verbosity level (use -vv or more for greater effect. \[CAUTION\] 4 -v'\''s is probably too much)]' \
'(-q --quiet)--silent[Only print URLs + turn off logging (good for piping a list of urls to other commands)]' \
'(-q --quiet)--silent[Only print URLs (or JSON w/ --json) + turn off logging (good for piping a list of urls to other commands)]' \
'-q[Hide progress bars and banner (good for tmux windows w/ notifications)]' \
'--quiet[Hide progress bars and banner (good for tmux windows w/ notifications)]' \
'--json[Emit JSON logs to --output and --debug-log instead of normal text]' \

View File

@@ -26,51 +26,56 @@ Register-ArgumentCompleter -Native -CommandName 'feroxbuster' -ScriptBlock {
[CompletionResult]::new('--resume-from', 'resume-from', [CompletionResultType]::ParameterName, 'State file from which to resume a partially complete scan (ex. --resume-from ferox-1606586780.state)')
[CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)')
[CompletionResult]::new('--proxy', 'proxy', [CompletionResultType]::ParameterName, 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)')
[CompletionResult]::new('-P', 'P', [CompletionResultType]::ParameterName, 'Send only unfiltered requests through a Replay Proxy, instead of all requests')
[CompletionResult]::new('-P', 'P ', [CompletionResultType]::ParameterName, 'Send only unfiltered requests through a Replay Proxy, instead of all requests')
[CompletionResult]::new('--replay-proxy', 'replay-proxy', [CompletionResultType]::ParameterName, 'Send only unfiltered requests through a Replay Proxy, instead of all requests')
[CompletionResult]::new('-R', 'R', [CompletionResultType]::ParameterName, 'Status Codes to send through a Replay Proxy when found (default: --status-codes value)')
[CompletionResult]::new('-R', 'R ', [CompletionResultType]::ParameterName, 'Status Codes to send through a Replay Proxy when found (default: --status-codes value)')
[CompletionResult]::new('--replay-codes', 'replay-codes', [CompletionResultType]::ParameterName, 'Status Codes to send through a Replay Proxy when found (default: --status-codes value)')
[CompletionResult]::new('-a', 'a', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.9.5)')
[CompletionResult]::new('--user-agent', 'user-agent', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.9.5)')
[CompletionResult]::new('-x', 'x', [CompletionResultType]::ParameterName, 'File extension(s) to search for (ex: -x php -x pdf js)')
[CompletionResult]::new('--extensions', 'extensions', [CompletionResultType]::ParameterName, 'File extension(s) to search for (ex: -x php -x pdf js)')
[CompletionResult]::new('-a', 'a', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.10.4)')
[CompletionResult]::new('--user-agent', 'user-agent', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.10.4)')
[CompletionResult]::new('-x', 'x', [CompletionResultType]::ParameterName, 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)')
[CompletionResult]::new('--extensions', 'extensions', [CompletionResultType]::ParameterName, 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)')
[CompletionResult]::new('-m', 'm', [CompletionResultType]::ParameterName, 'Which HTTP request method(s) should be sent (default: GET)')
[CompletionResult]::new('--methods', 'methods', [CompletionResultType]::ParameterName, 'Which HTTP request method(s) should be sent (default: GET)')
[CompletionResult]::new('--data', 'data', [CompletionResultType]::ParameterName, 'Request''s Body; can read data from a file if input starts with an @ (ex: @post.bin)')
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'Specify HTTP headers to be used in each request (ex: -H Header:val -H ''stuff: things'')')
[CompletionResult]::new('-H', 'H ', [CompletionResultType]::ParameterName, 'Specify HTTP headers to be used in each request (ex: -H Header:val -H ''stuff: things'')')
[CompletionResult]::new('--headers', 'headers', [CompletionResultType]::ParameterName, 'Specify HTTP headers to be used in each request (ex: -H Header:val -H ''stuff: things'')')
[CompletionResult]::new('-b', 'b', [CompletionResultType]::ParameterName, 'Specify HTTP cookies to be used in each request (ex: -b stuff=things)')
[CompletionResult]::new('--cookies', 'cookies', [CompletionResultType]::ParameterName, 'Specify HTTP cookies to be used in each request (ex: -b stuff=things)')
[CompletionResult]::new('-Q', 'Q', [CompletionResultType]::ParameterName, 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)')
[CompletionResult]::new('-Q', 'Q ', [CompletionResultType]::ParameterName, 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)')
[CompletionResult]::new('--query', 'query', [CompletionResultType]::ParameterName, 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)')
[CompletionResult]::new('--dont-scan', 'dont-scan', [CompletionResultType]::ParameterName, 'URL(s) or Regex Pattern(s) to exclude from recursion/scans')
[CompletionResult]::new('-S', 'S', [CompletionResultType]::ParameterName, 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)')
[CompletionResult]::new('-S', 'S ', [CompletionResultType]::ParameterName, 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)')
[CompletionResult]::new('--filter-size', 'filter-size', [CompletionResultType]::ParameterName, 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)')
[CompletionResult]::new('-X', 'X', [CompletionResultType]::ParameterName, 'Filter out messages via regular expression matching on the response''s body (ex: -X ''^ignore me$'')')
[CompletionResult]::new('--filter-regex', 'filter-regex', [CompletionResultType]::ParameterName, 'Filter out messages via regular expression matching on the response''s body (ex: -X ''^ignore me$'')')
[CompletionResult]::new('-W', 'W', [CompletionResultType]::ParameterName, 'Filter out messages of a particular word count (ex: -W 312 -W 91,82)')
[CompletionResult]::new('-X', 'X ', [CompletionResultType]::ParameterName, 'Filter out messages via regular expression matching on the response''s body/headers (ex: -X ''^ignore me$'')')
[CompletionResult]::new('--filter-regex', 'filter-regex', [CompletionResultType]::ParameterName, 'Filter out messages via regular expression matching on the response''s body/headers (ex: -X ''^ignore me$'')')
[CompletionResult]::new('-W', 'W ', [CompletionResultType]::ParameterName, 'Filter out messages of a particular word count (ex: -W 312 -W 91,82)')
[CompletionResult]::new('--filter-words', 'filter-words', [CompletionResultType]::ParameterName, 'Filter out messages of a particular word count (ex: -W 312 -W 91,82)')
[CompletionResult]::new('-N', 'N', [CompletionResultType]::ParameterName, 'Filter out messages of a particular line count (ex: -N 20 -N 31,30)')
[CompletionResult]::new('-N', 'N ', [CompletionResultType]::ParameterName, 'Filter out messages of a particular line count (ex: -N 20 -N 31,30)')
[CompletionResult]::new('--filter-lines', 'filter-lines', [CompletionResultType]::ParameterName, 'Filter out messages of a particular line count (ex: -N 20 -N 31,30)')
[CompletionResult]::new('-C', 'C', [CompletionResultType]::ParameterName, 'Filter out status codes (deny list) (ex: -C 200 -C 401)')
[CompletionResult]::new('-C', 'C ', [CompletionResultType]::ParameterName, 'Filter out status codes (deny list) (ex: -C 200 -C 401)')
[CompletionResult]::new('--filter-status', 'filter-status', [CompletionResultType]::ParameterName, 'Filter out status codes (deny list) (ex: -C 200 -C 401)')
[CompletionResult]::new('--filter-similar-to', 'filter-similar-to', [CompletionResultType]::ParameterName, 'Filter out pages that are similar to the given page (ex. --filter-similar-to http://site.xyz/soft404)')
[CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Status Codes to include (allow list) (default: All Status Codes)')
[CompletionResult]::new('--status-codes', 'status-codes', [CompletionResultType]::ParameterName, 'Status Codes to include (allow list) (default: All Status Codes)')
[CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'Number of seconds before a client''s request times out (default: 7)')
[CompletionResult]::new('-T', 'T ', [CompletionResultType]::ParameterName, 'Number of seconds before a client''s request times out (default: 7)')
[CompletionResult]::new('--timeout', 'timeout', [CompletionResultType]::ParameterName, 'Number of seconds before a client''s request times out (default: 7)')
[CompletionResult]::new('--server-certs', 'server-certs', [CompletionResultType]::ParameterName, 'Add custom root certificate(s) for servers with unknown certificates')
[CompletionResult]::new('--client-cert', 'client-cert', [CompletionResultType]::ParameterName, 'Add a PEM encoded certificate for mutual authentication (mTLS)')
[CompletionResult]::new('--client-key', 'client-key', [CompletionResultType]::ParameterName, 'Add a PEM encoded private key for mutual authentication (mTLS)')
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Number of concurrent threads (default: 50)')
[CompletionResult]::new('--threads', 'threads', [CompletionResultType]::ParameterName, 'Number of concurrent threads (default: 50)')
[CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)')
[CompletionResult]::new('--depth', 'depth', [CompletionResultType]::ParameterName, 'Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)')
[CompletionResult]::new('-L', 'L', [CompletionResultType]::ParameterName, 'Limit total number of concurrent scans (default: 0, i.e. no limit)')
[CompletionResult]::new('-L', 'L ', [CompletionResultType]::ParameterName, 'Limit total number of concurrent scans (default: 0, i.e. no limit)')
[CompletionResult]::new('--scan-limit', 'scan-limit', [CompletionResultType]::ParameterName, 'Limit total number of concurrent scans (default: 0, i.e. no limit)')
[CompletionResult]::new('--parallel', 'parallel', [CompletionResultType]::ParameterName, 'Run parallel feroxbuster instances (one child process per url passed via stdin)')
[CompletionResult]::new('--rate-limit', 'rate-limit', [CompletionResultType]::ParameterName, 'Limit number of requests per second (per directory) (default: 0, i.e. no limit)')
[CompletionResult]::new('--time-limit', 'time-limit', [CompletionResultType]::ParameterName, 'Limit total run time of all scans (ex: --time-limit 10m)')
[CompletionResult]::new('-w', 'w', [CompletionResultType]::ParameterName, 'Path or URL of the wordlist')
[CompletionResult]::new('--wordlist', 'wordlist', [CompletionResultType]::ParameterName, 'Path or URL of the wordlist')
[CompletionResult]::new('-I', 'I', [CompletionResultType]::ParameterName, 'File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)')
[CompletionResult]::new('-B', 'B ', [CompletionResultType]::ParameterName, 'Automatically request likely backup extensions for "found" urls (default: ~, .bak, .bak2, .old, .1)')
[CompletionResult]::new('--collect-backups', 'collect-backups', [CompletionResultType]::ParameterName, 'Automatically request likely backup extensions for "found" urls (default: ~, .bak, .bak2, .old, .1)')
[CompletionResult]::new('-I', 'I ', [CompletionResultType]::ParameterName, 'File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)')
[CompletionResult]::new('--dont-collect', 'dont-collect', [CompletionResultType]::ParameterName, 'File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)')
[CompletionResult]::new('-o', 'o', [CompletionResultType]::ParameterName, 'Output file to write results to (use w/ --json for JSON entries)')
[CompletionResult]::new('--output', 'output', [CompletionResultType]::ParameterName, 'Output file to write results to (use w/ --json for JSON entries)')
@@ -80,7 +85,7 @@ Register-ArgumentCompleter -Native -CommandName 'feroxbuster' -ScriptBlock {
[CompletionResult]::new('--burp-replay', 'burp-replay', [CompletionResultType]::ParameterName, 'Set --replay-proxy to http://127.0.0.1:8080 and set --insecure to true')
[CompletionResult]::new('--smart', 'smart', [CompletionResultType]::ParameterName, 'Set --auto-tune, --collect-words, and --collect-backups to true')
[CompletionResult]::new('--thorough', 'thorough', [CompletionResultType]::ParameterName, 'Use the same settings as --smart and set --collect-extensions to true')
[CompletionResult]::new('-A', 'A', [CompletionResultType]::ParameterName, 'Use a random User-Agent')
[CompletionResult]::new('-A', 'A ', [CompletionResultType]::ParameterName, 'Use a random User-Agent')
[CompletionResult]::new('--random-agent', 'random-agent', [CompletionResultType]::ParameterName, 'Use a random User-Agent')
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Append / to each request''s URL')
[CompletionResult]::new('--add-slash', 'add-slash', [CompletionResultType]::ParameterName, 'Append / to each request''s URL')
@@ -96,26 +101,24 @@ Register-ArgumentCompleter -Native -CommandName 'feroxbuster' -ScriptBlock {
[CompletionResult]::new('--dont-extract-links', 'dont-extract-links', [CompletionResultType]::ParameterName, 'Don''t extract links from response body (html, javascript, etc...)')
[CompletionResult]::new('--auto-tune', 'auto-tune', [CompletionResultType]::ParameterName, 'Automatically lower scan rate when an excessive amount of errors are encountered')
[CompletionResult]::new('--auto-bail', 'auto-bail', [CompletionResultType]::ParameterName, 'Automatically stop scanning when an excessive amount of errors are encountered')
[CompletionResult]::new('-D', 'D', [CompletionResultType]::ParameterName, 'Don''t auto-filter wildcard responses')
[CompletionResult]::new('-D', 'D ', [CompletionResultType]::ParameterName, 'Don''t auto-filter wildcard responses')
[CompletionResult]::new('--dont-filter', 'dont-filter', [CompletionResultType]::ParameterName, 'Don''t auto-filter wildcard responses')
[CompletionResult]::new('-E', 'E', [CompletionResultType]::ParameterName, 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)')
[CompletionResult]::new('-E', 'E ', [CompletionResultType]::ParameterName, 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)')
[CompletionResult]::new('--collect-extensions', 'collect-extensions', [CompletionResultType]::ParameterName, 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)')
[CompletionResult]::new('-B', 'B', [CompletionResultType]::ParameterName, 'Automatically request likely backup extensions for "found" urls')
[CompletionResult]::new('--collect-backups', 'collect-backups', [CompletionResultType]::ParameterName, 'Automatically request likely backup extensions for "found" urls')
[CompletionResult]::new('-g', 'g', [CompletionResultType]::ParameterName, 'Automatically discover important words from within responses and add them to the wordlist')
[CompletionResult]::new('--collect-words', 'collect-words', [CompletionResultType]::ParameterName, 'Automatically discover important words from within responses and add them to the wordlist')
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)')
[CompletionResult]::new('--verbosity', 'verbosity', [CompletionResultType]::ParameterName, 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)')
[CompletionResult]::new('--silent', 'silent', [CompletionResultType]::ParameterName, 'Only print URLs + turn off logging (good for piping a list of urls to other commands)')
[CompletionResult]::new('--silent', 'silent', [CompletionResultType]::ParameterName, 'Only print URLs (or JSON w/ --json) + turn off logging (good for piping a list of urls to other commands)')
[CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Hide progress bars and banner (good for tmux windows w/ notifications)')
[CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Hide progress bars and banner (good for tmux windows w/ notifications)')
[CompletionResult]::new('--json', 'json', [CompletionResultType]::ParameterName, 'Emit JSON logs to --output and --debug-log instead of normal text')
[CompletionResult]::new('--no-state', 'no-state', [CompletionResultType]::ParameterName, 'Disable state output file (*.state)')
[CompletionResult]::new('-U', 'U', [CompletionResultType]::ParameterName, 'Update feroxbuster to the latest version')
[CompletionResult]::new('-U', 'U ', [CompletionResultType]::ParameterName, 'Update feroxbuster to the latest version')
[CompletionResult]::new('--update', 'update', [CompletionResultType]::ParameterName, 'Update feroxbuster to the latest version')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('-V', 'V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version')
break
}

View File

@@ -19,7 +19,7 @@ _feroxbuster() {
case "${cmd}" in
feroxbuster)
opts="-u -p -P -R -a -A -x -m -H -b -Q -f -S -X -W -N -C -s -T -r -k -t -n -d -e -L -w -D -E -B -g -I -v -q -o -U -h -V --url --stdin --resume-from --burp --burp-replay --smart --thorough --proxy --replay-proxy --replay-codes --user-agent --random-agent --extensions --methods --data --headers --cookies --query --add-slash --dont-scan --filter-size --filter-regex --filter-words --filter-lines --filter-status --filter-similar-to --status-codes --timeout --redirects --insecure --threads --no-recursion --depth --force-recursion --extract-links --dont-extract-links --scan-limit --parallel --rate-limit --time-limit --wordlist --auto-tune --auto-bail --dont-filter --collect-extensions --collect-backups --collect-words --dont-collect --verbosity --silent --quiet --json --output --debug-log --no-state --update --help --version"
opts="-u -p -P -R -a -A -x -m -H -b -Q -f -S -X -W -N -C -s -T -r -k -t -n -d -e -L -w -D -E -B -g -I -v -q -o -U -h -V --url --stdin --resume-from --burp --burp-replay --smart --thorough --proxy --replay-proxy --replay-codes --user-agent --random-agent --extensions --methods --data --headers --cookies --query --add-slash --dont-scan --filter-size --filter-regex --filter-words --filter-lines --filter-status --filter-similar-to --status-codes --timeout --redirects --insecure --server-certs --client-cert --client-key --threads --no-recursion --depth --force-recursion --extract-links --dont-extract-links --scan-limit --parallel --rate-limit --time-limit --wordlist --auto-tune --auto-bail --dont-filter --collect-extensions --collect-backups --collect-words --dont-collect --verbosity --silent --quiet --json --output --debug-log --no-state --update --help --version"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
@@ -34,7 +34,18 @@ _feroxbuster() {
return 0
;;
--resume-from)
local oldifs
if [ -n "${IFS+x}" ]; then
oldifs="$IFS"
fi
IFS=$'\n'
COMPREPLY=($(compgen -f "${cur}"))
if [ -n "${oldifs+x}" ]; then
IFS="$oldifs"
fi
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
compopt -o filenames
fi
return 0
;;
--proxy)
@@ -177,6 +188,51 @@ _feroxbuster() {
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--server-certs)
local oldifs
if [ -n "${IFS+x}" ]; then
oldifs="$IFS"
fi
IFS=$'\n'
COMPREPLY=($(compgen -f "${cur}"))
if [ -n "${oldifs+x}" ]; then
IFS="$oldifs"
fi
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
compopt -o filenames
fi
return 0
;;
--client-cert)
local oldifs
if [ -n "${IFS+x}" ]; then
oldifs="$IFS"
fi
IFS=$'\n'
COMPREPLY=($(compgen -f "${cur}"))
if [ -n "${oldifs+x}" ]; then
IFS="$oldifs"
fi
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
compopt -o filenames
fi
return 0
;;
--client-key)
local oldifs
if [ -n "${IFS+x}" ]; then
oldifs="$IFS"
fi
IFS=$'\n'
COMPREPLY=($(compgen -f "${cur}"))
if [ -n "${oldifs+x}" ]; then
IFS="$oldifs"
fi
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
compopt -o filenames
fi
return 0
;;
--threads)
COMPREPLY=($(compgen -f "${cur}"))
return 0
@@ -214,10 +270,40 @@ _feroxbuster() {
return 0
;;
--wordlist)
local oldifs
if [ -n "${IFS+x}" ]; then
oldifs="$IFS"
fi
IFS=$'\n'
COMPREPLY=($(compgen -f "${cur}"))
if [ -n "${oldifs+x}" ]; then
IFS="$oldifs"
fi
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
compopt -o filenames
fi
return 0
;;
-w)
local oldifs
if [ -n "${IFS+x}" ]; then
oldifs="$IFS"
fi
IFS=$'\n'
COMPREPLY=($(compgen -f "${cur}"))
if [ -n "${oldifs+x}" ]; then
IFS="$oldifs"
fi
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
compopt -o filenames
fi
return 0
;;
--collect-backups)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-B)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
@@ -230,15 +316,48 @@ _feroxbuster() {
return 0
;;
--output)
local oldifs
if [ -n "${IFS+x}" ]; then
oldifs="$IFS"
fi
IFS=$'\n'
COMPREPLY=($(compgen -f "${cur}"))
if [ -n "${oldifs+x}" ]; then
IFS="$oldifs"
fi
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
compopt -o filenames
fi
return 0
;;
-o)
local oldifs
if [ -n "${IFS+x}" ]; then
oldifs="$IFS"
fi
IFS=$'\n'
COMPREPLY=($(compgen -f "${cur}"))
if [ -n "${oldifs+x}" ]; then
IFS="$oldifs"
fi
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
compopt -o filenames
fi
return 0
;;
--debug-log)
local oldifs
if [ -n "${IFS+x}" ]; then
oldifs="$IFS"
fi
IFS=$'\n'
COMPREPLY=($(compgen -f "${cur}"))
if [ -n "${oldifs+x}" ]; then
IFS="$oldifs"
fi
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
compopt -o filenames
fi
return 0
;;
*)
@@ -251,4 +370,8 @@ _feroxbuster() {
esac
}
complete -F _feroxbuster -o bashdefault -o default -o plusdirs feroxbuster
if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then
complete -F _feroxbuster -o nosort -o bashdefault -o default -o plusdirs feroxbuster
else
complete -F _feroxbuster -o bashdefault -o default -o plusdirs feroxbuster
fi

View File

@@ -27,10 +27,10 @@ set edit:completion:arg-completer[feroxbuster] = {|@words|
cand --replay-proxy 'Send only unfiltered requests through a Replay Proxy, instead of all requests'
cand -R 'Status Codes to send through a Replay Proxy when found (default: --status-codes value)'
cand --replay-codes 'Status Codes to send through a Replay Proxy when found (default: --status-codes value)'
cand -a 'Sets the User-Agent (default: feroxbuster/2.9.5)'
cand --user-agent 'Sets the User-Agent (default: feroxbuster/2.9.5)'
cand -x 'File extension(s) to search for (ex: -x php -x pdf js)'
cand --extensions 'File extension(s) to search for (ex: -x php -x pdf js)'
cand -a 'Sets the User-Agent (default: feroxbuster/2.10.4)'
cand --user-agent 'Sets the User-Agent (default: feroxbuster/2.10.4)'
cand -x 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)'
cand --extensions 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)'
cand -m 'Which HTTP request method(s) should be sent (default: GET)'
cand --methods 'Which HTTP request method(s) should be sent (default: GET)'
cand --data 'Request''s Body; can read data from a file if input starts with an @ (ex: @post.bin)'
@@ -43,8 +43,8 @@ set edit:completion:arg-completer[feroxbuster] = {|@words|
cand --dont-scan 'URL(s) or Regex Pattern(s) to exclude from recursion/scans'
cand -S 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)'
cand --filter-size 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)'
cand -X 'Filter out messages via regular expression matching on the response''s body (ex: -X ''^ignore me$'')'
cand --filter-regex 'Filter out messages via regular expression matching on the response''s body (ex: -X ''^ignore me$'')'
cand -X 'Filter out messages via regular expression matching on the response''s body/headers (ex: -X ''^ignore me$'')'
cand --filter-regex 'Filter out messages via regular expression matching on the response''s body/headers (ex: -X ''^ignore me$'')'
cand -W 'Filter out messages of a particular word count (ex: -W 312 -W 91,82)'
cand --filter-words 'Filter out messages of a particular word count (ex: -W 312 -W 91,82)'
cand -N 'Filter out messages of a particular line count (ex: -N 20 -N 31,30)'
@@ -56,6 +56,9 @@ set edit:completion:arg-completer[feroxbuster] = {|@words|
cand --status-codes 'Status Codes to include (allow list) (default: All Status Codes)'
cand -T 'Number of seconds before a client''s request times out (default: 7)'
cand --timeout 'Number of seconds before a client''s request times out (default: 7)'
cand --server-certs 'Add custom root certificate(s) for servers with unknown certificates'
cand --client-cert 'Add a PEM encoded certificate for mutual authentication (mTLS)'
cand --client-key 'Add a PEM encoded private key for mutual authentication (mTLS)'
cand -t 'Number of concurrent threads (default: 50)'
cand --threads 'Number of concurrent threads (default: 50)'
cand -d 'Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)'
@@ -67,6 +70,8 @@ set edit:completion:arg-completer[feroxbuster] = {|@words|
cand --time-limit 'Limit total run time of all scans (ex: --time-limit 10m)'
cand -w 'Path or URL of the wordlist'
cand --wordlist 'Path or URL of the wordlist'
cand -B 'Automatically request likely backup extensions for "found" urls (default: ~, .bak, .bak2, .old, .1)'
cand --collect-backups 'Automatically request likely backup extensions for "found" urls (default: ~, .bak, .bak2, .old, .1)'
cand -I 'File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)'
cand --dont-collect 'File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)'
cand -o 'Output file to write results to (use w/ --json for JSON entries)'
@@ -97,13 +102,11 @@ set edit:completion:arg-completer[feroxbuster] = {|@words|
cand --dont-filter 'Don''t auto-filter wildcard responses'
cand -E 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)'
cand --collect-extensions 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)'
cand -B 'Automatically request likely backup extensions for "found" urls'
cand --collect-backups 'Automatically request likely backup extensions for "found" urls'
cand -g 'Automatically discover important words from within responses and add them to the wordlist'
cand --collect-words 'Automatically discover important words from within responses and add them to the wordlist'
cand -v 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)'
cand --verbosity 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)'
cand --silent 'Only print URLs + turn off logging (good for piping a list of urls to other commands)'
cand --silent 'Only print URLs (or JSON w/ --json) + turn off logging (good for piping a list of urls to other commands)'
cand -q 'Hide progress bars and banner (good for tmux windows w/ notifications)'
cand --quiet 'Hide progress bars and banner (good for tmux windows w/ notifications)'
cand --json 'Emit JSON logs to --output and --debug-log instead of normal text'

View File

@@ -1,13 +1,15 @@
use super::entry::BannerEntry;
use crate::{
client,
config::Configuration,
event_handlers::Handles,
utils::{logged_request, parse_url_with_raw_path, status_colorizer},
utils::{make_request, parse_url_with_raw_path, status_colorizer},
DEFAULT_IGNORED_EXTENSIONS, DEFAULT_METHOD, DEFAULT_STATUS_CODES, VERSION,
};
use anyhow::{bail, Result};
use console::{style, Emoji};
use serde_json::Value;
use std::collections::HashMap;
use std::{io::Write, sync::Arc};
/// Url used to query github's api; specifically used to look for the latest tagged release name
@@ -58,6 +60,15 @@ pub struct Banner {
/// represents Configuration.proxy
proxy: BannerEntry,
/// represents Configuration.client_key
client_key: BannerEntry,
/// represents Configuration.client_cert
client_cert: BannerEntry,
/// represents Configuration.server_certs
server_certs: BannerEntry,
/// represents Configuration.replay_proxy
replay_proxy: BannerEntry,
@@ -322,6 +333,13 @@ impl Banner {
let auto_bail = BannerEntry::new("🙅", "Auto Bail", &config.auto_bail.to_string());
let cfg = BannerEntry::new("💉", "Config File", &config.config);
let proxy = BannerEntry::new("💎", "Proxy", &config.proxy);
let server_certs = BannerEntry::new(
"🏅",
"Server Certificates",
&format!("[{}]", config.server_certs.join(", ")),
);
let client_cert = BannerEntry::new("🏅", "Client Certificate", &config.client_cert);
let client_key = BannerEntry::new("🔑", "Client Key", &config.client_key);
let threads = BannerEntry::new("🚀", "Threads", &config.threads.to_string());
let wordlist = BannerEntry::new("📖", "Wordlist", &config.wordlist);
let timeout = BannerEntry::new("💥", "Timeout (secs)", &config.timeout.to_string());
@@ -401,6 +419,9 @@ impl Banner {
auto_bail,
auto_tune,
proxy,
client_cert,
client_key,
server_certs,
replay_codes,
replay_proxy,
headers,
@@ -479,7 +500,34 @@ by Ben "epi" Risher {} ver: {}"#,
let api_url = parse_url_with_raw_path(url)?;
let result = logged_request(&api_url, DEFAULT_METHOD, None, handles.clone()).await?;
// we don't want to leak sensitive header info / include auth headers
// with the github api request, so we'll build a client specifically
// for this task. thanks to @stuhlmann for the suggestion!
let client = client::initialize(
handles.config.timeout,
"feroxbuster-update-check",
handles.config.redirects,
handles.config.insecure,
&HashMap::new(),
Some(&handles.config.proxy),
&handles.config.server_certs,
Some(&handles.config.client_cert),
Some(&handles.config.client_key),
)?;
let level = handles.config.output_level;
let tx_stats = handles.stats.tx.clone();
let result = make_request(
&client,
&api_url,
DEFAULT_METHOD,
None,
level,
&handles.config,
tx_stats,
)
.await?;
let body = result.text().await?;
let json_response: Value = serde_json::from_str(&body)?;
@@ -555,6 +603,18 @@ by Ben "epi" Risher {} ver: {}"#,
writeln!(&mut writer, "{}", self.proxy)?;
}
if !config.client_cert.is_empty() {
writeln!(&mut writer, "{}", self.client_cert)?;
}
if !config.client_key.is_empty() {
writeln!(&mut writer, "{}", self.client_key)?;
}
if !config.server_certs.is_empty() {
writeln!(&mut writer, "{}", self.server_certs)?;
}
if !config.replay_proxy.is_empty() {
// i include replay codes logic here because in config.rs, replay codes are set to the
// value in status codes, meaning it's never empty

View File

@@ -1,19 +1,29 @@
use anyhow::Result;
use anyhow::{Context, Result};
use reqwest::header::HeaderMap;
use reqwest::{redirect::Policy, Client, Proxy};
use std::collections::HashMap;
use std::convert::TryInto;
use std::path::Path;
use std::time::Duration;
/// Create and return an instance of [reqwest::Client](https://docs.rs/reqwest/latest/reqwest/struct.Client.html)
pub fn initialize(
/// For now, silence clippy for this one
#[allow(clippy::too_many_arguments)]
pub fn initialize<I>(
timeout: u64,
user_agent: &str,
redirects: bool,
insecure: bool,
headers: &HashMap<String, String>,
proxy: Option<&str>,
) -> Result<Client> {
server_certs: I,
client_cert: Option<&str>,
client_key: Option<&str>,
) -> Result<Client>
where
I: IntoIterator,
I::Item: AsRef<Path> + std::fmt::Debug,
{
let policy = if redirects {
Policy::limited(10)
} else {
@@ -22,7 +32,7 @@ pub fn initialize(
let header_map: HeaderMap = headers.try_into()?;
let client = Client::builder()
let mut client = Client::builder()
.timeout(Duration::new(timeout, 0))
.user_agent(user_agent)
.danger_accept_invalid_certs(insecure)
@@ -34,7 +44,41 @@ pub fn initialize(
if !some_proxy.is_empty() {
// it's not an empty string; set the proxy
let proxy_obj = Proxy::all(some_proxy)?;
return Ok(client.proxy(proxy_obj).build()?);
// just add the proxy to the client
// don't build and return it just yet
client = client.proxy(proxy_obj);
}
}
for cert_path in server_certs {
let buf = std::fs::read(&cert_path)?;
let cert = match reqwest::Certificate::from_pem(&buf) {
Ok(cert) => cert,
Err(err) => reqwest::Certificate::from_der(&buf).with_context(|| {
format!(
"{:?} does not contain a valid PEM or DER certificate\n{}",
&cert_path, err
)
})?,
};
client = client.add_root_certificate(cert);
}
if let (Some(cert_path), Some(key_path)) = (client_cert, client_key) {
if !cert_path.is_empty() && !key_path.is_empty() {
let cert = std::fs::read(cert_path)?;
let key = std::fs::read(key_path)?;
let identity = reqwest::Identity::from_pkcs8_pem(&cert, &key).with_context(|| {
format!(
"either {} or {} are invalid; expecting PEM encoded certificate and key",
cert_path, key_path
)
})?;
client = client.identity(identity);
}
}
@@ -50,7 +94,18 @@ mod tests {
/// create client with a bad proxy, expect panic
fn client_with_bad_proxy() {
let headers = HashMap::new();
initialize(0, "stuff", true, false, &headers, Some("not a valid proxy")).unwrap();
initialize(
0,
"stuff",
true,
false,
&headers,
Some("not a valid proxy"),
Vec::<String>::new(),
None,
None,
)
.unwrap();
}
#[test]
@@ -58,6 +113,99 @@ mod tests {
fn client_with_good_proxy() {
let headers = HashMap::new();
let proxy = "http://127.0.0.1:8080";
initialize(0, "stuff", true, true, &headers, Some(proxy)).unwrap();
initialize(
0,
"stuff",
true,
true,
&headers,
Some(proxy),
Vec::<String>::new(),
None,
None,
)
.unwrap();
}
#[test]
/// create client with a server cert in pem format, expect no error
fn client_with_valid_server_pem() {
let headers = HashMap::new();
initialize(
0,
"stuff",
true,
true,
&headers,
None,
vec!["tests/mutual-auth/certs/server/server.crt.1".to_string()],
None,
None,
)
.unwrap();
}
#[test]
/// create client with a server cert in der format, expect no error
fn client_with_valid_server_der() {
let headers = HashMap::new();
initialize(
0,
"stuff",
true,
true,
&headers,
None,
vec!["tests/mutual-auth/certs/server/server.der".to_string()],
None,
None,
)
.unwrap();
}
#[test]
/// create client with two server certs (pem and der), expect no error
fn client_with_valid_server_pem_and_der() {
let headers = HashMap::new();
println!("{}", std::env::current_dir().unwrap().display());
initialize(
0,
"stuff",
true,
true,
&headers,
None,
vec![
"tests/mutual-auth/certs/server/server.crt.1".to_string(),
"tests/mutual-auth/certs/server/server.der".to_string(),
],
None,
None,
)
.unwrap();
}
/// create client with invalid certificate, expect panic
#[test]
#[should_panic]
fn client_with_invalid_server_cert() {
let headers = HashMap::new();
initialize(
0,
"stuff",
true,
true,
&headers,
None,
vec!["tests/mutual-auth/certs/client/client.key".to_string()],
None,
None,
)
.unwrap();
}
}

View File

@@ -1,6 +1,6 @@
use super::utils::{
depth, extract_links, ignored_extensions, methods, report_and_exit, save_state,
serialized_type, status_codes, threads, timeout, user_agent, wordlist, OutputLevel,
backup_extensions, depth, extract_links, ignored_extensions, methods, report_and_exit,
save_state, serialized_type, status_codes, threads, timeout, user_agent, wordlist, OutputLevel,
RequesterPolicy,
};
use crate::config::determine_output_level;
@@ -21,7 +21,7 @@ use std::{
collections::HashMap,
env::{current_dir, current_exe},
fs::read_to_string,
path::PathBuf,
path::{Path, PathBuf},
};
/// macro helper to abstract away repetitive configuration updates
@@ -104,6 +104,18 @@ pub struct Configuration {
#[serde(default)]
pub replay_proxy: String,
/// Path to a custom root certificate for connecting to servers with a self-signed certificate
#[serde(default)]
pub server_certs: Vec<String>,
/// Path to a client's PEM encoded X509 certificate used during mutual authentication
#[serde(default)]
pub client_cert: String,
/// Path to a client's PEM encoded PKSC #8 private key used during mutual authentication
#[serde(default)]
pub client_key: String,
/// The target URL
#[serde(default)]
pub target_url: String,
@@ -306,6 +318,9 @@ pub struct Configuration {
#[serde(default)]
pub collect_backups: bool,
#[serde(default = "backup_extensions")]
pub backup_extensions: Vec<String>,
/// Automatically discover important words from within responses and add them to the wordlist
#[serde(default)]
pub collect_words: bool,
@@ -324,8 +339,18 @@ impl Default for Configuration {
fn default() -> Self {
let timeout = timeout();
let user_agent = user_agent();
let client = client::initialize(timeout, &user_agent, false, false, &HashMap::new(), None)
.expect("Could not build client");
let client = client::initialize(
timeout,
&user_agent,
false,
false,
&HashMap::new(),
None,
Vec::<String>::new(),
None,
None,
)
.expect("Could not build client");
let replay_client = None;
let status_codes = status_codes();
let replay_codes = status_codes.clone();
@@ -369,6 +394,8 @@ impl Default for Configuration {
force_recursion: false,
update_app: false,
proxy: String::new(),
client_cert: String::new(),
client_key: String::new(),
config: String::new(),
output: String::new(),
debug_log: String::new(),
@@ -376,6 +403,7 @@ impl Default for Configuration {
time_limit: String::new(),
resume_from: String::new(),
replay_proxy: String::new(),
server_certs: Vec::new(),
queries: Vec::new(),
extensions: Vec::new(),
methods: methods(),
@@ -393,6 +421,7 @@ impl Default for Configuration {
threads: threads(),
wordlist: wordlist(),
dont_collect: ignored_extensions(),
backup_extensions: backup_extensions(),
}
}
}
@@ -425,6 +454,7 @@ impl Configuration {
/// - **extensions**: `None`
/// - **collect_extensions**: `false`
/// - **collect_backups**: `false`
/// - **backup_extensions**: [`DEFAULT_BACKUP_EXTENSIONS`](constant.DEFAULT_BACKUP_EXTENSIONS.html)
/// - **collect_words**: `false`
/// - **dont_collect**: [`DEFAULT_IGNORED_EXTENSIONS`](constant.DEFAULT_RESPONSE_CODES.html)
/// - **methods**: [`DEFAULT_METHOD`](constant.DEFAULT_METHOD.html)
@@ -546,9 +576,7 @@ impl Configuration {
// - current directory
// merge a config found at /etc/feroxbuster/ferox-config.toml
let config_file = PathBuf::new()
.join("/etc/feroxbuster")
.join(DEFAULT_CONFIG_NAME);
let config_file = Path::new("/etc/feroxbuster").join(DEFAULT_CONFIG_NAME);
Self::parse_and_merge_config(config_file, config)?;
// merge a config found at ~/.config/feroxbuster/ferox-config.toml
@@ -592,7 +620,7 @@ impl Configuration {
update_config_if_present!(&mut config.resume_from, args, "resume_from", String);
if let Ok(Some(inner)) = args.try_get_one::<String>("time_limit") {
config.time_limit = inner.to_owned();
inner.clone_into(&mut config.time_limit);
}
if let Some(arg) = args.get_many::<String>("status_codes") {
@@ -616,7 +644,7 @@ impl Configuration {
.collect();
} else {
// not passed in by the user, use whatever value is held in status_codes
config.replay_codes = config.status_codes.clone();
config.replay_codes.clone_from(&config.status_codes);
}
if let Some(arg) = args.get_many::<String>("filter_status") {
@@ -630,9 +658,27 @@ impl Configuration {
}
if let Some(arg) = args.get_many::<String>("extensions") {
config.extensions = arg
.map(|val| val.trim_start_matches('.').to_string())
.collect();
let mut extensions = Vec::<String>::new();
for ext in arg {
if let Some(stripped) = ext.strip_prefix('@') {
let contents = read_to_string(stripped)
.unwrap_or_else(|e| report_and_exit(&e.to_string()));
let exts_from_file = contents.split('\n').filter_map(|s| {
let trimmed = s.trim().trim_start_matches('.');
if trimmed.is_empty() || trimmed.starts_with('#') {
None
} else {
Some(trimmed.to_string())
}
});
extensions.extend(exts_from_file);
} else {
extensions.push(ext.trim().trim_start_matches('.').to_string());
}
}
config.extensions = extensions;
}
if let Some(arg) = args.get_many::<String>("dont_collect") {
@@ -658,6 +704,11 @@ impl Configuration {
} else {
config.data = arg.as_bytes().to_vec();
}
if config.methods == methods() {
// if the user didn't specify a method, we're going to assume they meant to use POST
config.methods = vec![Method::POST.as_str().to_string()];
}
}
if came_from_cli!(args, "stdin") {
@@ -745,18 +796,22 @@ impl Configuration {
.collect();
}
if came_from_cli!(args, "silent") {
if came_from_cli!(args, "quiet") {
config.quiet = true;
config.output_level = OutputLevel::Quiet;
}
if came_from_cli!(args, "silent") || (config.parallel > 0 && !config.quiet) {
// the reason this is protected by an if statement:
// consider a user specifying silent = true in ferox-config.toml
// if the line below is outside of the if, we'd overwrite true with
// false if no --silent is used on the command line
config.silent = true;
config.output_level = OutputLevel::Silent;
}
if came_from_cli!(args, "quiet") {
config.quiet = true;
config.output_level = OutputLevel::Quiet;
config.output_level = if config.json {
OutputLevel::SilentJSON
} else {
OutputLevel::Silent
};
}
if came_from_cli!(args, "auto_tune")
@@ -789,6 +844,20 @@ impl Configuration {
|| came_from_cli!(args, "thorough")
{
config.collect_backups = true;
config.backup_extensions = backup_extensions();
if came_from_cli!(args, "collect_backups") {
if let Some(arg) = args.get_many::<String>("collect_backups") {
let backup_exts = arg
.map(|ext| ext.trim().to_string())
.collect::<Vec<String>>();
if !backup_exts.is_empty() {
// have at least one cli backup, override the defaults
config.backup_extensions = backup_exts;
}
}
}
}
if came_from_cli!(args, "collect_words")
@@ -832,6 +901,8 @@ impl Configuration {
// organizational breakpoint; all options below alter the Client configuration
////
update_config_if_present!(&mut config.proxy, args, "proxy", String);
update_config_if_present!(&mut config.client_cert, args, "client_cert", String);
update_config_if_present!(&mut config.client_key, args, "client_key", String);
update_config_if_present!(&mut config.replay_proxy, args, "replay_proxy", String);
update_config_if_present!(&mut config.user_agent, args, "user_agent", String);
update_config_with_num_type_if_present!(&mut config.timeout, args, "timeout", u64);
@@ -869,7 +940,15 @@ impl Configuration {
// all other items in the iterator returned by split, when combined with the
// original split deliminator (:), make up the header's final value
let value = split_val.collect::<Vec<&str>>().join(":");
config.headers.insert(name.to_string(), value.to_string());
if value.starts_with(' ') && !value.starts_with(" ") {
// first character is a space and the second character isn't
// we can trim the leading space
let trimmed = value.trim_start();
config.headers.insert(name.to_string(), trimmed.to_string());
} else {
config.headers.insert(name.to_string(), value.to_string());
}
}
}
@@ -877,15 +956,27 @@ impl Configuration {
config.headers.insert(
// we know the header name is always "cookie"
"Cookie".to_string(),
// on splitting, there should be only two elements,
// a key and a value
cookies
.map(|cookie| cookie.split('=').collect::<Vec<&str>>()[..].to_owned())
.filter(|parts| parts.len() == 2)
.map(|parts| format!("{}={}", parts[0].trim(), parts[1].trim()))
// trim the spaces, join with an equals sign
.flat_map(|cookie| {
cookie.split(';').filter_map(|part| {
// trim the spaces
let trimmed = part.trim();
if trimmed.is_empty() {
None
} else {
// join with an equals sign
let parts = trimmed.split('=').collect::<Vec<&str>>();
Some(format!(
"{}={}",
parts[0].trim(),
parts[1..].join("").trim()
))
}
})
})
.collect::<Vec<String>>()
.join("; "), // join all the cookies with semicolons for the final header
// join all the cookies with semicolons for the final header
.join("; "),
);
}
@@ -902,6 +993,12 @@ impl Configuration {
}
}
if let Some(certs) = args.get_many::<String>("server_certs") {
for val in certs {
config.server_certs.push(val.to_string());
}
}
config
}
@@ -909,35 +1006,53 @@ impl Configuration {
/// either the config file or command line arguments; if we have, we need to rebuild
/// the client and store it in the config struct
fn try_rebuild_clients(configuration: &mut Configuration) {
if !configuration.proxy.is_empty()
// check if the proxy and certificate fields are empty
// and parse them into Some or None variants ahead of time
// so we may use the is_some method on them instead of
// multiple initializations
let proxy = if configuration.proxy.is_empty() {
None
} else {
Some(configuration.proxy.as_str())
};
let server_certs = &configuration.server_certs;
let client_cert = if configuration.client_cert.is_empty() {
None
} else {
Some(configuration.client_cert.as_str())
};
let client_key = if configuration.client_key.is_empty() {
None
} else {
Some(configuration.client_key.as_str())
};
if proxy.is_some()
|| configuration.timeout != timeout()
|| configuration.user_agent != user_agent()
|| configuration.redirects
|| configuration.insecure
|| !configuration.headers.is_empty()
|| configuration.resumed
|| !server_certs.is_empty()
|| client_cert.is_some()
|| client_key.is_some()
{
if configuration.proxy.is_empty() {
configuration.client = client::initialize(
configuration.timeout,
&configuration.user_agent,
configuration.redirects,
configuration.insecure,
&configuration.headers,
None,
)
.expect("Could not rebuild client")
} else {
configuration.client = client::initialize(
configuration.timeout,
&configuration.user_agent,
configuration.redirects,
configuration.insecure,
&configuration.headers,
Some(&configuration.proxy),
)
.expect("Could not rebuild client")
}
configuration.client = client::initialize(
configuration.timeout,
&configuration.user_agent,
configuration.redirects,
configuration.insecure,
&configuration.headers,
proxy,
server_certs,
client_cert,
client_key,
)
.expect("Could not rebuild client");
}
if !configuration.replay_proxy.is_empty() {
@@ -950,6 +1065,9 @@ impl Configuration {
configuration.insecure,
&configuration.headers,
Some(&configuration.replay_proxy),
server_certs,
client_cert,
client_key,
)
.expect("Could not rebuild client"),
);
@@ -958,7 +1076,7 @@ impl Configuration {
/// Given a configuration file's location and an instance of `Configuration`, read in
/// the config file if found and update the current settings with the settings found therein
fn parse_and_merge_config(config_file: PathBuf, mut config: &mut Self) -> Result<()> {
fn parse_and_merge_config(config_file: PathBuf, config: &mut Self) -> Result<()> {
if config_file.exists() {
// save off a string version of the path before it goes out of scope
let conf_str = config_file.to_str().unwrap_or("").to_string();
@@ -984,6 +1102,14 @@ impl Configuration {
update_if_not_default!(&mut conf.target_url, new.target_url, "");
update_if_not_default!(&mut conf.time_limit, new.time_limit, "");
update_if_not_default!(&mut conf.proxy, new.proxy, "");
update_if_not_default!(
&mut conf.server_certs,
new.server_certs,
Vec::<String>::new()
);
update_if_not_default!(&mut conf.json, new.json, false);
update_if_not_default!(&mut conf.client_cert, new.client_cert, "");
update_if_not_default!(&mut conf.client_key, new.client_key, "");
update_if_not_default!(&mut conf.verbosity, new.verbosity, 0);
update_if_not_default!(&mut conf.silent, new.silent, false);
update_if_not_default!(&mut conf.quiet, new.quiet, false);
@@ -993,7 +1119,7 @@ impl Configuration {
update_if_not_default!(&mut conf.collect_backups, new.collect_backups, false);
update_if_not_default!(&mut conf.collect_words, new.collect_words, false);
// use updated quiet/silent values to determine output level; same for requester policy
conf.output_level = determine_output_level(conf.quiet, conf.silent);
conf.output_level = determine_output_level(conf.quiet, conf.silent, conf.json);
conf.requester_policy = determine_requester_policy(conf.auto_tune, conf.auto_bail);
update_if_not_default!(&mut conf.output, new.output, "");
update_if_not_default!(&mut conf.redirects, new.redirects, false);
@@ -1051,10 +1177,14 @@ impl Configuration {
update_if_not_default!(&mut conf.replay_proxy, new.replay_proxy, "");
update_if_not_default!(&mut conf.debug_log, new.debug_log, "");
update_if_not_default!(&mut conf.resume_from, new.resume_from, "");
update_if_not_default!(&mut conf.json, new.json, false);
update_if_not_default!(&mut conf.timeout, new.timeout, timeout());
update_if_not_default!(&mut conf.user_agent, new.user_agent, user_agent());
update_if_not_default!(
&mut conf.backup_extensions,
new.backup_extensions,
backup_extensions()
);
update_if_not_default!(&mut conf.random_agent, new.random_agent, false);
update_if_not_default!(&mut conf.threads, new.threads, threads());
update_if_not_default!(&mut conf.depth, new.depth, depth());

View File

@@ -56,6 +56,10 @@ fn setup_config_test() -> Configuration {
filter_word_count = [994, 992]
filter_line_count = [34]
filter_status = [201]
server_certs = ["/some/cert.pem", "/some/other/cert.pem"]
client_cert = "/some/client/cert.pem"
client_key = "/some/client/key.pem"
backup_extensions = [".save"]
"#;
let tmp_dir = TempDir::new().unwrap();
let file = tmp_dir.path().join(DEFAULT_CONFIG_NAME);
@@ -117,6 +121,10 @@ fn default_configuration() {
assert_eq!(config.filter_line_count, Vec::<usize>::new());
assert_eq!(config.filter_status, Vec::<u16>::new());
assert_eq!(config.headers, HashMap::new());
assert_eq!(config.server_certs, Vec::<String>::new());
assert_eq!(config.client_cert, String::new());
assert_eq!(config.client_key, String::new());
assert_eq!(config.backup_extensions, backup_extensions());
}
#[test]
@@ -443,6 +451,37 @@ fn config_reads_resume_from() {
assert_eq!(config.resume_from, "/some/state/file");
}
#[test]
/// parse the test config and see that the value parsed is correct
fn config_reads_server_certs() {
let config = setup_config_test();
assert_eq!(
config.server_certs,
["/some/cert.pem", "/some/other/cert.pem"]
);
}
#[test]
/// parse the test config and see that the value parsed is correct
fn config_reads_backup_extensions() {
let config = setup_config_test();
assert_eq!(config.backup_extensions, [".save"]);
}
#[test]
/// parse the test config and see that the value parsed is correct
fn config_reads_client_cert() {
let config = setup_config_test();
assert_eq!(config.client_cert, "/some/client/cert.pem");
}
#[test]
/// parse the test config and see that the value parsed is correct
fn config_reads_client_key() {
let config = setup_config_test();
assert_eq!(config.client_key, "/some/client/key.pem");
}
#[test]
/// parse the test config and see that the values parsed are correct
fn config_reads_headers() {

View File

@@ -1,6 +1,7 @@
use crate::{
utils::{module_colorizer, status_colorizer},
DEFAULT_IGNORED_EXTENSIONS, DEFAULT_METHOD, DEFAULT_STATUS_CODES, DEFAULT_WORDLIST, VERSION,
DEFAULT_BACKUP_EXTENSIONS, DEFAULT_IGNORED_EXTENSIONS, DEFAULT_METHOD, DEFAULT_STATUS_CODES,
DEFAULT_WORDLIST, VERSION,
};
#[cfg(not(test))]
use std::process::exit;
@@ -69,6 +70,14 @@ pub(super) fn ignored_extensions() -> Vec<String> {
.collect()
}
/// default backup extensions to collect
pub(super) fn backup_extensions() -> Vec<String> {
DEFAULT_BACKUP_EXTENSIONS
.iter()
.map(|s| s.to_string())
.collect()
}
/// default wordlist
pub(super) fn wordlist() -> String {
String::from(DEFAULT_WORDLIST)
@@ -100,6 +109,9 @@ pub enum OutputLevel {
/// silent scan, only print urls (used to be --quiet in versions 1.x.x)
Silent,
/// silent scan, but with JSON output
SilentJSON,
}
/// implement a default for OutputLevel
@@ -111,14 +123,22 @@ impl Default for OutputLevel {
}
/// given the current settings for quiet and silent, determine output_level (DRY helper)
pub fn determine_output_level(quiet: bool, silent: bool) -> OutputLevel {
pub fn determine_output_level(quiet: bool, silent: bool, json: bool) -> OutputLevel {
if quiet && silent {
// user COULD have both as true in config file, take the more quiet of the two
OutputLevel::Silent
if json {
OutputLevel::SilentJSON
} else {
OutputLevel::Silent
}
} else if quiet {
OutputLevel::Quiet
} else if silent {
OutputLevel::Silent
if json {
OutputLevel::SilentJSON
} else {
OutputLevel::Silent
}
} else {
OutputLevel::Default
}
@@ -166,16 +186,28 @@ mod tests {
#[test]
/// test determine_output_level returns higher of the two levels if both given values are true
fn determine_output_level_returns_correct_results() {
let mut level = determine_output_level(true, true);
let mut level = determine_output_level(true, true, false);
assert_eq!(level, OutputLevel::Silent);
level = determine_output_level(false, true);
level = determine_output_level(false, true, false);
assert_eq!(level, OutputLevel::Silent);
level = determine_output_level(false, false);
let mut level = determine_output_level(true, true, true);
assert_eq!(level, OutputLevel::SilentJSON);
level = determine_output_level(false, true, true);
assert_eq!(level, OutputLevel::SilentJSON);
level = determine_output_level(false, false, false);
assert_eq!(level, OutputLevel::Default);
level = determine_output_level(true, false);
level = determine_output_level(true, false, false);
assert_eq!(level, OutputLevel::Quiet);
level = determine_output_level(false, false, true);
assert_eq!(level, OutputLevel::Default);
level = determine_output_level(true, false, true);
assert_eq!(level, OutputLevel::Quiet);
}

View File

@@ -1,4 +1,5 @@
use std::sync::Arc;
use std::time::Duration;
use reqwest::StatusCode;
use tokio::sync::oneshot::Sender;
@@ -85,4 +86,10 @@ pub enum Command {
/// Give a handler access to an Arc<Handles> instance after the handler has
/// already been initialized
AddHandles(Arc<Handles>),
/// inform the Stats object about which targets are being scanned
UpdateTargets(Vec<String>),
/// query the Stats handler about the position of the overall progress bar
QueryOverallBarEta(Sender<Duration>),
}

View File

@@ -12,6 +12,7 @@ use anyhow::Result;
use console::style;
use crossterm::event::{self, Event, KeyCode};
use std::{
env::temp_dir,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
@@ -103,10 +104,36 @@ impl TermInputHandler {
// User didn't set the --no-state flag (so saved_state is still the default true)
if handles.config.save_state {
let state_file = open_file(&filename);
let Ok(mut state_file) = open_file(&filename) else {
// couldn't open the file, let the user know we're going to try again
let error = format!(
"❌ Could not save {}, falling back to {}",
filename,
temp_dir().to_string_lossy()
);
PROGRESS_PRINTER.println(error);
let mut buffered_file = state_file?;
write_to(&state, &mut buffered_file, true)?;
let temp_filename = temp_dir().join(&filename);
let Ok(mut state_file) = open_file(&temp_filename.to_string_lossy()) else {
// couldn't open the fallback file, let the user know
let error = format!("❌❌ Could not save {:?}, giving up...", temp_filename);
PROGRESS_PRINTER.println(error);
log::trace!("exit: sigint_handler (failed to write)");
std::process::exit(1);
};
write_to(&state, &mut state_file, true)?;
let msg = format!("✅ Saved scan state to {:?}", temp_filename);
PROGRESS_PRINTER.println(msg);
log::trace!("exit: sigint_handler (saved to temp folder)");
std::process::exit(1);
};
write_to(&state, &mut state_file, true)?;
}
log::trace!("exit: sigint_handler (end of program)");

View File

@@ -98,6 +98,8 @@ impl FileOutHandler {
log::info!("Writing scan results to {}", self.config.output);
write_to(&*self.config, &mut file, self.config.json)?;
while let Some(command) = self.receiver.recv().await {
match command {
Command::Report(response) => {
@@ -209,8 +211,12 @@ impl TermOutHandler {
while let Some(command) = self.receiver.recv().await {
match command {
Command::Report(resp) => {
self.process_response(tx_stats.clone(), resp, ProcessResponseCall::Recursive)
.await?;
if let Err(err) = self
.process_response(tx_stats.clone(), resp, ProcessResponseCall::Recursive)
.await
{
log::warn!("{}", err);
}
}
Command::Sync(sender) => {
sender.send(true).unwrap_or_default();
@@ -328,6 +334,21 @@ impl TermOutHandler {
)
.await;
let Some(handles) = self.handles.as_ref() else {
// shouldn't ever happen, but we'll log and return early if it does
log::error!("handles were unexpectedly None, this shouldn't happen");
return Ok(());
};
if handles
.filters
.data
.should_filter_response(&ferox_response, tx_stats.clone())
{
// response was filtered for one reason or another, don't process it
continue;
}
self.process_response(
tx_stats.clone(),
Box::new(ferox_response),
@@ -385,7 +406,7 @@ impl TermOutHandler {
if !filename.is_empty() {
// append rules
for suffix in ["~", ".bak", ".bak2", ".old", ".1"] {
for suffix in &self.config.backup_extensions {
self.add_new_url_to_vec(url, &format!("{filename}{suffix}"), &mut urls);
}

View File

@@ -125,6 +125,12 @@ impl StatsHandler {
Command::Sync(sender) => {
sender.send(true).unwrap_or_default();
}
Command::QueryOverallBarEta(sender) => {
sender.send(self.bar.eta()).unwrap_or_default();
}
Command::UpdateTargets(targets) => {
self.stats.update_targets(targets);
}
Command::Exit => break,
_ => {} // no more commands needed
}
@@ -132,7 +138,7 @@ impl StatsHandler {
self.bar.finish();
log::debug!("{:#?}", *self.stats);
log::info!("{:#?}", *self.stats);
log::trace!("exit: start");
Ok(())
}

View File

@@ -641,6 +641,20 @@ impl<'a> Extractor<'a> {
Some(self.handles.config.proxy.as_str())
};
let server_certs = &self.handles.config.server_certs;
let client_cert = if self.handles.config.client_cert.is_empty() {
None
} else {
Some(self.handles.config.client_cert.as_str())
};
let client_key = if self.handles.config.client_key.is_empty() {
None
} else {
Some(self.handles.config.client_key.as_str())
};
client = client::initialize(
self.handles.config.timeout,
&self.handles.config.user_agent,
@@ -648,6 +662,9 @@ impl<'a> Extractor<'a> {
self.handles.config.insecure,
&self.handles.config.headers,
proxy,
server_certs,
client_cert,
client_key,
)?;
}

View File

@@ -30,10 +30,13 @@ impl FeroxFilter for RegexFilter {
log::trace!("enter: should_filter_response({:?} {})", self, response);
let result = self.compiled.is_match(response.text());
let other = response.headers().iter().any(|(k, v)| {
self.compiled.is_match(k.as_str()) || self.compiled.is_match(v.to_str().unwrap_or(""))
});
log::trace!("exit: should_filter_response -> {}", result);
log::trace!("exit: should_filter_response -> {}", result || other);
result
result || other
}
/// Compare one SizeFilter to another

View File

@@ -271,7 +271,7 @@ fn remove_function_works_as_expected() {
assert_eq!(data.filters.read().unwrap().len(), 5);
let expected = vec![
let expected = [
WordsFilter { word_count: 1 },
WordsFilter { word_count: 3 },
WordsFilter { word_count: 5 },

View File

@@ -120,12 +120,15 @@ impl HeuristicTests {
) {
if e.to_string().contains(":SSL") {
ferox_print(
&format!("Could not connect to {target_url} due to SSL errors (run with -k to ignore), skipping..."),
&format!("Could not connect to {target_url} due to SSL errors (run with -k to ignore), skipping...\n => {}\n", e.root_cause()),
&PROGRESS_PRINTER,
);
} else {
ferox_print(
&format!("Could not connect to {target_url}, skipping..."),
&format!(
"Could not connect to {target_url}, skipping...\n => {}\n",
e.root_cause()
),
&PROGRESS_PRINTER,
);
}
@@ -329,10 +332,10 @@ impl HeuristicTests {
// - http://localhost/adminf1d2541e73c44dcb9d1fb7d93334b280
// - http://localhost/admin92969beae6bf4beb855d1622406d87e395c87387a9ad432e8a11245002b709b03cf609d471004154b83bcc1c6ec49f6f
let Ok(response) =
logged_request(&nonexistent_url, method, data, self.handles.clone())
.await else {
return None;
};
logged_request(&nonexistent_url, method, data, self.handles.clone()).await
else {
return None;
};
if !self
.handles
@@ -369,7 +372,9 @@ impl HeuristicTests {
}
// check the responses for similarities on which we can filter, multiple may be returned
let Some((wildcard_filters, wildcard_responses)) = self.examine_404_like_responses(&responses) else {
let Some((wildcard_filters, wildcard_responses)) =
self.examine_404_like_responses(&responses)
else {
// no match was found during analysis of responses
log::warn!("no match found for 404 responses");
continue;

View File

@@ -59,6 +59,9 @@ pub(crate) const DEFAULT_IGNORED_EXTENSIONS: [&str; 38] = [
"webm", "ogv", "oga", "flac", "aac", "3gp", "css", "zip", "xls", "xml", "gz", "tgz",
];
/// Default set of extensions to search for when auto-collecting backups during scans
pub(crate) const DEFAULT_BACKUP_EXTENSIONS: [&str; 5] = ["~", ".bak", ".bak2", ".old", ".1"];
/// Default wordlist to use when `-w|--wordlist` isn't specified and not `wordlist` isn't set
/// in a [ferox-config.toml](constant.DEFAULT_CONFIG_NAME.html) config file.
///

View File

@@ -8,7 +8,7 @@ use std::{
io::{stderr, BufRead, BufReader},
ops::Index,
path::Path,
process::{exit, Command},
process::{exit, Command, Stdio},
sync::{atomic::Ordering, Arc},
};
@@ -25,7 +25,8 @@ use feroxbuster::{
config::{Configuration, OutputLevel},
event_handlers::{
Command::{
AddHandles, CreateBar, Exit, JoinTasks, LoadStats, ScanInitialUrls, UpdateWordlist,
AddHandles, CreateBar, Exit, JoinTasks, LoadStats, ScanInitialUrls, UpdateTargets,
UpdateWordlist,
},
FiltersHandler, Handles, ScanHandler, StatsHandler, Tasks, TermInputHandler,
TermOutHandler, SCAN_COMPLETE,
@@ -238,7 +239,15 @@ async fn wrapped_main(config: Arc<Configuration>) -> Result<()> {
let words = if config.wordlist.starts_with("http") {
// found a url scheme, attempt to download the wordlist
let response = config.client.get(&config.wordlist).send().await?;
let response = config
.client
.get(&config.wordlist)
.send()
.await
.context(format!(
"Unable to download wordlist from remote url: {}",
config.wordlist
))?;
if !response.status().is_success() {
// status code isn't a 200, bail
@@ -250,14 +259,15 @@ async fn wrapped_main(config: Arc<Configuration>) -> Result<()> {
}
// attempt to get the filename from the url's path
let Some(path_segments) = response
.url()
.path_segments() else {
bail!("Unable to parse path from url: {}", response.url());
};
let Some(path_segments) = response.url().path_segments() else {
bail!("Unable to parse path from url: {}", response.url());
};
let Some(filename) = path_segments.last() else {
bail!("Unable to parse filename from url's path: {}", response.url().path());
bail!(
"Unable to parse filename from url's path: {}",
response.url().path()
);
};
let filename = filename.to_string();
@@ -316,9 +326,15 @@ async fn wrapped_main(config: Arc<Configuration>) -> Result<()> {
// create new Tasks object, each of these handles is one that will be joined on later
let tasks = Tasks::new(out_task, stats_task, filters_task, scan_task);
if !config.time_limit.is_empty() {
if !config.time_limit.is_empty() && config.parallel == 0 {
// --time-limit value not an empty string, need to kick off the thread that enforces
// the limit
//
// if --parallel is used, this branch won't execute in the main process, but will in the
// children. This is because --parallel is stripped from the children's command line
// arguments, so, when spawned, they won't have --parallel, the parallel value will be set
// to the default of 0, and will hit this branch. This makes it so that the time limit
// is enforced on each individual child process, instead of the main process
let time_handles = handles.clone();
tokio::spawn(async move { scan_manager::start_max_time_thread(time_handles).await });
}
@@ -361,8 +377,7 @@ async fn wrapped_main(config: Arc<Configuration>) -> Result<()> {
let invocation = args();
let para_regex =
Regex::new("--stdin|-q|--quiet|--silent|--verbosity|-v|-vv|-vvv|-vvvv").unwrap();
let para_regex = Regex::new("--stdin").unwrap();
// remove stdin since only the original process will process targets
// remove quiet and silent so we can force silent later to normalize output
@@ -370,8 +385,6 @@ async fn wrapped_main(config: Arc<Configuration>) -> Result<()> {
.filter(|s| !para_regex.is_match(s))
.collect::<Vec<String>>();
original.push("--silent".to_string()); // only output modifier allowed
// we need remove --parallel from command line so we don't hit this branch over and over
// but we must remove --parallel N manually; the filter above never sees --parallel and the
// value passed to it at the same time, so can't filter them out in one pass
@@ -446,16 +459,32 @@ async fn wrapped_main(config: Arc<Configuration>) -> Result<()> {
log::debug!("parallel exec: {} {}", bin, args.join(" "));
tokio::task::spawn_blocking(move || {
let result = Command::new(bin)
tokio::task::spawn(async move {
let mut output = Command::new(bin)
.args(&args)
.stdout(Stdio::piped())
.spawn()
.expect("failed to spawn a child process")
.wait()
.expect("child process errored during execution");
.expect("failed to spawn a child process");
let stdout = output.stdout.take().unwrap();
let mut bufread = BufReader::new(stdout);
// output for a single line is a minimum of 51 bytes, so we'll start with that
// + a little wiggle room, and grow as needed
let mut buf: String = String::with_capacity(128);
while let Ok(n) = bufread.read_line(&mut buf) {
if n > 0 {
let trimmed = buf.trim();
if !trimmed.is_empty() {
println!("{}", trimmed);
}
buf.clear();
} else {
break;
}
}
drop(permit);
result
});
}
@@ -479,6 +508,14 @@ async fn wrapped_main(config: Arc<Configuration>) -> Result<()> {
return Ok(());
}
// in order for the Stats object to know about which targets are being scanned, we need to
// wait until the parallel branch has been handled before sending the UpdateTargets command
// this ensures that only the targets being scanned are sent to the Stats object
//
// if sent before the parallel branch is handled, the Stats object will have duplicate
// targets
handles.stats.send(UpdateTargets(targets.clone()))?;
if matches!(config.output_level, OutputLevel::Default) {
// only print banner if output level is default (no banner on --quiet|--silent)
let std_stderr = stderr(); // std::io::stderr
@@ -637,7 +674,7 @@ fn main() -> Result<()> {
// print the banner to stderr
let std_stderr = stderr(); // std::io::stderr
let banner = Banner::new(&targets, &config);
if !config.quiet && !config.silent {
if (!config.quiet && !config.silent) || config.parallel != 0 {
banner.print_to(std_stderr, config).unwrap();
}
}

View File

@@ -35,20 +35,19 @@ impl Document {
fn add_term(&mut self, word: &str) {
let term = Term::new(word);
let metadata = self.terms.entry(term).or_insert_with(TermMetaData::new);
let metadata = self.terms.entry(term).or_default();
*metadata.count_mut() += 1;
}
/// create a new `Document` from the given HTML string
pub(crate) fn from_html(raw_html: &str) -> Self {
pub(crate) fn from_html(raw_html: &str) -> Option<Self> {
let selector = Selector::parse("body").unwrap();
let html = Html::parse_document(raw_html);
let text = html
.select(&selector)
.next()
.unwrap()
let element = html.select(&selector).next()?;
let text = element
.descendants()
.filter_map(|node| {
if !node.value().is_text() && !node.value().is_comment() {
@@ -95,7 +94,7 @@ impl Document {
// call `new` to push the parsed html through the pre-processing pipeline and process all
// the words
Self::new(&text)
Some(Self::new(&text))
}
/// Log normalized weighting scheme for term frequency
@@ -146,19 +145,20 @@ mod tests {
#[test]
/// `Document::new` should preprocess html and generate a hashmap of `Term, TermMetadata`
fn nlp_document_creation_from_html() {
let empty = Document::from_html("<html></html>");
let empty = Document::from_html("<html></html>").unwrap();
assert_eq!(empty.number_of_terms, 0);
let other_empty = Document::from_html("<html><body><p></p></body></html>");
let other_empty = Document::from_html("<html><body><p></p></body></html>").unwrap();
assert_eq!(other_empty.number_of_terms, 0);
let third_empty = Document::from_html("<!DOCTYPE html><html><!DOCTYPE html><p></p></html>");
let third_empty =
Document::from_html("<!DOCTYPE html><html><!DOCTYPE html><p></p></html>").unwrap();
assert_eq!(third_empty.number_of_terms, 0);
// p tag for is_text check and comment for is_comment
let doc = Document::from_html(
"<html><body><p>The air quality in Singapore.</p><!--got worse on Wednesday--></body></html>",
);
).unwrap();
let expected_terms = ["air", "quality", "singapore", "worse", "wednesday"];
@@ -209,7 +209,7 @@ mod tests {
/// ensure words in script/style tags aren't processed
fn document_creation_skips_script_and_style_tags() {
let html = "<body><script>The air quality</script><style>in Singapore</style><p>got worse on Wednesday.</p></body>";
let doc = Document::from_html(html);
let doc = Document::from_html(html).unwrap();
let keys = doc.terms().keys().map(|key| key.raw()).collect::<Vec<_>>();
let expected = ["worse", "wednesday"];

View File

@@ -35,11 +35,6 @@ pub(super) struct TermMetaData {
}
impl TermMetaData {
/// create a new metadata container
pub(super) fn new() -> Self {
Self::default()
}
/// number of times a `Term` has appeared in any `Document` within the corpus
pub(super) fn document_frequency(&self) -> usize {
self.term_frequencies().len()
@@ -90,7 +85,7 @@ mod tests {
#[test]
/// test accessors for correctness
fn nlp_term_metadata_accessor_test() {
let mut metadata = TermMetaData::new();
let mut metadata = TermMetaData::default();
*metadata.count_mut() += 1;
assert_eq!(metadata.count(), 1);

View File

@@ -177,7 +177,7 @@ pub fn initialize() -> Command {
.use_value_delimiter(true)
.help_heading("Request settings")
.help(
"File extension(s) to search for (ex: -x php -x pdf js)",
"File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)",
),
)
.arg(
@@ -211,7 +211,6 @@ pub fn initialize() -> Command {
.num_args(1..)
.action(ArgAction::Append)
.help_heading("Request settings")
.use_value_delimiter(true)
.help(
"Specify HTTP headers to be used in each request (ex: -H Header:val -H 'stuff: things')",
),
@@ -292,7 +291,7 @@ pub fn initialize() -> Command {
.use_value_delimiter(true)
.help_heading("Response filters")
.help(
"Filter out messages via regular expression matching on the response's body (ex: -X '^ignore me$')",
"Filter out messages via regular expression matching on the response's body/headers (ex: -X '^ignore me$')",
),
)
.arg(
@@ -390,6 +389,35 @@ pub fn initialize() -> Command {
.num_args(0)
.help_heading("Client settings")
.help("Disables TLS certificate validation in the client"),
)
.arg(
Arg::new("server_certs")
.long("server-certs")
.value_name("PEM|DER")
.value_hint(ValueHint::FilePath)
.num_args(1..)
.help_heading("Client settings")
.help("Add custom root certificate(s) for servers with unknown certificates"),
)
.arg(
Arg::new("client_cert")
.long("client-cert")
.value_name("PEM")
.value_hint(ValueHint::FilePath)
.num_args(1)
.requires("client_key")
.help_heading("Client settings")
.help("Add a PEM encoded certificate for mutual authentication (mTLS)"),
)
.arg(
Arg::new("client_key")
.long("client-key")
.value_name("PEM")
.value_hint(ValueHint::FilePath)
.num_args(1)
.requires("client_cert")
.help_heading("Client settings")
.help("Add a PEM encoded private key for mutual authentication (mTLS)"),
);
/////////////////////////////////////////////////////////////////////
@@ -457,6 +485,7 @@ pub fn initialize() -> Command {
Arg::new("parallel")
.long("parallel")
.value_name("PARALLEL_SCANS")
.conflicts_with("verbosity")
.num_args(1)
.requires("stdin")
.help_heading("Scan settings")
@@ -521,9 +550,9 @@ pub fn initialize() -> Command {
Arg::new("collect_backups")
.short('B')
.long("collect-backups")
.num_args(0)
.num_args(0..)
.help_heading("Dynamic collection settings")
.help("Automatically request likely backup extensions for \"found\" urls")
.help("Automatically request likely backup extensions for \"found\" urls (default: ~, .bak, .bak2, .old, .1)")
)
.arg(
Arg::new("collect_words")
@@ -565,7 +594,7 @@ pub fn initialize() -> Command {
.num_args(0)
.conflicts_with("quiet")
.help_heading("Output settings")
.help("Only print URLs + turn off logging (good for piping a list of urls to other commands)")
.help("Only print URLs (or JSON w/ --json) + turn off logging (good for piping a list of urls to other commands)")
)
.arg(
Arg::new("quiet")
@@ -616,9 +645,14 @@ pub fn initialize() -> Command {
let mut app = app
.group(
ArgGroup::new("output_files")
.args(["debug_log", "output"])
.args(["debug_log", "output", "silent"])
.multiple(true),
)
.group(
ArgGroup::new("output_limiters")
.args(["quiet", "silent"])
.multiple(false),
)
.arg(
Arg::new("update_app")
.short('U')

View File

@@ -1,6 +1,4 @@
use std::time::Duration;
use indicatif::{HumanDuration, MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle};
use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle};
use lazy_static::lazy_static;
lazy_static! {
@@ -33,45 +31,23 @@ pub enum BarType {
/// Add an [indicatif::ProgressBar](https://docs.rs/indicatif/latest/indicatif/struct.ProgressBar.html)
/// to the global [PROGRESS_BAR](../config/struct.PROGRESS_BAR.html)
pub fn add_bar(prefix: &str, length: u64, bar_type: BarType) -> ProgressBar {
let mut style = ProgressStyle::default_bar()
.progress_chars("#>-")
.with_key(
"smoothed_per_sec",
|state: &indicatif::ProgressState, w: &mut dyn std::fmt::Write| match (
state.pos(),
state.elapsed().as_millis(),
) {
// https://github.com/console-rs/indicatif/issues/394#issuecomment-1309971049
//
// indicatif released a change to how they reported eta/per_sec
// and the results looked really weird based on how we use the progress
// bars. this fixes that
(pos, elapsed_ms) if elapsed_ms > 0 => {
write!(w, "{:.0}/s", pos as f64 * 1000_f64 / elapsed_ms as f64).unwrap()
}
_ => write!(w, "-").unwrap(),
},
)
.with_key(
"smoothed_eta",
|state: &indicatif::ProgressState, w: &mut dyn std::fmt::Write| match (
state.pos(),
state.len(),
) {
(pos, Some(len)) => write!(
w,
"{:#}",
HumanDuration(Duration::from_millis(
(state.elapsed().as_millis()
* ((len as u128).checked_sub(pos as u128).unwrap_or(1))
.checked_div(pos as u128)
.unwrap_or(1)) as u64
))
)
.unwrap(),
_ => write!(w, "-").unwrap(),
},
);
let mut style = ProgressStyle::default_bar().progress_chars("#>-").with_key(
"smoothed_per_sec",
|state: &indicatif::ProgressState, w: &mut dyn std::fmt::Write| match (
state.pos(),
state.elapsed().as_millis(),
) {
// https://github.com/console-rs/indicatif/issues/394#issuecomment-1309971049
//
// indicatif released a change to how they reported eta/per_sec
// and the results looked really weird based on how we use the progress
// bars. this fixes that
(pos, elapsed_ms) if elapsed_ms > 0 => {
write!(w, "{:.0}/s", pos as f64 * 1000_f64 / elapsed_ms as f64).unwrap()
}
_ => write!(w, "-").unwrap(),
},
);
style = match bar_type {
BarType::Hidden => style.template("").unwrap(),
@@ -85,7 +61,7 @@ pub fn add_bar(prefix: &str, length: u64, bar_type: BarType) -> ProgressBar {
))
.unwrap(),
BarType::Total => style
.template("[{bar:.yellow/blue}] - {elapsed:<4} {pos:>7}/{len:7} {smoothed_eta:7} {msg}")
.template("[{bar:.yellow/blue}] - {elapsed:<4} {pos:>7}/{len:7} {eta:7} {msg}")
.unwrap(),
BarType::Quiet => style.template("Scanning: {prefix}").unwrap(),
};

View File

@@ -21,7 +21,7 @@ use crate::{
event_handlers::{Command, Handles},
traits::FeroxSerialize,
url::FeroxUrl,
utils::{self, fmt_err, parse_url_with_raw_path, status_colorizer},
utils::{self, fmt_err, parse_url_with_raw_path, status_colorizer, timestamp},
CommandSender,
};
@@ -63,6 +63,9 @@ pub struct FeroxResponse {
/// Url's file extension, if one exists
pub(crate) extension: Option<String>,
/// Timestamp of when this response was received
timestamp: f64,
}
/// implement Default trait for FeroxResponse
@@ -82,6 +85,7 @@ impl Default for FeroxResponse {
wildcard: false,
output_level: Default::default(),
extension: None,
timestamp: timestamp(),
}
}
}
@@ -138,6 +142,11 @@ impl FeroxResponse {
self.content_length
}
/// Get the timestamp of this response
pub fn timestamp(&self) -> f64 {
self.timestamp
}
/// Set `FeroxResponse`'s `url` attribute, has no affect if an error occurs
pub fn set_url(&mut self, url: &str) {
match parse_url_with_raw_path(url) {
@@ -216,6 +225,7 @@ impl FeroxResponse {
let status = response.status();
let headers = response.headers().clone();
let content_length = response.content_length().unwrap_or(0);
let timestamp = timestamp();
// .text() consumes the response, must be called last
let text = response
@@ -248,6 +258,7 @@ impl FeroxResponse {
output_level,
wildcard: false,
extension: None,
timestamp,
}
}
@@ -423,8 +434,12 @@ impl FeroxSerialize for FeroxResponse {
let mut url_with_redirect = match (
self.status().is_redirection(),
self.headers().get("Location").is_some(),
matches!(
self.output_level,
OutputLevel::Silent | OutputLevel::SilentJSON
),
) {
(true, true) => {
(true, true, false) => {
// redirect with Location header, show where it goes if possible
let loc = self
.headers()
@@ -485,15 +500,19 @@ impl FeroxSerialize for FeroxResponse {
message
} else {
// not a wildcard, just create a normal entry
utils::create_report_string(
self.status.as_str(),
method,
&lines,
&words,
&chars,
&url_with_redirect,
self.output_level,
)
if matches!(self.output_level, OutputLevel::SilentJSON) {
self.as_json().unwrap_or_default()
} else {
utils::create_report_string(
self.status.as_str(),
method,
&lines,
&words,
&chars,
&url_with_redirect,
self.output_level,
)
}
}
}
@@ -566,6 +585,7 @@ impl Serialize for FeroxResponse {
"extension",
self.extension.as_ref().unwrap_or(&String::new()),
)?;
state.serialize_field("timestamp", &self.timestamp)?;
state.end()
}
@@ -591,6 +611,7 @@ impl<'de> Deserialize<'de> for FeroxResponse {
line_count: 0,
word_count: 0,
extension: None,
timestamp: timestamp(),
};
let map: HashMap<String, Value> = HashMap::deserialize(deserializer)?;
@@ -664,6 +685,11 @@ impl<'de> Deserialize<'de> for FeroxResponse {
response.extension = Some(result.to_string());
}
}
"timestamp" => {
if let Some(result) = value.as_f64() {
response.timestamp = result;
}
}
_ => {}
}
}

View File

@@ -1,8 +1,10 @@
use std::time::Duration;
use crate::filters::filter_lookup;
use crate::progress::PROGRESS_BAR;
use crate::traits::FeroxFilter;
use console::{measure_text_width, pad_str, style, Alignment, Term};
use indicatif::ProgressDrawTarget;
use indicatif::{HumanDuration, ProgressDrawTarget};
use regex::Regex;
/// Data container for a command entered by the user interactively
@@ -43,6 +45,9 @@ pub(super) struct Menu {
/// footer: instructions surrounded by separators
footer: String,
/// length of longest displayed line (suitable for ascii/unicode)
longest: usize,
/// unicode line border, matched to longest displayed line
border: String,
@@ -110,7 +115,7 @@ impl Menu {
commands.push_str(&valid_filters);
commands.push_str(&rm_filter_cmd);
let longest = measure_text_width(&canx_cmd).max(measure_text_width(&name));
let longest = measure_text_width(&canx_cmd).max(measure_text_width(&name)) + 1;
let border = separator.repeat(longest);
@@ -123,6 +128,7 @@ impl Menu {
header,
footer,
border,
longest,
term: Term::stderr(),
}
}
@@ -142,6 +148,13 @@ impl Menu {
self.println(&self.footer);
}
/// print menu footer
pub(super) fn print_eta(&self, eta: Duration) {
let inner = format!("{} remaining ⏳", HumanDuration(eta));
let padded_eta = pad_str(&inner, self.longest, Alignment::Center, None);
self.println(&format!("{padded_eta}\n{}", self.border));
}
/// set PROGRESS_BAR bar target to hidden
pub(super) fn hide_progress_bars(&self) {
PROGRESS_BAR.set_draw_target(ProgressDrawTarget::hidden());

View File

@@ -8,7 +8,7 @@ mod state;
#[cfg(test)]
mod tests;
pub(self) use menu::Menu;
use menu::Menu;
pub use menu::{MenuCmd, MenuCmdResult};
pub use order::ScanOrder;
pub use response_container::FeroxResponses;

View File

@@ -110,7 +110,7 @@ impl FeroxScan {
match self.task.try_lock() {
Ok(mut guard) => {
if let Some(task) = std::mem::replace(&mut *guard, None) {
if let Some(task) = guard.take() {
log::trace!("aborting {:?}", self);
task.abort();
self.set_status(ScanStatus::Cancelled)?;
@@ -175,7 +175,7 @@ impl FeroxScan {
let bar_type = match self.output_level {
OutputLevel::Default => BarType::Default,
OutputLevel::Quiet => BarType::Quiet,
OutputLevel::Silent => BarType::Hidden,
OutputLevel::Silent | OutputLevel::SilentJSON => BarType::Hidden,
};
let pb = add_bar(&self.url, self.num_requests, bar_type);
@@ -194,7 +194,7 @@ impl FeroxScan {
let bar_type = match self.output_level {
OutputLevel::Default => BarType::Default,
OutputLevel::Quiet => BarType::Quiet,
OutputLevel::Silent => BarType::Hidden,
OutputLevel::Silent | OutputLevel::SilentJSON => BarType::Hidden,
};
let pb = add_bar(&self.url, self.num_requests, bar_type);
@@ -268,7 +268,7 @@ impl FeroxScan {
let mut guard = self.task.lock().await;
if guard.is_some() {
if let Some(task) = std::mem::replace(&mut *guard, None) {
if let Some(task) = guard.take() {
task.await.unwrap();
self.set_status(ScanStatus::Complete)
.unwrap_or_else(|e| log::warn!("Could not mark scan complete: {}", e))

View File

@@ -33,6 +33,7 @@ use std::{
},
thread::sleep,
};
use tokio::sync::oneshot;
use tokio::time::{self, Duration};
/// Single atomic number that gets incremented once, used to track first thread to interact with
@@ -330,6 +331,13 @@ impl FeroxScans {
self.menu
.println(&format!("{}:", style("Scans").bright().blue()));
}
if let Ok(guard) = scan.status.lock() {
if matches!(*guard, ScanStatus::Cancelled) {
continue;
}
}
// we're only interested in displaying directory scans, as those are
// the only ones that make sense to be stopped
let scan_msg = format!("{i:3}: {scan}");
@@ -360,7 +368,14 @@ impl FeroxScans {
sleep(menu_pause_duration);
continue;
}
u_scans.index(num).clone()
let selected = u_scans.index(num);
if matches!(selected.scan_type, ScanType::File) {
continue;
}
selected.clone()
}
Err(..) => continue,
};
@@ -416,6 +431,13 @@ impl FeroxScans {
self.menu.hide_progress_bars();
self.menu.clear_screen();
self.menu.print_header();
let (tx, rx) = oneshot::channel::<Duration>();
if handles.stats.send(Command::QueryOverallBarEta(tx)).is_ok() {
if let Ok(y) = rx.await {
self.menu.print_eta(y);
}
}
self.display_scans().await;
self.display_filters(handles.clone());
self.menu.print_footer();
@@ -502,7 +524,7 @@ impl FeroxScans {
let bar_type = match self.output_level {
OutputLevel::Default => BarType::Message,
OutputLevel::Quiet => BarType::Quiet,
OutputLevel::Silent => return Ok(()), // fast exit when --silent was used
OutputLevel::Silent | OutputLevel::SilentJSON => return Ok(()), // fast exit when --silent was used
};
if let Ok(scans) = self.scans.read() {
@@ -595,7 +617,7 @@ impl FeroxScans {
let bar_type = match self.output_level {
OutputLevel::Default => BarType::Default,
OutputLevel::Quiet => BarType::Quiet,
OutputLevel::Silent => BarType::Hidden,
OutputLevel::Silent | OutputLevel::SilentJSON => BarType::Hidden,
};
let progress_bar = add_bar(url, bar_length, bar_type);

View File

@@ -202,6 +202,7 @@ fn partial_eq_compares_the_id_field() {
assert!(!scan.eq(&scan_two));
#[allow(clippy::redundant_clone)]
let scan_two = scan.clone();
assert!(scan.eq(&scan_two));
@@ -313,7 +314,7 @@ fn ferox_scans_serialize() {
#[test]
/// given a FeroxResponses, test that it serializes into the proper JSON entry
fn ferox_responses_serialize() {
let json_response = r#"{"type":"response","url":"https://nerdcore.com/css","original_url":"https://nerdcore.com","path":"/css","wildcard":true,"status":301,"method":"GET","content_length":173,"line_count":10,"word_count":16,"headers":{"server":"nginx/1.16.1"},"extension":""}"#;
let json_response = r#"{"type":"response","url":"https://nerdcore.com/css","original_url":"https://nerdcore.com","path":"/css","wildcard":true,"status":301,"method":"GET","content_length":173,"line_count":10,"word_count":16,"headers":{"server":"nginx/1.16.1"},"extension":"","timestamp":1711796681.3455093}"#;
let response: FeroxResponse = serde_json::from_str(json_response).unwrap();
let responses = FeroxResponses::default();
@@ -331,7 +332,7 @@ fn ferox_responses_serialize() {
/// given a FeroxResponse, test that it serializes into the proper JSON entry
fn ferox_response_serialize_and_deserialize() {
// deserialize
let json_response = r#"{"type":"response","url":"https://nerdcore.com/css","original_url":"https://nerdcore.com","path":"/css","wildcard":true,"status":301,"method":"GET","content_length":173,"line_count":10,"word_count":16,"headers":{"server":"nginx/1.16.1"},"extension":""}"#;
let json_response = r#"{"type":"response","url":"https://nerdcore.com/css","original_url":"https://nerdcore.com","path":"/css","wildcard":true,"status":301,"method":"GET","content_length":173,"line_count":10,"word_count":16,"headers":{"server":"nginx/1.16.1"},"extension":"","timestamp":1711796681.3455093}"#;
let response: FeroxResponse = serde_json::from_str(json_response).unwrap();
assert_eq!(response.url().as_str(), "https://nerdcore.com/css");
@@ -342,6 +343,7 @@ fn ferox_response_serialize_and_deserialize() {
assert_eq!(response.line_count(), 10);
assert_eq!(response.word_count(), 16);
assert_eq!(response.headers().get("server").unwrap(), "nginx/1.16.1");
assert_eq!(response.timestamp(), 1711796681.3455093);
// serialize, however, this can fail when headers are out of order
let new_json = serde_json::to_string(&response).unwrap();
@@ -489,6 +491,9 @@ fn feroxstates_feroxserialize_implementation() {
r#""url_denylist":[]"#,
r#""responses""#,
r#""type":"response""#,
r#""client_cert":"""#,
r#""client_key":"""#,
r#""server_certs":[]"#,
r#""url":"https://nerdcore.com/css""#,
r#""path":"/css""#,
r#""wildcard":true"#,

View File

@@ -214,9 +214,9 @@ impl FeroxScanner {
.url(&self.target_url)
.handles(self.handles.clone())
.build()?;
let result = extractor.extract().await?;
extraction_tasks.push(extractor.request_links(result).await?)
if let Ok(result) = extractor.extract().await {
extraction_tasks.push(extractor.request_links(result).await?)
}
}
let scanned_urls = self.handles.ferox_scans()?;
@@ -251,60 +251,57 @@ impl FeroxScanner {
// heuristics test block:
let test = heuristics::HeuristicTests::new(self.handles.clone());
if let Ok(dirlist_result) = test.directory_listing(&self.target_url).await {
if dirlist_result.is_some() {
let dirlist_result = dirlist_result.unwrap();
// at this point, we have a DirListingType, and it's not the None variant
// which means we found directory listing based on the heuristic; now we need
// to process the links that are available if --extract-links was used
if let Ok(Some(dirlist_result)) = test.directory_listing(&self.target_url).await {
// at this point, we have a DirListingType, and it's not the None variant
// which means we found directory listing based on the heuristic; now we need
// to process the links that are available if --extract-links was used
if self.handles.config.extract_links {
let mut extractor = ExtractorBuilder::default()
.response(&dirlist_result.response)
.target(ExtractionTarget::DirectoryListing)
.url(&self.target_url)
.handles(self.handles.clone())
.build()?;
if self.handles.config.extract_links {
let mut extractor = ExtractorBuilder::default()
.response(&dirlist_result.response)
.target(ExtractionTarget::DirectoryListing)
.url(&self.target_url)
.handles(self.handles.clone())
.build()?;
let result = extractor.extract_from_dir_listing().await?;
let result = extractor.extract_from_dir_listing().await?;
extraction_tasks.push(extractor.request_links(result).await?);
extraction_tasks.push(extractor.request_links(result).await?);
log::trace!("exit: scan_url -> Directory listing heuristic");
log::trace!("exit: scan_url -> Directory listing heuristic");
self.handles.stats.send(AddToF64Field(
DirScanTimes,
scan_timer.elapsed().as_secs_f64(),
))?;
self.handles.stats.send(AddToF64Field(
DirScanTimes,
scan_timer.elapsed().as_secs_f64(),
))?;
self.handles.stats.send(SubtractFromUsizeField(
TotalExpected,
progress_bar.length().unwrap_or(0) as usize,
))?;
self.handles.stats.send(SubtractFromUsizeField(
TotalExpected,
progress_bar.length().unwrap_or(0) as usize,
))?;
}
let mut message = format!("=> {}", style("Directory listing").blue().bright());
if !self.handles.config.extract_links {
write!(
message,
" (remove {} to scan)",
style("--dont-extract-links").bright().yellow()
)?;
}
if !self.handles.config.force_recursion {
for handle in extraction_tasks.into_iter().flatten() {
_ = handle.await;
}
let mut message = format!("=> {}", style("Directory listing").blue().bright());
progress_bar.reset_eta();
progress_bar.finish_with_message(message);
if !self.handles.config.extract_links {
write!(
message,
" (remove {} to scan)",
style("--dont-extract-links").bright().yellow()
)?;
}
ferox_scan.finish()?;
if !self.handles.config.force_recursion {
for handle in extraction_tasks.into_iter().flatten() {
_ = handle.await;
}
progress_bar.reset_eta();
progress_bar.finish_with_message(message);
ferox_scan.finish()?;
return Ok(()); // nothing left to do if we found a dir listing
}
return Ok(()); // nothing left to do if we found a dir listing
}
}

View File

@@ -475,12 +475,13 @@ impl Requester {
if self.handles.config.collect_words {
if let Ok(mut guard) = TF_IDF.write() {
let doc = Document::from_html(ferox_response.text());
guard.add_document(doc);
if guard.num_documents() % 12 == 0
|| (guard.num_documents() < 5 && guard.num_documents() % 2 == 0)
{
guard.calculate_tf_idf_scores();
if let Some(doc) = Document::from_html(ferox_response.text()) {
guard.add_document(doc);
if guard.num_documents() % 12 == 0
|| (guard.num_documents() < 5 && guard.num_documents() % 2 == 0)
{
guard.calculate_tf_idf_scores();
}
}
}
}

View File

@@ -132,6 +132,9 @@ pub struct Stats {
/// tracker for whether to use json during serialization or not
json: bool,
/// tracker for the initial targets that were passed in to the scan
targets: Mutex<Vec<String>>,
}
/// FeroxSerialize implementation for Stats
@@ -196,6 +199,7 @@ impl Serialize for Stats {
state.serialize_field("request_errors", &atomic_load!(self.request_errors))?;
state.serialize_field("directory_scan_times", &self.directory_scan_times)?;
state.serialize_field("total_runtime", &self.total_runtime)?;
state.serialize_field("targets", &self.targets)?;
state.end()
}
@@ -446,6 +450,17 @@ impl<'a> Deserialize<'a> for Stats {
}
}
}
"targets" => {
if let Some(arr) = value.as_array() {
for val in arr {
if let Some(parsed) = val.as_str() {
if let Ok(mut guard) = stats.targets.lock() {
guard.push(parsed.to_string())
}
}
}
}
}
_ => {}
}
}
@@ -514,6 +529,13 @@ impl Stats {
}
}
/// update targets with the given vector of strings
pub fn update_targets(&self, targets: Vec<String>) {
if let Ok(mut locked_targets) = self.targets.lock() {
*locked_targets = targets;
}
}
/// save an instance of `Stats` to disk after updating the total runtime for the scan
pub fn save(&self, seconds: f64, location: &str) -> Result<()> {
let mut file = open_file(location)?;

View File

@@ -55,7 +55,7 @@ fn save_writes_stats_object_to_disk() {
stats.add_status_code(StatusCode::OK);
stats.add_status_code(StatusCode::OK);
let outfile = NamedTempFile::new().unwrap();
if stats.save(174.33, outfile.path().to_str().unwrap()).is_ok() {}
assert!(stats.save(174.33, outfile.path().to_str().unwrap()).is_ok());
assert!(stats.as_json().unwrap().contains("statistics"));
assert!(stats.as_json().unwrap().contains("11")); // requests made

View File

@@ -270,7 +270,7 @@ mod tests {
let pdf = Url::parse("http://localhost/turbo.pdf").unwrap();
let tar = Url::parse("http://localhost/turbo.tar.gz").unwrap();
let expected = vec![
let expected = [
vec![base.clone(), js.clone()],
vec![base.clone(), js.clone(), php.clone()],
vec![base.clone(), js.clone(), php.clone(), pdf.clone()],

View File

@@ -68,6 +68,20 @@ pub fn fmt_err(msg: &str) -> String {
format!("{}: {}", status_colorizer("ERROR"), msg)
}
/// simple wrapper to get the current system time as
/// time elapsed from unix epoch
pub fn timestamp() -> f64 {
let since_the_epoch = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_else(|_| Duration::from_secs(0));
let secs = since_the_epoch.as_secs() as f64;
let nanos = since_the_epoch.subsec_nanos() as f64;
// Convert nanoseconds to fractional seconds and add to secs
secs + (nanos / 1_000_000_000.0)
}
/// given a FeroxResponse, send a TryRecursion command
///
/// moved to utils to allow for calls from extractor and scanner
@@ -563,6 +577,13 @@ pub fn parse_url_with_raw_path(url: &str) -> Result<Url> {
bail!("url to parse has no authority and is therefore invalid");
}
// thanks to @devx00: the possibility exists for Url to return true for
// has_authority, but not have a host/port, so we'll check for that
// and bail if it's the case
if parsed.host().is_none() {
bail!("url to parse doesn't have a host");
}
// we have a valid url, the next step is to check the path and see if it's
// something that url::Url::parse would silently transform
//
@@ -648,7 +669,7 @@ pub fn parse_url_with_raw_path(url: &str) -> Result<Url> {
// each of the following is a string that we can expect url::Url::parse to
// transform. The variety is to ensure we cover most common path traversal
// encodings
let transformation_detectors = vec![
let transformation_detectors = [
// ascii
"..",
// single url encoded
@@ -729,6 +750,18 @@ mod tests {
use crate::config::Configuration;
use crate::scan_manager::{FeroxScans, ScanOrder};
#[test]
/// parse_url_with_raw_path with javascript:// should not throw an unimplemented! error
fn utils_parse_url_with_raw_path_javascript() {
let url = "javascript://";
let parsed = parse_url_with_raw_path(url);
assert!(parsed.is_err());
assert!(parsed
.unwrap_err()
.to_string()
.contains("url to parse doesn't have a host"));
}
#[test]
/// multiple tests for parse_url_with_raw_path
fn utils_parse_url_with_raw_path() {

View File

@@ -0,0 +1,17 @@
(mTLS) {
tls {
client_auth {
mode require_and_verify
trusted_ca_cert_file certs/server/ca.crt
}
}
}
https://localhost:8001 {
import mTLS
log
handle / {
file_server browse
}
}

View File

@@ -0,0 +1,6 @@
# Testing mTLS
- run `gen-certs.sh`
- run `sudo /path/to/caddy run`
- expect listener on port 8001
- run `feroxbuster -u https://localhost:8001 --client-key certs/client/client.key --client-cert certs/client/client.crt`

View File

@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICqzCCAZMCFE22XDzrLwkJIkb3EdP333d4HoXQMA0GCSqGSIb3DQEBCwUAMBMx
ETAPBgNVBAMMCFNlcnZlckNBMB4XDTIzMDUwNjExMDYyM1oXDTI0MDUwNTExMDYy
M1owETEPMA0GA1UEAwwGQ2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAz3EPWMsh+dfPdbHtpNhizZZs+r0djzdHHgkbnNQ1PodWDnv0Rf1YgNEa
umQuUvIgjMtorRqbz9HLG4+H2aR5KHgPwBNHyKS4PEiQvWDV88aJxdMbgL/IfzAt
di85UcBUkyqUe1r6vIS0smJo1wVwxLEmD6kdt1BEI3LaK1j99JeG8TAS8f+/xf4s
ouE4lA+y3bJQP18wUGuyudntFQBKgjY2Tx+RWbBcx0zW68M7IMQ5bDz0oK9MYw8G
q2vwcRyMLuoyNpbDT5mI2wsQu/r2O0CCNbtkg5JxasdYR7Llw9YTl74st3dshM9e
4V5uuVotcWXW6U518nWHOQy9qiBSOQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQB4
xOVvWrRZ4SBqzaen32COXpjddX28Q7YmNB/UKl3ZT7R1dIjUMfJz2le0mj2UpSAr
rDT7PCsXnDP0KswGiJC3IVTa/hnkUk798jwUvp221jvebyy8/NMWfWPoIKfhELdb
3uJfrGyQuB8Zf9Q1hc9jYDX27EbGaDSpOrpE9Ej2riVnbgBKZsS5jcfY8JDrkv+F
4cP2pTu6mVRuU1Bzx3SB0Vg2uGi1QTJuuA905Y3zpoRfTtybKlRRkMQk+46xrdyV
x64wq9zcL6Kq4D/UE3EjLnjbRw6H6g8jbnBjT5KRfP2tmbF9RTZs44Dl0hYvXber
HrvWtxHG8OJ8BLQg1rQd
-----END CERTIFICATE-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPcQ9YyyH51891
se2k2GLNlmz6vR2PN0ceCRuc1DU+h1YOe/RF/ViA0Rq6ZC5S8iCMy2itGpvP0csb
j4fZpHkoeA/AE0fIpLg8SJC9YNXzxonF0xuAv8h/MC12LzlRwFSTKpR7Wvq8hLSy
YmjXBXDEsSYPqR23UEQjctorWP30l4bxMBLx/7/F/iyi4TiUD7LdslA/XzBQa7K5
2e0VAEqCNjZPH5FZsFzHTNbrwzsgxDlsPPSgr0xjDwara/BxHIwu6jI2lsNPmYjb
CxC7+vY7QII1u2SDknFqx1hHsuXD1hOXviy3d2yEz17hXm65Wi1xZdbpTnXydYc5
DL2qIFI5AgMBAAECggEAC8XVeoM1w4uITDxLucMnkVYgC3dj5/K5zCY1bVg8SNcO
rt4BSh8TkKT9ZLZmjCHOb9sj7s4PqXLVOXRTAAq17xJoR2z4shYKGC7AmyTVo6MB
AuuFGDCaMQCzlc1ejgmRqzP7jwgl6oDIDgcofsqB4MHSgIlHJNYO9emQ4OypJgJA
xd8KT5S/hThJG1VqJ6P0oiB/WBlzcJ5wX4GSVE25RlpRX8ogqCyI9V+SRq2CrG7U
Jqv3Kbag7derTfqmsKyjv/kckOgfKH/rm61HMrYshcPfgxL2fZe2Q8wCTexvhZwZ
8vD8bvR++SxOxbigCIB7ReYgmoj4bocjqDX4vUhe8QKBgQD35oDdOa2uiOs7NWVf
IV1ZwPWxxwnYFIEA8paQwsYGIxHrYNdGSsGBzwvLDPpTeOO0VdoC+sP5zytTv547
djeOzGf9Hj6swa5tPdzkYjZV/85mnmGKaEmmCN4AvpYol5l2BTetFtX0v6QEaqvU
uZbV5X2UcuClExA0frNUJDVHkQKBgQDWOCZq1r9X3iEkcFSBhironVNj80jFqIum
rMbGUUcOI05U2hkmMDluSW1NNL2k+SNJXq7fmkjIQEXffqcbsXUSIQB0MU6yddt5
7+c19ioZChx91Kl049rKQ21kPTh7D0TCUvDQLapt2xbUNg6rGCLSrkkVlWwxLnDU
pNk/c4QcKQKBgEreedLWhabtwSV7pecKO5hM16dedpGk96UinuiPeqEF3HabI8kd
8L1Um7oybDPjkdm4CATYWXHL6Mj9WTuaI4NkJo/in4krYZOqmFj9dG2auWpysQDN
KFkV2n6dENqnlnh3cO48tFebvVx8HvM7Ldvh2ICKBWC1ljJUhbKG0PSRAoGBAJVy
fNLCWKEbVbHPMBVgnaTExT2Qp29F4493MAGBCHpDhU1LDoqG0DoxvbBEIB3stYJl
LMjQIQCbXmPKPxjh15O7NE7ba1SzRleuV3Zc8wee9zuN1l6265d6LOHml/W6NDUB
mgESKrkTRLztrZQNdZXXgyMsqFszVAH1s55Bn6PpAoGBAN13Ev7Ynysdvkc3aHO6
qM0hH6mAlEOAyCTk5r/0cyz9rGyYWXiVXen0ftSaBcISdzhrVkRDs3rLrHwEXdu1
Y2Z1HhZkILw/C4t+Eaa6FOWfwwPAdOpaxYpxKxCEeCBKmkd1z0Dx0vDEDrt+AaHa
UYIQ9wAbZpuKGfFQceyr1lBO
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDBzCCAe+gAwIBAgIUG3vb4pIbvaI/+LzOpu6Z4b6s4iIwDQYJKoZIhvcNAQEL
BQAwEzERMA8GA1UEAwwIU2VydmVyQ0EwHhcNMjMwNTA2MTEwNjIzWhcNMzMwNTAz
MTEwNjIzWjATMREwDwYDVQQDDAhTZXJ2ZXJDQTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAKazNKPaH8LDzcaZRvBLrDNJkL1pukmB36mbczj07hZVbPmS
/hyBvAdBFom0ZTw5dIpsUtRSZbDPrsCVpdY9O1jxwhrDi6mfvyJtKLEbTW4PvARq
WwDhpa2SYwBMI+0ilXWTAzwJuWT1NhuUsAcB6SGwkNm3iKqZUDxn3V2L2AHRcKEJ
9Zn9ePP4BsvtAS8ZBLxTnoo7R2SHiWwjDwuTtS4fQ5bWzGkmdmeuJ7JJt4ZzQV+m
MBqrK3XVi+MXayvt5affGvHj/KuhlVBXHnUgSvEgFpuhK9elsds2iRho8mp1d0iH
EIMp9LHVftsIpUxbKt/Pa/JL7oG9LBIvPj/SIjsCAwEAAaNTMFEwHQYDVR0OBBYE
FLlRKsDb/ducVIBirME0VJZ3TwfkMB8GA1UdIwQYMBaAFLlRKsDb/ducVIBirME0
VJZ3TwfkMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAD7lqNzU
wxuyO60Gn2q6DUBb1Kseq6bSndNHeagdfMfKManKl1YObnB0ciTO3bnmNXiXktSu
BsQzlmr3O+H6X39Vpdyqq4SoOcOt0I+bvBykk1UZqEoc7jGXdZVmnk9Q0uoKtWxJ
rV9CHEhyPNnEh4W07y05UUn9S6EiKy5232yi4USdmk44GXhFblS5inhTTxca2vEq
9h+FH+QZ7ehaAaWR+EaQjXNwm2mN7gWxM3Q6RfK9N67MHD9ggmfdyZmnyt5gCidC
ys4W4stEh6d6fXZT77dcGaHKdXW3GwP3ZcAlRFYPqpAvWzndC9kDCgIULeSP1ALy
cILcb0HQvNS0t60=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICrzCCAZcCFGMKRtmMLuut+sxC+TbWQfum7oXZMA0GCSqGSIb3DQEBCwUAMBQx
EjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMzA1MDYxMTA2MjNaFw0zMzA1MDMxMTA2
MjNaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAKazNKPaH8LDzcaZRvBLrDNJkL1pukmB36mbczj07hZVbPmS/hyB
vAdBFom0ZTw5dIpsUtRSZbDPrsCVpdY9O1jxwhrDi6mfvyJtKLEbTW4PvARqWwDh
pa2SYwBMI+0ilXWTAzwJuWT1NhuUsAcB6SGwkNm3iKqZUDxn3V2L2AHRcKEJ9Zn9
ePP4BsvtAS8ZBLxTnoo7R2SHiWwjDwuTtS4fQ5bWzGkmdmeuJ7JJt4ZzQV+mMBqr
K3XVi+MXayvt5affGvHj/KuhlVBXHnUgSvEgFpuhK9elsds2iRho8mp1d0iHEIMp
9LHVftsIpUxbKt/Pa/JL7oG9LBIvPj/SIjsCAwEAATANBgkqhkiG9w0BAQsFAAOC
AQEAjPAtZs1by2h/1fr/ypojw16llzbReT8J+T8YHSTf6YwjoE83I0QDOLEo1ax+
e/8qyQLs0EnlfdomNyA4Z/ECbY5c1nY0Dp//u6WH7AwLUx5HiwUw4Fmxu9Q/oB1o
3vhIPl5Vd/VpdxDzuO8q8WvagwjVaxsZP3PVaBDRzZZPldPgTakfk+w5XnjNfgJi
RDRutTRe6KBOxt7PAzAVV71FtOIq0b4xCNJGNurYBhRgZ5iQ7yMw+I5Vte1TakWr
9gfE/yoKbU1W+y0QxSDTsnTCO4i3mXmBTuceTVWELwqZcr34W7n3vD8UtZQfanML
cHCZaLPSMDuDtS74FSamP3i+oQ==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICrzCCAZcCFGMKRtmMLuut+sxC+TbWQfum7oXZMA0GCSqGSIb3DQEBCwUAMBQx
EjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMzA1MDYxMTA2MjNaFw0zMzA1MDMxMTA2
MjNaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAKazNKPaH8LDzcaZRvBLrDNJkL1pukmB36mbczj07hZVbPmS/hyB
vAdBFom0ZTw5dIpsUtRSZbDPrsCVpdY9O1jxwhrDi6mfvyJtKLEbTW4PvARqWwDh
pa2SYwBMI+0ilXWTAzwJuWT1NhuUsAcB6SGwkNm3iKqZUDxn3V2L2AHRcKEJ9Zn9
ePP4BsvtAS8ZBLxTnoo7R2SHiWwjDwuTtS4fQ5bWzGkmdmeuJ7JJt4ZzQV+mMBqr
K3XVi+MXayvt5affGvHj/KuhlVBXHnUgSvEgFpuhK9elsds2iRho8mp1d0iHEIMp
9LHVftsIpUxbKt/Pa/JL7oG9LBIvPj/SIjsCAwEAATANBgkqhkiG9w0BAQsFAAOC
AQEAjPAtZs1by2h/1fr/ypojw16llzbReT8J+T8YHSTf6YwjoE83I0QDOLEo1ax+
e/8qyQLs0EnlfdomNyA4Z/ECbY5c1nY0Dp//u6WH7AwLUx5HiwUw4Fmxu9Q/oB1o
3vhIPl5Vd/VpdxDzuO8q8WvagwjVaxsZP3PVaBDRzZZPldPgTakfk+w5XnjNfgJi
RDRutTRe6KBOxt7PAzAVV71FtOIq0b4xCNJGNurYBhRgZ5iQ7yMw+I5Vte1TakWr
9gfE/yoKbU1W+y0QxSDTsnTCO4i3mXmBTuceTVWELwqZcr34W7n3vD8UtZQfanML
cHCZaLPSMDuDtS74FSamP3i+oQ==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICrzCCAZcCFGMKRtmMLuut+sxC+TbWQfum7oXZMA0GCSqGSIb3DQEBCwUAMBQx
EjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMzA1MDYxMTA2MjNaFw0zMzA1MDMxMTA2
MjNaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAKazNKPaH8LDzcaZRvBLrDNJkL1pukmB36mbczj07hZVbPmS/hyB
vAdBFom0ZTw5dIpsUtRSZbDPrsCVpdY9O1jxwhrDi6mfvyJtKLEbTW4PvARqWwDh
pa2SYwBMI+0ilXWTAzwJuWT1NhuUsAcB6SGwkNm3iKqZUDxn3V2L2AHRcKEJ9Zn9
ePP4BsvtAS8ZBLxTnoo7R2SHiWwjDwuTtS4fQ5bWzGkmdmeuJ7JJt4ZzQV+mMBqr
K3XVi+MXayvt5affGvHj/KuhlVBXHnUgSvEgFpuhK9elsds2iRho8mp1d0iHEIMp
9LHVftsIpUxbKt/Pa/JL7oG9LBIvPj/SIjsCAwEAATANBgkqhkiG9w0BAQsFAAOC
AQEAjPAtZs1by2h/1fr/ypojw16llzbReT8J+T8YHSTf6YwjoE83I0QDOLEo1ax+
e/8qyQLs0EnlfdomNyA4Z/ECbY5c1nY0Dp//u6WH7AwLUx5HiwUw4Fmxu9Q/oB1o
3vhIPl5Vd/VpdxDzuO8q8WvagwjVaxsZP3PVaBDRzZZPldPgTakfk+w5XnjNfgJi
RDRutTRe6KBOxt7PAzAVV71FtOIq0b4xCNJGNurYBhRgZ5iQ7yMw+I5Vte1TakWr
9gfE/yoKbU1W+y0QxSDTsnTCO4i3mXmBTuceTVWELwqZcr34W7n3vD8UtZQfanML
cHCZaLPSMDuDtS74FSamP3i+oQ==
-----END CERTIFICATE-----

Binary file not shown.

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCmszSj2h/Cw83G
mUbwS6wzSZC9abpJgd+pm3M49O4WVWz5kv4cgbwHQRaJtGU8OXSKbFLUUmWwz67A
laXWPTtY8cIaw4upn78ibSixG01uD7wEalsA4aWtkmMATCPtIpV1kwM8Cblk9TYb
lLAHAekhsJDZt4iqmVA8Z91di9gB0XChCfWZ/Xjz+AbL7QEvGQS8U56KO0dkh4ls
Iw8Lk7UuH0OW1sxpJnZnrieySbeGc0FfpjAaqyt11YvjF2sr7eWn3xrx4/yroZVQ
Vx51IErxIBaboSvXpbHbNokYaPJqdXdIhxCDKfSx1X7bCKVMWyrfz2vyS+6BvSwS
Lz4/0iI7AgMBAAECggEAKvz2u7Rh0WOWGrtnQEt7bkRv13C+8frUd1QXnB4JkefY
sOmXrzlDiGlgCwXiv2ufopy5pXhUMgr0qUROHlfvCIpbwHQh/Y2tCA83WajNSG81
ULwumKUYCRFBh4+bCimLemT9hguJ7D+SAv3OgRgciywRxpteWoQr3U/5lYidHSZ/
gv13lVKbn72zD5opeGA2hS1MlLZV/xueSvhT3lzbv61hqdersACj2Tvi8O2/imVy
XjEZnPRQhlFPtiAN5J3on6Xo+MqieuhA3yxhBrYoLsMrTCK6ePThdXfcgmpEjQ8l
6HxNmnPri5KbxCTbGgCjMiSidnRim2IpBMEP32eN8QKBgQDLr2CoMdyliFU6Gm1P
rxWTMnvzdVbXUp4B8YEdyNyKdWt50cqbB3UvnFX2gpELYdy3uYcXTKm+Nynruam1
Z/Ya1HXwN+wdgQhjq9n4izLvEfUkXWDNNikQmts8Uxkp+uEK+OOp1/NjZlA6YdS3
crq5wPxLoAP2JxiaoIJF74QMqwKBgQDRhABgZbWVHwPcLVVqZ5+MJYvQORqIqapf
kGe/jR/CMC0Tkop2O1tY3f68bMNKkXfj7QEtDlppMswZ9MOqBBr56yGZzrQa+cB3
lF4+hP06OvyIkdmZlP4NHm/DtF9gt1KjWPF2VcIfD3VfZO8E/XJn2n1KnKCU+4cb
lyJYi9AgsQKBgQCWkgPy8kE5QSo3tJeAI17gnJ5SoDhdHp7dsukO2pBl7l1QBY0v
w3iWhIxrmaOddW+ThZve1nZYvjDIKEzTZJHizZKNzNlICj3oaH7OpCA36N9+TWUk
7le3BbLxykA870/zK4Ao6xHqNhUyw2VbY32zmX0obpbfHZGrpOIIzwGf1wKBgDlM
U1oJls5QbBrT3w85hZ2rSwBIDaSgWfLGqEjvjGbsC/fVVL6e3w1/sMHRMNt8yv/v
einbSgiJFt5mXPhrJQGCN28742+ZK/TIA7ovXp2FMjkbQhpJb+0gjMpF0uu9VwFL
OsX1ECC0dpH/JYsE0TvrueYkzZnQ7BM0kvUKT4IRAoGAOPVed0zkDh3iobQ3A3IG
JepRygabC68iOHlrD6sVxST0HdyP9pxwMe9gnz5TDAZkWvhJV0UUmaMCpbShsc+n
ymKSNnXAxt+G6XHH3Mg9aDNi70og4HhhT6dU2579xUOBY2057ZgpWXK3rf+JKls4
XlkplyHw0UqkEhCw+FMa3Gs=
-----END PRIVATE KEY-----

31
tests/mutual-auth/gen-certs.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/bash
# Create server and client certificate directories
mkdir -p certs/server
mkdir -p certs/client
# Generate server key
openssl genrsa -out certs/server/server.key 2048
# Generate a Certificate Signing Request (CSR) for the server key
openssl req -new -key certs/server/server.key -out certs/server/server.csr -subj "/CN=localhost"
# Self-sign the server CSR to create the server certificate
openssl x509 -req -in certs/server/server.csr -signkey certs/server/server.key -out certs/server/server.crt -days 3650
# Generate server-side Certificate Authority (CA) file
openssl req -x509 -nodes -new -key certs/server/server.key -sha256 -days 3650 -out certs/server/ca.crt -subj "/CN=ServerCA"
# Generate client key
openssl genrsa -out certs/client/client.key 2048
# Generate a Certificate Signing Request (CSR) for the client key
openssl req -new -key certs/client/client.key -out certs/client/client.csr -subj "/CN=Client"
# Sign the client CSR with the server CA to create the client certificate
openssl x509 -req -in certs/client/client.csr -CA certs/server/ca.crt -CAkey certs/server/server.key -CAcreateserial -out certs/client/client.crt -days 365
# Cleanup
rm -f certs/server/server.csr
rm -f certs/client/client.csr

View File

@@ -661,6 +661,70 @@ fn banner_prints_recursion_depth() {
);
}
#[test]
/// test allows non-existent wordlist to trigger the banner printing to stderr
/// expect to see all mandatory prints + server certs
fn banner_prints_server_certs() {
Command::cargo_bin("feroxbuster")
.unwrap()
.arg("--url")
.arg("http://localhost")
.arg("--server-certs")
.arg("tests/mutual-auth/certs/server/server.crt.1")
.arg("tests/mutual-auth/certs/server/server.crt.2")
.arg("--wordlist")
.arg("/definitely/doesnt/exist/0cd7fed0-47f4-4b18-a1b0-ac39708c1676")
.assert()
.success()
.stderr(
predicate::str::contains("─┬─")
.and(predicate::str::contains("Target Url"))
.and(predicate::str::contains("http://localhost"))
.and(predicate::str::contains("Threads"))
.and(predicate::str::contains("Wordlist"))
.and(predicate::str::contains("Status Codes"))
.and(predicate::str::contains("Timeout (secs)"))
.and(predicate::str::contains("User-Agent"))
.and(predicate::str::contains("Server Certificates"))
.and(predicate::str::contains("server.crt.1"))
.and(predicate::str::contains("server.crt.2"))
.and(predicate::str::contains("─┴─")),
);
}
#[test]
/// test allows non-existent wordlist to trigger the banner printing to stderr
/// expect to see all mandatory prints + server certs
fn banner_prints_client_cert_and_key() {
Command::cargo_bin("feroxbuster")
.unwrap()
.arg("--url")
.arg("http://localhost")
.arg("--client-cert")
.arg("tests/mutual-auth/certs/client/client.crt")
.arg("--client-key")
.arg("tests/mutual-auth/certs/client/client.key")
.arg("--wordlist")
.arg("/definitely/doesnt/exist/0cd7fed0-47f4-4b18-a1b0-ac39708c1676")
.assert()
.success()
.stderr(
predicate::str::contains("─┬─")
.and(predicate::str::contains("Target Url"))
.and(predicate::str::contains("http://localhost"))
.and(predicate::str::contains("Threads"))
.and(predicate::str::contains("Wordlist"))
.and(predicate::str::contains("Status Codes"))
.and(predicate::str::contains("Timeout (secs)"))
.and(predicate::str::contains("User-Agent"))
.and(predicate::str::contains("Client Certificate"))
.and(predicate::str::contains("Client Key"))
.and(predicate::str::contains("certs/client/client.crt"))
.and(predicate::str::contains("certs/client/client.key"))
.and(predicate::str::contains("─┴─")),
);
}
#[test]
/// test allows non-existent wordlist to trigger the banner printing to stderr
/// expect to see all mandatory prints + no recursion
@@ -1082,6 +1146,7 @@ fn banner_prints_parallel() {
Command::cargo_bin("feroxbuster")
.unwrap()
.arg("--stdin")
.arg("--quiet")
.arg("--parallel")
.arg("4316")
.arg("--wordlist")
@@ -1366,6 +1431,7 @@ fn banner_prints_all_composite_settings_burp() {
.and(predicate::str::contains("─┴─")),
);
}
#[test]
/// test allows non-existent wordlist to trigger the banner printing to stderr
/// expect to see all mandatory prints + collect words

View File

@@ -1,5 +1,6 @@
mod utils;
use assert_cmd::prelude::*;
use httpmock::MockServer;
use predicates::prelude::*;
use std::process::Command;
use utils::{setup_tmp_directory, teardown_tmp_directory};
@@ -7,13 +8,15 @@ use utils::{setup_tmp_directory, teardown_tmp_directory};
#[test]
/// send a single valid request, expect a 200 response
fn read_in_config_file_for_settings() -> Result<(), Box<dyn std::error::Error>> {
let srv = MockServer::start();
let (tmp_dir, file) = setup_tmp_directory(&["threads = 37".to_string()], "ferox-config.toml")?;
Command::cargo_bin("feroxbuster")
.unwrap()
.current_dir(&tmp_dir)
.arg("--url")
.arg("http://localhost")
.arg(srv.url("/"))
.arg("--wordlist")
.arg(file.as_os_str())
.arg("-vvvv")

View File

@@ -640,3 +640,73 @@ fn extractor_recurses_into_403_directories() -> Result<(), Box<dyn std::error::E
teardown_tmp_directory(tmp_dir);
Ok(())
}
#[test]
/// robots.txt requests shouldn't fire when --dont-extract-links is used
fn robots_text_extraction_doesnt_run_with_dont_extract_links() {
let srv = MockServer::start();
let (tmp_dir, file) = setup_tmp_directory(&["LICENSE".to_string()], "wordlist").unwrap();
let mock = srv.mock(|when, then| {
when.method(GET).path("/LICENSE");
then.status(200).body("im a little teapot"); // 18
});
let mock_two = srv.mock(|when, then| {
when.method(GET).path("/robots.txt");
then.status(200).body(
r#"
User-agent: *
Crawl-delay: 10
# CSS, JS, Images
Allow: /misc/*.css$
Disallow: /misc/stupidfile.php
Disallow: /disallowed-subdir/
"#,
);
});
let mock_file = srv.mock(|when, then| {
when.method(GET).path("/misc/stupidfile.php");
then.status(200).body("im a little teapot too"); // 22
});
let mock_scanned_file = srv.mock(|when, then| {
when.method(GET).path("/misc/LICENSE");
then.status(200).body("i too, am a container for tea"); // 29
});
let mock_dir = srv.mock(|when, _| {
when.method(GET).path("/misc/");
});
let mock_disallowed = srv.mock(|when, then| {
when.method(GET).path("/disallowed-subdir");
then.status(404);
});
let cmd = Command::cargo_bin("feroxbuster")
.unwrap()
.arg("--url")
.arg(srv.url("/"))
.arg("--wordlist")
.arg(file.as_os_str())
.arg("--dont-extract-links")
.arg("--no-recursion")
.unwrap();
cmd.assert().success().stdout(
predicate::str::contains("/LICENSE")
.and(predicate::str::contains("18c"))
.and(predicate::str::contains("/misc/stupidfile.php"))
.not(),
);
assert_eq!(mock.hits(), 1);
assert_eq!(mock_dir.hits(), 0);
assert_eq!(mock_two.hits(), 0);
assert_eq!(mock_file.hits(), 0);
assert_eq!(mock_disallowed.hits(), 0);
assert_eq!(mock_scanned_file.hits(), 0);
teardown_tmp_directory(tmp_dir);
}

View File

@@ -247,3 +247,95 @@ fn filters_similar_should_filter_response() {
assert_eq!(not_similar.hits(), 1);
teardown_tmp_directory(tmp_dir);
}
#[test]
/// when using --collect-backups, should only see results in output
/// when the response shouldn't be otherwise filtered
fn collect_backups_should_be_filtered() {
let srv = MockServer::start();
let (tmp_dir, file) = setup_tmp_directory(&["LICENSE".to_string()], "wordlist").unwrap();
let mock = srv.mock(|when: httpmock::When, then| {
when.method(GET).path("/LICENSE");
then.status(200).body("this is a test");
});
let mock_two = srv.mock(|when, then| {
when.method(GET).path("/LICENSE.bak");
then.status(201)
.body("im a backup file, but filtered out because im not 200");
});
let cmd = Command::cargo_bin("feroxbuster")
.unwrap()
.arg("--url")
.arg(srv.url("/"))
.arg("--wordlist")
.arg(file.as_os_str())
.arg("--status-codes")
.arg("200")
.arg("--collect-backups")
.unwrap();
cmd.assert().success().stdout(
predicate::str::contains("/LICENSE")
.and(predicate::str::contains("200"))
.and(predicate::str::contains("/LICENSE.bak"))
.not()
.and(predicate::str::contains("201"))
.not(),
);
assert_eq!(mock.hits(), 1);
assert_eq!(mock_two.hits(), 1);
teardown_tmp_directory(tmp_dir);
}
#[test]
/// create a FeroxResponse that should elicit a true from
/// RegexFilter::should_filter_response
fn filters_regex_should_filter_response_based_on_headers() {
let srv = MockServer::start();
let (tmp_dir, file) = setup_tmp_directory(
&["not-matching".to_string(), "matching".to_string()],
"wordlist",
)
.unwrap();
let mock = srv.mock(|when, then| {
when.method(GET).path("/not-matching");
then.status(200)
.header("content-type", "text/html")
.body("this is a test");
});
let mock_two = srv.mock(|when, then| {
when.method(GET).path("/matching");
then.status(200)
.header("content-type", "application/json")
.body("this is also a test");
});
let cmd = Command::cargo_bin("feroxbuster")
.unwrap()
.arg("--url")
.arg(srv.url("/"))
.arg("--wordlist")
.arg(file.as_os_str())
.arg("--filter-regex")
.arg("content-type:application/json")
.unwrap();
cmd.assert().success().stdout(
predicate::str::contains("/not-matching")
.and(predicate::str::contains("200"))
.and(predicate::str::contains("/matching"))
.not()
.and(predicate::str::contains("200"))
.not(),
);
assert_eq!(mock.hits(), 1);
assert_eq!(mock_two.hits(), 1);
teardown_tmp_directory(tmp_dir);
}

View File

@@ -104,9 +104,9 @@ fn test_single_target_cannot_connect_due_to_ssl_errors() -> Result<(), Box<dyn s
.arg(file.as_os_str())
.assert()
.success()
.stdout(
predicate::str::contains("Could not connect to https://expired.badssl.com due to SSL errors (run with -k to ignore), skipping...", )
);
.stdout(predicate::str::contains(
"Could not connect to https://expired.badssl.com",
));
teardown_tmp_directory(tmp_dir);
Ok(())

View File

@@ -108,10 +108,11 @@ fn main_parallel_spawns_children() -> Result<(), Box<dyn std::error::Error>> {
Command::cargo_bin("feroxbuster")
.unwrap()
.env("RUST_LOG", "trace")
.arg("--stdin")
.arg("--parallel")
.arg("2")
.arg("-vvvv")
.arg("--quiet")
.arg("--debug-log")
.arg(outfile.as_os_str())
.arg("--wordlist")
@@ -172,6 +173,7 @@ fn main_parallel_creates_output_directory() -> Result<(), Box<dyn std::error::Er
Command::cargo_bin("feroxbuster")
.unwrap()
.arg("--stdin")
.arg("--quiet")
.arg("--parallel")
.arg("2")
.arg("--output")

View File

@@ -22,6 +22,7 @@ use utils::{setup_tmp_directory, teardown_tmp_directory};
// these words will be used along with pattern matching to trigger different policies
#[test]
#[ignore]
/// --auto-bail should cancel a scan with spurious errors
fn auto_bail_cancels_scan_with_timeouts() {
let srv = MockServer::start();
@@ -37,7 +38,7 @@ fn auto_bail_cancels_scan_with_timeouts() {
let error_mock = srv.mock(|when, then| {
when.method(GET)
.path_matches(Regex::new("/[a-zA-Z]{6}error[a-zA-Z]{6}").unwrap());
then.delay(Duration::new(3, 0))
then.delay(Duration::new(2, 5000))
.status(200)
.body("verboten, nerd");
});
@@ -57,7 +58,7 @@ fn auto_bail_cancels_scan_with_timeouts() {
Command::cargo_bin("feroxbuster")
.unwrap()
.arg("--url")
.arg(srv.url("/"))
.arg(&srv.url("/"))
.arg("--wordlist")
.arg(file.as_os_str())
.arg("--auto-bail")
@@ -65,10 +66,10 @@ fn auto_bail_cancels_scan_with_timeouts() {
.arg("--timeout")
.arg("2")
.arg("--threads")
.arg("4")
.arg("8")
.arg("--debug-log")
.arg(logfile.as_os_str())
.arg("-vvvv")
.arg("-vv")
.arg("--json")
.assert()
.success();
@@ -146,7 +147,7 @@ fn auto_bail_cancels_scan_with_403s() {
.arg("4")
.arg("--debug-log")
.arg(logfile.as_os_str())
.arg("-vvvv")
.arg("-vv")
.arg("--json")
.assert()
.success();
@@ -228,7 +229,7 @@ fn auto_bail_cancels_scan_with_429s() {
.arg("4")
.arg("--debug-log")
.arg(logfile.as_os_str())
.arg("-vvvv")
.arg("-vvv")
.arg("--json")
.assert()
.success();

View File

@@ -693,7 +693,7 @@ fn collect_backups_makes_appropriate_requests() {
let srv = MockServer::start();
let (tmp_dir, file) = setup_tmp_directory(&["LICENSE.txt".to_string()], "wordlist").unwrap();
let valid_paths = vec![
let valid_paths = [
"/LICENSE.txt",
"/LICENSE.txt~",
"/LICENSE.txt.bak",