Repository: xmendez/wfuzz
Branch: master
Commit: 2263cd0932fe
Files: 235
Total size: 1.7 MB
Directory structure:
gitextract_30du5tiz/
├── .flake8
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ └── docker-release.yml
├── .gitignore
├── .travis.yml
├── Dockerfile
├── ISSUE_TEMPLATE.md
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.md
├── docs/
│ ├── Makefile
│ ├── _templates/
│ │ └── sidebarlogo.html
│ ├── conf.py
│ ├── dev/
│ │ └── plugins.rst
│ ├── index.rst
│ ├── library/
│ │ └── guide.rst
│ ├── make.bat
│ └── user/
│ ├── advanced.rst
│ ├── basicusage.rst
│ ├── breaking.rst
│ ├── getting.rst
│ ├── installation.rst
│ └── wfpayload.rst
├── requirements.txt
├── setup.py
├── src/
│ ├── wfencode.py
│ ├── wfpayload.py
│ ├── wfuzz/
│ │ ├── __init__.py
│ │ ├── __main__.py
│ │ ├── api.py
│ │ ├── core.py
│ │ ├── dictionaries.py
│ │ ├── exception.py
│ │ ├── externals/
│ │ │ ├── __init__.py
│ │ │ ├── moduleman/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── loader.py
│ │ │ │ ├── modulefilter.py
│ │ │ │ ├── plugin.py
│ │ │ │ └── registrant.py
│ │ │ ├── reqresp/
│ │ │ │ ├── Request.py
│ │ │ │ ├── Response.py
│ │ │ │ ├── TextParser.py
│ │ │ │ ├── Variables.py
│ │ │ │ ├── __init__.py
│ │ │ │ ├── cache.py
│ │ │ │ └── exceptions.py
│ │ │ └── settings/
│ │ │ ├── __init__.py
│ │ │ └── settings.py
│ │ ├── facade.py
│ │ ├── factories/
│ │ │ ├── __init__.py
│ │ │ ├── dictfactory.py
│ │ │ ├── fuzzfactory.py
│ │ │ ├── fuzzresfactory.py
│ │ │ ├── payman.py
│ │ │ ├── plugin_factory.py
│ │ │ └── reqresp_factory.py
│ │ ├── filters/
│ │ │ ├── __init__.py
│ │ │ ├── ppfilter.py
│ │ │ └── simplefilter.py
│ │ ├── fuzzobjects.py
│ │ ├── fuzzqueues.py
│ │ ├── fuzzrequest.py
│ │ ├── helpers/
│ │ │ ├── __init__.py
│ │ │ ├── file_func.py
│ │ │ ├── obj_dic.py
│ │ │ ├── obj_dyn.py
│ │ │ ├── obj_factory.py
│ │ │ ├── str_func.py
│ │ │ └── utils.py
│ │ ├── mixins.py
│ │ ├── myhttp.py
│ │ ├── myqueues.py
│ │ ├── options.py
│ │ ├── plugin_api/
│ │ │ ├── __init__.py
│ │ │ ├── base.py
│ │ │ ├── mixins.py
│ │ │ ├── payloadtools.py
│ │ │ └── urlutils.py
│ │ ├── plugins/
│ │ │ ├── __init__.py
│ │ │ ├── encoders/
│ │ │ │ ├── __init__.py
│ │ │ │ └── encoders.py
│ │ │ ├── iterators/
│ │ │ │ ├── __init__.py
│ │ │ │ └── iterations.py
│ │ │ ├── payloads/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── autorize.py
│ │ │ │ ├── bing.py
│ │ │ │ ├── buffer_overflow.py
│ │ │ │ ├── burpitem.py
│ │ │ │ ├── burplog.py
│ │ │ │ ├── burpstate.py
│ │ │ │ ├── dirwalk.py
│ │ │ │ ├── file.py
│ │ │ │ ├── guitab.py
│ │ │ │ ├── hexrand.py
│ │ │ │ ├── hexrange.py
│ │ │ │ ├── ipnet.py
│ │ │ │ ├── iprange.py
│ │ │ │ ├── list.py
│ │ │ │ ├── names.py
│ │ │ │ ├── permutation.py
│ │ │ │ ├── range.py
│ │ │ │ ├── shodanp.py
│ │ │ │ ├── stdin.py
│ │ │ │ └── wfuzzp.py
│ │ │ ├── printers/
│ │ │ │ ├── __init__.py
│ │ │ │ └── printers.py
│ │ │ └── scripts/
│ │ │ ├── __init__.py
│ │ │ ├── backups.py
│ │ │ ├── cookies.py
│ │ │ ├── cvs_extractor.py
│ │ │ ├── errors.py
│ │ │ ├── grep.py
│ │ │ ├── headers.py
│ │ │ ├── links.py
│ │ │ ├── listing.py
│ │ │ ├── npm_deps.py
│ │ │ ├── robots.py
│ │ │ ├── screenshot.py
│ │ │ ├── sitemap.py
│ │ │ ├── svn_extractor.py
│ │ │ ├── title.py
│ │ │ └── wcdb.py
│ │ ├── ui/
│ │ │ ├── __init__.py
│ │ │ ├── console/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── clparser.py
│ │ │ │ ├── common.py
│ │ │ │ ├── getch.py
│ │ │ │ ├── mvc.py
│ │ │ │ └── output.py
│ │ │ └── gui/
│ │ │ ├── __init__.py
│ │ │ ├── controller.py
│ │ │ ├── guicontrols.py
│ │ │ └── model.py
│ │ └── wfuzz.py
│ ├── wfuzz-cli.py
│ └── wxfuzz.py
├── tests/
│ ├── acceptance/
│ │ └── test_saved_filter.py
│ ├── api/
│ │ ├── test_encoders.py
│ │ ├── test_payload.py
│ │ └── test_session.py
│ ├── conftest.py
│ ├── factories/
│ │ └── test_seedbasebuilder.py
│ ├── filters/
│ │ ├── test_filter.py
│ │ ├── test_filter_codes.py
│ │ ├── test_filter_urlp.py
│ │ ├── test_prefilter_mangle.py
│ │ └── test_prefilter_mangle_codes.py
│ ├── helpers/
│ │ ├── test_dotdict.py
│ │ └── test_insensitive_dict.py
│ ├── plugins/
│ │ ├── test_burplog.py
│ │ ├── test_links.py
│ │ └── test_summary.py
│ ├── server_dir/
│ │ ├── Dockerfile
│ │ ├── dir/
│ │ │ ├── a
│ │ │ ├── b
│ │ │ ├── c
│ │ │ └── one
│ │ ├── docker-compose.yml
│ │ ├── iterators/
│ │ │ ├── aa
│ │ │ ├── ac
│ │ │ └── bb
│ │ ├── plugins/
│ │ │ └── robots.txt
│ │ ├── recursive_dir/
│ │ │ └── a/
│ │ │ └── b/
│ │ │ └── c/
│ │ │ └── placeholder.txt
│ │ ├── simple_server.py
│ │ └── static/
│ │ └── placeholder.txt
│ ├── test_acceptance.py
│ ├── test_api.py
│ ├── test_clparser.py
│ ├── test_filterintro.py
│ ├── test_moduleman.py
│ ├── test_relativeurl.py
│ ├── test_req_parse.py
│ └── test_reqresp.py
├── tox.ini
├── wfencode
├── wfencode.bat
├── wfpayload
├── wfpayload.bat
├── wfuzz
├── wfuzz.bat
├── wfuzz_bash_completion
├── wordlist/
│ ├── Injections/
│ │ ├── All_attack.txt
│ │ ├── SQL.txt
│ │ ├── Traversal.txt
│ │ ├── XML.txt
│ │ ├── XSS.txt
│ │ └── bad_chars.txt
│ ├── general/
│ │ ├── admin-panels.txt
│ │ ├── big.txt
│ │ ├── catala.txt
│ │ ├── common.txt
│ │ ├── euskera.txt
│ │ ├── extensions_common.txt
│ │ ├── http_methods.txt
│ │ ├── medium.txt
│ │ ├── megabeast.txt
│ │ ├── mutations_common.txt
│ │ ├── spanish.txt
│ │ └── test.txt
│ ├── others/
│ │ ├── common_pass.txt
│ │ └── names.txt
│ ├── stress/
│ │ ├── alphanum_case.txt
│ │ ├── alphanum_case_extra.txt
│ │ ├── char.txt
│ │ ├── doble_uri_hex.txt
│ │ ├── test_ext.txt
│ │ └── uri_hex.txt
│ ├── vulns/
│ │ ├── apache.txt
│ │ ├── cgis.txt
│ │ ├── coldfusion.txt
│ │ ├── dirTraversal-nix.txt
│ │ ├── dirTraversal-win.txt
│ │ ├── dirTraversal.txt
│ │ ├── domino.txt
│ │ ├── fatwire.txt
│ │ ├── fatwire_pagenames.txt
│ │ ├── frontpage.txt
│ │ ├── iis.txt
│ │ ├── iplanet.txt
│ │ ├── jrun.txt
│ │ ├── netware.txt
│ │ ├── oracle9i.txt
│ │ ├── sharepoint.txt
│ │ ├── sql_inj.txt
│ │ ├── sunas.txt
│ │ ├── tests.txt
│ │ ├── tomcat.txt
│ │ ├── vignette.txt
│ │ ├── weblogic.txt
│ │ └── websphere.txt
│ └── webservices/
│ ├── ws-dirs.txt
│ └── ws-files.txt
├── wxfuzz
└── wxfuzz.bat
================================================
FILE CONTENTS
================================================
================================================
FILE: .flake8
================================================
[flake8]
max-line-length = 80
select = C,E,F,W,B,B950
ignore = E203, E501, W503, E402, F401, W504
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "daily"
================================================
FILE: .github/workflows/docker-release.yml
================================================
name: docker-release
on:
release:
types: [published]
jobs:
docker:
name: Build and push Docker image
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to ghcr.io
uses: docker/login-action@v1.4.1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.CR_PAT }}
- name: Build and push Docker images
uses: docker/build-push-action@v2
with:
context: .
file: Dockerfile
push: true
tags: |
ghcr.io/${{ github.repository_owner }}/wfuzz:${{ github.event.release.tag_name }}
ghcr.io/${{ github.repository_owner }}/wfuzz:latest
================================================
FILE: .gitignore
================================================
wfuzz.ini
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# vim
*.swp
*.swo
wfuzz.ini
# Jetbrains IDE
.idea
================================================
FILE: .travis.yml
================================================
sudo: required
language: python
services:
- docker
python:
- "3.4"
- "3.5"
- "3.6"
- "3.7"
- "3.8"
before_install:
- docker-compose -f tests/server_dir/docker-compose.yml up -d
install:
- make install-dev
- python setup.py install
script:
- flake8 src tests
- coverage run --append -m unittest discover -v -s tests/
- if [[ $TRAVIS_PYTHON_VERSION == '3.6' && $TRAVIS_BRANCH == 'master' ]]; then codecov; fi
- if [[ $TRAVIS_PYTHON_VERSION != '3.4' && $TRAVIS_PYTHON_VERSION != '3.5' ]]; then black --check src tests; fi
deploy:
provider: pypi
user: x4vi_mendez
password:
secure: qE2hD6gyopogdJh6Qs9B1s8LkTLiZ2b4jZzDojDOnhITve2hosOfoi2T/a9JrRxP9xeMJmt7t4B7F6h+qiSdi6fz2CLT8qAG5zJFfk/+ZqIQX3zvhthoG6QS8F4Qk7kNDMuaMOeMF3qtK5oSR/cqBY3Fs7SiF9wmH2OH7XBjFdOhRzs7Y8vVEXfxy6O4wHqXkwa6ZHXfuFPly/aZGj8CwlVF4qT6zQGpOrTAJneUonQGei2qIBGVSMSLGXHxndN3a1/RA0L+J3jZKb7zi6XyqAJvXTa3OqbxwSSEdLlUdzPrjLPuMuArgTgDErgSiDlwbceDwx7TlBJy2VEF2OwQ9KAIQFKkE6Rp/sp38l3Dnriv8gzi7N0sdaSAMDH5n8zvl6xJ5hqOnB+1jfpEiSQmvr7chi3OxpniG0eW9ThgZOSLjGp0TXGSh9P3jAiZPlt1HWmNoiwOuTwjue0Lx0MH2vYW1smHJSM+FMbdCL1GwFMsEmBX+2bFzaniuyUEmM5GBpj66Pa9yULho4FTC00Aumffl2A7gnSinYwLzjIB3zUMWFzZBaijLr8caeTYMnMdccNYxWcU4kE1h584FGtMDAO8IdEwW907ZTn0H/sTrb+lFs+x3H4oLc9i+/9j/K1G3jrKJfcTOuMm4D9df+lcfgRCQzB6RyiHJWlEdGEBrJM=
distributions: sdist bdist_wheel
on:
branch:
- master
- /^v.*$/
tags: true
python: 3.6
addons:
apt:
packages:
- libcurl4-openssl-dev
================================================
FILE: Dockerfile
================================================
FROM python:3.8-alpine3.12 as builder
RUN apk add --no-cache build-base curl-dev
COPY . wfuzz/
WORKDIR wfuzz/
RUN python setup.py install
FROM python:3.8-alpine3.12
RUN apk add --no-cache curl-dev
COPY --from=builder /usr/local /usr/local
CMD wfuzz
================================================
FILE: ISSUE_TEMPLATE.md
================================================
# Issue template
## Context
**Please check:**
- [ ] I've read the docs for [Wfuzz](http://wfuzz.readthedocs.io/)
**Please describe your local environment:**
Wfuzz version: Output of wfuzz --version
Python version: Output of python --version
OS: X
## Report
**What is the current behavior?**
X
**What is the expected or desired behavior?**
X
**Please provide steps to reproduce, including exact wfuzz command executed and output:**
X
**Other relevant information:**
X
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{description}
Copyright (C) {year} {fullname}
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
{signature of Ty Coon}, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
================================================
FILE: MANIFEST.in
================================================
include README.md
include LICENSE
include docs/*
================================================
FILE: Makefile
================================================
.PHONY: docs
tox:
pip install tox
tox --recreate
test:
pytest -v -s tests/
flake8:
black --check src tests
flake8 src tests
publish:
pip install 'twine>=1.5.0'
python setup.py sdist
twine upload dist/*
rm -fr build dist
publish-dev:
pip install 'twine>=1.5.0'
python setup.py sdist
twine upload --repository-url https://test.pypi.org/legacy/ dist/*
rm -fr build dist
docs:
pip install -e ".[docs]"
cd docs && make html
coverage:
coverage report --skip-covered --include "*python3.8/site-packages/wfuzz*" -m
install: install-dev
pip install -r requirements.txt
install-dev:
pip install -e ".[dev]"
freeze:
pip-compile --output-file requirements.txt setup.py
help:
@echo "make help Show this help message"
@echo "make test Run local tests with tox"
@echo "make flake8 Run the code linter(s) and print any warnings"
@echo "make publish Publish pip lib to pypi"
@echo "make publish-dev Publish pip lib to pypi test"
@echo "make docs Create html docs"
@echo "make install Install requirements"
@echo "make install-dev Install dev requirements"
================================================
FILE: README.md
================================================
[](https://travis-ci.org/xmendez/wfuzz)
# Wfuzz - The Web Fuzzer
Wfuzz has been created to facilitate the task in web applications assessments and it is based on a simple concept: it replaces any reference to the FUZZ keyword by the value of a given payload.
A payload in Wfuzz is a source of data.
This simple concept allows any input to be injected in any field of an HTTP request, allowing to perform complex web security attacks in different web application components such as: parameters, authentication, forms, directories/files, headers, etc.
Wfuzz is more than a web content scanner:
* Wfuzz could help you to secure your web applications by finding and exploiting web application vulnerabilities. Wfuzz’s web application vulnerability scanner is supported by plugins.
* Wfuzz is a completely modular framework and makes it easy for even the newest of Python developers to contribute. Building plugins is simple and takes little more than a few minutes.
* Wfuzz exposes a simple language interface to the previous HTTP requests/responses performed using Wfuzz or other tools, such as Burp. This allows you to perform manual and semi-automatic tests with full context and understanding of your actions, without relying on a web application scanner underlying implementation.
It was created to facilitate the task in web applications assessments, it's a tool by pentesters for pentesters ;)
## Installation
To install WFuzz, simply use pip:
```
pip install wfuzz
```
To run Wfuzz from a docker image, run:
```
$ docker run -v $(pwd)/wordlist:/wordlist/ -it ghcr.io/xmendez/wfuzz wfuzz
```
## Documentation
Documentation is available at http://wfuzz.readthedocs.io
## Download
Check github releases. Latest is available at https://github.com/xmendez/wfuzz/releases/latest
================================================
FILE: docs/Makefile
================================================
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = Wfuzz
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
================================================
FILE: docs/_templates/sidebarlogo.html
================================================
WFuzz is a web application security fuzzer tool and library for Python.
================================================
FILE: docs/conf.py
================================================
# -*- coding: utf-8 -*-
#
# Wfuzz documentation build configuration file, created by
# sphinx-quickstart on Thu Mar 2 13:44:00 2017.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = []
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'Wfuzz'
copyright = '2011-2020, Xavi Mendez'
author = 'Xavi Mendez'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '2.1.4'
# The full version, including alpha/beta/rc tags.
release = '2.1.4'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
html_theme_options = {
'show_powered_by': False,
'github_user': 'xmendez',
'github_repo': 'wfuzz',
'github_banner': True,
'show_related': False,
'sidebar_collapse': True,
}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'Wfuzzdoc'
# Custom sidebar templates, maps document names to template names.
html_sidebars = {
'**': [
'sidebarlogo.html',
'navigation.html',
'searchbox.html'
]
}
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'Wfuzz.tex', 'Wfuzz Documentation',
'Xavi Mendez', 'manual'),
]
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'wfuzz', 'Wfuzz Documentation',
[author], 1)
]
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'Wfuzz', 'Wfuzz Documentation',
author, 'Wfuzz', 'One line description of project.',
'Miscellaneous'),
]
================================================
FILE: docs/dev/plugins.rst
================================================
Plugin template
===============
Printer template
===============
Encoder template
===============
================================================
FILE: docs/index.rst
================================================
.. Wfuzz documentation master file, created by
sphinx-quickstart on Thu Mar 2 13:44:00 2017.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Wfuzz: The Web fuzzer
==================================
.. image:: https://img.shields.io/pypi/v/wfuzz.svg
:target: https://pypi.org/project/wfuzz/
.. image:: https://img.shields.io/pypi/l/wfuzz.svg
:target: https://pypi.org/project/wfuzz/
.. image:: https://img.shields.io/pypi/pyversions/wfuzz.svg
:target: https://pypi.org/project/wfuzz/
.. image:: https://codecov.io/github/xmendez/wfuzz/coverage.svg?branch=master
:target: https://codecov.io/github/xmendez/wfuzz
Wfuzz provides a framework to automate web applications security assessments and could help you to secure your web applications by finding and exploiting web application vulnerabilities.
See Wfuzz in action
-------------------
* Wfuzz cli::
$ wfuzz -w wordlist/general/common.txt --hc 404 http://testphp.vulnweb.com/FUZZ
********************************************************
* Wfuzz 2.2 - The Web Bruteforcer *
********************************************************
Target: http://testphp.vulnweb.com/FUZZ
Total requests: 950
==================================================================
ID Response Lines Word Chars Request
==================================================================
00022: C=301 7 L 12 W 184 Ch "admin"
00130: C=403 10 L 29 W 263 Ch "cgi-bin"
00378: C=301 7 L 12 W 184 Ch "images"
00690: C=301 7 L 12 W 184 Ch "secured"
00938: C=301 7 L 12 W 184 Ch "CVS"
Total time: 5.519253
Processed Requests: 950
Filtered Requests: 945
Requests/sec.: 172.1247
* Wfuzz library::
>>> import wfuzz
>>> for r in wfuzz.get_payload(range(100)).fuzz(hl=[97], url="http://testphp.vulnweb.com/listproducts.php?cat=FUZZ"):
... print r
...
00125: C=200 102 L 434 W 7011 Ch "1"
00126: C=200 99 L 302 W 4442 Ch "2"
other tools included in the wfuzz framework.
* Wfuzz payload generator::
$ wfpayload -z range,0-10
0
1
2
3
4
5
6
7
8
9
10
* Wfuzz encoder/decoder::
$ wfencode -e md5 test
098f6bcd4621d373cade4e832627b4f6
* You can also run wfuzz from the official docker image::
$ docker run -v $(pwd)/wordlist:/wordlist/ -it ghcr.io/xmendez/wfuzz wfuzz
********************************************************
* Wfuzz 3.0.3 - The Web Fuzzer *
* *
* Version up to 1.4c coded by: *
* Christian Martorella (cmartorella@edge-security.com) *
* Carlos del ojo (deepbit@gmail.com) *
* *
* Version 1.4d to 3.0.3 coded by: *
* Xavier Mendez (xmendez@edge-security.com) *
********************************************************
Usage: wfuzz [options] -z payload,params
FUZZ, ..., FUZnZ wherever you put these keywords wfuzz will replace them with the values of the specified payload.
FUZZ{baseline_value} FUZZ will be replaced by baseline_value. It will be the first request performed and could be used as a base for filtering.
Examples:
wfuzz -c -z file,users.txt -z file,pass.txt --sc 200 http://www.site.com/log.asp?user=FUZZ&pass=FUZ2Z
wfuzz -c -z range,1-10 --hc=BBB http://www.site.com/FUZZ{something not there}
wfuzz --script=robots -z list,robots.txt http://www.webscantest.com/FUZZ
Type wfuzz -h for further information or --help for advanced usage.
How it works
------------
Wfuzz it is based on a simple concept: it replaces any reference to the FUZZ keyword by the value of a given payload.
A payload in Wfuzz is a source of data.
This simple concept allows any input to be injected in any field of an HTTP request, allowing to perform complex web security attacks in different web application components such as: parameters, authentication, forms, directories/files, headers, etc.
Wfuzz is more than a web brute forcer:
- Wfuzz's web application vulnerability scanner is supported by plugins.
- Wfuzz is a completely modular framework and makes it easy for even the newest of Python developers to contribute. Building plugins is simple and takes little more than a few minutes.
- Wfuzz exposes a simple language interface to the previous HTTP requests/responses performed using Wfuzz or other tools, such as Burp. This allows you to perform manual and semi-automatic tests with full context and understanding of your actions, without relying on a web application scanner underlying implementation.
Installation Guide
==================
.. toctree::
:maxdepth: 4
user/installation
user/breaking
User Guide
==================
.. toctree::
:maxdepth: 4
user/getting
user/basicusage
user/advanced
user/wfpayload
Library Guide
==================
.. toctree::
:maxdepth: 4
library/guide
================================================
FILE: docs/library/guide.rst
================================================
Python library
===============
Wfuzz's Python library allows to automate tasks and integrate Wfuzz into new tools or scripts.
Library Options
---------------
All options that are available within the Wfuzz command line interface are available as library options:
======================== =====================================================================================
CLI Option Library Option
======================== =====================================================================================
url="url"
--recipe recipe=["filename"]
--oF save="filename"
-f filename,printer printer=("filename", "printer")
--dry-run transport="dryrun"
-p addr proxies=[("ip","port","type")]
-t N concurrent=N
-s N delay=0.0
-R depth rleve=depth
--follow follow=True
-Z scanmode=True
--req-delay N req_delay=0
--conn-delay N conn_delay=0.0
--no-cache no_cache=True
--script= script="plugins"
--script-args n1=v1,... script_args={n1: v1}
-m iterator iterator="iterator"
-z payload payloads=[("name",{default="",encoder=["md5"]},slice=""),]
-V alltype allvars="alltype"
-X method method="method"
--hc/hl/hw/hh N[,N]+ hc/hl/hw/hh=[N,N]
--sc/sl/sw/sh N[,N]+ sc/sl/sw/sh=[N,N]
--ss/hs regex ss/hs="regex"
--filter filter="filter exp"
--prefilter prefilter=["prefilter exp"]
-b cookie cookie=["cookie1=value1",]
-d postdata postdata="postdata"
-H header headers=[("header1", "value1"),]
--basic/ntlm/digest auth auth=("basic", "user:pass")
======================== =====================================================================================
These options can be used in the main library interfaces: fuzz, payload or session indistinctly.
Fuzzing a URL
-------------
Fuzzing a URL with wfuzz library is very simple. Firstly, import the wfuzz module::
>>> import wfuzz
Now, let's try to fuzz a web page to look for hidden content, such as directories. For this example, let's use Acunetix's testphp (http://testphp.vulnweb.com/)::
>>> import wfuzz
>>> for r in wfuzz.fuzz(url="http://testphp.vulnweb.com/FUZZ", hc=[404], payloads=[("file",dict(fn="wordlist/general/common.txt"))]):
... print r
...
00060: C=301 7 L 12 W 184 Ch "admin"
00183: C=403 10 L 29 W 263 Ch "cgi-bin"
00429: C=301 7 L 12 W 184 Ch "images"
...
Now, we have a FuzzResult object called r. We can get all the information we need from this object.
FuzzSession object
------------------
A FuzzSession object has all the methods of the main wfuzz API.
The FuzzSession object allows you to persist certain parameters across fuzzing sessions::
>>> import wfuzz
>>> s = wfuzz.FuzzSession(url="http://testphp.vulnweb.com/FUZZ")
>>> for r in s.fuzz(hc=[404], payloads=[("file",dict(fn="wordlist/general/common.txt"))]):
... print r
...
00060: C=301 7 L 12 W 184 Ch "admin"
00183: C=403 10 L 29 W 263 Ch "cgi-bin"
...
FuzzSession can also be used as context manager::
>>> with wfuzz.FuzzSession(url="http://testphp.vulnweb.com/FUZZ", hc=[404], payloads=[("file",dict(fn="wordlist/general/common.txt"))]) as s:
... for r in s.fuzz():
... print r
...
00295: C=301 7 L 12 W 184 Ch "admin"
00418: C=403 10 L 29 W 263 Ch "cgi-bin"
Get payload
-----------
The get_payload function generates a Wfuzz payload from a Python iterable. It is a quick and flexible way of getting a payload programmatically without using Wfuzz payloads plugins.
Generating a new payload and start fuzzing is really simple::
>>> import wfuzz
>>> s = wfuzz.get_payload(range(5))
>>> for r in s.fuzz(url="http://testphp.vulnweb.com/FUZZ"):
... print r
...
00012: C=404 7 L 12 W 168 Ch "0"
00013: C=404 7 L 12 W 168 Ch "1"
00014: C=404 7 L 12 W 168 Ch "2"
00015: C=404 7 L 12 W 168 Ch "3"
00016: C=404 7 L 12 W 168 Ch "4"
The get_payloads method can be used when various payloads are needed::
>>> import wfuzz
>>> s = wfuzz.get_payloads([range(5), ["a","b"]])
>>> for r in s.fuzz(url="http://testphp.vulnweb.com/FUZZ/FUZ2Z"):
... print r
...
00028: C=404 7 L 12 W 168 Ch "4 - b"
00027: C=404 7 L 12 W 168 Ch "4 - a"
00024: C=404 7 L 12 W 168 Ch "2 - b"
00026: C=404 7 L 12 W 168 Ch "3 - b"
00025: C=404 7 L 12 W 168 Ch "3 - a"
00022: C=404 7 L 12 W 168 Ch "1 - b"
00021: C=404 7 L 12 W 168 Ch "1 - a"
00020: C=404 7 L 12 W 168 Ch "0 - b"
00023: C=404 7 L 12 W 168 Ch "2 - a"
00019: C=404 7 L 12 W 168 Ch "0 - a"
Get session
-----------
The get_session function generates a Wfuzz session object from the specified command line. It is a quick way of getting a payload programmatically from a string representing CLI options::
$ python
>>> import wfuzz
>>> s = wfuzz.get_session("-z range,0-10 http://testphp.vulnweb.com/FUZZ")
>>> for r in s.fuzz():
... print r
...
00002: C=404 7 L 12 W 168 Ch "1"
00011: C=404 7 L 12 W 168 Ch "10"
00008: C=404 7 L 12 W 168 Ch "7"
00001: C=404 7 L 12 W 168 Ch "0"
00003: C=404 7 L 12 W 168 Ch "2"
00004: C=404 7 L 12 W 168 Ch "3"
00005: C=404 7 L 12 W 168 Ch "4"
00006: C=404 7 L 12 W 168 Ch "5"
00007: C=404 7 L 12 W 168 Ch "6"
00009: C=404 7 L 12 W 168 Ch "8"
00010: C=404 7 L 12 W 168 Ch "9"
Interacting with the results
----------------------------
Once a Wfuzz result is available the grammar defined in the filter language can be used to work with the results' values. For example::
$ python
>>> import wfuzz
>>> with wfuzz.get_session("-z list --zD test -u http://testphp.vulnweb.com/userinfo.php -d uname=FUZZ&pass=FUZZ") as s:
... for r in s.fuzz():
... print(r.history.cookies.response)
... print(r.history.params.all)
... print(r.history.params.post)
... print(r.history.params.post.uname)
... print(r.history.params.post['pass'])
{'login': 'test%2Ftest'}
{'uname': 'test', 'pass': 'test'}
{'uname': 'test', 'pass': 'test'}
test
test
>>>
The result object has also a method to evaluate a language expression::
>> print(r.eval("r.cookies.response"))
login=test%2Ftest
================================================
FILE: docs/make.bat
================================================
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
set SPHINXPROJ=Wfuzz
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd
================================================
FILE: docs/user/advanced.rst
================================================
Advanced Usage
===============
Wfuzz global options
--------------------
Wfuzz global options can be tweaked by modifying the "wfuzz.ini" at the user's home directory::
~/.wfuzz$ cat wfuzz.ini
[connection]
concurrent = 10
conn_delay = 90
req_delay = 90
retries = 3
user-agent = Wfuzz/2.2
[general]
default_printer = raw
cancel_on_plugin_except = 1
concurrent_plugins = 3
encode_space = 1
lookup_dirs = .,/home/xxx/tools/fuzzdb
A useful option is "lookup_dirs". This option will indicate Wfuzz, which directories to look for files, avoiding to specify a full path in the command line. For example, when fuzzing using a dictionary.
Iterators: Combining payloads
-----------------------------
Payloads can be combined by using the -m parameter, in wfuzz this functionality is provided by what is called iterators, the following types are provided by default::
$ wfuzz -e iterators
Available iterators:
Name | Summary
----------------------------------------------------------------------------------------------
product | Returns an iterator cartesian product of input iterables.
zip | Returns an iterator that aggregates elements from each of the iterables.
chain | Returns an iterator returns elements from the first iterable until it is exhaust
| ed, then proceeds to the next iterable, until all of the iterables are exhausted
Below are shown some examples using two different payloads containing the elements a,b,c and 1,2,3 respectively and how they can be combined using the existing iterators.
* zip::
wfuzz -z list,a-b-c -z list,1-2-3 -m zip http://google.com/FUZZ/FUZ2Z
00001: C=404 9 L 32 W 276 Ch "a - 1"
00002: C=404 9 L 32 W 276 Ch "c - 3"
00003: C=404 9 L 32 W 276 Ch "b - 2"
* chain::
wfuzz -z list,a-b-c -z list,1-2-3 -m chain http://google.com/FUZZ
00001: C=404 9 L 32 W 280 Ch "b"
00002: C=404 9 L 32 W 280 Ch "a"
00003: C=404 9 L 32 W 280 Ch "c"
00004: C=404 9 L 32 W 280 Ch "1"
00006: C=404 9 L 32 W 280 Ch "3"
00005: C=404 9 L 32 W 280 Ch "2"
* product::
wfuzz -z list,a-b-c -z list,1-2-3 http://mysite.com/FUZZ/FUZ2Z
00001: C=404 9 L 32 W 276 Ch "a - 2"
00002: C=404 9 L 32 W 276 Ch "a - 1"
00005: C=404 9 L 32 W 276 Ch "b - 2"
00004: C=404 9 L 32 W 276 Ch "a - 3"
00008: C=404 9 L 32 W 276 Ch "c - 2"
00003: C=404 9 L 32 W 276 Ch "b - 1"
00007: C=404 9 L 32 W 276 Ch "c - 1"
00006: C=404 9 L 32 W 276 Ch "b - 3"
00009: C=404 9 L 32 W 276 Ch "c - 3"
Encoders
--------
In Wfuzz, a encoder is a transformation of a payload from one format to another. A list of the available encoders can be obtained using the following command::
$ wfuzz -e encoders
Specifying an encoder
^^^^^^^^^^^^^^^^^^^^^^
Encoders are specified as a payload parameter. There are two equivalent ways of specifying an encoder within a payload:
* The long way::
$ wfuzz -z file --zP fn=wordlist/general/common.txt,encoder=md5 http://testphp.vulnweb.com/FUZZ
********************************************************
* Wfuzz 2.2 - The Web Fuzzer *
********************************************************
Target: http://testphp.vulnweb.com/FUZZ
Total requests: 950
==================================================================
ID Response Lines Word Chars Request
==================================================================
00002: C=404 7 L 12 W 168 Ch "b4b147bc522828731f1a016bfa72c073"
00003: C=404 7 L 12 W 168 Ch "96a3be3cf272e017046d1b2674a52bd3"
00004: C=404 7 L 12 W 168 Ch "a2ef406e2c2351e0b9e80029c909242d"
...
* The not so long way using the zE command line switch::
$ wfuzz -z file --zD wordlist/general/common.txt --zE md5 http://testphp.vulnweb.com/FUZZ
* The not so long way::
$ wfuzz -z file,wordlist/general/common.txt,md5 http://testphp.vulnweb.com/FUZZ
Specifying multiple encoders
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Several encoders can be specified at once, using "-" as a separator::
$ wfuzz -z list,1-2-3,md5-sha1-none http://webscantest.com/FUZZ
********************************************************
* Wfuzz 2.2 - The Web Fuzzer *
********************************************************
Target: http://webscantest.com/FUZZ
Total requests: 9
==================================================================
ID Response Lines Word Chars Request
==================================================================
00000: C=200 38 L 121 W 1486 Ch "da4b9237bacccdf19c0760cab7aec4a8359010b0"
00001: C=200 38 L 121 W 1486 Ch "c4ca4238a0b923820dcc509a6f75849b"
00002: C=200 38 L 121 W 1486 Ch "3"
00003: C=200 38 L 121 W 1486 Ch "77de68daecd823babbb58edb1c8e14d7106e83bb"
00004: C=200 38 L 121 W 1486 Ch "1"
00005: C=200 38 L 121 W 1486 Ch "356a192b7913b04c54574d18c28d46e6395428ab"
00006: C=200 38 L 121 W 1486 Ch "eccbc87e4b5ce2fe28308fd9f2a7baf3"
00007: C=200 38 L 121 W 1486 Ch "2"
00008: C=200 38 L 121 W 1486 Ch "c81e728d9d4c2f636f067f89cc14862c"
Total time: 0.428943
Processed Requests: 9
Filtered Requests: 0
Requests/sec.: 20.98180
* Encoders can also be chained using the "@" char::
$ wfuzz -z list,1-2-3,sha1-sha1@none http://webscantest.com/FUZZ
********************************************************
* Wfuzz 2.2 - The Web Fuzzer *
********************************************************
Target: http://webscantest.com/FUZZ
Total requests: 6
==================================================================
ID Response Lines Word Chars Request
==================================================================
00000: C=200 38 L 121 W 1486 Ch "356a192b7913b04c54574d18c28d46e6395428ab"
00001: C=200 38 L 121 W 1486 Ch "356a192b7913b04c54574d18c28d46e6395428ab"
00002: C=200 38 L 121 W 1486 Ch "77de68daecd823babbb58edb1c8e14d7106e83bb"
00003: C=200 38 L 121 W 1486 Ch "da4b9237bacccdf19c0760cab7aec4a8359010b0"
00004: C=200 38 L 121 W 1486 Ch "da4b9237bacccdf19c0760cab7aec4a8359010b0"
00005: C=200 38 L 121 W 1486 Ch "77de68daecd823babbb58edb1c8e14d7106e83bb"
The above "sha1@none" parameter specification will encode the payload using the sha1 encoder and the result will be encoded again using the none encoder.
* Encoders are grouped by categories. This allows to select several encoders by category, for example::
$ wfuzz -z list,1-2-3,hashes http://webscantest.com/FUZZ
00000: C=200 38 L 121 W 1486 Ch "Mw=="
00001: C=200 38 L 121 W 1486 Ch "c81e728d9d4c2f636f067f89cc14862c"
00002: C=200 38 L 121 W 1486 Ch "77de68daecd823babbb58edb1c8e14d7106e83bb"
00003: C=200 38 L 121 W 1486 Ch "da4b9237bacccdf19c0760cab7aec4a8359010b0"
00004: C=200 38 L 121 W 1486 Ch "c4ca4238a0b923820dcc509a6f75849b"
00005: C=200 38 L 121 W 1486 Ch "356a192b7913b04c54574d18c28d46e6395428ab"
00006: C=200 38 L 121 W 1486 Ch "MQ=="
00007: C=200 38 L 121 W 1486 Ch "Mg=="
00008: C=200 38 L 121 W 1486 Ch "eccbc87e4b5ce2fe28308fd9f2a7baf3"
Scan/Parse Plugins
------------------
Wfuzz is more than a Web Content Scanner. Wfuzz could help you to secure your web applications by finding and exploiting web application vulnerabilities.
Wfuzz's web application vulnerability scanner is supported by plugins. A list of scanning plugins can be obtained using the following command::
$ wfuzz -e scripts
Scripts are grouped in categories. A script could belong to several categories at the same time.
There are two general categories:
* passive: Passive scripts analyse existing requests and responses without performing new requests.
* active: Active scripts perform new requests to the application to probe it for vulnerabilities.
Additional categories are:
* discovery: Discovery plugins help crawling a website by automatically enqueuing discovered content to wfuzz request's pool.
The default category groups the plugins that are run by default.
Scanning mode is indicated when using the --script parameter followed by the selected plugins. Plugins could be selected by category or name, wildcards can also be used.
The -A switch is an alias for --script=default.
Script's detailed information can be obtained using --scrip-help, for example::
$ wfuzz --script-help=default
An example, parsing a "robots.txt" file is shown below::
$ wfuzz --script=robots -z list,robots.txt http://www.webscantest.com/FUZZ
********************************************************
* Wfuzz 2.2 - The Web Fuzzer *
********************************************************
Target: http://www.webscantest.com/FUZZ
Total requests: 1
==================================================================
ID Response Lines Word Chars Request
==================================================================
00001: C=200 6 L 10 W 101 Ch "robots.txt"
|_ Plugin robots enqueued 4 more requests (rlevel=1)
00002: C=200 40 L 117 W 1528 Ch "/osrun/"
00003: C=200 55 L 132 W 1849 Ch "/cal_endar/"
00004: C=200 40 L 123 W 1611 Ch "/crawlsnags/"
00005: C=200 85 L 197 W 3486 Ch "/static/"
Total time: 0
Processed Requests: 5 (1 + 4)
Filtered Requests: 0
Requests/sec.: 0
In order to not scan the same requests (with the same parameters) over an over again, there is a cache,the cache can be disabled with the --no-cache flag.
For example, if we target a web server with the same URL but different parameter values, we get::
$ wfuzz -z range --zD 0-3 -z list --zD "'" -u http://testphp.vulnweb.com/artists.php?artist=FUZZFUZ2Z -A
000000004: 0.195s 200 101 L 287 W 3986 Ch nginx/1.4.1 "3 - '"
|_ Error identified: Warning: mysql_fetch_array()
000000001: 0.198s 200 101 L 287 W 3986 Ch nginx/1.4.1 "0 - '"
000000002: 0.198s 200 101 L 287 W 3986 Ch nginx/1.4.1 "1 - '"
000000003: 0.198s 200 101 L 287 W 3986 Ch nginx/1.4.1 "2 - '"
But, if we do the same but disabling the cache::
$ wfuzz -z range --zD 0-3 -z list --zD "'" -u http://testphp.vulnweb.com/artists.php?artist=FUZZFUZ2Z -A --no-cache
000000004: 1.170s 200 101 L 287 W 3986 Ch nginx/1.4.1 "3 - '"
|_ Error identified: Warning: mysql_fetch_array()
000000002: 1.173s 200 101 L 287 W 3986 Ch nginx/1.4.1 "1 - '"
|_ Error identified: Warning: mysql_fetch_array()
000000001: 1.174s 200 101 L 287 W 3986 Ch nginx/1.4.1 "0 - '"
|_ Error identified: Warning: mysql_fetch_array()
000000003: 1.173s 200 101 L 287 W 3986 Ch nginx/1.4.1 "2 - '"
|_ Error identified: Warning: mysql_fetch_array()
Custom scripts
^^^^^^^^^^^^^^
If you would like to create customs scripts, place them in your home directory. In order to leverage this feature, a directory named "scripts" must be created underneath the ".wfuzz" directory.
Recipes
-------
You could save Wfuzz command line options to a file for later execution or for easy distribution.
To create a recipe, execute the following::
$ wfuzz --script=robots -z list,robots.txt --dump-recipe /tmp/recipe http://www.webscantest.com/FUZZ
Then, execute Wfuzz using the stored options by using the "--recipe" option::
$ wfuzz --recipe /tmp/recipe
********************************************************
* Wfuzz 2.2 - The Web Fuzzer *
********************************************************
Target: http://www.webscantest.com/FUZZ
Total requests: 1
==================================================================
ID Response Lines Word Chars Request
==================================================================
00001: C=200 6 L 10 W 101 Ch "robots.txt"
|_ Plugin robots enqueued 4 more requests (rlevel=1)
00002: C=200 40 L 117 W 1528 Ch "/osrun/"
00003: C=200 55 L 132 W 1849 Ch "/cal_endar/"
00004: C=200 40 L 123 W 1611 Ch "/crawlsnags/"
00005: C=200 85 L 197 W 3486 Ch "/static/"
Total time: 1.341176
Processed Requests: 5 (1 + 4)
Filtered Requests: 0
Requests/sec.: 3.728071
You can combine a recipe with additional command line options, for example::
$ wfuzz --recipe /tmp/recipe -b cookie1=value
Several recipes can also be combined::
$ wfuzz --recipe /tmp/recipe --recipe /tmp/recipe2
In case of repeated options, command line options have precedence over options included in the recipe. Last recipe has precedence.
Connect to an specific host
---------------------------------------
The --ip option can be used to connect to a specific host and port instead of the URL's host and port::
$ wfuzz -z range,1-1 --ip 127.0.0.1 http://www.google.com/anything/FUZZ
This useful, for example, to test if a reverse proxy can be manipulated into misrouting requests to a destination of our choice.
Scan Mode: Ignore Errors and Exceptions
---------------------------------------
In the event of a network problem (e.g. DNS failure, refused connection, etc.), Wfuzz will raise an exception and stop execution as shown below::
$ wfuzz -z list,support-web-none http://FUZZ.google.com/
********************************************************
* Wfuzz 2.2 - The Web Fuzzer *
********************************************************
Target: http://FUZZ.google.com/
Total requests: 3
==================================================================
ID Response Lines Word Chars Request
==================================================================
Fatal exception: Pycurl error 6: Could not resolve host: none.google.com
You can tell Wfuzz to continue execution, ignoring errors by supplying the -Z switch. The latter command in scan mode will get the following results::
$ wfuzz -z list,support-web-none -Z http://FUZZ.google.com/
********************************************************
* Wfuzz 2.2 - The Web Fuzzer *
********************************************************
Target: http://FUZZ.google.com/
Total requests: 3
==================================================================
ID Response Lines Word Chars Request
==================================================================
00002: C=404 11 L 72 W 1561 Ch "web"
00003: C=XXX 0 L 0 W 0 Ch "none! Pycurl error 6: Could not resolve host: none.google.com"
00001: C=301 6 L 14 W 224 Ch "support"
Total time: 1.064229
Processed Requests: 3
Filtered Requests: 0
Requests/sec.: 2.818939
Errors are shown as a result with the XXX code, the payload used followed by an exclamation mark and the companion exception message. Error codes can be filtered using the "XXX" expression. For example::
$ wfuzz -z list,support-web-none -Z --hc XXX http://FUZZ.google.com/
********************************************************
* Wfuzz 2.2 - The Web Fuzzer *
********************************************************
Target: http://FUZZ.google.com/
Total requests: 3
==================================================================
ID Response Lines Word Chars Request
==================================================================
00002: C=404 11 L 72 W 1561 Ch "web"
00001: C=301 6 L 14 W 224 Ch "support"
Total time: 0.288635
Processed Requests: 3
Filtered Requests: 1
Requests/sec.: 10.39374
When Wfuzz is used in scan mode, HTTP requests will take longer time due to network error timeouts. These can be tweaked using the --req-delay and --conn-delay command line parameters.
Timeouts
^^^^^^^^
You can tell Wfuzz to stop waiting for server to response a connection request after a given number of seconds --conn-delay and also the maximum number of seconds that the response is allowed to take using --req-delay parameter.
These timeouts are really handy when you are using Wfuzz to brute force resources behind a proxy, ports, hostnames, virtual hosts, etc.
Filter Language
---------------
Wfuzz's filter language grammar is build using `pyparsing `_, therefore it must be installed before using the command line parameters "--filter, --prefilter, --slice, --field and --efield".
The information about the filter language can be also obtained executing::
wfuzz --filter-help
A filter expression must be built using the following symbols and operators:
* Boolean Operators
"and", "or" and "not" operators could be used to build conditional expressions.
* Expression Operators
Expressions operators such as "= != < > >= <=" could be used to check values. Additionally, the following operators for matching text are available:
============ ====================================================================
Operator Description
============ ====================================================================
=~ True when the regular expression specified matches the value.
~ Equivalent to Python's "str2" in "str1" (case insensitive)
!~ Equivalent to Python's "str2" not in "str1" (case insensitive)
============ ====================================================================
Also, assignment operators:
============ ====================================================================
Operator Description
============ ====================================================================
:= Assigns a value
=+ Concatenates value at the left
=- Concatenates value at the right
============ ====================================================================
Where values could be:
* Basic primitives:
============ ====================
Long Name Description
============ ====================
'string' Quoted string
0..9+ Integer values
XXX HTTP request error code
BBB Baseline
============ ====================
* Values can also be modified using the following operators:
================================ ======================= =============================================
Name Short version Description
================================ ======================= =============================================
value|unquote() value|un() Unquotes the value
value|lower() value|l() lower-case of the value
value|upper() upper-case of the value
value|encode('encoder', 'value') value|e('enc', 'val') Returns encoder.encode(value)
value|decode('decoder', 'value') value|d('dec', 'val') Returns encoder.decode(value)
value|replace('what', 'with') value|r('what', 'with') Returns value replacing what for with
value|unique() value|u() Returns True if a value is unique.
value|startswith('value') value|sw('value') Returns true if the value string starts with param
value|gregex('expression') value|gre('exp') Returns first regex group that matches in value
value|diff(expression) Returns diff comparison between value and expression
================================ ======================= =============================================
* When a FuzzResult is available, you could perform runtime introspection of the objects using the following symbols
============ ============== =============================================
Name Short version Description
============ ============== =============================================
url Wfuzz's result HTTP request url
description Wfuzz's result description
nres Wfuzz's result identifier
code c Wfuzz's result HTTP response's code
chars h Wfuzz's result HTTP response chars
lines l Wfuzz's result HTTP response lines
words w Wfuzz's result HTTP response words
md5 Wfuzz's result HTTP response md5 hash
history r Wfuzz's result associated FuzzRequest object
plugins Wfuzz's plugins scan results
============ ============== =============================================
FuzzRequest object's attribute (you need to use the r. prefix) such as:
============================ =============================================
Name Description
============================ =============================================
url HTTP request's url
urlp HTTP request's parsed url (see section below).
method HTTP request's verb
scheme HTTP request's scheme
host HTTP request's host
content HTTP response's content
raw_content HTTP response's content including headers
cookies.all All HTTP request and response cookies
cookies.request HTTP requests cookieS
cookies.response HTTP response cookies
cookies.request.<> Specified HTTP request cookie
cookies.response.<> Specified HTTP response cookie
headers.all All HTTP request and response headers
headers.request HTTP request headers
headers.response HTTP response headers
headers.request.<> Specified HTTP request header case insensitive
headers.response.<> Specified HTTP response header insensitive
params.all All HTTP request GET and POST parameters
params.get All HTTP request GET parameters
params.post HTTP request POST parameters in returned as a dictionary
params.raw_post HTTP request POST parameters payload
params.get.<> Spcified HTTP request GET parameter
params.post.<> Spcified HTTP request POST parameter
pstrip Returns a signature of the HTTP request using the parameter's names without values (useful for unique operations)
is_path Returns true when the HTTP request path refers to a directory.
reqtime Returns the total time that HTTP request took to be retrieved
============================ =============================================
It is worth noting that Wfuzz will try to parse the POST parameters according to the specified content type header. Currently, application/x-www-form-urlencoded, multipart/form-dat and application/json are supported. This is prone to error depending on the data format, raw_post will not try to do any processing.
FuzzRequest URL field is broken in smaller (read only) parts using the urlparse Python's module in the urlp attribute.
Urlparse parses a URL into: scheme://netloc/path;parameters?query#fragment. For example, for the "http://www.google.com/dir/test.php?id=1" URL you can get the following values:
=================== =============================================
Name Value
=================== =============================================
urlp.scheme http
urlp.netloc www.google.com
urlp.path /dir/test.php
urlp.params
urlp.query id=1
urlp.fragment
urlp.ffname test.php
urlp.fext .php
urlp.fname test
urlp.hasquery Returns true when the URL contains a query string.
urlp.isbllist Returns true when the URL file extension is included in the configuration discovery's blacklist
=================== =============================================
Payload introspection can also be performed by using the keyword FUZZ:
============ ==============================================
Name Description
============ ==============================================
FUZnZ Allows to access the Nth payload string
FUZnZ[field] Allows to access the Nth payload attributes
============ ==============================================
Where field is one of the described above.
Filtering results
^^^^^^^^^^^^^^^^^
The --filter command line parameter in conjunction with the described filter language allows you to perform more complex result triage than the standard filter switches such as "--hc/hl/hw/hh", "--sc/sl/sw/sh" and "-ss/hs".
An example below::
$ wfuzz -z range,0-10 --filter "c=200 and l>97" http://testphp.vulnweb.com/listproducts.php?cat=FUZZ
********************************************************
* Wfuzz 2.2 - The Web Fuzzer *
********************************************************
Target: http://testphp.vulnweb.com/listproducts.php?cat=FUZZ
Total requests: 11
==================================================================
ID Response Lines Word Chars Request
==================================================================
00003: C=200 99 L 302 W 4442 Ch "2"
00002: C=200 102 L 434 W 7011 Ch "1"
Total time: 1.452705
Processed Requests: 11
Filtered Requests: 9
Requests/sec.: 7.572076
Using result and payload introspection to look for specific content returned in the response::
$ wfuzz -z list,echoedback -d searchFor=FUZZ --filter "content~FUZZ" http://testphp.vulnweb.com/search.php?test=query
Which is equivalent to::
$ wfuzz -z list,echoedback -d searchFor=FUZZ --ss "echoedback" http://testphp.vulnweb.com/search.php?test=query
A more interesting variation of the above examples could be::
$ wfuzz -w fuzzdb/attack/xss/xss-rsnake.txt -d searchFor=FUZZ --filter "content~FUZZ" http://testphp.vulnweb.com/search.php?test=query
You can use the fields as boolean values as well. For example, this filter will show only the requests with parameters::
$ wfuzz -z range --zD 0-1 -u http://testphp.vulnweb.com/artists.php?artist=FUZZ --filter 'r.params.all'
Results with plugin issues can be filter as well::
$ wfuzz -z list --zD index -u http://testphp.vulnweb.com/FUZZ.php --script headers --filter "plugins~'nginx'"
Payload mangling
^^^^^^^^^^^^^^^^^^^^^^^^^^
Slicing a payload
"""""""
The --slice command line parameter in conjunction with the described language allows you to filter a payload.
The payload to filter, specified by the -z switch must precede --slice command line parameter.
The specified expression must return a boolean value, an example, using the unique operator is shown below::
$ wfuzz -z list --zD one-two-one-one --slice "FUZZ|u()" http://localhost:9000/FUZZ
********************************************************
* Wfuzz 2.2 - The Web Fuzzer *
********************************************************
Target: http://localhost:9000/FUZZ
Total requests: <>
==================================================================
ID Response Lines Word Chars Request
==================================================================
00001: C=404 9 L 32 W 277 Ch "one"
00002: C=404 9 L 32 W 277 Ch "two"
Total time: 0.031817
Processed Requests: 2
Filtered Requests: 0
Requests/sec.: 62.85908
It is worth noting that, the type of payload dictates the available language symbols. For example, a dictionary payload such as in the example
above does not have a full FuzzResult object context and therefore object fields cannot be used.
When slicing a FuzzResult payload, you are accessing the FuzzResult directly, therefore given a previous session such as::
$ wfuzz -z range --zD 0-0 -u http://www.google.com/FUZZ --oF /tmp/test1
...
000000001: 404 11 L 72 W 1558 Ch "0"
...
this can be used to filter the payload::
$ wfpayload -z wfuzzp --zD /tmp/test1 --slice "c=404"
...
000000001: 404 11 L 72 W 1558 Ch "0"
...
$ wfpayload -z wfuzzp --zD /tmp/test1 --slice "c!=404"
...
wfuzz.py:168: UserWarning:Fatal exception: Empty dictionary! Please check payload or filter.
...
In fact, in this situation, FUZZ refers to the previous result (if any)::
$ wfuzz -z wfuzzp --zD /tmp/test1 -u FUZZ --oF /tmp/test2
...
000000001: 404 11 L 72 W 1558 Ch "http://www.google.com/0"
...
$ wfpayload -z wfuzzp --zD /tmp/test2 --efield r.headers.response.date --efield FUZZ[r.headers.response.date]
...
000000001: 404 11 L 72 W 1558 Ch "http://www.google.com/0 | Mon, 02 Nov 2020 19:29:03 GMT | Mon, 02 Nov 2020 19:27:27 GMT"
...
Re-writing a payload
"""""""
The slice command parameter also allows to re-write a payload. Any value, other than a boolean, returned by the
specified expression will be interpreted not to filter the source payload but to change its value.
For example::
$ ./wfuzz -z list --zD one-two-three --slice "FUZZ|upper()" -u https://www.wfuzz.io/FUZZ
000000001: 404 11 L 72 W 1560 Ch "ONE"
000000003: 404 11 L 72 W 1562 Ch "THREE"
000000002: 404 11 L 72 W 1560 Ch "TWO"
Prefilter
"""""""""
The --prefilter command line parameter is similar to --slice but is not associated to any payload. It is a general filtering
performed just before any HTTP request is done.
In this context you are filtering a FuzzResult object, which is the result of combining all the input payloads, that is has not been updated with the result of performing its associated HTTP request yet and therefore lacking some information.
The --prefilter command cannot be used to re-write a payload. The assignment operators can be used to modify the FuzzResult object's fields but expressions other booleans will be ignored.
Reutilising previous results
--------------------------------------
Previously performed HTTP requests/responses contain a treasure trove of data. Wfuzz payloads and object introspection (explained in the filter grammar section) exposes a Python object interface to requests/responses recorded by Wfuzz or other tools.
This allows you to perform manual and semi-automatic tests with full context and understanding of your actions, without relying on a web application scanner underlying implementation.
Some ideas:
* Replaying individual requests as-is
* Comparing response bodies and headers of fuzzed requests against their original
* Looking for requests with the CSRF token exposed in the URL
* Looking for responses with JSON content with an incorrect content type
To reutilise previous results, a payload that generates a full FuzzResult object context should be used.
* wfuzzp payload:
Wfuzz results can be stored using the --oF option as illustrated below::
$ wfuzz --oF /tmp/session -z range,0-10 http://www.google.com/dir/test.php?id=FUZZ
* burpstate and burplog payloads:
Wfuzz can read burp's (TM) log or saved states. This allows to filter or reutilise burp proxy requests and responses.
Then, you can reutilise those results by using the denoted payloads. To repeat a request exactly how it was stored, you must use the FUZZ keyword on the command line::
$ wfuzz -z burpstate,a_burp_state.burp FUZZ
$ wfuzz -z burplog,a_burp_log.burp FUZZ
$ wfuzz -z wfuzzp,/tmp/session FUZZ
Previous requests can also be modified by using the usual command line switches. Some examples below:
* Adding a new header::
$ wfuzz -z burpstate,a_burp_state.burp -H "addme: header" FUZZ
* Using new cookies specified by another payload::
$ wfuzz -z burpstate,a_burp_state.burp -z list,1-2-3 -b "cookie=FUZ2Z" FUZZ
* The stored HTTP requests can be printed using the --prev flag for comparing old vs new results::
$ wfuzz -z burpstate,testphp.burp --slice "cookies.request and url|u()" --filter "c!=FUZZ[c]" -b "" --prev FUZZ
...
000076: C=302 0 L 3 W 14 Ch "http://testphp.vulnweb.com/userinfo.php"
|__ C=200 114 L 373 W 5347 Ch "http://testphp.vulnweb.com/userinfo.php"
* Same request against another URL::
$ wfuzz -z burpstate,a_burp_state.burp -H "addme: header" -u http://www.otherhost.com FUZZ
If you do not want to use the full saved request:
* Accessing specific HTTP object fields can be achieved by using the attr payload's parameter::
$ wfuzz -z wfuzzp,/tmp/session --zP attr=url FUZZ
* Or by specifying the FUZZ keyword and a field name in the form of FUZZ[field]::
$ wfuzz -z wfuzzp,/tmp/session FUZZ[url]
This could be used, for example, to perform new requests based on stored values::
$ wfuzz -z wfuzzp,/tmp/session -p localhost:8080 http://testphp.vulnweb.com/FUZZ[url.path]?FUZZ[url.query]
00001: C=200 25 L 155 W 1362 Ch "/dir/test.php - id=0"
...
00002: C=200 25 L 155 W 1362 Ch "/dir/test.php - id=1"
The above command will generate HTTP requests such as the following::
GET /dir/test.php?id=10 HTTP/1.1
Host: testphp.vulnweb.com
Accept: */*
Content-Type: application/x-www-form-urlencoded
User-Agent: Wfuzz/2.2
Connection: close
You can filter the payload using the filter grammar as described before.
Reutilising previous results
--------------------------------------
Plugins results contain a treasure trove of data. Wfuzz payloads and object introspection (explained in the filter grammar section) exposes a Python object interface to plugins results.
This allows you to perform semi-automatic tests based on plugins results or compile a set of results to be used in another tool.
Request mangling
^^^^^^^^^
The assignment operators can be used to modify previous requests, for example, let's add a quote to every string parameter prior of performing the HTTP request::
$ wfuzz -z range,1-5 --oF /tmp/session http://testphp.vulnweb.com/artists.php?artist=FUZZ
000003: C=200 118 L 455 W 5326 Ch "3"
...
000004: C=200 99 L 272 W 3868 Ch "4"
$ wfuzz -z wfuzzp,/tmp/session --prefilter "r.params.get=+'\''" -A FUZZ
00010: 0.161s C=200 101 L 287 W 3986 Ch nginx/1.4.1 "http://testphp.vulnweb.com/artists.php?artist=1'"
|_ Error identified: Warning: mysql_fetch_array()
...
The above command looks for simple SQL injection issues.
================================================
FILE: docs/user/basicusage.rst
================================================
Basic Usage
===============
Fuzzing Paths and Files
-----------------------
Wfuzz can be used to look for hidden content, such as files and directories, within a web server, allowing to find further attack vectors. It is worth noting that, the success of this task depends highly on the dictionaries used.
However, due to the limited number of platforms, default installations, known resources such as logfiles, administrative directories, a considerable number of resources are located in predictable locations. Therefore, brute forcing these contents becomes a more feasible task.
Wfuzz contains some dictionaries, other larger and up to date open source word lists are:
* `fuzzdb `_
* `seclists `_
Below is shown an example of wfuzz looking for common directories::
$ wfuzz -w wordlist/general/common.txt http://testphp.vulnweb.com/FUZZ
Below is shown an example of wfuzz looking for common files::
$ wfuzz -w wordlist/general/common.txt http://testphp.vulnweb.com/FUZZ.php
Fuzzing Parameters In URLs
--------------------------
You often want to fuzz some sort of data in the URL's query string, this can be achieved by specifying the FUZZ keyword in the URL after a question mark::
$ wfuzz -z range,0-10 --hl 97 http://testphp.vulnweb.com/listproducts.php?cat=FUZZ
Fuzzing POST Requests
---------------------
If you want to fuzz some form-encoded data like an HTML form will do, simply pass a -d command line argument::
$ wfuzz -z file,wordlist/others/common_pass.txt -d "uname=FUZZ&pass=FUZZ" --hc 302 http://testphp.vulnweb.com/userinfo.php
********************************************************
* Wfuzz 2.2 - The Web Fuzzer *
********************************************************
Target: http://testphp.vulnweb.com/userinfo.php
Total requests: 52
==================================================================
ID Response Lines Word Chars Request
==================================================================
00044: C=200 114 L 356 W 5111 Ch "test"
Total time: 2.140146
Processed Requests: 52
Filtered Requests: 51
Requests/sec.: 24.29739
Fuzzing Cookies
---------------
To send your own cookies to the server, for example, to associate a request to HTTP sessions, you can use the -b parameter (repeat for various cookies)::
$ wfuzz -z file,wordlist/general/common.txt -b cookie=value1 -b cookie2=value2 http://testphp.vulnweb.com/FUZZ
The command above will generate HTTP requests such as the one below::
GET /attach HTTP/1.1
Host: testphp.vulnweb.com
Accept: */*
Content-Type: application/x-www-form-urlencoded
Cookie: cookie=value1; cookie2=value2
User-Agent: Wfuzz/2.2
Connection: close
Cookies can also be fuzzed::
$ wfuzz -z file,wordlist/general/common.txt -b cookie=FUZZ http://testphp.vulnweb.com/
Fuzzing Custom headers
----------------------
If you'd like to add HTTP headers to a request, simply use the -H parameter (repeat for various headers)::
$ wfuzz -z file,wordlist/general/common.txt -H "myheader: headervalue" -H "myheader2: headervalue2" http://testphp.vulnweb.com/FUZZ
The command above will generate HTTP requests such as the one below::
GET /agent HTTP/1.1
Host: testphp.vulnweb.com
Accept: */*
Myheader2: headervalue2
Myheader: headervalue
Content-Type: application/x-www-form-urlencoded
User-Agent: Wfuzz/2.2
Connection: close
You can modify existing headers, for example, for specifying a custom user agent, execute the following::
$ wfuzz -z file,wordlist/general/common.txt -H "myheader: headervalue" -H "User-Agent: Googlebot-News" http://testphp.vulnweb.com/FUZZ
The command above will generate HTTP requests such as the one below::
GET /asp HTTP/1.1
Host: testphp.vulnweb.com
Accept: */*
Myheader: headervalue
Content-Type: application/x-www-form-urlencoded
User-Agent: Googlebot-News
Connection: close
Headers can also be fuzzed::
$ wfuzz -z file,wordlist/general/common.txt -H "User-Agent: FUZZ" http://testphp.vulnweb.com/
Fuzzing HTTP Verbs
------------------
HTTP verbs fuzzing can be specified using the -X switch::
$ wfuzz -z list,GET-HEAD-POST-TRACE-OPTIONS -X FUZZ http://testphp.vulnweb.com/
********************************************************
* Wfuzz 2.2 - The Web Fuzzer *
********************************************************
Target: http://testphp.vulnweb.com/
Total requests: 5
==================================================================
ID Response Lines Word Chars Request
==================================================================
00002: C=200 0 L 0 W 0 Ch "HEAD"
00004: C=405 7 L 12 W 172 Ch "TRACE"
00005: C=405 7 L 12 W 172 Ch "OPTIONS"
00001: C=200 104 L 296 W 4096 Ch "GET"
00003: C=200 104 L 296 W 4096 Ch "POST"
Total time: 1.030354
Processed Requests: 5
Filtered Requests: 0
Requests/sec.: 4.852696
If you want to perform the requests using a specific verb you can also use "-X HEAD".
Proxies
-------
If you need to use a proxy, simply use the -p parameter::
$ wfuzz -z file,wordlist/general/common.txt -p localhost:8080 http://testphp.vulnweb.com/FUZZ
In addition to basic HTTP proxies, Wfuzz also supports proxies using the SOCKS4 and SOCKS5 protocol::
$ wfuzz -z file,wordlist/general/common.txt -p localhost:2222:SOCKS5 http://testphp.vulnweb.com/FUZZ
Multiple proxies can be used simultaneously by supplying various -p parameters::
$ wfuzz -z file,wordlist/general/common.txt -p localhost:8080 -p localhost:9090 http://testphp.vulnweb.com/FUZZ
Each request will be performed using a different proxy each time.
Authentication
--------------
Wfuzz can set an authentication headers by using the --basic/ntlm/digest command line switches.
For example, a protected resource using Basic authentication can be fuzzed using the following command::
$ wfuzz -z list,nonvalid-httpwatch --basic FUZZ:FUZZ https://www.httpwatch.com/httpgallery/authentication/authenticatedimage/default.aspx
********************************************************
* Wfuzz 2.2 - The Web Fuzzer *
********************************************************
Target: https://www.httpwatch.com/httpgallery/authentication/authenticatedimage/default.aspx
Total requests: 2
==================================================================
ID Response Lines Word Chars Request
==================================================================
00001: C=401 0 L 11 W 58 Ch "nonvalid"
00002: C=200 20 L 91 W 5294 Ch "httpwatch"
Total time: 0.820029
Processed Requests: 2
Filtered Requests: 0
Requests/sec.: 2.438938
If you want to fuzz a resource from a protected website you can also use "--basic user:pass".
Recursion
---------
The -R switch can be used to specify a payload recursion's depth. For example, if you want to search for existing directories and then fuzz within these directories again using the same payload you can use the following command::
$ wfuzz -z list,"admin-CVS-cgi\-bin" -R1 http://testphp.vulnweb.com/FUZZ
********************************************************
* Wfuzz 2.2 - The Web Fuzzer *
********************************************************
Target: http://testphp.vulnweb.com/FUZZ
Total requests: 3
==================================================================
ID Response Lines Word Chars Request
==================================================================
00003: C=403 10 L 29 W 263 Ch "cgi-bin"
00002: C=301 7 L 12 W 184 Ch "CVS"
|_ Enqueued response for recursion (level=1)
00001: C=301 7 L 12 W 184 Ch "admin"
|_ Enqueued response for recursion (level=1)
00008: C=404 7 L 12 W 168 Ch "admin - CVS"
00007: C=404 7 L 12 W 168 Ch "admin - admin"
00005: C=404 7 L 12 W 168 Ch "CVS - CVS"
00006: C=404 7 L 12 W 168 Ch "CVS - cgi-bin"
00009: C=404 7 L 12 W 168 Ch "admin - cgi-bin"
00004: C=404 7 L 12 W 168 Ch "CVS - admin"
Perfomance
----------
Several options lets you fine tune the HTTP request engine, depending on the performance impact on the application, and on your own processing power and bandwidth.
You can increase or decrease the number of simultaneous requests to make your attack proceed faster or slower by using the -t switch.
You can tell Wfuzz to stop a given number of seconds before performing another request using the -s parameter.
Writing to a file
-----------------
Wfuzz supports writing the results to a file in a different format. This is performed by plugins called "printers". The available printers can be listed executing::
$ wfuzz -e printers
For example, to write results to an output file in JSON format use the following command::
$ wfuzz -f /tmp/outfile,json -w wordlist/general/common.txt http://testphp.vulnweb.com/FUZZ
Different output
-----------------
Wfuzz supports showing the results in various formats. This is performed by plugins called "printers". The available printers can be listed executing::
$ wfuzz -e printers
For example, to show results in JSON format use the following command::
$ wfuzz -o json -w wordlist/general/common.txt http://testphp.vulnweb.com/FUZZ
When using the default or raw output you can also select additional FuzzResult's fields to show, using --efield, together with the payload description::
$ wfuzz -z range --zD 0-1 -u http://testphp.vulnweb.com/artists.php?artist=FUZZ --efield r
...
000000001: 200 99 L 272 W 3868 Ch 0 | GET /artists.php?artist=0 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
User-Agent: Wfuzz/2.4
Host: testphp.vulnweb.com
...
The above command is useful, for example, to debug what exact HTTP request Wfuzz sent to the remote Web server.
To completely replace the default payload output you can use --field instead::
$ wfuzz -z range --zD 0-1 -u http://testphp.vulnweb.com/artists.php?artist=FUZZ --field url
...
000000001: 200 104 L 364 W 4735 Ch "http://testphp.vulnweb.com/artists.php?artist=0"
...
--efield and --field can be repeated to show several fields::
$ wfuzz -z range --zD 0-1 -u http://testphp.vulnweb.com/artists.php?artist=FUZZ --efield url --efield h
...
000000001: 200 104 L 364 W 4735 Ch "0 | http://testphp.vulnweb.com/artists.php?artist=0 | 4735"
...
The field printer can be used with a --efield or --field expression to list only the specified filter expressions without a header or footer::
$ wfuzz -z list --zD https://www.airbnb.com/ --script=links --script-args=links.regex=.*js$,links.enqueue=False -u FUZZ -o field --field plugins.links.link | head -n3
https://a0.muscache.com/airbnb/static/packages/4e8d-d5c346ee.js
https://a0.muscache.com/airbnb/static/packages/7afc-ac814a17.js
https://a0.muscache.com/airbnb/static/packages/7642-dcf4f8dc.js
The above command is useful, for example, to pipe wfuzz into other tools or perform console scripts.
--efield and --field are in fact filter expressions. Check the filter language section in the advance usage document for the available fields and operators.
================================================
FILE: docs/user/breaking.rst
================================================
Breaking changes
=============
Following https://semver.org/ versioning since Wfuzz 3.0.0.
* Wfuzz 3.0.0:
* In wfuzz library prefilter is a list of filters not a string.
* When using --recipe, stored options that are a list are appended. Previously, the last one took precedence.
================================================
FILE: docs/user/getting.rst
================================================
Getting Started
===============
A typical Wfuzz command line execution, specifying a dictionary payload and a URL, looks like this::
$ wfuzz -w wordlist/general/common.txt http://testphp.vulnweb.com/FUZZ
The obtained output is shown below::
********************************************************
* Wfuzz 2.2 - The Web Fuzzer *
********************************************************
Target: http://testphp.vulnweb.com/FUZZ
Total requests: 950
==================================================================
ID Response Lines Word Chars Request
==================================================================
00006: C=301 7 L 12 W 184 Ch "admin"
00135: C=403 10 L 29 W 263 Ch "cgi-bin"
00379: C=301 7 L 12 W 184 Ch "images"
00686: C=301 7 L 12 W 184 Ch "secured"
...
00935: C=301 7 L 12 W 184 Ch "CVS"
Total time: 4.214460
Processed Requests: 950
Filtered Requests: 0
Requests/sec.: 225.4143
Wfuzz output allows to analyse the web server responses and filter the desired results based on the HTTP response message obtained, for example, response codes, response length, etc.
Each line provides the following information:
- ID: The request number in the order that it was performed.
- Response: Shows the HTTP response code.
- Lines: Shows the number of lines in the HTTP response.
- Word: Shows the number of words in the HTTP response.
- Chars: Shows the number of characters in the HTTP response.
- Payload: Shows the payload used.
Getting help
------------
Use the --h and --help switch to get basic and advanced help usage respectively.
Wfuzz is a completely modular framework, you can check the available modules by using the -e <> switch::
$ wfuzz -e iterators
Available iterators:
Name | Summary
----------------------------------------------------------------------------------------------
product | Returns an iterator cartesian product of input iterables.
zip | Returns an iterator that aggregates elements from each of the iterables.
chain | Returns an iterator returns elements from the first iterable until it is exhaust
| ed, then proceeds to the next iterable, until all of the iterables are exhausted
| .
Valid categories are: payloads, encoders, iterators, printers or scripts.
Payloads
--------
Wfuzz is based on a simple concept: it replaces any reference to the keyword FUZZ by the value of a given payload. A payload in Wfuzz is a source of input data.
The available payloads can be listed by executing::
$ wfuzz -e payloads
Detailed information about payloads could be obtained by executing::
$ wfuzz -z help
The latter can be filtered using the --slice parameter::
$ wfuzz -z help --slice "dirwalk"
Name: dirwalk 0.1
Categories: default
Summary: Returns filename's recursively from a local directory.
Description:
Returns all the file paths found in the specified directory.
Handy if you want to check a directory structure against a webserver,
for example, because you have previously downloaded a specific version
of what is supposed to be on-line.
Parameters:
+ dir: Directory path to walk and generate payload from.
Specifying a payload:
^^^^^^^^^^^^^^^^^^^^^
Each FUZZ keyword must have its corresponding payload. There are several equivalent ways of specifying a payload:
* The long way explicitly defining the payload's parameter name through the command line::
$ wfuzz -z file --zP fn=wordlist/general/common.txt http://testphp.vulnweb.com/FUZZ
* The not so long way explicitly defining the payload's default parameter through the --zD command line option::
$ wfuzz -z file --zD wordlist/general/common.txt http://testphp.vulnweb.com/FUZZ
* The not so long way defining only the value of the payload's default parameter::
$ wfuzz -z file,wordlist/general/common.txt http://testphp.vulnweb.com/FUZZ
* The short way when using the file payload alias::
$ wfuzz -w wordlist/general/common.txt http://testphp.vulnweb.com/FUZZ
The stdin payload could be used when using a external wordlist generator::
$ crunch 2 2 ab | wfuzz -z stdin http://testphp.vulnweb.com/FUZZ
Crunch will now generate the following amount of data: 12 bytes
0 MB
0 GB
0 TB
0 PB
Crunch will now generate the following number of lines: 4
********************************************************
* Wfuzz 2.2 - The Web Fuzzer *
********************************************************
Target: http://testphp.vulnweb.com/FUZZ
Total requests: <>
==================================================================
ID Response Lines Word Chars Request
==================================================================
00002: C=404 7 L 12 W 168 Ch "ab"
00001: C=404 7 L 12 W 168 Ch "aa"
00003: C=404 7 L 12 W 168 Ch "ba"
00004: C=404 7 L 12 W 168 Ch "bb"
Total time: 3.643738
Processed Requests: 4
Filtered Requests: 0
Requests/sec.: 1.097773
Multiple payloads
^^^^^^^^^^^^^^^^^
Several payloads can be used by specifying several -z or -w parameters and the corresponding FUZZ, ... , FUZnZ keyword where n is the payload number. The following example, brute forces files, extension files and directories at the same time::
$ wfuzz -w wordlist/general/common.txt -w wordlist/general/common.txt -w wordlist/general/extensions_common.txt --hc 404 http://testphp.vulnweb.com/FUZZ/FUZ2ZFUZ3Z
Filters
-------
Filtering results in Wfuzz is paramount:
* Big dictionaries could generate a great amount of output and can easily drown out legitimate valid results.
* Triaging HTTP responses is key to perform some attacks, for example, in order to check for the presence of a SQL injection vulnerability we need to distinguish a legitimate response from the one that generates an error or different data.
Wfuzz allows to filter based on the HTTP responses code and the length of the received information (in the form of words, characters or lines). Regular expressions can also be used. Two approaches can be taken: showing or hiding results matching a given filter.
Hiding responses
^^^^^^^^^^^^^^^^
The following command line parameters can be used to hide certain HTTP responses "--hc, --hl, --hw, --hh". For example, the following command filters the web resources unknown by the web server (http://en.wikipedia.org/wiki/HTTP_404)::
wfuzz -w wordlist/general/common.txt --hc 404 http://testphp.vulnweb.com/FUZZ
Multiple values can be specified, for example, the following wfuzz execution adds the forbidden resources to the filter::
wfuzz -w wordlist/general/common.txt --hc 404,403 http://testphp.vulnweb.com/FUZZ
Lines, words or chars are handy when we are looking for resources with the same HTTP status code. For example, it is a common behaviour (sometimes due to misconfiguration) that web servers return a custom error page with a 200 response code, this is known as soft 404.
Below is shown an example::
$ wfuzz -w wordlist/general/common.txt --hc 404 http://datalayer.io/FUZZ
********************************************************
* Wfuzz 2.2 - The Web Fuzzer *
********************************************************
Target: http://datalayer.io/FUZZ
Total requests: 950
==================================================================
ID Response Lines Word Chars Request
==================================================================
00000: C=200 279 L 635 W 8972 Ch "W3SVC3"
00001: C=200 279 L 635 W 8972 Ch "Log"
00002: C=200 279 L 635 W 8972 Ch "10"
00003: C=200 279 L 635 W 8972 Ch "02"
00004: C=200 279 L 635 W 8972 Ch "2005"
...
00024: C=200 301 L 776 W 9042 Ch "about"
...
Looking carefully at the above results, is easy to ascertain that all the "not found" resources have a common patter of 279 lines, 635 words and 8972 chars.
Thus, we can improve our "--hc 404" filter by using this information (various filters can be combined)::
$ wfuzz -w wordlist/general/common.txt --hc 404 --hh 8972 http://datalayer.io/FUZZ
00022: C=200 301 L 776 W 9042 Ch "about"
00084: C=302 0 L 0 W 0 Ch "blog"
00192: C=302 0 L 0 W 0 Ch "css"
...
00696: C=200 456 L 1295 W 15119 Ch "service"
00751: C=200 238 L 512 W 6191 Ch "store"
00788: C=302 0 L 0 W 0 Ch "text"
00913: C=302 0 L 0 W 0 Ch "template"
Showing responses
^^^^^^^^^^^^^^^^^
Showing results works the same way but using the command line parameters preceded by an "s": "--sc, --sl, --sw, --sh".
Using the baseline
^^^^^^^^^^^^^^^^^^
Filters can be built against a reference HTTP response, called the "baseline". For example, the previous command for filtering "not found" resources using the --hh switch could have be done with the following command::
$ wfuzz -w wordlist/general/common.txt --hh BBB http://datalayer.io/FUZZ{notthere}
...
00000: C=200 279 L 635 W 8972 Ch "notthere"
00001: C=200 301 L 776 W 9042 Ch "about"
00004: C=200 456 L 1295 W 15119 Ch "service"
...
Here the {} defines the value of the FUZZ word for this first HTTP request, and then the response can be used specifying "BBB" as a filter value.
Regex filters
^^^^^^^^^^^^^
The command line parameters "--ss" and "--hs" allow to filter the responses using a regular expression against the returned content. For example, the following allows to find web servers vulnerable to "shellshock" (see http://edge-security.blogspot.co.uk/2014/10/scan-for-shellshock-with-wfuzz.html for more information)::
$ wfuzz -H "User-Agent: () { :;}; echo; echo vulnerable" --ss vulnerable -w cgis.txt http://localhost:8000/FUZZ
A valid python regex should be used within these switches or an error will be prompted::
$ wfuzz -w wordlist/general/common.txt --hs "error)" http://testphp.vulnweb.com/FUZZ
Fatal exception: Invalid regex expression: unbalanced parenthesis
================================================
FILE: docs/user/installation.rst
================================================
Installation
==================================
Pip install Wfuzz
--------------------
To install WFuzz using `pip `_ ::
$ pip install wfuzz
Use the wfuzz docker image
------------------
You can pull wfuzz docker image from github registry by executing::
$ docker pull ghcr.io/xmendez/wfuzz
Get the Source Code
-------------------
Wfuzz is actively developed on
`GitHub `_.
You can either clone the public repository::
$ git clone git://github.com/xmendez/wfuzz.git
Or download last `release `_.
Once you have a copy of the source, you can embed it in your own Python
package, or install it into your site-packages easily::
$ python setup.py install
Dependencies
-----------
Wfuzz uses:
* `pycurl `_ library to perform HTTP requests.
* `pyparsing `_ library to create filter's grammars.
* `JSON.miniy (C) Gerald Storer `_ to read json recipes.
* `chardet `_ to detect dictionaries encoding.
* `coloroma `_ to support ANSI escape characters in Windows.
Installation issues
===================
Pycurl on MacOS
--------------------------
Wfuzz uses pycurl as HTTP library. You might get errors like the listed below when running Wfuzz::
pycurl: libcurl link-time ssl backend (openssl) is different from compile-time ssl backend (none/other)
Or::
pycurl: libcurl link-time ssl backend (none/other) is different from compile-time ssl backend (openssl)
This is due to the fact that, MacOS might need some tweaks before pycurl is installed correctly:
#. First you need to install OpenSSL via Homebrew::
$ brew install openssl
#. Curl is normally already installed in MacOs, but to be sure it uses OpenSSL, we need to install it using brew::
$ brew install curl-openssl
#. Curl is installed keg-only by brew. This means that is installed but not linked. Therefore, we need to instruct pip to use the recently installed curl before installing pycurl. We can do this permanently by changing our bash_profile::
$ echo 'export PATH="/usr/local/opt/curl-openssl/bin:$PATH"' >> ~/.bash_profile
#. Or temporary in the current shell::
$ export PATH="/usr/local/opt/curl-openssl/bin:$PATH"
#. Then, we need to install pycurl as follows::
$ PYCURL_SSL_LIBRARY=openssl LDFLAGS="-L/usr/local/opt/openssl/lib" CPPFLAGS="-I/usr/local/opt/openssl/include" pip install --no-cache-dir pycurl
#. Finally, if we re-install or execute wfuzz again it should work correctly.
If you get errors such as::
Fatal exception: dlopen(xxx/lib/python3.7/site-packages/pycurl.cpython-37m-darwin.so, 2): Library not loaded: /usr/local/opt/openssl/lib/libssl.1.0.0.dylib
Referenced from: /usr/local/opt/curl-openssl/lib/libcurl.4.dylib
Reason: image not found. Wfuzz needs pycurl to run. Pycurl could be installed using the following command:
Run brew update && brew upgrade
If you get an error such as::
ImportError: pycurl: libcurl link-time ssl backends (secure-transport, openssl) do not include compile-time ssl backend (none/other)
That might indicate that pycurl was reinstalled and not linked to the SSL correctly. Uninstall pycurl as follows::
$ pip uninstall pycurl
and re-install pycurl starting from step 4 above.
Pycurl on Windows
-----------------
Install pycurl matching your python version from https://pypi.org/project/pycurl/#files
PyCurl SSL bug
--------------
If you experience errors when using Wfuzz against SSL sites, it could be because an old know issue:
http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=515200
Briefly, pycurl is built against libcurl3-gnutls, which does not work with a number of web sites. Pycurl fails with the following error message::
pycurl.error: (35, 'gnutls_handshake() failed: A TLS packet with unexpected length was received.')
Verifying the problem
^^^^^^^^^^^^^^^^^^^^^
* Pycurl linked against gnutls::
$ python
>>> import pycurl
>>> pycurl.version
libcurl/7.21.3 GnuTLS/2.8.6 zlib/1.2.3.4 libidn/1.18'
* Pycurl linked against openssl::
$ python
>>> import pycurl
>>> pycurl.version
'libcurl/7.21.3 OpenSSL/0.9.8o zlib/1.2.3.4 libidn/1.18'
Installing pycurl openssl flavour
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In newer Ubuntu versions, you can install libcurl openssl flavour::
$ sudo apt install libcurl4-openssl-dev
$ sudo pip3 install --upgrade wfuzz
Installing pycurl against openssl
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Alternatively, it can be done manually:
1. sudo apt-get install build-essential fakeroot dpkg-dev
2. mkdir ~/python-pycurl-openssl
3. cd ~/python-pycurl-openssl
4. sudo apt-get source python-pycurl
5. sudo apt-get build-dep python-pycurl -y
6. sudo apt-get install libcurl4-openssl-dev -y
*** CAUTION: BE CAREFUL WITH THIS OR DELETE THE DIRECTORY MANUALLY TO BE SAFE ***
7. sudo rm -r ./*/ ; dpkg-source -x pycurl_7*.dsc # *** CAUTION: BE CAREFUL WITH THIS OR DELETE THE DIRECTORY MANUALLY TO BE SAFE ***
8. cd pycurl*/
9. edit debian/control file and replace all instances of “libcurl4-gnutls-dev” with “libcurl4-openssl-dev”:
sed -i 's/libcurl4-gnutls-dev/libcurl4-openssl-dev/g' debian/control
sed -i 's/rm -f/rm -rf/g' debian/rules # fix debian/rules 'rm -r' typo preventing existing directory delete
10. sudo PYCURL_SSL_LIBRARY=openssl; dpkg-buildpackage -rfakeroot -b -uc -us
11. sudo dpkg -i ../python-pycurl_7*.deb
If there is still the error::
ImportError?: No module named bottle
Check this http://stackoverflow.com/questions/9122200/importerror-no-module-named-bottle
================================================
FILE: docs/user/wfpayload.rst
================================================
wfpayload
=========
wfpayload uses the same motor as wfuzz but instead of performing HTTP requests, uses wfuzz's payload plugins to generate new content or analyse saved sessions.
Generating new dictionaries
-------------------
You can use wfpayload to create new dictionaries::
$ wfpayload -z range --zD 0-10
0
1
2
3
4
5
6
7
8
9
10
The same wfuzz's syntax can be used, for example::
$ wfpayload -z range --zD 0-10 --filter "FUZZ<3"
0
1
2
Analysing saved sessions
------------------
Previously performed HTTP requests/responses contain a treasure trove of data. You can use wfpayload to filter and analyse previously saved sessions. Wfpayload can also read sessions from external tools, such as burp.
This allows you to look for new vulnerabilities or understand the underlying target without performing new HTTP requests.
For example, the following will return a unique list of HTTP requests including the authtoken parameter as a GET parameter::
$ wfpayload -z burplog,a_burp_log.log --slice "params.get~'authtoken'"
Authtoken is the parameter used by BEA WebLogic Commerce Servers (TM) as a CSRF token, and therefore the above will find all the requests exposing the CSRF token in the URL.
You can also look for specific parameters or headers, for example, the following will look for HTTP responses accepting any CORS origin::
$ wfpayload -z burplog --zD burp_log_05032020.log --prefilter "r.headers.response.Access-Control-Allow-Origin='*'"
It is worth noting that, if the header is not present in the response it will be return an empty value, not raising any error.
You can also select the fields to show with --efield and --field, for example::
$ wfpayload -z wfuzzp --zD /tmp/session --field r.params.get
artist=5
...
Or::
$ wfpayload -z wfuzzp --zD /tmp/session --efield r.params.get
000000006: 200 99 L 272 W 3868 Ch "5 | artist=5"
...
Running plugins against saved sessions
-------------------
Plugins can be run against a saved session. For example::
$ ./wfpayload -z burplog --zD ./burp_log_05032020.log --script=headers --filter "plugins~'akamai'"
...
000000124: 302 0 L 0 W 0 Ch "https://trial-eum-clientnsv4-s.akamaihd.net/eum/getdns.txt?c=pjq71x1r7"
|_ New Server header - AkamaiGHost
000000913: 200 10 L 6571 W 289832 Ch "https://assets.adobedtm.com/2eed2bf00c8bca0c98d97ffee50a306922bc8c98/satelliteLib-27b81756e778cc85cc1a2f067764cd3abf072aa9.js"
|_ New Server header - AkamaiNetStorage
...
Re-writing saved sessions
-------------------
The content of a saved session can be re-written. For example, let's say there is a session with a bunch of 404/400 results that you want to remove::
$ wfpayload -z burplog --zD ./burp_log_05032020.log --hc 404 --oF /tmp/no404
and then::
$ wfpayload -z wfuzzp --zD /tmp/no404
================================================
FILE: requirements.txt
================================================
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --output-file=requirements.txt setup.py
#
attrs==20.1.0 # via pytest
chardet==3.0.4 # via wfuzz (setup.py)
iniconfig==1.0.1 # via pytest
more-itertools==8.5.0 # via pytest
packaging==20.4 # via pytest
pluggy==0.13.1 # via pytest
py==1.9.0 # via pytest
pycurl==7.43.0.6 # via wfuzz (setup.py)
pyparsing==2.4.7 # via packaging
pytest==6.0.1 # via wfuzz (setup.py)
six==1.15.0 # via packaging, wfuzz (setup.py)
toml==0.10.1 # via pytest
================================================
FILE: setup.py
================================================
import os
import sys
import re
from setuptools import setup, find_packages
with open("README.md", "rb") as f:
long_descr = f.read().decode("utf-8")
version = re.search(
r'^__version__\s*=\s*"(.*)"',
open('src/wfuzz/__init__.py').read(),
re.M
).group(1)
docs_requires = [
"Sphinx",
]
dev_requires = [
'mock',
'coverage',
'codecov',
'netaddr', # tests/api/test_payload.py uses ipranges payload
'pip-tools',
'flake8==3.8.3',
'black==19.10b0;python_version>"3.5"',
'pytest',
]
install_requires = [
'pycurl',
'pyparsing<2.4.2;python_version<="3.4"',
'pyparsing>=2.4.2;python_version>="3.5"',
'six',
'configparser;python_version<"3.5"',
'chardet',
]
if sys.platform.startswith("win"):
install_requires += ["colorama>=0.4.0"]
try:
os.symlink('../../docs/user/advanced.rst', 'src/wfuzz/advanced.rst')
setup(
name="wfuzz",
packages=find_packages(where='src'),
package_dir={'wfuzz': 'src/wfuzz'},
include_package_data=True,
package_data={'wfuzz': ['*.rst']},
entry_points={
'console_scripts': [
'wfuzz = wfuzz.wfuzz:main',
'wfpayload = wfuzz.wfuzz:main_filter',
'wfencode = wfuzz.wfuzz:main_encoder',
],
'gui_scripts': [
'wxfuzz = wfuzz.wfuzz:main_gui',
]
},
version=version,
description="Wfuzz - The web fuzzer",
long_description=long_descr,
long_description_content_type='text/markdown',
author="Xavi Mendez (@x4vi_mendez)",
author_email="xmendez@edge-security.com",
url="http://wfuzz.org",
license="GPLv2",
install_requires=install_requires,
extras_require={
'dev': dev_requires,
'docs': docs_requires,
},
python_requires=">=2.6",
classifiers=(
'Development Status :: 4 - Beta',
'Natural Language :: English',
'License :: OSI Approved :: GNU General Public License v2 (GPLv2)',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
),
)
finally:
os.unlink('src/wfuzz/advanced.rst')
================================================
FILE: src/wfencode.py
================================================
#!/usr/bin/env python
from wfuzz.wfuzz import main_encoder
if __name__ == "__main__":
main_encoder()
================================================
FILE: src/wfpayload.py
================================================
#!/usr/bin/env python
from wfuzz.wfuzz import main_filter
if __name__ == "__main__":
main_filter()
================================================
FILE: src/wfuzz/__init__.py
================================================
__title__ = "wfuzz"
__version__ = "3.1.0"
__build__ = 0x023000
__author__ = "Xavier Mendez"
__license__ = "GPL 2.0"
__copyright__ = "Copyright 2011-2020 Xavier Mendez"
import logging
import sys
import warnings
# define a logging Handler
console = logging.StreamHandler()
console.setLevel(logging.WARNING)
formatter = logging.Formatter("%(name)-12s: %(levelname)-8s %(message)s")
console.setFormatter(formatter)
logging.getLogger("").addHandler(console)
# define warnings format
def warning_on_one_line(message, category, filename, lineno, file=None, line=None):
return " %s:%s: %s:%s\n" % (filename, lineno, category.__name__, message)
warnings.formatwarning = warning_on_one_line
try:
import pycurl
if "openssl".lower() not in pycurl.version.lower():
warnings.warn(
"Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information."
)
if not hasattr(pycurl, "CONNECT_TO"):
warnings.warn(
"Pycurl and/or libcurl version is old. CONNECT_TO option is missing. Wfuzz --ip option will not be available."
)
if not hasattr(pycurl, "PATH_AS_IS"):
warnings.warn(
"Pycurl and/or libcurl version is old. PATH_AS_IS option is missing. Wfuzz might not correctly fuzz URLS with '..'."
)
except ImportError:
warnings.warn(
"fuzz needs pycurl to run. Pycurl could be installed using the following command: $ pip install pycurl"
)
sys.exit(1)
from .options import FuzzSession
from .api import fuzz, get_payload, get_payloads, encode, decode, payload, get_session
================================================
FILE: src/wfuzz/__main__.py
================================================
from .wfuzz import main
main()
================================================
FILE: src/wfuzz/api.py
================================================
from .options import FuzzSession
from .facade import Facade
from .ui.console.clparser import CLParser
"""
Wfuzz API
"""
def fuzz(**kwargs):
return FuzzSession(**kwargs).fuzz()
def get_payloads(iterator):
fs = FuzzSession()
return fs.get_payloads(iterator)
def get_payload(iterator):
fs = FuzzSession()
return fs.get_payload(iterator)
def encode(name, value):
return Facade().encoders.get_plugin(name)().encode(value)
def decode(name, value):
return Facade().encoders.get_plugin(name)().decode(value)
def payload(**kwargs):
return FuzzSession(**kwargs).payload()
def get_session(cline):
cl = ["wfuzz"] + cline.split(" ")
return FuzzSession(**CLParser(cl).parse_cl())
================================================
FILE: src/wfuzz/core.py
================================================
from .fuzzobjects import FuzzType
from .myqueues import MyPriorityQueue, QueueManager
from .fuzzqueues import (
SeedQ,
SaveQ,
PrinterQ,
RoutingQ,
FilterQ,
SliceQ,
JobQ,
RecursiveQ,
DryRunQ,
HttpQueue,
HttpReceiver,
AllVarQ,
CLIPrinterQ,
ConsolePrinterQ,
PassPayloadQ,
)
# python 2 and 3: iterator
from builtins import object
class Fuzzer(object):
def __init__(self, options):
# Create queues
# genReq ---> seed_queue -> [slice_queue] -> http_queue/dryrun -> [round_robin -> plugins_queue] * N
# -> [recursive_queue -> routing_queue] -> [filter_queue] -> [save_queue] -> [printer_queue] ---> results
self.options = options
self.qmanager = QueueManager(options)
self.results_queue = MyPriorityQueue()
if options["allvars"]:
self.qmanager.add("seed_queue", AllVarQ(options))
else:
self.qmanager.add("seed_queue", SeedQ(options))
for prefilter_idx, prefilter in enumerate(options.get("compiled_prefilter")):
if prefilter.is_active():
self.qmanager.add(
"slice_queue_{}".format(prefilter_idx), SliceQ(options, prefilter)
)
if options.get("transport") == "dryrun":
self.qmanager.add("transport_queue", DryRunQ(options))
elif options.get("transport") == "payload":
self.qmanager.add("transport_queue", PassPayloadQ(options))
else:
# http_queue breaks process rules due to being asynchronous. Someone has to collects its sends, for proper fuzzqueue's count and sync purposes
self.qmanager.add("transport_queue", HttpQueue(options))
self.qmanager.add("http_receiver", HttpReceiver(options))
if options.get("script"):
self.qmanager.add("plugins_queue", JobQ(options))
if options.get("rlevel") > 0:
self.qmanager.add("recursive_queue", RecursiveQ(options))
if (options.get("script") or options.get("rlevel") > 0) and options.get(
"transport"
) == "http":
rq = RoutingQ(
options,
{
FuzzType.SEED: self.qmanager["seed_queue"],
FuzzType.BACKFEED: self.qmanager["transport_queue"],
},
)
self.qmanager.add("routing_queue", rq)
if options.get("compiled_filter").is_active():
self.qmanager.add(
"filter_queue", FilterQ(options, options["compiled_filter"])
)
if options.get("compiled_simple_filter").is_active():
self.qmanager.add(
"simple_filter_queue",
FilterQ(options, options["compiled_simple_filter"]),
)
if options.get("save"):
self.qmanager.add("save_queue", SaveQ(options))
if options.get("compiled_printer"):
self.qmanager.add("printer_queue", PrinterQ(options))
if options.get("exec_mode") == "cli":
if options["console_printer"]:
self.qmanager.add("printer_cli", ConsolePrinterQ(options))
else:
self.qmanager.add("printer_cli", CLIPrinterQ(options))
self.qmanager.bind(self.results_queue)
# initial seed request
self.qmanager.start()
def __iter__(self):
return self
def __next__(self):
# http://bugs.python.org/issue1360
res = self.results_queue.get()
self.results_queue.task_done()
# done! (None sent has gone through all queues).
if not res:
raise StopIteration
elif res.item_type == FuzzType.ERROR:
raise res.exception
return res
def stats(self):
return dict(
list(self.qmanager.get_stats().items())
+ list(self.qmanager["transport_queue"].http_pool.job_stats().items())
+ list(self.options.stats.get_stats().items())
)
def cancel_job(self):
self.qmanager.cancel()
def pause_job(self):
self.qmanager["transport_queue"].pause.clear()
def resume_job(self):
self.qmanager["transport_queue"].pause.set()
================================================
FILE: src/wfuzz/dictionaries.py
================================================
from .exception import FuzzExceptNoPluginError, FuzzExceptBadOptions
from .facade import Facade
from .filters.ppfilter import FuzzResFilterSlice, FuzzResFilter
from .fuzzobjects import FuzzWord, FuzzWordType
class BaseIterator:
def count(self):
raise NotImplementedError
def width(self):
raise NotImplementedError
def payloads(self):
raise NotImplementedError
def cleanup(self):
for payload in self.payloads():
payload.close()
class BaseDictionary:
def count(self):
raise NotImplementedError
def next_word(self):
raise NotImplementedError
def __next__(self):
return self.next_word()
def __iter__(self):
return self
def close(self):
pass
class EncodeIt(BaseDictionary):
def __init__(self, parent, encoders_list):
self.parent = parent
self.encoders = encoders_list
self.__generator = self._gen()
def count(self):
return self.parent.count() * len(self.encoders)
def concatenate(self, encoder_name, payload_word):
string = payload_word.content
for plugin_name in reversed(encoder_name.split("@")):
string = Facade().encoders.get_plugin(plugin_name)().encode(string)
return FuzzWord(string, FuzzWordType.WORD)
def encode(self, encoder_name, payload_word):
plugin_list = Facade().encoders.get_plugins(encoder_name)
if not plugin_list:
raise FuzzExceptNoPluginError(
encoder_name
+ " encoder does not exists (-e encodings for a list of available encoders)"
)
for plugin_class in plugin_list:
yield FuzzWord(
plugin_class().encode(payload_word.content), FuzzWordType.WORD
)
def next_word(self):
return next(self.__generator)
def _gen(self):
while 1:
try:
payload_word = next(self.parent)
except StopIteration:
return
for encoder_name in self.encoders:
if encoder_name.find("@") > 0:
yield self.concatenate(encoder_name, payload_word)
else:
for string in self.encode(encoder_name, payload_word):
yield string
def __next__(self):
return next(self.__generator)
class TupleIt(BaseDictionary, BaseIterator):
def __init__(self, parent):
self.parent = parent
def count(self):
return self.parent.count()
def width(self):
return 1
def payloads(self):
return [self.parent]
def next_word(self):
return (next(self.parent),)
class WrapperIt(BaseDictionary):
def __init__(self, iterator):
self._it = iter(iterator)
def count(self):
return -1
def get_type(self):
return FuzzWordType.WORD
def next_word(self):
return FuzzWord(str(next(self._it)), FuzzWordType.WORD)
class SliceIt(BaseDictionary):
def __init__(self, payload, slicestr):
self.ffilter = FuzzResFilter(filter_string=slicestr)
self.ffilter_slice = FuzzResFilterSlice(filter_string=slicestr)
self.payload = payload
def count(self):
return -1
def get_type(self):
return self.payload.get_type()
def _get_filtered_value(self, item):
if item.type == FuzzWordType.FUZZRES:
filter_ret = self.ffilter.is_visible(item.content)
else:
filter_ret = self.ffilter_slice.is_visible(item.content)
return filter_ret
def next_word(self):
# can be refactored using the walrus operator in python 3.8
item = next(self.payload)
filter_ret = self._get_filtered_value(item)
if not isinstance(filter_ret, bool) and item.type == FuzzWordType.FUZZRES:
raise FuzzExceptBadOptions(
"The payload type cannot be modified from FuzzResult to word."
)
while isinstance(filter_ret, bool) and not filter_ret:
item = next(self.payload)
filter_ret = self._get_filtered_value(item)
if not isinstance(filter_ret, bool):
return FuzzWord(filter_ret, item.type)
return item
class AllVarDictio(BaseDictionary, BaseIterator):
def __init__(self, iterator, allvar_len):
self._it = iter(iterator)
self._count = allvar_len
def count(self):
return self._count
def width(self):
return 0
def payloads(self):
return []
def next_word(self):
var_name, fuzz_word = next(self._it)
return (FuzzWord(var_name, FuzzWordType.WORD), fuzz_word)
================================================
FILE: src/wfuzz/exception.py
================================================
class FuzzException(Exception):
pass
class FuzzExceptBadOptions(FuzzException):
pass
class FuzzExceptNoPluginError(FuzzException):
pass
class FuzzExceptPluginLoadError(FuzzException):
pass
class FuzzExceptIncorrectFilter(FuzzException):
pass
class FuzzExceptBadAPI(FuzzException):
pass
class FuzzExceptInternalError(FuzzException):
pass
class FuzzExceptBadFile(FuzzException):
pass
class FuzzExceptBadInstall(FuzzException):
pass
class FuzzExceptBadRecipe(FuzzException):
pass
class FuzzExceptMissingAPIKey(FuzzException):
pass
class FuzzExceptPluginBadParams(FuzzException):
pass
class FuzzExceptResourceParseError(FuzzException):
pass
class FuzzExceptPluginError(FuzzException):
pass
class FuzzExceptNetError(FuzzException):
pass
================================================
FILE: src/wfuzz/externals/__init__.py
================================================
================================================
FILE: src/wfuzz/externals/moduleman/__init__.py
================================================
================================================
FILE: src/wfuzz/externals/moduleman/loader.py
================================================
import inspect
import logging
import imp
import os.path
class IModuleLoader:
def __init__(self, **params):
self.set_params(**params)
def set_params(self, **params):
raise NotImplementedError
def load(self, registrant):
raise NotImplementedError
class FileLoader(IModuleLoader):
def __init__(self, **params):
IModuleLoader.__init__(self, **params)
self.__logger = logging.getLogger("libraries.FileLoader")
def set_params(self, **params):
if "base_path" not in params:
return
elif "filename" not in params:
return
self.filename = params["filename"]
self.base_path = params["base_path"]
if self.base_path.endswith("/"):
self.base_path = self.base_path[:-1]
def load(self, registrant):
self.module_registrant = registrant
self._load_py_from_file(os.path.join(self.base_path, self.filename))
def _build_id(self, filename, objname):
filepath, filename = os.path.split(filename)
relative_path = os.path.relpath(filepath, self.base_path)
identifier = relative_path + "/" + objname
if identifier.startswith("./"):
identifier = identifier[2:]
return identifier
def _load_py_from_file(self, filename):
"""
Opens "filename", inspects it and calls the registrant
"""
self.__logger.debug("__load_py_from_file. START, file=%s" % (filename,))
dirname, filename = os.path.split(filename)
fn = os.path.splitext(filename)[0]
exten_file = None
module = None
try:
exten_file, filename, description = imp.find_module(fn, [dirname])
module = imp.load_module(fn, exten_file, filename, description)
except ImportError as msg:
self.__logger.critical(
"__load_py_from_file. Filename: %s Exception, msg=%s" % (filename, msg)
)
# raise msg
pass
except SyntaxError as msg:
# incorrect python syntax in file
self.__logger.critical(
"__load_py_from_file. Filename: %s Exception, msg=%s" % (filename, msg)
)
# raise msg
pass
finally:
if exten_file:
exten_file.close()
if module is None:
return
for objname in dir(module):
obj = getattr(module, objname)
self.__logger.debug("__load_py_from_file. inspecting=%s" % (objname,))
if inspect.isclass(obj):
if "__PLUGIN_MODULEMAN_MARK" in dir(obj):
if self.module_registrant:
self.module_registrant.register(
self._build_id(filename, objname), obj
)
self.__logger.debug("__load_py_from_file. END, loaded file=%s" % (filename,))
class DirLoader(FileLoader):
def __init__(self, **params):
FileLoader.__init__(self, **params)
self.__logger = logging.getLogger("libraries.DirLoader")
def set_params(self, **params):
if "base_dir" not in params:
return
elif "base_path" not in params:
return
self.base_dir = params["base_dir"]
self.base_path = params["base_path"]
if self.base_path.endswith("/"):
self.base_path = self.base_path[:-1]
def load(self, registrant):
self.module_registrant = registrant
self.structure = self.__load_all(self.base_dir)
def _build_id(self, filename, objname):
filepath, filename = os.path.split(filename)
relative_path = os.path.relpath(
filepath, os.path.join(self.base_path, self.base_dir)
)
identifier = relative_path + "/" + objname
if identifier.startswith("./"):
identifier = identifier[2:]
return identifier
def __load_all(self, dir_name):
"""
loads all plugins and creates a loaded list of scripts from directory plugins like:
[ ( category,[script1, script2,...] ), (category2,[script1, (subcategory,[script1,script2]),...]) ]
"""
walked = []
current = os.path.join(self.base_path, dir_name)
if os.path.isdir(current):
dir_list = self.__walk_dir_tree(current)
walked.append((current, dir_list))
if self.module_registrant:
self.module_registrant.end_loading()
return walked
def __walk_dir_tree(self, dirname):
dir_list = []
self.__logger.debug("__walk_dir_tree. START dir=%s", dirname)
for f in os.listdir(dirname):
current = os.path.join(dirname, f)
if os.path.isfile(current) and f.endswith("py"):
if self.module_registrant:
self._load_py_from_file(current)
dir_list.append(current)
elif os.path.isdir(current):
ret = self.__walk_dir_tree(current)
if ret:
dir_list.append((f, ret))
return dir_list
================================================
FILE: src/wfuzz/externals/moduleman/modulefilter.py
================================================
# mimicking nmap script filter
# nmap --script "http-*"
# Loads all scripts whose name starts with http-, such as http-auth and http-open-proxy. The argument to --script had to be in quotes to protect the wildcard from the shell.
# not valid for categories!
#
# More complicated script selection can be done using the and, or, and not operators to build Boolean expressions. The operators have the same precedence[12] as in Lua: not is the
# highest, followed by and and then or. You can alter precedence by using parentheses. Because expressions contain space characters it is necessary to quote them.
#
# nmap --script "not intrusive"
# Loads every script except for those in the intrusive category.
#
# nmap --script "default or safe"
# This is functionally equivalent to nmap --script "default,safe". It loads all scripts that are in the default category or the safe category or both.
#
# nmap --script "default and safe"
# Loads those scripts that are in both the default and safe categories.
#
# nmap --script "(default or safe or intrusive) and not http-*"
# Loads scripts in the default, safe, or intrusive categories, except for those whose names start with http-.
PYPARSING = True
try:
from pyparsing import (
Word,
Group,
oneOf,
Optional,
Suppress,
ZeroOrMore,
Literal,
alphas,
alphanums,
)
except ImportError:
PYPARSING = False
class IFilter:
def is_visible(self, plugin, filter_string):
raise NotImplementedError
class Filter(IFilter):
def __init__(self):
if PYPARSING:
category = Word(alphas + "_-*", alphanums + "_-*")
operator = oneOf("and or ,")
neg_operator = "not"
elementRef = category
definition = elementRef + ZeroOrMore(operator + elementRef)
nestedformula = Group(
Suppress(Optional(Literal("(")))
+ definition
+ Suppress(Optional(Literal(")")))
)
neg_nestedformula = Optional(neg_operator) + nestedformula
self.finalformula = neg_nestedformula + ZeroOrMore(
operator + neg_nestedformula
)
elementRef.setParseAction(self.__compute_element)
neg_nestedformula.setParseAction(self.__compute_neg_formula)
nestedformula.setParseAction(self.__compute_formula)
self.finalformula.setParseAction(self.__myreduce)
def __compute_neg_formula(self, tokens):
if len(tokens) > 1 and tokens[0] == "not":
return not tokens[1]
else:
return tokens[0]
def __compute_element(self, tokens):
item = tokens[0]
wildc_index = item.find("*")
if wildc_index > 0:
return self.plugin.name.startswith(item[:wildc_index])
else:
if isinstance(self.plugin.category, list):
return item in self.plugin.category or self.plugin.name == item
else:
return self.plugin.category == item or self.plugin.name == item
def __myreduce(self, elements):
first = elements[0]
for i in range(1, len(elements), 2):
if elements[i] == "and":
first = first and elements[i + 1]
elif elements[i] == "or" or elements[i] == ",":
first = first or elements[i + 1]
return first
def __compute_formula(self, tokens):
return self.__myreduce(tokens[0])
def simple_filter(self, plugin, filter_string):
ret = []
for item in filter_string.split(","):
wildc_index = item.find("*")
if wildc_index > 0:
ret.append(
(
item in plugin.category
or plugin.name.startswith(item[:wildc_index])
)
)
else:
ret.append((item in plugin.category or plugin.name == item))
return any(ret)
def simple_filter_banned_keywords(self, filter_string):
if filter_string.find("(") >= 0:
return True
elif filter_string.find(")") >= 0:
return True
elif any(x in ["or", "not", "and"] for x in filter_string.split(" ")):
return True
else:
return False
def is_visible(self, plugin, filter_string):
self.plugin = plugin
if PYPARSING:
return self.finalformula.parseString(filter_string)[0]
else:
if self.simple_filter_banned_keywords(filter_string):
raise Exception("Pyparsing missing, complex filters not allowed.")
else:
return self.simple_filter(plugin, filter_string)
================================================
FILE: src/wfuzz/externals/moduleman/plugin.py
================================================
try:
from collections.abc import Callable
except ImportError:
from collections import Callable
def moduleman_plugin(*args):
method_args = []
def inner_decorator(cls):
for method in method_args:
if not (method in dir(cls)):
raise Exception("Required method %s not implemented" % method)
cls.__PLUGIN_MODULEMAN_MARK = "Plugin mark"
return cls
if not isinstance(args[0], Callable):
method_args += args
return inner_decorator
return inner_decorator(args[0])
================================================
FILE: src/wfuzz/externals/moduleman/registrant.py
================================================
from .modulefilter import Filter
from collections import defaultdict
try:
from collections.abc import MutableMapping
except ImportError:
from collections import MutableMapping
from threading import Lock
class IRegistrant:
def __init__(self, loader, plg_filter):
self.plg_filter = plg_filter
self.loader = loader
self.start_loading()
self.load()
self.end_loading()
def register(self, identifier, module):
raise NotImplementedError
def start_loading(self):
raise NotImplementedError
def load(self):
raise NotImplementedError
def end_loading(self):
raise NotImplementedError
def modify_instance(self, module):
raise NotImplementedError
class KnowledgeBase(MutableMapping):
def __init__(self, *args, **kwargs):
self.__data = defaultdict(list)
self.mutex = Lock()
def __getitem__(self, key):
with self.mutex:
return self.__data[key]
def __setitem__(self, key, value):
with self.mutex:
self.__data[key].append(value)
def __delitem__(self, key):
with self.mutex:
del self.__data[key]
def __len__(self):
with self.mutex:
return len(self.__data)
def __str__(self):
with self.mutex:
return str(self.__data)
def __iter__(self):
return iter(self.__data)
class BRegistrant(IRegistrant):
def __init__(self, loader, plg_filter=Filter()):
self.__plugins = {}
self.__active_plugins = {}
self.kbase = KnowledgeBase()
IRegistrant.__init__(self, loader, plg_filter)
def register(self, identifier, module):
self.__plugins[identifier] = self.modify_instance(module)
self.__active_plugins[identifier] = True
def load(self):
self.loader.load(self)
def start_loading(self):
pass
def end_loading(self):
pass
def modify_instance(self, module):
module.kbase = self.kbase
return module
# ------------------------------------------------
# plugin management functions
# ------------------------------------------------
def plugin_state(self, identifier, state):
self.__active_plugins[identifier] = state
def __get_plugins(self, category, sorting):
def plugin_filter(x):
plgid, plg = x
if category == "$all$":
return True
elif not self.__active_plugins[plgid]:
return False
else:
return self.plg_filter.is_visible(plg, category)
def key_funtion(x):
return x[1].priority
plugin_list = list(filter(plugin_filter, list(self.__plugins.items())))
if sorting:
plugin_list.sort(key=key_funtion)
return plugin_list
def get_plugin(self, identifier):
# strict and fuzzy search
if identifier in self.__plugins:
return self.__plugins[identifier]
else:
plugin_list = [
plg
for plg_id, plg in self.__get_plugins("$all$", True)
if identifier in plg_id
]
if not plugin_list:
raise KeyError("No plugins found!")
elif len(plugin_list) == 1:
return plugin_list[0]
else:
raise KeyError(
"Multiple plugins found: %s"
% ",".join([plg.name for plg in plugin_list])
)
raise KeyError("No plugins found!")
def get_plugins(self, category="$all$", sorting="true"):
return [plg for plg_id, plg in self.__get_plugins(category, sorting)]
def get_plugins_ext(self, category="$all$", sorting="true"):
plugin_list = [["Id", "Priority", "Category", "Name", "Summary"]]
for plg_id, plg in self.__get_plugins(category, sorting):
plugin_list.append(
[
plg_id,
str(plg.priority),
", ".join(plg.category),
str(plg.name),
str(plg.summary),
]
)
return plugin_list
def get_plugins_names(self, category="$all$", sorting="true"):
return [plg.name for plg_id, plg in self.__get_plugins(category, sorting)]
def get_plugins_ids(self, category="$all$", sorting="true"):
return [plg_id for plg_id, plg in self.__get_plugins(category, sorting)]
class MulRegistrant(BRegistrant):
def load(self):
for loader in self.loader:
loader.load(self)
================================================
FILE: src/wfuzz/externals/reqresp/Request.py
================================================
# Covered by GPL V2.0
# Coded by Carlos del Ojo Elias (deepbit@gmail.com)
# Lately maintained by Xavi Mendez (xmendez@edge-security.com)
# Python 2 and 3
import sys
if sys.version_info >= (3, 0):
from urllib.parse import urlparse
from urllib.parse import urlunparse
else:
from urlparse import urlparse
from urlparse import urlunparse
import re
import pycurl
from .Variables import VariablesSet
from .exceptions import ReqRespException
from .Response import Response
from wfuzz.helpers.str_func import python2_3_convert_to_unicode
from wfuzz.helpers.obj_dic import CaseInsensitiveDict
from .TextParser import TextParser
PYCURL_PATH_AS_IS = True
if not hasattr(pycurl, "PATH_AS_IS"):
PYCURL_PATH_AS_IS = False
class Request:
def __init__(self):
self.__host = None # www.google.com:80
self.__path = None # /index.php
self.__params = None # Mierdaza de index.php;lskjflkasjflkasjfdlkasdf?
self.schema = "http" # http
# #### Variables calculadas por getters NO SE PUEDEN MODIFICAR
# self.urlWithoutPath # http://www.google.es
# self.pathWithVariables # /index.php?a=b&c=d
# self.urlWithoutVariables=None # http://www.google.es/index.php
# self.completeUrl="" # http://www.google.es/index.php?a=b
# self.finalUrl="" # Url despues de hacer el FollowLocation
# self.redirectUrl="" # Url redirected
# self.postdata="" # Datos por POST, toto el string
# ###############
self.ContentType = (
"application/x-www-form-urlencoded" # None es normal encoding
)
self.multiPOSThead = {}
self.__variablesGET = VariablesSet()
self._variablesPOST = VariablesSet()
self._non_parsed_post = None
# diccionario, por ejemplo headers["Cookie"]
self._headers = CaseInsensitiveDict(
{
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1)",
}
)
self.response = None # Apunta a la response que produce dicha request
# ################## lo de debajo no se deberia acceder directamente
self.time = None # 23:00:00
self.ip = None # 192.168.1.1
self._method = None
self.protocol = "HTTP/1.1" # HTTP/1.1
self.__performHead = ""
self.__performBody = ""
self.__authMethod = None
self.__userpass = ""
self.description = "" # For temporally store imformation
self.__proxy = None
self.proxytype = None
self.__timeout = None
self.__totaltimeout = None
self.__finalurl = ""
self.followLocation = False
self.__userpass = ""
self.totaltime = None
@property
def method(self):
if self._method is None:
return "POST" if self._non_parsed_post is not None else "GET"
return self._method
@method.setter
def method(self, value):
if value == "None":
value = None
self._method = value
def setFinalUrl(self, fu):
self.__finalurl = fu
def __str__(self):
str = "[ URL: %s" % (self.completeUrl)
if self.postdata:
str += ' - {}: "{}"'.format(self.method, self.postdata)
if "Cookie" in self._headers:
str += ' - COOKIE: "%s"' % self._headers["Cookie"]
str += " ]"
return str
def getHost(self):
return self.__host
def getXML(self, obj):
r = obj.createElement("request")
r.setAttribute("method", self.method)
url = obj.createElement("URL")
url.appendChild(obj.createTextNode(self.completeUrl))
r.appendChild(url)
if self.postdata:
pd = obj.createElement("PostData")
pd.appendChild(obj.createTextNode(self.postdata))
r.appendChild(pd)
if "Cookie" in self._headers:
ck = obj.createElement("Cookie")
ck.appendChild(obj.createTextNode(self._headers["Cookie"]))
r.appendChild(ck)
return r
def __getattr__(self, name):
if name == "urlWithoutVariables":
return urlunparse((self.schema, self.__host, self.__path, "", "", ""))
elif name == "pathWithVariables":
return urlunparse(
("", "", self.__path, "", self.__variablesGET.urlEncoded(), "")
)
elif name == "completeUrl":
return urlunparse(
(
self.schema,
self.__host,
self.__path,
self.__params,
self.__variablesGET.urlEncoded(),
"",
)
)
elif name == "finalUrl":
if self.__finalurl:
return self.__finalurl
return self.completeUrl
elif name == "urlWithoutPath":
return "%s://%s" % (self.schema, self._headers["Host"])
elif name == "path":
return self.__path
elif name == "postdata":
if self.ContentType == "application/x-www-form-urlencoded":
return self._variablesPOST.urlEncoded()
elif self.ContentType == "multipart/form-data":
return self._variablesPOST.multipartEncoded()
elif self.ContentType == "application/json":
return self._variablesPOST.json_encoded()
else:
return self._variablesPOST.urlEncoded()
else:
raise AttributeError
def setUrl(self, urltmp):
self.__variablesGET = VariablesSet()
self.schema, self.__host, self.__path, self.__params, variables, f = urlparse(
urltmp
)
if "Host" not in self._headers or (not self._headers["Host"]):
self._headers["Host"] = self.__host
if variables:
self.__variablesGET.parseUrlEncoded(variables)
# ############## PROXY ##################################
def getProxy(self):
return self.__proxy
def setProxy(self, prox, ptype):
self.__proxy = prox
self.proxytype = ptype
# ############## FOLLOW LOCATION ########################
def setFollowLocation(self, value):
self.followLocation = value
# ############# TIMEOUTS ################################
def setConnTimeout(self, time):
self.__timeout = time
def getConnTimeout(self):
return self.__timeout
def setTotalTimeout(self, time):
self.__totaltimeout = time
def getTotalTimeout(self):
return self.__totaltimeout
# ############# Autenticacion ###########################
def setAuth(self, method, string):
self.__authMethod = method
self.__userpass = string
def getAuth(self):
return self.__authMethod, self.__userpass
# ############# TRATAMIENTO VARIABLES GET & POST #########################
def existsGETVar(self, key):
return self.__variablesGET.existsVar(key)
def existPOSTVar(self, key):
return self._variablesPOST.existsVar(key)
def setVariablePOST(self, key, value):
v = self._variablesPOST.getVariable(key)
v.update(value)
# self._headers["Content-Length"] = str(len(self.postdata))
def setVariableGET(self, key, value):
v = self.__variablesGET.getVariable(key)
v.update(value)
def getGETVars(self):
return self.__variablesGET.variables
def getPOSTVars(self):
return self._variablesPOST.variables
def setPostData(self, pd, boundary=None):
self._non_parsed_post = pd
self._variablesPOST = VariablesSet()
try:
if self.ContentType == "multipart/form-data":
self._variablesPOST.parseMultipart(pd, boundary)
elif self.ContentType == "application/json":
self._variablesPOST.parse_json_encoded(pd)
else:
self._variablesPOST.parseUrlEncoded(pd)
except Exception:
try:
self._variablesPOST.parseUrlEncoded(pd)
except Exception:
print("Warning: POST parameters not parsed")
pass
############################################################################
def addHeader(self, key, value):
self._headers[key] = value
def delHeader(self, key):
if key in self._headers:
del self._headers[key]
def __getitem__(self, key):
if key in self._headers:
return self._headers[key]
else:
return ""
def getHeaders(self):
header_list = []
for i, j in self._headers.items():
header_list += ["%s: %s" % (i, j)]
return header_list
def head(self):
conn = pycurl.Curl()
conn.setopt(pycurl.SSL_VERIFYPEER, False)
conn.setopt(pycurl.SSL_VERIFYHOST, 0)
conn.setopt(pycurl.URL, self.completeUrl)
conn.setopt(pycurl.NOBODY, True) # para hacer un pedido HEAD
conn.setopt(pycurl.WRITEFUNCTION, self.header_callback)
conn.perform()
rp = Response()
rp.parseResponse(self.__performHead)
self.response = rp
def createPath(self, newpath):
"""Creates new url from a location header || Hecho para el followLocation=true"""
if "http" in newpath[:4].lower():
return newpath
parts = urlparse(self.completeUrl)
if "/" != newpath[0]:
newpath = "/".join(parts[2].split("/")[:-1]) + "/" + newpath
return urlunparse([parts[0], parts[1], newpath, "", "", ""])
# pycurl - reqresp conversions
@staticmethod
def to_pycurl_object(c, req):
c.setopt(pycurl.MAXREDIRS, 5)
c.setopt(pycurl.WRITEFUNCTION, req.body_callback)
c.setopt(pycurl.HEADERFUNCTION, req.header_callback)
c.setopt(pycurl.NOSIGNAL, 1)
c.setopt(pycurl.SSL_VERIFYPEER, False)
c.setopt(pycurl.SSL_VERIFYHOST, 0)
if PYCURL_PATH_AS_IS:
c.setopt(pycurl.PATH_AS_IS, 1)
c.setopt(pycurl.URL, python2_3_convert_to_unicode(req.completeUrl))
if req.getConnTimeout():
c.setopt(pycurl.CONNECTTIMEOUT, req.getConnTimeout())
if req.getTotalTimeout():
c.setopt(pycurl.TIMEOUT, req.getTotalTimeout())
authMethod, userpass = req.getAuth()
if authMethod or userpass:
if authMethod == "basic":
c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)
elif authMethod == "ntlm":
c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_NTLM)
elif authMethod == "digest":
c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)
c.setopt(pycurl.USERPWD, python2_3_convert_to_unicode(userpass))
else:
c.unsetopt(pycurl.USERPWD)
c.setopt(pycurl.HTTPHEADER, python2_3_convert_to_unicode(req.getHeaders()))
curl_options = {
"GET": pycurl.HTTPGET,
"POST": pycurl.POST,
"PATCH": pycurl.UPLOAD,
"HEAD": pycurl.NOBODY,
}
for o in curl_options.values():
c.setopt(o, False)
if req.method in curl_options:
c.unsetopt(pycurl.CUSTOMREQUEST)
c.setopt(curl_options[req.method], True)
else:
c.setopt(pycurl.CUSTOMREQUEST, req.method)
if req._non_parsed_post is not None:
c.setopt(
pycurl.POSTFIELDS, python2_3_convert_to_unicode(req._non_parsed_post)
)
c.setopt(pycurl.FOLLOWLOCATION, 1 if req.followLocation else 0)
# proxy = req.getProxy()
# if proxy is not None:
# c.setopt(pycurl.PROXY, python2_3_convert_to_unicode(proxy))
# if req.proxytype == "SOCKS5":
# c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5)
# elif req.proxytype == "SOCKS4":
# c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS4)
# req.delHeader("Proxy-Connection")
# else:
# c.setopt(pycurl.PROXY, "")
return c
def response_from_conn_object(self, conn, header, body):
# followlocation
if conn.getinfo(pycurl.EFFECTIVE_URL) != self.completeUrl:
self.setFinalUrl(conn.getinfo(pycurl.EFFECTIVE_URL))
self.totaltime = conn.getinfo(pycurl.TOTAL_TIME)
self.response = Response()
self.response.parseResponse(header, rawbody=body)
return self.response
def perform(self):
self.__performHead = ""
self.__performBody = ""
self.__headersSent = ""
try:
conn = Request.to_pycurl_object(pycurl.Curl(), self)
conn.perform()
self.response_from_conn_object(conn, self.__performHead, self.__performBody)
except pycurl.error as error:
errno, errstr = error
raise ReqRespException(ReqRespException.FATAL, errstr)
finally:
conn.close()
# ######## ESTE conjunto de funciones no es necesario para el uso habitual de la clase
def getAll(self):
pd = self._non_parsed_post if self._non_parsed_post else ""
string = (
str(self.method)
+ " "
+ str(self.pathWithVariables)
+ " "
+ str(self.protocol)
+ "\n"
)
for i, j in self._headers.items():
string += i + ": " + j + "\n"
string += "\n" + pd
return string
# #########################################################################
def header_callback(self, data):
self.__performHead += data
def body_callback(self, data):
self.__performBody += data
def Substitute(self, src, dst):
a = self.getAll()
rx = re.compile(src)
b = rx.sub(dst, a)
del rx
self.parseRequest(b, self.schema)
def parseRequest(self, rawRequest, prot="http"):
""" Aun esta en fase BETA y por probar"""
tp = TextParser()
tp.setSource("string", rawRequest)
self._variablesPOST = VariablesSet()
self._headers = {} # diccionario, por ejemplo headers["Cookie"]
tp.readLine()
try:
tp.search(r"^(\S+) (.*) (HTTP\S*)$")
self.method = tp[0][0]
self.protocol = tp[0][2]
except Exception as a:
print(rawRequest)
raise a
pathTMP = tp[0][1].replace(" ", "%20")
pathTMP = ("", "") + urlparse(pathTMP)[2:]
pathTMP = urlunparse(pathTMP)
while True:
tp.readLine()
if tp.search("^([^:]+): (.*)$"):
self.addHeader(tp[0][0], tp[0][1])
else:
break
self.setUrl(prot + "://" + self._headers["Host"] + pathTMP)
# ignore CRLFs until request line
while tp.lastline == "" and tp.readLine():
pass
# TODO: hacky, might need to change tp.readline returning read bytes instead
pd = ""
if tp.lastFull_line:
pd += tp.lastFull_line
while tp.readLine():
pd += tp.lastFull_line
if pd:
boundary = None
if "Content-Type" in self._headers:
values = self._headers["Content-Type"].split(";")
self.ContentType = values[0].strip().lower()
if self.ContentType == "multipart/form-data":
boundary = values[1].split("=")[1].strip()
self.setPostData(pd, boundary)
================================================
FILE: src/wfuzz/externals/reqresp/Response.py
================================================
import re
import cgi
from io import BytesIO
import gzip
import zlib
from .TextParser import TextParser
from wfuzz.helpers.str_func import python2_3_convert_from_unicode
def get_encoding_from_headers(headers):
"""Returns encodings from given HTTP Header Dict.
:param headers: dictionary to extract encoding from.
:rtype: str
"""
content_type = headers.get("Content-Type")
if not content_type:
return None
content_type, params = cgi.parse_header(content_type)
if "charset" in params:
return params["charset"].strip("'\"")
if "text" in content_type:
return "ISO-8859-1"
if "image" in content_type:
return "utf-8"
if "application/json" in content_type:
return "utf-8"
def get_encodings_from_content(content):
"""Returns encodings from given content string.
:param content: bytestring to extract encodings from.
"""
charset_re = re.compile(r']', flags=re.I)
pragma_re = re.compile(r']', flags=re.I)
xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]')
return (
charset_re.findall(content)
+ pragma_re.findall(content)
+ xml_re.findall(content)
)
class Response:
def __init__(self, protocol="", code="", message=""):
self.protocol = protocol # HTTP/1.1
self.code = code # 200
self.message = message # OK
self._headers = [] # bueno pues las cabeceras igual que en la request
self.__content = (
"" # contenido de la response (si i solo si Content-Length existe)
)
self.md5 = "" # hash de los contenidos del resultado
self.charlen = "" # Cantidad de caracteres de la respuesta
def addHeader(self, key, value):
self._headers += [(key, value)]
def delHeader(self, key):
for i in self._headers:
if i[0].lower() == key.lower():
self._headers.remove(i)
def addContent(self, text):
self.__content = self.__content + text
def __getitem__(self, key):
for i, j in self._headers:
if key == i:
return j
print("Error al obtener header!!!")
def getCookie(self):
str = []
for i, j in self._headers:
if i.lower() == "set-cookie":
str.append(j.split(";")[0])
return "; ".join(str)
def has_header(self, key):
for i, j in self._headers:
if i.lower() == key.lower():
return True
return False
def getLocation(self):
for i, j in self._headers:
if i.lower() == "location":
return j
return None
def header_equal(self, header, value):
for i, j in self._headers:
if i == header and j.lower() == value.lower():
return True
return False
def getHeaders(self):
return self._headers
def getContent(self):
return self.__content
def getTextHeaders(self):
string = (
str(self.protocol) + " " + str(self.code) + " " + str(self.message) + "\r\n"
)
for i, j in self._headers:
string += i + ": " + j + "\r\n"
return string
def getAll(self):
string = self.getTextHeaders() + "\r\n" + self.getContent()
return string
def Substitute(self, src, dst):
a = self.getAll()
b = a.replace(src, dst)
self.parseResponse(b)
def getAll_wpost(self):
string = (
str(self.protocol) + " " + str(self.code) + " " + str(self.message) + "\r\n"
)
for i, j in self._headers:
string += i + ": " + j + "\r\n"
return string
def parseResponse(self, rawheader, rawbody=None, type="curl"):
self.__content = ""
self._headers = []
tp = TextParser()
tp.setSource("string", rawheader)
tp.readUntil(r"(HTTP/[0-9.]+) ([0-9]+)")
while True:
while True:
try:
self.protocol = tp[0][0]
except Exception:
self.protocol = "unknown"
try:
self.code = tp[0][1]
except Exception:
self.code = "0"
if self.code != "100":
break
else:
tp.readUntil(r"(HTTP/[0-9.]+) ([0-9]+)")
self.code = int(self.code)
while True:
tp.readLine()
if tp.search("^([^:]+): ?(.*)$"):
self.addHeader(tp[0][0], tp[0][1])
else:
break
# curl sometimes sends two headers when using follow, 302 and the final header
# also when using proxies
tp.readLine()
if not tp.search(r"(HTTP/[0-9.]+) ([0-9]+)"):
break
else:
self._headers = []
# ignore CRLFs until request line
while tp.lastline == "" and tp.readLine():
pass
# TODO: this should be added to rawbody not directly to __content
if tp.lastFull_line:
self.addContent(tp.lastFull_line)
while tp.skip(1):
self.addContent(tp.lastFull_line)
if type == "curl":
self.delHeader("Transfer-Encoding")
if self.header_equal("Transfer-Encoding", "chunked"):
result = ""
content = BytesIO(rawbody)
hexa = content.readline()
nchunk = int(hexa.strip(), 16)
while nchunk:
result += content.read(nchunk)
content.readline()
hexa = content.readline()
nchunk = int(hexa.strip(), 16)
rawbody = result
if self.header_equal("Content-Encoding", "gzip"):
compressedstream = BytesIO(rawbody)
gzipper = gzip.GzipFile(fileobj=compressedstream)
rawbody = gzipper.read()
self.delHeader("Content-Encoding")
elif self.header_equal("Content-Encoding", "deflate"):
deflated_data = None
try:
deflater = zlib.decompressobj()
deflated_data = deflater.decompress(rawbody)
deflated_data += deflater.flush()
except zlib.error:
try:
deflater = zlib.decompressobj(-zlib.MAX_WBITS)
deflated_data = deflater.decompress(rawbody)
deflated_data += deflater.flush()
except zlib.error:
deflated_data = ""
rawbody = deflated_data
self.delHeader("Content-Encoding")
if rawbody is not None:
# Try to get charset encoding from headers
content_encoding = get_encoding_from_headers(dict(self.getHeaders()))
# fallback to default encoding
if content_encoding is None:
content_encoding = "utf-8"
self.__content = python2_3_convert_from_unicode(
rawbody.decode(content_encoding, errors="replace")
)
================================================
FILE: src/wfuzz/externals/reqresp/TextParser.py
================================================
# Covered by GPL V2.0
# Coded by Carlos del Ojo Elias (deepbit@gmail.com)
import sys
import re
# python 2 and 3: iterator
from builtins import object
class TextParser(object):
def __init__(self):
self.string = ""
self.oldindex = 0
self.newindex = 0
self.type = ""
self.lastFull_line = None
self.lastline = None
self.actualIndex = 0
def __del__(self):
if self.type == "file":
self.fd.close()
def __str__(self):
return str(self.matches)
def __iter__(self):
self.actualIndex = 0
return self
def __next__(self):
try:
value = self.matches[self.actualIndex]
self.actualIndex += 1
return value
except Exception:
raise StopIteration
def setSource(self, t, *args):
"""Se especifica el tipo de entrada. Puede ser fichero o entrada estandard
Ejemplos: setSource("file","/tmp/file")
setSource("stdin")\n"""
if t == "file":
self.type = t
self.fd = open(args[0], "r")
elif t == "stdin":
if self.type == "file":
self.fd.close()
self.type = t
elif t == "string":
if self.type == "file":
self.fd.close()
self.type = t
self.string = args[0]
self.oldindex = 0
self.newindex = 0
else:
print("Bad argument -- TextParser.setSource()\n")
sys.exit(-1)
def seekinit(self):
self.oldindex = 0
self.newindex = 0
def readUntil(self, pattern, caseSens=True):
"Lee lineas hasta que el patron (pattern) conincide en alguna linea"
while True:
if self.readLine() == 0:
return False
if self.search(pattern, caseSens) is True:
break
return True
def search(self, pattern, caseSens=True, debug=0):
"Intenta hacer Matching entre el pattern pasado por parametro y la ultima linea leida"
if not caseSens:
self.regexp = re.compile(pattern, re.IGNORECASE)
else:
self.regexp = re.compile(pattern)
self.matches = self.regexp.findall(self.lastline)
j = 0
for i in self.matches:
if not isinstance(i, tuple):
self.matches[j] = tuple([self.matches[j]])
j += 1
# DEBUG PARA MATCHING
if debug == 1:
print(("[", self.lastline, "-", pattern, "]"))
print((len(self.matches)))
print((self.matches))
if len(self.matches) == 0:
return False
else:
return True
def __getitem__(self, key):
"Para acceder a cada uno de los patrones que coinciden, esta preparado paragrupos de patrones, no para solo un patron"
return self.matches[key]
def skip(self, lines):
"Salta las lines que se indiquen en el parametro"
for i in range(lines):
if self.readLine() == 0:
return False
return True
def readLine(self):
"Lee la siguiente linea eliminando retornos de carro"
if self.type == "file":
self.lastFull_line = self.fd.readline()
elif self.type == "stdin":
self.lastFull_line = input()
elif self.type == "string":
if self.newindex == -1:
return 0
if self.oldindex >= 0:
self.newindex = self.string.find("\n", self.oldindex, len(self.string))
if self.newindex == -1:
self.newindex = len(self.string) - 1
self.lastFull_line = self.string[self.oldindex : self.newindex + 1]
self.oldindex = self.newindex + 1
else:
self.lastFull_line = ""
bytes_read = len(self.lastFull_line)
s = self.lastFull_line
self.lastline = s
if s[-2:] == "\r\n":
self.lastline = s[:-2]
elif s[-1:] == "\r" or s[-1:] == "\n":
self.lastline = s[:-1]
return bytes_read
================================================
FILE: src/wfuzz/externals/reqresp/Variables.py
================================================
from .TextParser import TextParser
import json
class Variable:
def __init__(self, name, value="", extraInfo=""):
self.name = name
self.value = value
self.initValue = value
self.extraInfo = extraInfo
def restore(self):
self.value = self.initValue
def change(self, newval):
self.initValue = self.value = newval
def update(self, val):
self.value = val
def append(self, val):
self.value += val
def __str__(self):
return "[ %s : %s ]" % (self.name, self.value)
class VariablesSet:
def __init__(self):
self.variables = []
self.boundary = None
def names(self):
dicc = []
for i in self.variables:
dicc.append(i.name)
return dicc
def existsVar(self, name):
return name in self.names()
def addVariable(self, name, value="", extraInfo=""):
self.variables.append(Variable(name, value, extraInfo))
def getVariable(self, name):
dicc = []
for i in self.variables:
if i.name == name:
dicc.append(i)
if len(dicc) > 1:
raise Exception("Variable exists more than one time!!! :D" % (name))
if not dicc:
var = Variable(name)
self.variables.append(var)
return var
return dicc[0]
def urlEncoded(self):
return "&".join(
[
"=".join([i.name, i.value]) if i.value is not None else i.name
for i in self.variables
]
)
def json_encoded(self):
dicc = {i.name: i.value for i in self.variables}
return json.dumps(dicc)
def parse_json_encoded(self, cad):
dicc = []
for key, value in json.loads(cad).items():
dicc.append(Variable(key, value))
self.variables = dicc
def parseUrlEncoded(self, cad):
dicc = []
if cad == "":
dicc.append(Variable("", None))
for i in cad.split("&"):
if i:
var_list = i.split("=", 1)
if len(var_list) == 1:
dicc.append(Variable(var_list[0], None))
elif len(var_list) == 2:
dicc.append(Variable(var_list[0], var_list[1]))
self.variables = dicc
def multipartEncoded(self):
if not self.boundary:
self.boundary = "---------------------------D33PB1T0R3QR3SP0B0UND4RY2203"
pd = ""
for i in self.variables:
pd += "--" + self.boundary + "\r\n"
pd += "%s\r\n\r\n%s\r\n" % ("\r\n".join(i.extraInfo), i.value)
pd += "--" + self.boundary + "--\r\n"
return pd
def parseMultipart(self, cad, boundary):
self.boundary = boundary
dicc = []
tp = TextParser()
tp.setSource("string", cad)
while True:
headers = []
if not tp.readUntil('name="([^"]+)"'):
break
var = tp[0][0]
headers.append(tp.lastFull_line.strip())
while True:
tp.readLine()
if tp.search("^([^:]+): (.*)$"):
headers.append(tp.lastFull_line.strip())
else:
break
value = ""
while True:
tp.readLine()
if not tp.search(boundary):
value += tp.lastFull_line
else:
break
if value[-2:] == "\r\n":
value = value[:-2]
dicc.append(Variable(var, value.strip(), headers))
self.variables = dicc
================================================
FILE: src/wfuzz/externals/reqresp/__init__.py
================================================
from .Request import Request
from .Response import Response
================================================
FILE: src/wfuzz/externals/reqresp/cache.py
================================================
from collections import defaultdict
class HttpCache:
def __init__(self):
# cache control
self.__cache_map = defaultdict(list)
def update_cache(self, req, category="default"):
key = req.to_cache_key()
# first hit
if key not in self.__cache_map:
self.__cache_map[key].append(category)
return True
elif key in self.__cache_map and category not in self.__cache_map[key]:
self.__cache_map[key].append(category)
return True
return False
def msg_in_cache(self, req, category="default"):
key = req.to_cache_key()
return key in self.__cache_map and category in self.__cache_map[key]
================================================
FILE: src/wfuzz/externals/reqresp/exceptions.py
================================================
class ReqRespException(Exception):
FATAL, RESOLVE_PROXY, RESOLVE_HOST, CONNECT_HOST, SSL, TIMEOUT = list(range(6))
def __init__(self, etype, msg):
self.etype = etype
self.msg = msg
Exception.__init__(self, msg)
================================================
FILE: src/wfuzz/externals/settings/__init__.py
================================================
================================================
FILE: src/wfuzz/externals/settings/settings.py
================================================
# Python 2 and 3 (after ``pip install configparser``):
try:
from configparser import ConfigParser
except ImportError:
import ConfigParser
import os
import sys
class SettingsBase:
"""
Contains application settings. uses a ConfigParser
"""
def __init__(self, save=False):
self.cparser = ConfigParser()
self.set_all(self.set_defaults())
self.filename = os.path.join(
self._path_to_program_dir(), self.get_config_file()
)
self.cparser.read(self.filename)
# Base members should implement
def get_config_file(self):
"""Returns the name of the file where the config is saved."""
raise NotImplementedError
def set_defaults(self):
"""
Returns a dictionary with the default settings in the form of
{ \
Section: [ \
("setting_x", '5'),
...
("setting_y", '5'),
],
...
}
"""
raise NotImplementedError
def has_option(self, section, setting):
return self.cparser.has_option(section, setting)
def set(self, section, setting, value):
self.cparser.set(section, setting, value)
def get(self, section, setting):
value = self.cparser.get(section, setting)
return value
def get_section(self, section):
return self.cparser.items(section)
def get_options(self, section):
return self.cparser.options(section)
def get_sections(self):
return self.cparser.sections()
def get_all(self):
sett = {}
# dump entire config file
for section in self.cparser.sections():
for option in self.cparser.options(section):
if section not in sett:
sett[section] = []
sett[section].append((option, self.cparser.get(section, option)))
return sett
def set_all(self, sett):
self.cparser = ConfigParser()
for section, settings in sett.items():
self.cparser.add_section(section)
for key, value in settings:
self.cparser.set(section, key, value)
def save(self):
try:
with open(self.filename, "w") as iniFile:
self.cparser.write(iniFile)
except Exception:
return False
return True
def _path_to_program_dir(self):
"""
Returns path to program directory
"""
path = sys.argv[0]
if not os.path.isdir(path):
path = os.path.dirname(path)
if not path:
return "."
return path
================================================
FILE: src/wfuzz/facade.py
================================================
from .helpers.file_func import get_home, get_path, get_config_dir
from .helpers.obj_factory import Singleton
from . import __version__ as version
from .externals.moduleman.registrant import MulRegistrant
from .externals.moduleman.loader import DirLoader
from .externals.settings.settings import SettingsBase
from .exception import FuzzExceptNoPluginError, FuzzExceptPluginLoadError
import os
ERROR_CODE = -1
BASELINE_CODE = -2
class Settings(SettingsBase):
def get_config_file(self):
config_file = "wfuzz.ini"
config = os.path.join(get_config_dir(check=False), config_file)
legacy_config = os.path.join(get_home(check=False), config_file)
if os.path.exists(config):
return config
elif os.path.exists(legacy_config):
return legacy_config
return os.path.join(get_config_dir(check=True), config_file)
def set_defaults(self):
return dict(
plugins=[("bing_apikey", ""), ("shodan_apikey", "")],
kbase=[
(
"discovery.blacklist",
".svg-.css-.js-.jpg-.gif-.png-.jpeg-.mov-.avi-.flv-.ico",
)
],
connection=[
("concurrent", "10"),
("conn_delay", "90"),
("req_delay", "90"),
("retries", "3"),
("User-Agent", "Wfuzz/%s" % version),
],
general=[
("default_printer", "raw"),
("cancel_on_plugin_except", "0"),
("concurrent_plugins", "3"),
("lookup_dirs", "."),
("encode_space", "1"),
],
)
class MyRegistrant(MulRegistrant):
def get_plugin(self, identifier):
try:
return MulRegistrant.get_plugin(self, identifier)
except KeyError as e:
raise FuzzExceptNoPluginError(
"Requested plugin %s. Error: %s" % (identifier, str(e))
)
class Facade(metaclass=Singleton):
def __init__(self):
self.__plugins = dict(
printers=None, scripts=None, encoders=None, iterators=None, payloads=None,
)
self.sett = Settings()
def _load(self, cat):
try:
if cat not in self.__plugins:
raise FuzzExceptNoPluginError("Non-existent plugin category %s" % cat)
if not self.__plugins[cat]:
loader_list = []
loader_list.append(
DirLoader(**{"base_dir": cat, "base_path": get_path("../plugins")})
)
loader_list.append(
DirLoader(**{"base_dir": cat, "base_path": get_home()})
)
self.__plugins[cat] = MyRegistrant(loader_list)
return self.__plugins[cat]
except Exception as e:
raise FuzzExceptPluginLoadError("Error loading plugins: %s" % str(e))
def proxy(self, which):
return self._load(which)
def get_registrants(self):
return self.__plugins.keys()
def __getattr__(self, name):
if name in ["printers", "payloads", "iterators", "encoders", "scripts"]:
return self._load(name)
else:
raise AttributeError
================================================
FILE: src/wfuzz/factories/__init__.py
================================================
================================================
FILE: src/wfuzz/factories/dictfactory.py
================================================
# Python 2 and 3: zip_longest
try:
from itertools import zip_longest
except ImportError:
from itertools import izip_longest as zip_longest
from ..helpers.obj_factory import ObjectFactory
from ..exception import FuzzExceptBadOptions
from ..facade import Facade
from ..dictionaries import (
TupleIt,
WrapperIt,
SliceIt,
EncodeIt,
AllVarDictio,
)
class DictionaryFactory(ObjectFactory):
def __init__(self):
ObjectFactory.__init__(
self,
{
"dictio_from_iterable": DictioFromIterableBuilder(),
"dictio_from_payload": DictioFromPayloadBuilder(),
"dictio_from_allvar": DictioFromAllVarBuilder(),
"dictio_from_options": DictioFromOptions(),
},
)
class BaseDictioBuilder:
@staticmethod
def validate(options, selected_dic):
if not selected_dic:
raise FuzzExceptBadOptions("Empty dictionary! Check payload and filter")
if len(selected_dic) == 1 and options["iterator"]:
raise FuzzExceptBadOptions(
"Several dictionaries must be used when specifying an iterator"
)
@staticmethod
def get_dictio(options, selected_dic):
if len(selected_dic) == 1:
return TupleIt(selected_dic[0])
elif options["iterator"]:
return Facade().iterators.get_plugin(options["iterator"])(*selected_dic)
else:
return Facade().iterators.get_plugin("product")(*selected_dic)
class DictioFromIterableBuilder(BaseDictioBuilder):
def __call__(self, options):
selected_dic = []
self._payload_list = []
for d in [WrapperIt(x) for x in options["dictio"]]:
selected_dic.append(d)
self.validate(options, selected_dic)
return self.get_dictio(options, selected_dic)
class DictioFromPayloadBuilder(BaseDictioBuilder):
def __call__(self, options):
selected_dic = []
for payload in options["payloads"]:
try:
name, params, slicestr = [
x[0] for x in zip_longest(payload, (None, None, None))
]
except ValueError:
raise FuzzExceptBadOptions(
"You must supply a list of payloads in the form of [(name, {params}), ... ]"
)
if not params:
raise FuzzExceptBadOptions(
"You must supply a list of payloads in the form of [(name, {params}), ... ]"
)
dictionary = Facade().payloads.get_plugin(name)(params)
if "encoder" in params and params["encoder"] is not None:
dictionary = EncodeIt(dictionary, params["encoder"])
selected_dic.append(
SliceIt(dictionary, slicestr) if slicestr else dictionary
)
self.validate(options, selected_dic)
return self.get_dictio(options, selected_dic)
class DictioFromAllVarBuilder(BaseDictioBuilder):
@staticmethod
def from_all_fuzz_request_gen(options, dictio_list):
for payload in dictio_list:
if len(payload) > 1:
raise FuzzExceptBadOptions(
"Only one payload is allowed when fuzzing all parameters!"
)
for var_name in options["compiled_seed"].history.wf_allvars_set.keys():
yield (var_name, payload[0])
def __call__(self, options):
dictio_list = DictioFromOptions()(options)
return AllVarDictio(
self.from_all_fuzz_request_gen(options, dictio_list),
dictio_list.count() * len(options["compiled_seed"].history.wf_allvars_set),
)
class DictioFromOptions(BaseDictioBuilder):
def __call__(self, options):
if options["dictio"]:
return DictioFromIterableBuilder()(options)
else:
return DictioFromPayloadBuilder()(options)
dictionary_factory = DictionaryFactory()
================================================
FILE: src/wfuzz/factories/fuzzfactory.py
================================================
from ..fuzzrequest import FuzzRequest
from ..helpers.obj_factory import ObjectFactory, SeedBuilderHelper
class FuzzRequestFactory(ObjectFactory):
def __init__(self):
ObjectFactory.__init__(
self,
{
"request_from_options": RequestBuilder(),
"seed_from_options": SeedBuilder(),
},
)
class RequestBuilder:
def __call__(self, options):
fr = FuzzRequest()
fr.url = options["url"]
fr.wf_fuzz_methods = options["method"]
fr.update_from_options(options)
return fr
class SeedBuilder:
def __call__(self, options):
seed = reqfactory.create("request_from_options", options)
marker_dict = SeedBuilderHelper.get_marker_dict(seed)
SeedBuilderHelper.remove_baseline_markers(seed, marker_dict)
return seed
reqfactory = FuzzRequestFactory()
================================================
FILE: src/wfuzz/factories/fuzzresfactory.py
================================================
import copy
from .fuzzfactory import reqfactory
from .payman import payman_factory
from ..fuzzobjects import FuzzResult, FuzzType, FuzzWord, FuzzWordType
from ..helpers.obj_factory import ObjectFactory, SeedBuilderHelper
class FuzzResultFactory(ObjectFactory):
def __init__(self):
ObjectFactory.__init__(
self,
{
"fuzzres_from_options_and_dict": FuzzResultDictioBuilder(),
"fuzzres_from_allvar": FuzzResultAllVarBuilder(),
"fuzzres_from_recursion": FuzzResRecursiveBuilder(),
"seed_from_recursion": SeedRecursiveBuilder(),
"seed_from_options": SeedResultBuilder(),
"seed_from_options_and_dict": FuzzResultDictSeedBuilder(),
"baseline_from_options": BaselineResultBuilder(),
},
)
class FuzzResultDictioBuilder:
def __call__(self, options, dictio_item):
res = copy.deepcopy(options["compiled_seed"])
res.item_type = FuzzType.RESULT
res.discarded = False
res.payload_man.update_from_dictio(dictio_item)
res.update_from_options(options)
SeedBuilderHelper.replace_markers(res.history, res.payload_man)
res.nres = next(FuzzResult.newid)
return res
class SeedResultBuilder:
def __call__(self, options):
seed = reqfactory.create("seed_from_options", options)
res = FuzzResult(seed)
res.payload_man = payman_factory.create("payloadman_from_request", seed)
return res
class BaselineResultBuilder:
def __call__(self, options):
raw_seed = reqfactory.create("request_from_options", options)
baseline_payloadman = payman_factory.create(
"payloadman_from_baseline", raw_seed
)
if baseline_payloadman.payloads:
res = FuzzResult(raw_seed)
res.payload_man = baseline_payloadman
res.update_from_options(options)
res.is_baseline = True
SeedBuilderHelper.replace_markers(raw_seed, baseline_payloadman)
return res
else:
return None
class FuzzResultAllVarBuilder:
def __call__(self, options, var_name, payload):
fuzzres = copy.deepcopy(options["compiled_seed"])
fuzzres.item_type = FuzzType.RESULT
fuzzres.discarded = False
fuzzres.payload_man = payman_factory.create("empty_payloadman", payload)
fuzzres.payload_man.update_from_dictio([payload])
fuzzres.history.wf_allvars_set = {var_name: payload.content}
fuzzres.nres = next(FuzzResult.newid)
return fuzzres
class FuzzResultDictSeedBuilder:
def __call__(self, options, dictio):
fuzzres = copy.deepcopy(dictio[0].content)
fuzzres.history.update_from_options(options)
fuzzres.update_from_options(options)
fuzzres.payload_man = payman_factory.create("empty_payloadman", dictio[0])
fuzzres.payload_man.update_from_dictio(dictio)
return fuzzres
class SeedRecursiveBuilder:
def __call__(self, seed):
new_seed = copy.deepcopy(seed)
new_seed.history.url = seed.history.recursive_url + "FUZZ"
new_seed.rlevel += 1
if new_seed.rlevel_desc:
new_seed.rlevel_desc += " - "
new_seed.rlevel_desc += seed.payload_man.description()
new_seed.item_type = FuzzType.SEED
new_seed.discarded = False
new_seed.payload_man = payman_factory.create(
"payloadman_from_request", new_seed.history
)
return new_seed
class FuzzResRecursiveBuilder:
def __call__(self, seed, url):
fr = copy.deepcopy(seed)
fr.history.url = str(url)
fr.rlevel = seed.rlevel + 1
if fr.rlevel_desc:
fr.rlevel_desc += " - "
fr.rlevel_desc += seed.payload_man.description()
fr.item_type = FuzzType.BACKFEED
fr.discarded = False
fr.is_baseline = False
fr.payload_man = payman_factory.create(
"empty_payloadman", FuzzWord(url, FuzzWordType.WORD)
)
return fr
resfactory = FuzzResultFactory()
================================================
FILE: src/wfuzz/factories/payman.py
================================================
from ..fuzzobjects import FPayloadManager, FuzzWord, FuzzWordType
from ..helpers.obj_factory import ObjectFactory, SeedBuilderHelper
class PayManFactory(ObjectFactory):
def __init__(self):
ObjectFactory.__init__(
self,
{
"payloadman_from_baseline": BaselinePayloadManBuilder(),
"payloadman_from_request": FuzzReqPayloadManBuilder(),
"empty_payloadman": OnePayloadManBuilder(),
},
)
class FuzzReqPayloadManBuilder:
def __call__(self, freq):
fpm = FPayloadManager()
for pdict in [
pdict
for pdict in SeedBuilderHelper.get_marker_dict(freq)
if pdict["word"] is not None
]:
fpm.add(pdict)
return fpm
class OnePayloadManBuilder:
def __call__(self, content):
fpm = FPayloadManager()
fpm.add(
{"full_marker": None, "word": None, "index": None, "field": None}, content
)
return fpm
class BaselinePayloadManBuilder:
def __call__(self, freq):
fpm = FPayloadManager()
for pdict in [
pdict
for pdict in SeedBuilderHelper.get_marker_dict(freq)
if pdict["bl_value"] is not None
]:
fpm.add(pdict, FuzzWord(pdict["bl_value"], FuzzWordType.WORD), True)
return fpm
payman_factory = PayManFactory()
================================================
FILE: src/wfuzz/factories/plugin_factory.py
================================================
from ..helpers.obj_factory import ObjectFactory
from ..fuzzobjects import FuzzPlugin, FuzzError
from ..factories.fuzzresfactory import resfactory
class PluginFactory(ObjectFactory):
def __init__(self):
ObjectFactory.__init__(
self,
{
"plugin_from_recursion": PluginRecursiveBuilder(),
"plugin_from_error": PluginErrorBuilder(),
"plugin_from_finding": PluginFindingBuilder(),
"plugin_from_summary": PluginFindingSummaryBuilder(),
},
)
class PluginRecursiveBuilder:
def __call__(self, name, seed, url):
plugin = FuzzPlugin()
plugin.source = name
plugin._exception = None
plugin._seed = resfactory.create("fuzzres_from_recursion", seed, url)
return plugin
class PluginErrorBuilder:
def __call__(self, name, exception):
plugin = FuzzPlugin()
plugin.source = name
plugin.issue = "Exception within plugin %s: %s" % (name, str(exception))
plugin._exception = FuzzError(exception)
plugin._seed = None
return plugin
class PluginFindingBuilder:
def __call__(self, name, itype, message, data, severity):
plugin = FuzzPlugin()
plugin.source = name
plugin.issue = message
plugin.itype = itype
plugin.data = data
plugin._exception = None
plugin._seed = None
plugin.severity = severity
return plugin
class PluginFindingSummaryBuilder:
def __call__(self, message):
plugin = FuzzPlugin()
plugin.source = FuzzPlugin.OUTPUT_SOURCE
plugin.itype = FuzzPlugin.SUMMARY_ITYPE
plugin.severity = FuzzPlugin.NONE
plugin._exception = None
plugin.data = None
plugin._seed = None
plugin.issue = message
return plugin
plugin_factory = PluginFactory()
================================================
FILE: src/wfuzz/factories/reqresp_factory.py
================================================
import abc
import pycurl
from ..helpers.obj_factory import HttpRequestFactory
from ..helpers.str_func import (
python2_3_convert_to_unicode,
python2_3_convert_from_unicode,
)
from ..externals.reqresp import Response
PYCURL_PATH_AS_IS = True
if not hasattr(pycurl, "PATH_AS_IS"):
PYCURL_PATH_AS_IS = False
class ReqRespRequestFactory(HttpRequestFactory):
def to_http_object(options, req, pycurl_c):
pycurl_c.setopt(pycurl.MAXREDIRS, 5)
pycurl_c.setopt(pycurl.WRITEFUNCTION, req._request.body_callback)
pycurl_c.setopt(pycurl.HEADERFUNCTION, req._request.header_callback)
pycurl_c.setopt(pycurl.NOSIGNAL, 1)
pycurl_c.setopt(pycurl.SSL_VERIFYPEER, False)
pycurl_c.setopt(pycurl.SSL_VERIFYHOST, 0)
if PYCURL_PATH_AS_IS:
pycurl_c.setopt(pycurl.PATH_AS_IS, 1)
pycurl_c.setopt(
pycurl.URL, python2_3_convert_to_unicode(req._request.completeUrl)
)
if req._request.getConnTimeout():
pycurl_c.setopt(pycurl.CONNECTTIMEOUT, req._request.getConnTimeout())
if req._request.getTotalTimeout():
pycurl_c.setopt(pycurl.TIMEOUT, req._request.getTotalTimeout())
authMethod, userpass = req._request.getAuth()
if authMethod or userpass:
if authMethod == "basic":
pycurl_c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)
elif authMethod == "ntlm":
pycurl_c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_NTLM)
elif authMethod == "digest":
pycurl_c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)
pycurl_c.setopt(pycurl.USERPWD, python2_3_convert_to_unicode(userpass))
else:
pycurl_c.unsetopt(pycurl.USERPWD)
pycurl_c.setopt(
pycurl.HTTPHEADER, python2_3_convert_to_unicode(req._request.getHeaders())
)
curl_options = {
"GET": pycurl.HTTPGET,
"POST": pycurl.POST,
"PATCH": pycurl.UPLOAD,
"HEAD": pycurl.NOBODY,
}
for verb in curl_options.values():
pycurl_c.setopt(verb, False)
if req._request.method in curl_options:
pycurl_c.unsetopt(pycurl.CUSTOMREQUEST)
pycurl_c.setopt(curl_options[req._request.method], True)
else:
pycurl_c.setopt(pycurl.CUSTOMREQUEST, req._request.method)
if req._request._non_parsed_post is not None:
pycurl_c.setopt(
pycurl.POSTFIELDS,
python2_3_convert_to_unicode(req._request._non_parsed_post),
)
pycurl_c.setopt(pycurl.FOLLOWLOCATION, 1 if req._request.followLocation else 0)
# proxy = req._request.getProxy()
# if proxy is not None:
# pycurl_c.setopt(pycurl.PROXY, python2_3_convert_to_unicode(proxy))
# if req._request.proxytype == "SOCKS5":
# pycurl_c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5)
# elif req._request.proxytype == "SOCKS4":
# pycurl_c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS4)
# req._request.delHeader("Proxy-Connection")
# else:
# pycurl_c.setopt(pycurl.PROXY, "")
if req.wf_ip:
pycurl_c.setopt(
pycurl.CONNECT_TO,
["::{}:{}".format(req.wf_ip["ip"], req.wf_ip["port"])],
)
return pycurl_c
def from_http_object(options, req, pycurl_c, header, body):
raw_header = python2_3_convert_from_unicode(
header.decode("utf-8", errors="surrogateescape")
)
if pycurl_c.getinfo(pycurl.EFFECTIVE_URL) != req._request.completeUrl:
req._request.setFinalUrl(pycurl_c.getinfo(pycurl.EFFECTIVE_URL))
req._request.totaltime = pycurl_c.getinfo(pycurl.TOTAL_TIME)
req._request.response = Response()
req._request.response.parseResponse(raw_header, rawbody=body)
return req._request.response
================================================
FILE: src/wfuzz/filters/__init__.py
================================================
================================================
FILE: src/wfuzz/filters/ppfilter.py
================================================
from ..exception import FuzzExceptIncorrectFilter, FuzzExceptBadOptions
from ..helpers.obj_dyn import (
rgetattr,
rsetattr,
)
from ..helpers.str_func import value_in_any_list_item
from ..helpers.obj_dic import DotDict
from ..helpers.utils import diff
import re
import collections
import operator
# Python 2 and 3: alternative 4
try:
from urllib.parse import unquote
except ImportError:
from urllib import unquote
from ..facade import Facade, ERROR_CODE
PYPARSING = True
try:
from pyparsing import (
Word,
Group,
oneOf,
Optional,
Suppress,
ZeroOrMore,
Literal,
QuotedString,
ParseException,
Regex,
)
except ImportError:
PYPARSING = False
class FuzzResFilter:
FUZZ_MARKER_REGEX = re.compile(r"FUZ\d*Z", re.MULTILINE | re.DOTALL)
def __init__(self, filter_string=None):
self.filter_string = filter_string
self.baseline = None
quoted_str_value = QuotedString("'", unquoteResults=True, escChar="\\")
int_values = Word("0123456789").setParseAction(lambda s, l, t: [int(t[0])])
error_value = Literal("XXX").setParseAction(self.__compute_xxx_value)
operator_call = Regex(
r"\|(?P(m|d|e|un|u|r|l|sw|gre|gregex|unique|startswith|decode|encode|unquote|replace|lower|upper))"
r"\((?:(?P('.*?'|\d+))(?:,(?P('.*?'|\d+)))?)?\)",
asMatch=True,
).setParseAction(lambda s, l, t: [(l, t[0])])
fuzz_symbol = Regex(
r"FUZ(?P\d)*Z(?:\[(?P(\w|_|-|\.)+)\])?", asMatch=True
).setParseAction(self._compute_fuzz_symbol)
res_symbol = Regex(
r"(description|nres|code|chars|lines|words|md5|content|timer|url|l|w|c|(r|history|plugins)(\w|_|-|\.)*|h)"
).setParseAction(self._compute_res_symbol)
bbb_symbol = Regex(
r"BBB(?:\[(?P(\w|_|-|\.)+)\])?", asMatch=True
).setParseAction(self.__compute_bbb_symbol)
diff_call = Group(
Suppress(Literal("|"))
+ Literal("diff")
+ Suppress(Literal("("))
+ (fuzz_symbol | res_symbol | bbb_symbol | int_values | quoted_str_value)
+ Suppress(")")
)
fuzz_statement = Group(
(fuzz_symbol | res_symbol | bbb_symbol | int_values | quoted_str_value)
+ Optional(diff_call | operator_call, None)
).setParseAction(self.__compute_res_value)
operator = oneOf("and or")
not_operator = Optional(oneOf("not"), "notpresent")
symbol_expr = Group(
fuzz_statement
+ oneOf("= == != < > >= <= =~ !~ ~ := =+ =-")
+ (error_value | fuzz_statement)
).setParseAction(self.__compute_expr)
definition = symbol_expr ^ fuzz_statement
definition_not = not_operator + definition
definition_expr = definition_not + ZeroOrMore(operator + definition_not)
nested_definition = Group(Suppress("(") + definition_expr + Suppress(")"))
nested_definition_not = not_operator + nested_definition
self.finalformula = (nested_definition_not | definition_expr) + ZeroOrMore(
operator + (nested_definition_not | definition_expr)
)
definition_not.setParseAction(self.__compute_not_operator)
nested_definition_not.setParseAction(self.__compute_not_operator)
nested_definition.setParseAction(self.__compute_formula)
self.finalformula.setParseAction(self.__myreduce)
self.res = None
self.stack = []
self._cache = collections.defaultdict(set)
def set_baseline(self, res):
self.baseline = res
def _compute_res_symbol(self, tokens):
return self._get_field_value(self.res, tokens[0])
def _compute_fuzz_symbol(self, tokens):
match_dict = tokens[0].groupdict()
p_index = int(match_dict["index"]) if match_dict["index"] is not None else 1
fuzz_val = None
try:
fuzz_val = self.res.payload_man.get_payload_content(p_index)
except IndexError:
raise FuzzExceptIncorrectFilter(
"Non existent FUZZ payload! Use a correct index."
)
if match_dict["field"]:
fuzz_val = self._get_field_value(fuzz_val, match_dict["field"])
return fuzz_val
def __compute_res_value(self, tokens):
fuzz_val, token_tuple = tokens[0]
if token_tuple:
location, operator_match = token_tuple
if location == "diff":
return diff(operator_match, fuzz_val)
else:
if operator_match and operator_match.groupdict()["operator"]:
fuzz_val = self._get_operator_value(
location, fuzz_val, operator_match.groupdict()
)
if isinstance(fuzz_val, list):
return [fuzz_val]
return fuzz_val
def _get_payload_value(self, p_index):
try:
return self.res.payload_man.get_payload_content(p_index)
except IndexError:
raise FuzzExceptIncorrectFilter(
"Non existent FUZZ payload! Use a correct index."
)
def _get_field_value(self, fuzz_val, field):
self.stack.append(field)
try:
ret = rgetattr(fuzz_val, field)
except IndexError:
raise FuzzExceptIncorrectFilter(
"Non existent FUZZ payload! Use a correct index."
)
except AttributeError as e:
raise FuzzExceptIncorrectFilter(
"Attribute {} not found in fuzzresult or using a string payload. {}".format(
field, str(e)
)
)
if isinstance(ret, list):
return [ret]
return ret
def __compute_bbb_symbol(self, tokens):
if self.baseline is None:
raise FuzzExceptBadOptions(
"FilterQ: specify a baseline value when using BBB"
)
match_dict = tokens[0].groupdict()
ret = None
if match_dict["field"]:
ret = self._get_field_value(self.baseline, match_dict["field"])
else:
element = self.stack.pop() if self.stack else None
if element == "l" or element == "lines":
ret = self.baseline.lines
elif element == "c" or element == "code":
ret = self.baseline.code
elif element == "w" or element == "words":
ret = self.baseline.words
elif element == "h" or element == "chars":
return self.baseline.chars
elif element == "index" or element == "i":
ret = self.baseline.nres
else:
ret = self.baseline.payload_man.get_payload_content(1)
return ret
def _get_operator_value(self, location, fuzz_val, match_dict):
op = match_dict["operator"]
param1 = match_dict["param1"]
param2 = match_dict["param2"]
if param1:
param1 = param1.strip("'")
if param2:
param2 = param2.strip("'")
if (op == "un" or op == "unquote") and param1 is None and param2 is None:
ret = unquote(fuzz_val)
elif (op == "e" or op == "encode") and param1 is not None and param2 is None:
ret = Facade().encoders.get_plugin(param1)().encode(fuzz_val)
elif (op == "d" or op == "decode") and param1 is not None and param2 is None:
ret = Facade().encoders.get_plugin(param1)().decode(fuzz_val)
elif op == "r" or op == "replace":
return fuzz_val.replace(param1, param2)
elif op == "upper":
return fuzz_val.upper()
elif op == "lower" or op == "l":
return fuzz_val.lower()
elif op == "gregex" or op == "gre":
search_res = None
try:
regex = re.compile(param1)
search_res = regex.search(fuzz_val)
except re.error as e:
raise FuzzExceptBadOptions(
"Invalid regex expression used in expression: %s" % str(e)
)
if search_res is None:
return ""
return search_res.group(1)
elif op == "startswith" or op == "sw":
return fuzz_val.strip().startswith(param1)
elif op == "unique" or op == "u":
if fuzz_val not in self._cache[location]:
self._cache[location].add(fuzz_val)
return True
else:
return False
else:
raise FuzzExceptBadOptions(
"Bad format, expression should be m,d,e,r,s(value,value)"
)
return ret
def __compute_xxx_value(self, tokens):
return ERROR_CODE
def __compute_expr(self, tokens):
leftvalue, exp_operator, rightvalue = tokens[0]
# a bit hacky but we don't care about fields in the right hand side of the expression
if len(self.stack) > 1:
self.stack.pop()
field_to_set = self.stack.pop() if self.stack else None
try:
if exp_operator in ["=", "=="]:
return str(leftvalue) == str(rightvalue)
elif exp_operator == "<=":
return int(leftvalue) <= int(rightvalue)
elif exp_operator == ">=":
return int(leftvalue) >= int(rightvalue)
elif exp_operator == "<":
return int(leftvalue) < int(rightvalue)
elif exp_operator == ">":
return int(leftvalue) > int(rightvalue)
elif exp_operator == "!=":
return leftvalue != rightvalue
elif exp_operator == "=~":
regex = re.compile(rightvalue, re.MULTILINE | re.DOTALL)
return regex.search(leftvalue) is not None
elif exp_operator in ["!~", "~"]:
ret = True
if isinstance(leftvalue, str):
ret = rightvalue.lower() in leftvalue.lower()
elif isinstance(leftvalue, list):
ret = value_in_any_list_item(rightvalue, leftvalue)
elif isinstance(leftvalue, dict) or isinstance(leftvalue, DotDict):
ret = rightvalue.lower() in str(leftvalue).lower()
else:
raise FuzzExceptBadOptions(
"Invalid operand type {}".format(rightvalue)
)
return ret if exp_operator == "~" else not ret
elif exp_operator == ":=":
rsetattr(self.res, field_to_set, rightvalue, None)
elif exp_operator == "=+":
rsetattr(self.res, field_to_set, rightvalue, operator.add)
elif exp_operator == "=-":
if isinstance(rightvalue, str):
rsetattr(self.res, field_to_set, rightvalue, lambda x, y: y + x)
else:
rsetattr(self.res, field_to_set, rightvalue, operator.sub)
except re.error as e:
raise FuzzExceptBadOptions(
"Invalid regex expression used in expression: %s" % str(e)
)
except TypeError as e:
raise FuzzExceptBadOptions(
"Invalid operand types used in expression: %s" % str(e)
)
except ParseException as e:
raise FuzzExceptBadOptions("Invalid filter: %s" % str(e))
return True
def __myreduce(self, elements):
first = elements[0]
for i in range(1, len(elements), 2):
if elements[i] == "and":
first = first and elements[i + 1]
elif elements[i] == "or":
first = first or elements[i + 1]
self.stack = []
if isinstance(first, list):
return [first]
return first
def __compute_not_operator(self, tokens):
operator, value = tokens
if operator == "not":
return not value
if isinstance(value, list):
return [value]
return value
def __compute_formula(self, tokens):
return self.__myreduce(tokens[0])
def is_active(self):
return self.filter_string
def is_visible(self, res, filter_string=None):
if filter_string is None:
filter_string = self.filter_string
self.res = res
try:
return self.finalformula.parseString(filter_string, parseAll=True)[0]
except ParseException as e:
raise FuzzExceptIncorrectFilter(
"Incorrect filter expression, check documentation. {}".format(str(e))
)
except AttributeError as e:
raise FuzzExceptIncorrectFilter(
"It is only possible to use advanced filters when using a non-string payload. %s"
% str(e)
)
def get_fuzz_words(self):
fuzz_words = self.FUZZ_MARKER_REGEX.findall(self.filter_string)
return fuzz_words
class FuzzResFilterSlice(FuzzResFilter):
# When using slice we don't have previous payload context but directly a word from the dictionary
def _compute_fuzz_symbol(self, tokens):
match_dict = tokens[0].groupdict()
p_index = match_dict["index"] if match_dict["index"] is not None else 1
if p_index != 1:
raise FuzzExceptIncorrectFilter(
"Non existent FUZZ payload! Use a correct index."
)
fuzz_val = self.res
if match_dict["field"]:
fuzz_val = self._get_field_value(self.res, match_dict["field"])
return fuzz_val
================================================
FILE: src/wfuzz/filters/simplefilter.py
================================================
from ..exception import FuzzExceptBadOptions
import re
import collections
from ..facade import BASELINE_CODE
class FuzzResSimpleFilter:
def __init__(self, ffilter=None):
self.hideparams = dict(
regex_show=None,
codes_show=None,
codes=[],
words=[],
lines=[],
chars=[],
regex=None,
)
if ffilter is not None:
self.hideparams = ffilter
self.stack = []
self._cache = collections.defaultdict(set)
def is_active(self):
return any(
[
self.hideparams["regex_show"] is not None,
self.hideparams["codes_show"] is not None,
]
)
def set_baseline(self, res):
if BASELINE_CODE in self.hideparams["lines"]:
self.hideparams["lines"].append(res.lines)
if BASELINE_CODE in self.hideparams["codes"]:
self.hideparams["codes"].append(res.code)
if BASELINE_CODE in self.hideparams["words"]:
self.hideparams["words"].append(res.words)
if BASELINE_CODE in self.hideparams["chars"]:
self.hideparams["chars"].append(res.chars)
def is_visible(self, res):
if self.hideparams["codes_show"] is None:
cond1 = True
else:
cond1 = not self.hideparams["codes_show"]
if self.hideparams["regex_show"] is None:
cond2 = True
else:
cond2 = not self.hideparams["regex_show"]
if (
res.code in self.hideparams["codes"]
or res.lines in self.hideparams["lines"]
or res.words in self.hideparams["words"]
or res.chars in self.hideparams["chars"]
):
cond1 = self.hideparams["codes_show"]
if self.hideparams["regex"]:
if self.hideparams["regex"].search(res.history.content):
cond2 = self.hideparams["regex_show"]
return cond1 and cond2
@staticmethod
def from_options(filter_options):
ffilter = FuzzResSimpleFilter()
try:
if filter_options["ss"] is not None:
ffilter.hideparams["regex_show"] = True
ffilter.hideparams["regex"] = re.compile(
filter_options["ss"], re.MULTILINE | re.DOTALL
)
elif filter_options["hs"] is not None:
ffilter.hideparams["regex_show"] = False
ffilter.hideparams["regex"] = re.compile(
filter_options["hs"], re.MULTILINE | re.DOTALL
)
except Exception as e:
raise FuzzExceptBadOptions(
"Invalid regex expression used in filter: %s" % str(e)
)
if [x for x in ["sc", "sw", "sh", "sl"] if len(filter_options[x]) > 0]:
ffilter.hideparams["codes_show"] = True
ffilter.hideparams["codes"] = filter_options["sc"]
ffilter.hideparams["words"] = filter_options["sw"]
ffilter.hideparams["lines"] = filter_options["sl"]
ffilter.hideparams["chars"] = filter_options["sh"]
elif [x for x in ["hc", "hw", "hh", "hl"] if len(filter_options[x]) > 0]:
ffilter.hideparams["codes_show"] = False
ffilter.hideparams["codes"] = filter_options["hc"]
ffilter.hideparams["words"] = filter_options["hw"]
ffilter.hideparams["lines"] = filter_options["hl"]
ffilter.hideparams["chars"] = filter_options["hh"]
return ffilter
================================================
FILE: src/wfuzz/fuzzobjects.py
================================================
import time
import hashlib
import re
import itertools
from enum import Enum
from threading import Lock
from collections import defaultdict, namedtuple
from .filters.ppfilter import FuzzResFilter
from .facade import ERROR_CODE
from .helpers.str_func import python2_3_convert_to_unicode
from .helpers.obj_dyn import rgetattr
from .helpers.utils import MyCounter
from .helpers.obj_dic import DotDict
FuzzWord = namedtuple("FuzzWord", ["content", "type"])
class FuzzWordType(Enum):
WORD, FUZZRES = range(2)
class FuzzType(Enum):
(SEED, BACKFEED, RESULT, ERROR, STARTSEED, ENDSEED, CANCEL, PLUGIN,) = range(8)
class FuzzItem(object):
newid = itertools.count(0)
def __init__(self, item_type):
self.item_id = next(FuzzItem.newid)
self.item_type = item_type
self.rlevel = 1
self.discarded = False
def __str__(self):
return "FuzzItem, type: {}".format(self.item_type.name)
def __lt__(self, other):
return self.item_id < other.item_id
def __le__(self, other):
return self.item_id <= other.item_id
def __gt__(self, other):
return self.item_id > other.item_id
def __ge__(self, other):
return self.item_id >= other.item_id
def __eq__(self, other):
return self.item_id == other.item_id
def __ne__(self, other):
return self.item_id != other.item_id
class FuzzStats:
def __init__(self):
self.mutex = Lock()
self.url = ""
self.seed = None
self.total_req = 0
self.pending_fuzz = MyCounter()
self.pending_seeds = MyCounter()
self.processed = MyCounter()
self.backfeed = MyCounter()
self.filtered = MyCounter()
self.totaltime = 0
self.__starttime = 0
self._cancelled = False
@staticmethod
def from_options(options):
tmp_stats = FuzzStats()
tmp_stats.url = options["compiled_seed"].history.redirect_url
tmp_stats.total_req = options["compiled_dictio"].count()
tmp_stats.seed = options["compiled_seed"]
return tmp_stats
def get_stats(self):
return {
"url": self.url,
"total": self.total_req,
"backfed": self.backfeed(),
"processed": self.processed(),
"pending": self.pending_fuzz(),
"filtered": self.filtered(),
"pending_seeds": self.pending_seeds(),
"totaltime": time.time() - self.__starttime,
}
def mark_start(self):
with self.mutex:
self.__starttime = time.time()
def mark_end(self):
with self.mutex:
self.totaltime = time.time() - self.__starttime
@property
def cancelled(self):
with self.mutex:
return self._cancelled
@cancelled.setter
def cancelled(self, v):
with self.mutex:
self._cancelled = v
def __str__(self):
string = ""
string += "Total time: %s\n" % str(self.totaltime)[:8]
if self.backfeed() > 0:
string += "Processed Requests: %s (%d + %d)\n" % (
str(self.processed())[:8],
(self.processed() - self.backfeed()),
self.backfeed(),
)
else:
string += "Processed Requests: %s\n" % (str(self.processed())[:8])
string += "Filtered Requests: %s\n" % (str(self.filtered())[:8])
string += (
"Requests/sec.: %s\n"
% str(self.processed() / self.totaltime if self.totaltime > 0 else 0)[:8]
)
return string
def update(self, fuzzstats2):
self.url = fuzzstats2.url
self.total_req += fuzzstats2.total_req
self.totaltime += fuzzstats2.totaltime
self.backfeed._operation(fuzzstats2.backfeed())
self.processed._operation(fuzzstats2.processed())
self.pending_fuzz._operation(fuzzstats2.pending_fuzz())
self.filtered._operation(fuzzstats2.filtered())
self.pending_seeds._operation(fuzzstats2.pending_seeds())
class FuzzPayload:
def __init__(self):
self.marker = None
self.word = None
self.index = None
self.field = None
self.content = None
self.is_baseline = False
self.type = None
@property
def value(self):
if self.content is None:
return None
return (
self.content
if self.field is None
else str(rgetattr(self.content, self.field))
)
def description(self):
if self.is_baseline:
return self.content
if self.marker is None:
return ""
# return default value
if self.field is None and isinstance(self.content, FuzzResult):
return self.content.url
elif self.field is not None and isinstance(self.content, FuzzResult):
return str(rgetattr(self.content, self.field))
return self.value
def __str__(self):
return "type: {} index: {} marker: {} content: {} field: {} value: {}".format(
self.type,
self.index,
self.marker,
self.content.__class__,
self.field,
self.value,
)
class FPayloadManager:
def __init__(self):
self.payloads = defaultdict(list)
def add(self, payload_dict, fuzzword=None, is_baseline=False):
fp = FuzzPayload()
fp.marker = payload_dict["full_marker"]
fp.word = payload_dict["word"]
fp.index = (
int(payload_dict["index"]) if payload_dict["index"] is not None else 1
)
fp.field = payload_dict["field"]
fp.content = fuzzword.content if fuzzword else None
fp.type = fuzzword.type if fuzzword else None
fp.is_baseline = is_baseline
self.payloads[fp.index].append(fp)
def update_from_dictio(self, dictio_item):
for index, dictio_payload in enumerate(dictio_item, 1):
fuzz_payload = None
for fuzz_payload in self.payloads[index]:
fuzz_payload.content = dictio_payload.content
fuzz_payload.type = dictio_payload.type
# payload generated not used in seed but in filters
if fuzz_payload is None:
self.add(
{"full_marker": None, "word": None, "index": index, "field": None},
dictio_item[index - 1],
)
def get_fuzz_words(self):
return [payload.word for payload in self.get_payloads()]
def get_payload(self, index):
return self.payloads[index]
def get_payload_type(self, index):
return self.get_payload(index)[0].type
def get_payload_content(self, index):
return self.get_payload(index)[0].content
def get_payloads(self):
for index, elem_list in sorted(self.payloads.items()):
for elem in elem_list:
yield elem
def description(self):
payl_descriptions = [payload.description() for payload in self.get_payloads()]
ret_str = " - ".join([p_des for p_des in payl_descriptions if p_des])
return ret_str
def __str__(self):
return "\n".join([str(payload) for payload in self.get_payloads()])
class FuzzError(FuzzItem):
def __init__(self, exception):
FuzzItem.__init__(self, FuzzType.ERROR)
self.exception = exception
class FuzzResult(FuzzItem):
newid = itertools.count(0)
FUZZRESULT_SHARED_FILTER = FuzzResFilter()
def __init__(self, history=None, exception=None, track_id=True):
FuzzItem.__init__(self, FuzzType.RESULT)
self.history = history
self.exception = exception
self.is_baseline = False
self.rlevel_desc = ""
self.nres = next(FuzzResult.newid) if track_id else 0
self.chars = 0
self.lines = 0
self.words = 0
self.md5 = ""
self.update()
self.plugins_res = []
self.payload_man = None
self._fields = None
self._show_field = False
@property
def plugins(self):
dic = defaultdict(lambda: defaultdict(list))
for pl in self.plugins_res:
if pl.source == FuzzPlugin.OUTPUT_SOURCE:
continue
dic[pl.source][pl.itype].append(pl.data)
ret = DotDict()
for key, first in dic.items():
ret[key] = DotDict()
for seckey, second in first.items():
ret[key][seckey] = second
return ret
def update(self, exception=None):
self.item_type = FuzzType.RESULT
if exception:
self.exception = exception
if self.history and self.history.content:
m = hashlib.md5()
m.update(python2_3_convert_to_unicode(self.history.content))
self.md5 = m.hexdigest()
self.chars = len(self.history.content)
self.lines = self.history.content.count("\n")
self.words = len(re.findall(r"\S+", self.history.content))
return self
def __str__(self):
res = '%05d: C=%03d %4d L\t %5d W\t %5d Ch\t "%s"' % (
self.nres,
self.code,
self.lines,
self.words,
self.chars,
self.description,
)
for plugin in self.plugins_res:
if plugin.itype == FuzzPlugin.SUMMARY_ITYPE:
res += "\n |_ %s" % plugin.issue
return res
@property
def description(self):
res_description = self.payload_man.description() if self.payload_man else None
if not res_description:
res_description = self.url
ret_str = None
if self._show_field is True:
ret_str = self._field()
elif self._show_field is False and self._fields is not None:
ret_str = "{} | {}".format(res_description, self._field())
else:
ret_str = res_description
if self.exception:
return ret_str + "! " + str(self.exception)
if self.rlevel > 1:
return self.rlevel_desc + " - " + ret_str
return ret_str
def eval(self, expr):
return self.FUZZRESULT_SHARED_FILTER.is_visible(self, expr)
def _field(self, separator=", "):
list_eval = [self.eval(field) for field in self._fields]
return " | ".join(
[
separator.join(el) if isinstance(el, list) else str(el)
for el in list_eval
]
)
# parameters in common with fuzzrequest
@property
def content(self):
return self.history.content if self.history else ""
@property
def url(self):
return self.history.url if self.history else ""
@property
def code(self):
if self.history and self.history.code >= 0 and not self.exception:
return int(self.history.code)
# elif not self.history.code:
# return 0
else:
return ERROR_CODE
@property
def timer(self):
return self.history.reqtime if self.history and self.history.reqtime else 0
# factory methods
def update_from_options(self, options):
self._fields = options["fields"]
self._show_field = options["show_field"]
class FuzzPlugin(FuzzItem):
OUTPUT_SOURCE = "output"
SUMMARY_ITYPE = "summary"
NONE, INFO, LOW, MEDIUM, HIGH, CRITICAL = range(6)
MIN_VERBOSE = INFO
def __init__(self):
FuzzItem.__init__(self, FuzzType.PLUGIN)
self.source = ""
self.issue = ""
self.itype = ""
self.data = ""
self._exception = None
self._seed = None
self.severity = self.INFO
def is_visible(self, verbose):
if verbose and self.itype == self.SUMMARY_ITYPE:
return False
if not verbose and self.severity >= self.MIN_VERBOSE:
return False
return True
================================================
FILE: src/wfuzz/fuzzqueues.py
================================================
import time
import pickle as pickle
import gzip
from threading import Thread, Event
from queue import Queue
from collections import defaultdict
from .factories.fuzzresfactory import resfactory
from .factories.plugin_factory import plugin_factory
from .factories.payman import payman_factory
from .fuzzobjects import FuzzType, FuzzItem, FuzzWord, FuzzWordType
from .myqueues import FuzzQueue
from .exception import (
FuzzExceptInternalError,
FuzzExceptBadOptions,
FuzzExceptBadFile,
FuzzExceptPluginLoadError,
)
from .myqueues import FuzzRRQueue
from .facade import Facade
from .ui.console.mvc import View
class AllVarQ(FuzzQueue):
def __init__(self, options):
FuzzQueue.__init__(self, options)
self.delay = options.get("delay")
self.seed = options["compiled_seed"]
def get_name(self):
return "AllVarQ"
def cancel(self):
self.options["compiled_stats"].cancelled = True
def items_to_process(self):
return [FuzzType.STARTSEED]
def process(self, item):
self.stats.pending_seeds.inc()
for var_name, payload in self.options["compiled_dictio"]:
if self.options["compiled_stats"].cancelled:
break
self.stats.pending_fuzz.inc()
if self.delay:
time.sleep(self.delay)
self.send(
resfactory.create(
"fuzzres_from_allvar", self.options, var_name.content, payload
)
)
self.send_last(FuzzItem(FuzzType.ENDSEED))
class SeedQ(FuzzQueue):
def __init__(self, options):
FuzzQueue.__init__(self, options)
self.delay = options.get("delay")
def get_name(self):
return "SeedQ"
def cancel(self):
self.options["compiled_stats"].cancelled = True
def items_to_process(self):
return [FuzzType.STARTSEED, FuzzType.SEED]
def send_baseline(self):
fuzz_baseline = self.options["compiled_baseline"]
if fuzz_baseline is not None and self.stats.pending_seeds() == 1:
self.stats.pending_fuzz.inc()
self.send_first(fuzz_baseline)
# wait for BBB to be completed before generating more items
while self.stats.processed() == 0 and not self.stats.cancelled:
time.sleep(0.0001)
def restart(self, seed):
self.options["compiled_seed"] = seed
self.options.compile_dictio()
def process(self, item):
if item.item_type == FuzzType.STARTSEED:
self.stats.pending_seeds.inc()
elif item.item_type == FuzzType.SEED:
self.restart(item)
else:
raise FuzzExceptInternalError("SeedQ: Unknown item type in queue!")
self.send_baseline()
self.send_dictionary()
def get_fuzz_res(self, dictio_item):
if self.options["seed_payload"] and dictio_item[0].type == FuzzWordType.FUZZRES:
return resfactory.create(
"seed_from_options_and_dict", self.options, dictio_item
)
else:
return resfactory.create(
"fuzzres_from_options_and_dict", self.options, dictio_item
)
def send_dictionary(self):
# Empty dictionary?
try:
fuzzres = next(self.options["compiled_dictio"])
except StopIteration:
raise FuzzExceptBadOptions(
"Empty dictionary! Please check payload or filter."
)
# Enqueue requests
try:
while fuzzres:
if self.options["compiled_stats"].cancelled:
break
self.stats.pending_fuzz.inc()
if self.delay:
time.sleep(self.delay)
self.send(self.get_fuzz_res(fuzzres))
fuzzres = next(self.options["compiled_dictio"])
except StopIteration:
pass
self.send_last(FuzzItem(FuzzType.ENDSEED))
class SaveQ(FuzzQueue):
def __init__(self, options):
FuzzQueue.__init__(self, options)
self.output_fn = None
try:
self.output_fn = gzip.open(options.get("save"), "w+b")
except IOError as e:
raise FuzzExceptBadFile("Error opening results file!. %s" % str(e))
def get_name(self):
return "SaveQ"
def _cleanup(self):
self.output_fn.close()
def process(self, item):
pickle.dump(item, self.output_fn)
self.send(item)
class ConsolePrinterQ(FuzzQueue):
def __init__(self, options):
FuzzQueue.__init__(self, options)
self.printer = Facade().printers.get_plugin(self.options["console_printer"])(
None
)
def mystart(self):
self.printer.header(self.stats)
def get_name(self):
return "ConsolePrinterQ"
def _cleanup(self):
self.printer.footer(self.stats)
def process(self, item):
self.printer.result(item)
self.send(item)
class CLIPrinterQ(FuzzQueue):
def __init__(self, options):
FuzzQueue.__init__(self, options)
self.printer = View(self.options)
def mystart(self):
self.printer.header(self.stats)
def process_discarded(self):
return True
def get_name(self):
return "CLIPrinterQ"
def _cleanup(self):
self.printer.footer(self.stats)
def process(self, item):
self.printer.result(item)
self.send(item)
class PrinterQ(FuzzQueue):
def __init__(self, options):
FuzzQueue.__init__(self, options)
self.printer = options.get("compiled_printer")
self.printer.header(self.stats)
def get_name(self):
return "PrinterQ"
def _cleanup(self):
self.printer.footer(self.stats)
def process(self, item):
self.printer.result(item)
self.send(item)
class RoutingQ(FuzzQueue):
def __init__(self, options, routes):
FuzzQueue.__init__(self, options)
self.routes = routes
def get_name(self):
return "RoutingQ"
def items_to_process(self):
return [FuzzType.SEED, FuzzType.BACKFEED]
def process(self, item):
if item.item_type in self.routes:
self.routes[item.item_type].put(item)
else:
self.queue_out.put(item)
class FilterQ(FuzzQueue):
def __init__(self, options, ffilter):
FuzzQueue.__init__(self, options)
self.ffilter = ffilter
def get_name(self):
return "filter_thread"
def process(self, item):
if item.is_baseline:
self.ffilter.set_baseline(item)
if self.ffilter.is_visible(item) or item.is_baseline:
self.send(item)
else:
self.discard(item)
class SliceQ(FuzzQueue):
def __init__(self, options, prefilter):
FuzzQueue.__init__(self, options)
self.ffilter = prefilter
def get_name(self):
return "slice_thread"
def process(self, item):
if item.is_baseline or self.ffilter.is_visible(item):
self.send(item)
else:
self.discard(item)
class JobQ(FuzzRRQueue):
def __init__(self, options):
# Get active plugins
lplugins = [x() for x in Facade().scripts.get_plugins(options.get("script"))]
if not lplugins:
raise FuzzExceptBadOptions(
"No plugin selected, check the --script name or category introduced."
)
concurrent = int(Facade().sett.get("general", "concurrent_plugins"))
FuzzRRQueue.__init__(
self, options, [JobMan(options, lplugins) for i in range(concurrent)]
)
def get_name(self):
return "JobQ"
def process(self, item):
self.send(item)
class JobMan(FuzzQueue):
def __init__(self, options, selected_plugins):
FuzzQueue.__init__(self, options)
self.__walking_threads = Queue(20)
self.selected_plugins = selected_plugins
self.cache = options.cache
self.max_dlevel = options.get("dlevel")
def get_name(self):
return "Jobman"
# ------------------------------------------------
# threading
# ------------------------------------------------
def process(self, res):
# process request through plugins
if not res.exception:
if self.options["no_cache"] or self.cache.update_cache(
res.history, "processed"
):
plugins_res_queue = Queue()
for pl in self.selected_plugins:
try:
if not pl.validate(res):
continue
th = Thread(
target=pl.run,
kwargs={
"fuzzresult": res,
"control_queue": self.__walking_threads,
"results_queue": plugins_res_queue,
},
)
except Exception as e:
raise FuzzExceptPluginLoadError(
"Error initialising plugin %s: %s " % (pl.name, str(e))
)
self.__walking_threads.put(th)
th.start()
self.__walking_threads.join()
self.process_results(res, plugins_res_queue)
# add result to results queue
self.send(res)
def process_results(self, res, plugins_res_queue):
enq_item = defaultdict(lambda: defaultdict(int))
while not plugins_res_queue.empty():
item = plugins_res_queue.get()
if item._exception is not None:
if Facade().sett.get("general", "cancel_on_plugin_except") == "1":
self._throw(item._exception)
res.plugins_res.append(item)
elif item._seed is not None and self.options["transport"] == "http":
cache_hit = self.cache.update_cache(item._seed.history, "backfeed")
if (self.options["no_cache"] or cache_hit) and (
self.max_dlevel == 0 or self.max_dlevel >= res.rlevel
):
self.stats.backfeed.inc()
self.stats.pending_fuzz.inc()
self.send(item._seed)
enq_item[item.source]["request enqueued"] += 1
elif item.issue:
enq_item[item.source][item.itype] += 1
res.plugins_res.append(item)
for plugin_name, plugin_type in enq_item.items():
for domain, enq_num in plugin_type.items():
res.plugins_res.append(
plugin_factory.create(
"plugin_from_summary",
"Plugin {}: {} new {}(s) found.".format(
plugin_name, enq_num, domain
),
)
)
class RecursiveQ(FuzzQueue):
def __init__(self, options):
FuzzQueue.__init__(self, options)
self.cache = options.cache
self.max_rlevel = options.get("rlevel")
def get_name(self):
return "RecursiveQ"
def process(self, fuzz_res):
# check if recursion is needed
if self.max_rlevel >= fuzz_res.rlevel and fuzz_res.history.is_path:
if self.cache.update_cache(fuzz_res.history, "recursion"):
self.stats.pending_seeds.inc()
seed = resfactory.create("seed_from_recursion", fuzz_res)
self.send(seed)
fuzz_res.plugins_res.append(
plugin_factory.create(
"plugin_from_summary",
"Enqueued response for recursion (level=%d)" % (seed.rlevel),
)
)
# send new result
self.send(fuzz_res)
class PassPayloadQ(FuzzQueue):
def __init__(self, options):
FuzzQueue.__init__(self, options)
self.pause = Event()
def get_name(self):
return "PassPayloadQ"
def process(self, item):
if item.payload_man.get_payload_type(1) == FuzzWordType.FUZZRES:
item = item.payload_man.get_payload_content(1)
item.update_from_options(self.options)
if not item.payload_man:
item.payload_man = payman_factory.create(
"empty_payloadman", FuzzWord(item.url, FuzzWordType.WORD)
)
self.send(item)
class DryRunQ(FuzzQueue):
def __init__(self, options):
FuzzQueue.__init__(self, options)
self.pause = Event()
def get_name(self):
return "DryRunQ"
def process(self, item):
self.send(item)
class HttpQueue(FuzzQueue):
def __init__(self, options):
FuzzQueue.__init__(self, options, limit=options.get("concurrent") * 5)
self.http_pool = options.http_pool
self.pause = Event()
self.pause.set()
self.exit_job = False
def cancel(self):
self.pause.set()
def mystart(self):
self.poolid = self.http_pool.register()
th2 = Thread(target=self.__read_http_results)
th2.setName("__read_http_results")
th2.start()
def get_name(self):
return "HttpQueue"
def _cleanup(self):
self.http_pool.deregister()
self.exit_job = True
def items_to_process(self):
return [FuzzType.RESULT, FuzzType.BACKFEED]
def process(self, obj):
self.pause.wait()
self.http_pool.enqueue(obj, self.poolid)
def __read_http_results(self):
try:
while not self.exit_job:
res = next(self.http_pool.iter_results(self.poolid))
self.send(res)
except StopIteration:
pass
class HttpReceiver(FuzzQueue):
def __init__(self, options):
FuzzQueue.__init__(self, options)
def get_name(self):
return "HttpReceiver"
def process(self, res):
if res.exception and not self.options.get("scanmode"):
self._throw(res.exception)
else:
self.send(res)
================================================
FILE: src/wfuzz/fuzzrequest.py
================================================
import pycurl
# Python 2 and 3
import sys
if sys.version_info >= (3, 0):
from urllib.parse import urlparse
else:
from urlparse import urlparse
from collections import namedtuple
from .externals.reqresp import Request, Response
from .exception import FuzzExceptBadAPI, FuzzExceptBadOptions
from .facade import Facade
from .mixins import FuzzRequestUrlMixing, FuzzRequestSoupMixing
from .helpers.str_func import python2_3_convert_from_unicode
from .helpers.obj_dic import DotDict
class headers(object):
class header(DotDict):
def __str__(self):
return "\n".join(["{}: {}".format(k, v) for k, v in self.items()])
def __init__(self, req):
self._req = req
@property
def response(self):
return (
headers.header(self._req.response.getHeaders())
if self._req.response
else headers.header()
)
@property
def request(self):
return headers.header(self._req._headers)
@request.setter
def request(self, values_dict):
self._req._headers.update(values_dict)
if "Content-Type" in values_dict:
self._req.ContentType = values_dict["Content-Type"]
@property
def all(self):
return headers.header(self.request + self.response)
class cookies(object):
class cookie(DotDict):
def __str__(self):
return "\n".join(["{}={}".format(k, v) for k, v in self.items()])
def __init__(self, req):
self._req = req
@property
def response(self):
if self._req.response:
c = self._req.response.getCookie().split("; ")
if c[0]:
return cookies.cookie(
{x[0]: x[2] for x in [x.partition("=") for x in c]}
)
return cookies.cookie({})
@property
def request(self):
if "Cookie" in self._req._headers:
c = self._req._headers["Cookie"].split("; ")
if c[0]:
return cookies.cookie(
{x[0]: x[2] for x in [x.partition("=") for x in c]}
)
return cookies.cookie({})
@request.setter
def request(self, values):
self._req._headers["Cookie"] = "; ".join(values)
@property
def all(self):
return cookies.cookie(self.request + self.response)
class params(object):
class param(DotDict):
def __str__(self):
return "\n".join(["{}={}".format(k, v) for k, v in self.items()])
def __init__(self, req):
self._req = req
@property
def get(self):
return params.param({x.name: x.value for x in self._req.getGETVars()})
@get.setter
def get(self, values):
if isinstance(values, dict) or isinstance(values, DotDict):
for key, value in values.items():
self._req.setVariableGET(key, str(value))
else:
raise FuzzExceptBadAPI("GET Parameters must be specified as a dictionary")
@property
def post(self):
return params.param({x.name: x.value for x in self._req.getPOSTVars()})
@post.setter
def post(self, pp):
if isinstance(pp, dict) or isinstance(pp, DotDict):
for key, value in pp.items():
self._req.setVariablePOST(
key, str(value) if value is not None else value
)
self._req._non_parsed_post = self._req._variablesPOST.urlEncoded()
elif isinstance(pp, str):
self._req.setPostData(pp)
@property
def raw_post(self):
return self._req._non_parsed_post
@property
def all(self):
return params.param(self.get + self.post)
@all.setter
def all(self, values):
self.get = values
self.post = values
class FuzzRequest(FuzzRequestUrlMixing, FuzzRequestSoupMixing):
def __init__(self):
self._request = Request()
self._proxy = None
self._allvars = None
self.wf_fuzz_methods = None
self.wf_retries = 0
self.wf_ip = None
self.headers.request = {
"User-Agent": Facade().sett.get("connection", "user-agent")
}
# methods for accessing HTTP requests information consistenly accross the codebase
def __str__(self):
return self._request.getAll()
@property
def raw_request(self):
return self._request.getAll()
@raw_request.setter
def raw_request(self, rawReq, scheme):
self.update_from_raw_http(rawReq, scheme)
@property
def raw_content(self):
if self._request.response:
return self._request.response.getAll()
return ""
@property
def headers(self):
return headers(self._request)
@property
def params(self):
return params(self._request)
@property
def cookies(self):
return cookies(self._request)
@property
def method(self):
return self._request.method
@method.setter
def method(self, method):
self._request.method = method
@property
def scheme(self):
return self._request.schema
@scheme.setter
def scheme(self, s):
self._request.schema = s
@property
def host(self):
return self._request.getHost()
@property
def path(self):
return self._request.path
@property
def redirect_url(self):
return self._request.completeUrl
@property
def url(self):
return self._request.finalUrl
@url.setter
def url(self, u):
# urlparse goes wrong with IP:port without scheme (https://bugs.python.org/issue754016)
if not u.startswith("FUZ") and (
urlparse(u).netloc == "" or urlparse(u).scheme == ""
):
u = "http://" + u
if urlparse(u).path == "":
u += "/"
if Facade().sett.get("general", "encode_space") == "1":
u = u.replace(" ", "%20")
self._request.setUrl(u)
if self.scheme.startswith("fuz") and self.scheme.endswith("z"):
# avoid FUZZ to become fuzz
self.scheme = self.scheme.upper()
@property
def content(self):
return self._request.response.getContent() if self._request.response else ""
@property
def code(self):
return self._request.response.code if self._request.response else 0
@code.setter
def code(self, c):
self._request.response.code = int(c)
@property
def auth(self):
method, creds = self._request.getAuth()
return DotDict({"method": method, "credentials": creds})
@auth.setter
def auth(self, creds_dict):
self._request.setAuth(creds_dict["method"], creds_dict["credentials"])
method, creds = self._request.getAuth()
return DotDict({"method": method, "credentials": creds})
@property
def follow(self):
return self._request.followLocation
@follow.setter
def follow(self, f):
self._request.setFollowLocation(f)
@property
def reqtime(self):
return self._request.totaltime
@reqtime.setter
def reqtime(self, t):
self._request.totaltime = t
# Info extra that wfuzz needs within an HTTP request
@property
def wf_allvars_set(self):
if self.wf_allvars == "allvars":
return self.params.get
elif self.wf_allvars == "allpost":
return self.params.post
elif self.wf_allvars == "allheaders":
return self.headers.request
else:
raise FuzzExceptBadOptions("Unknown variable set: " + self.wf_allvars)
@wf_allvars_set.setter
def wf_allvars_set(self, varset):
try:
if self.wf_allvars == "allvars":
self.params.get = varset
elif self.wf_allvars == "allpost":
self.params.post = varset
elif self.wf_allvars == "allheaders":
self._request.headers.request = varset
else:
raise FuzzExceptBadOptions("Unknown variable set: " + self.wf_allvars)
except TypeError:
raise FuzzExceptBadOptions(
"It is not possible to use all fuzzing with duplicated parameters."
)
@property
def wf_allvars(self):
return self._allvars
@wf_allvars.setter
def wf_allvars(self, bl):
if bl is not None and bl not in ["allvars", "allpost", "allheaders"]:
raise FuzzExceptBadOptions(
"Incorrect all parameters brute forcing type specified, correct values are allvars, allpost or allheaders."
)
self._allvars = bl
@property
def wf_proxy(self):
return self._proxy
@wf_proxy.setter
def wf_proxy(self, proxy_tuple):
if proxy_tuple:
prox, ptype = proxy_tuple
self._request.setProxy("%s" % prox, ptype if ptype else "HTML")
self._proxy = proxy_tuple
# methods wfuzz needs to perform HTTP requests (this might change in the future).
def update_from_raw_http(self, raw, scheme, raw_response=None, raw_content=None):
self._request.parseRequest(raw, scheme)
# Parse request sets postdata = '' when there's POST request without data
if self.method == "POST" and self.params.raw_post is None:
self.params.post = ""
if raw_response:
rp = Response()
if not isinstance(raw_response, str):
raw_response = python2_3_convert_from_unicode(
raw_response.decode("utf-8", errors="surrogateescape")
)
rp.parseResponse(raw_response, raw_content)
self._request.response = rp
return self._request
def to_cache_key(self):
key = self._request.urlWithoutVariables
dicc = {"g{}".format(key): True for key in self.params.get.keys()}
dicc.update({"p{}".format(key): True for key in self.params.post.keys()})
# take URL parameters into consideration
url_params = list(dicc.keys())
url_params.sort()
key += "-" + "-".join(url_params)
return key
# methods wfuzz needs for substituing payloads and building dictionaries
def update_from_options(self, options):
if options["url"] != "FUZZ":
self.url = options["url"]
# headers must be parsed first as they might affect how reqresp parases other params
self.headers.request = dict(options["headers"])
if options["auth"].get("method") is not None:
self.auth = options["auth"]
if options["follow"]:
self.follow = options["follow"]
if options["postdata"] is not None:
self.params.post = options["postdata"]
if options["connect_to_ip"]:
self.wf_ip = options["connect_to_ip"]
if options["method"]:
self.method = options["method"]
self.wf_fuzz_methods = options["method"]
if options["cookie"]:
self.cookies.request = options["cookie"]
if options["allvars"]:
self.wf_allvars = options["allvars"]
================================================
FILE: src/wfuzz/helpers/__init__.py
================================================
================================================
FILE: src/wfuzz/helpers/file_func.py
================================================
import os
import sys
import re
import pkg_resources
from chardet.universaldetector import UniversalDetector
import chardet
from ..exception import FuzzExceptInternalError
def get_filter_help_file():
FILTER_HELP_FILE = "advanced.rst"
FILTER_HELP_DEV_FILE = "../../../docs/user/advanced.rst"
filter_help_text = None
try:
fname = pkg_resources.resource_filename("wfuzz", FILTER_HELP_FILE)
filter_help_text = open(fname).read()
except IOError:
filter_help_text = open(get_path(FILTER_HELP_DEV_FILE)).read()
return filter_help_text
def create_dir(dir_path):
if not os.path.exists(dir_path):
os.makedirs(dir_path)
def get_home(check=False, directory=None):
path = os.path.join(os.path.expanduser("~"), ".wfuzz")
if check:
create_dir(path)
return os.path.join(path, directory) if directory else path
def get_config_dir(check=False):
config_dir = os.environ.get("XDG_CONFIG_HOME") or os.path.join(
os.path.expanduser("~"), ".config"
)
wfuzz_config_dir = os.path.join(config_dir, "wfuzz")
if check:
create_dir(wfuzz_config_dir)
return wfuzz_config_dir
def get_path(directory=None):
abspath = os.path.abspath(__file__)
ret = os.path.dirname(abspath)
return os.path.join(ret, directory) if directory else ret
def find_file_in_paths(name, path):
for root, dirs, files in os.walk(path):
if name in files:
return os.path.join(root, name)
return None
class FileDetOpener:
typical_encodings = [
"UTF-8",
"ISO-8859-1",
"Windows-1251",
"Shift JIS",
"Windows-1252",
"GB2312",
"EUC-KR",
"EUC-JP",
"GBK",
"ISO-8859-2",
"Windows-1250",
"ISO-8859-15",
"Windows-1256",
"ISO-8859-9",
"Big5",
"Windows-1254",
]
def __init__(self, file_path, encoding=None):
self.cache = []
self.file_des = open(file_path, mode="rb")
self.det_encoding = encoding
self.encoding_forced = False
def close(self):
self.file_des.close()
def reset(self):
self.file_des.seek(0)
def __iter__(self):
return self
def __next__(self):
decoded_line = None
line = None
last_error = None
while decoded_line is None:
while self.det_encoding is None:
detect_encoding = self.detect_encoding().get("encoding", "utf-8")
self.det_encoding = (
detect_encoding if detect_encoding is not None else "utf-8"
)
if line is None:
if self.cache:
line = self.cache.pop()
else:
line = next(self.file_des)
if not line:
raise StopIteration
try:
decoded_line = line.decode(self.det_encoding)
except UnicodeDecodeError:
if last_error is not None and last_error:
self.det_encoding = last_error.pop()
elif last_error is None and not self.encoding_forced:
last_error = list(reversed(self.typical_encodings))
last_error.append(chardet.detect(line).get("encoding"))
elif not last_error:
raise FuzzExceptInternalError("Unable to decode wordlist file!")
decoded_line = None
return decoded_line
def detect_encoding(self):
detector = UniversalDetector()
detector.reset()
for line in self.file_des:
detector.feed(line)
self.cache.append(line)
if detector.done:
break
detector.close()
return detector.result
next = __next__ # for Python 2
def open_file_detect_encoding(file_path):
def detect_encoding(file_path):
detector = UniversalDetector()
detector.reset()
with open(file_path, mode="rb") as file_to_detect:
for line in file_to_detect:
detector.feed(line)
if detector.done:
break
detector.close()
return detector.result
if sys.version_info >= (3, 0):
return open(
file_path, "r", encoding=detect_encoding(file_path).get("encoding", "utf-8")
)
else:
return open(file_path, "r")
================================================
FILE: src/wfuzz/helpers/obj_dic.py
================================================
from collections.abc import MutableMapping
from itertools import chain
class CaseInsensitiveDict(MutableMapping):
def __init__(self, *args, **kwargs):
self.store = dict()
self.proxy = dict()
self.update(dict(*args, **kwargs)) # use the free update to set keys
def __contains__(self, k):
return k.lower() in self.proxy
def __delitem__(self, k):
key = self.proxy[k.lower()]
del self.store[key]
del self.proxy[k.lower()]
def __getitem__(self, k):
key = self.proxy[k.lower()]
return self.store[key]
def get(self, k, default=None):
key = self.proxy[k.lower()]
return self.store[key] if key in self.store else default
def __setitem__(self, k, v):
self.store[k] = v
self.proxy[k.lower()] = k
def __iter__(self):
return iter(self.store)
def __len__(self):
return len(self.store)
class DotDict(CaseInsensitiveDict):
def __getattr__(obj, name):
# Return {} if non-existent attr
if name not in obj:
return DotDict({})
# python 3 val = dict.get(*args, None)
val = obj.get(name)
return DotDict(val) if type(val) is dict else val
# return DotDict(val) if type(val) is dict else DotDict({args[1]: val})
def __add__(self, other):
if isinstance(other, str):
return DotDict({k: v + other for k, v in self.items() if v})
elif isinstance(other, DotDict):
# python 3 return DotDict({**self, **other})
new_dic = DotDict(self)
new_dic.update(other)
return new_dic
def __radd__(self, other):
if isinstance(other, str):
return DotDict({k: other + v for k, v in self.items() if v})
def __getitem__(self, key):
try:
return super(DotDict, self).__getitem__(key)
except KeyError:
return DotDict({})
def __str__(self):
return "\n".join(
[
"{}{} {}".format(k, "->" if isinstance(v, DotDict) else ":", v)
for k, v in self.items()
]
)
================================================
FILE: src/wfuzz/helpers/obj_dyn.py
================================================
import functools
from .obj_dic import DotDict
allowed_fields = [
"description",
"nres",
"code",
"chars",
"lines",
"words",
"md5",
"l",
"h",
"w",
"c",
"history",
"plugins",
"url",
"content",
"history.url",
"history.method",
"history.scheme",
"history.host",
"history.content",
"history.raw_content" "history.is_path",
"history.pstrip",
"history.cookies",
"history.headers",
"history.params",
"r",
"r.reqtime",
"r.url",
"r.method",
"r.scheme",
"r.host",
"r.content",
"r.raw_content" "r.is_path",
"r.pstrip",
"r.cookies.",
"r.headers.",
"r.params.",
]
def _check_allowed_field(attr):
if [field for field in allowed_fields if attr.startswith(field)]:
return True
return False
def _get_alias(attr):
attr_alias = {
"l": "lines",
"h": "chars",
"w": "words",
"c": "code",
"r": "history",
}
if attr in attr_alias:
return attr_alias[attr]
return attr
def rsetattr(obj, attr, new_val, operation):
# if not _check_allowed_field(attr):
# raise AttributeError("Unknown field {}".format(attr))
pre, _, post = attr.rpartition(".")
pre_post = None
if len(attr.split(".")) > 3:
pre_post = post
pre, _, post = pre.rpartition(".")
post = _get_alias(post)
try:
obj_to_set = rgetattr(obj, pre) if pre else obj
prev_val = rgetattr(obj, attr)
if pre_post is not None:
prev_val = DotDict({pre_post: prev_val})
if operation is not None:
val = operation(prev_val, new_val)
else:
if isinstance(prev_val, DotDict):
val = {k: new_val for k, v in prev_val.items()}
else:
val = new_val
return setattr(obj_to_set, post, val)
except AttributeError:
raise AttributeError(
"rsetattr: Can't set '{}' attribute of {}.".format(
post, obj_to_set.__class__
)
)
def rgetattr(obj, attr, *args):
def _getattr(obj, attr):
attr = _get_alias(attr)
try:
return getattr(obj, attr, *args)
except AttributeError:
raise AttributeError(
"rgetattr: Can't get '{}' attribute from '{}'.".format(
attr, obj.__class__
)
)
# if not _check_allowed_field(attr):
# raise AttributeError("Unknown field {}".format(attr))
return functools.reduce(_getattr, [obj] + attr.split("."))
================================================
FILE: src/wfuzz/helpers/obj_factory.py
================================================
import re
import abc
from ..helpers.obj_dyn import (
rgetattr,
rsetattr,
)
from ..exception import FuzzExceptBadOptions
class Singleton(type):
""" Singleton metaclass. Use by defining the metaclass of a class Singleton,
e.g.: class ThereCanBeOnlyOne:
__metaclass__ = Singleton
"""
def __call__(class_, *args, **kwargs):
if not class_.hasInstance():
class_.instance = super(Singleton, class_).__call__(*args, **kwargs)
return class_.instance
def deleteInstance(class_):
""" Delete the (only) instance. This method is mainly for unittests so
they can start with a clean slate. """
if class_.hasInstance():
del class_.instance
def hasInstance(class_):
""" Has the (only) instance been created already? """
return hasattr(class_, "instance")
class ObjectFactory:
def __init__(self, builders):
self._builders = builders
def create(self, key, *args, **kwargs):
builder = self._builders.get(key)
if not builder:
raise ValueError(key)
return builder(*args, **kwargs)
class HttpRequestFactory(abc.ABC):
@staticmethod
@abc.abstractmethod
def to_http_object(options, to_http, from_req):
pass
@staticmethod
@abc.abstractmethod
def from_http_object(options, from_http, raw_header, raw_body):
pass
class SeedBuilderHelper:
FUZZ_MARKERS_REGEX = re.compile(
r"(?P(?PFUZ(?P\d)*Z)(?P(?:\[(?P.*?)\])?(?P\{(?P.*?)\})?))"
)
REQ_ATTR = ["raw_request", "scheme", "method", "auth.credentials"]
@staticmethod
def _get_markers(text):
return [
m.groupdict() for m in SeedBuilderHelper.FUZZ_MARKERS_REGEX.finditer(text)
]
@staticmethod
def get_marker_dict(freq):
marker_dict_list = []
for text in [rgetattr(freq, field) for field in SeedBuilderHelper.REQ_ATTR]:
marker_dict_list += SeedBuilderHelper._get_markers(text)
# validate
if len({bd["bl_value"] is None for bd in marker_dict_list}) > 1:
raise FuzzExceptBadOptions(
"You must supply a baseline value per FUZZ word."
)
return marker_dict_list
@staticmethod
def _remove_markers(freq, markers, mark_name):
scheme = freq.scheme
for mark in [
mark[mark_name] for mark in markers if mark[mark_name] is not None
]:
for field in SeedBuilderHelper.REQ_ATTR:
old_value = rgetattr(freq, field)
new_value = old_value.replace(mark, "")
if field == "raw_request":
freq.update_from_raw_http(new_value, scheme)
else:
rsetattr(freq, field, new_value, None)
@staticmethod
def remove_baseline_markers(freq, markers):
SeedBuilderHelper._remove_markers(freq, markers, "full_bl")
return freq
@staticmethod
def remove_nonfuzz_markers(freq, markers):
SeedBuilderHelper._remove_markers(markers, "nonfuzz_marker")
return freq
# Not working due to reqresp internals
# def replace_markers(self, seed, fpm):
# for payload in fpm.get_payloads():
# for field in self.REQ_ATTR:
# old_value = rgetattr(seed, field)
# new_value = old_value.replace(payload.marker, payload.value)
# rsetattr(seed, field, new_value , None)
@staticmethod
def replace_markers(freq, fpm):
rawReq = str(freq)
rawUrl = freq.redirect_url
scheme = freq.scheme
old_auth = freq.auth
for payload in [
payload for payload in fpm.get_payloads() if payload.marker is not None
]:
if old_auth.method:
old_auth["credentials"] = old_auth["credentials"].replace(
payload.marker, str(payload.value)
)
rawUrl = rawUrl.replace(payload.marker, str(payload.value))
rawReq = rawReq.replace(payload.marker, str(payload.value))
scheme = scheme.replace(payload.marker, str(payload.value))
freq.update_from_raw_http(rawReq, scheme)
freq.url = rawUrl
if old_auth.method:
freq.auth = old_auth
return freq
================================================
FILE: src/wfuzz/helpers/str_func.py
================================================
import re
import sys
import six
from .obj_dic import DotDict
def json_minify(string, strip_space=True):
"""
Created on 20/01/2011
v0.2 (C) Gerald Storer
MIT License
Based on JSON.minify.js:
https://github.com/getify/JSON.minify
Contributers:
- Pradyun S. Gedam (conditions and variable names changed)
"""
tokenizer = re.compile(r'"|(/\*)|(\*/)|(//)|\n|\r')
end_slashes_re = re.compile(r"(\\)*$")
in_string = False
in_multi = False
in_single = False
new_str = []
index = 0
for match in re.finditer(tokenizer, string):
if not (in_multi or in_single):
tmp = string[index : match.start()]
if not in_string and strip_space:
# replace white space as defined in standard
tmp = re.sub("[ \t\n\r]+", "", tmp)
new_str.append(tmp)
index = match.end()
val = match.group()
if val == '"' and not (in_multi or in_single):
escaped = end_slashes_re.search(string, 0, match.start())
# start of string or unescaped quote character to end string
if not in_string or (escaped is None or len(escaped.group()) % 2 == 0):
in_string = not in_string
# include " character in next catch
index -= 1
elif not (in_string or in_multi or in_single):
if val == "/*":
in_multi = True
elif val == "//":
in_single = True
elif val == "*/" and in_multi and not (in_string or in_single):
in_multi = False
elif val in "\r\n" and not (in_multi or in_string) and in_single:
in_single = False
elif not ((in_multi or in_single) or (val in " \r\n\t" and strip_space)):
new_str.append(val)
new_str.append(string[index:])
return "".join(new_str)
def python2_3_convert_from_unicode(text):
if sys.version_info >= (3, 0):
return text
else:
return convert_to_unicode(text)
def python2_3_convert_to_unicode(text):
if sys.version_info >= (3, 0):
return convert_to_unicode(text)
else:
return text
def convert_to_unicode(text):
if isinstance(text, dict) or isinstance(text, DotDict):
return {
convert_to_unicode(key): convert_to_unicode(value)
for key, value in list(text.items())
}
elif isinstance(text, list):
return [convert_to_unicode(element) for element in text]
elif isinstance(text, six.string_types):
return text.encode("utf-8", errors="ignore")
else:
return text
def value_in_any_list_item(value, list_obj):
if isinstance(list_obj, list):
return len([item for item in list_obj if value.lower() in item.lower()]) > 0
elif isinstance(list_obj, str):
return value.lower() in list_obj.lower()
================================================
FILE: src/wfuzz/helpers/utils.py
================================================
from threading import Lock
import difflib
class MyCounter:
def __init__(self, count=0):
self._count = count
self._mutex = Lock()
def inc(self):
return self._operation(1)
def dec(self):
return self._operation(-1)
def _operation(self, dec):
with self._mutex:
self._count += dec
return self._count
def __call__(self):
with self._mutex:
return self._count
def diff(param1, param2):
delta = difflib.unified_diff(
str(param1).splitlines(False),
str(param2).splitlines(False),
fromfile="prev",
tofile="current",
n=0,
)
return "\n".join(delta)
================================================
FILE: src/wfuzz/mixins.py
================================================
from .plugin_api.urlutils import parse_url
from .exception import FuzzExceptBadInstall
# python 2 and 3
import sys
if sys.version_info >= (3, 0):
from urllib.parse import urljoin, urlparse
else:
from urlparse import urljoin, urlparse
class FuzzRequestSoupMixing(object):
def get_soup(self):
try:
from bs4 import BeautifulSoup
except ImportError:
raise FuzzExceptBadInstall("You need to install beautifulsoup4 first!")
soup = BeautifulSoup(self.content, "html.parser")
return soup
class FuzzRequestUrlMixing(object):
# urlparse functions
@property
def urlparse(self):
return parse_url(self.url)
@property
def urlp(self):
return parse_url(self.url)
@property
def pstrip(self):
return self.to_cache_key()
@property
def is_path(self):
if self.recursive_url and self.recursive_url[-1] == "/":
return True
return False
@property
def recursive_url(self):
if self.code >= 300 and self.code < 308 and "Location" in self.headers.response:
location_url = self.headers.response["Location"]
location_parsed_url = urlparse(location_url)
if not location_parsed_url.scheme and not location_parsed_url.netloc:
return urljoin(self.url, location_url)
elif self.code in [200, 401] and self.url[-1] == "/":
return self.url
return None
================================================
FILE: src/wfuzz/myhttp.py
================================================
import pycurl
from io import BytesIO
from threading import Thread, Lock
import itertools
from queue import Queue
import collections
from .exception import FuzzExceptBadOptions, FuzzExceptNetError
from .factories.reqresp_factory import ReqRespRequestFactory
# See https://curl.haxx.se/libcurl/c/libcurl-errors.html
UNRECOVERABLE_PYCURL_EXCEPTIONS = [
28, # Operation timeout. The specified time-out period was reached according to the conditions.
7, # Failed to connect() to host or proxy.
6, # Couldn't resolve host. The given remote host was not resolved.
5, # Couldn't resolve proxy. The given proxy host could not be resolved.
]
# Other common pycurl exceptions:
# Exception in perform (35, 'error:0B07C065:x509 certificate routines:X509_STORE_add_cert:cert already in hash table')
# Exception in perform (18, 'SSL read: error:0B07C065:x509 certificate routines:X509_STORE_add_cert:cert already in hash table, errno 11')
class HttpPool:
HTTPAUTH_BASIC, HTTPAUTH_NTLM, HTTPAUTH_DIGEST = ("basic", "ntlm", "digest")
newid = itertools.count(0)
def __init__(self, options):
self.processed = 0
self.exit_job = False
self.mutex_stats = Lock()
self.m = None
self.curlh_freelist = []
self._request_list = collections.deque()
self.handles = []
self.ths = None
self.pool_map = {}
self.options = options
self._registered = 0
def _initialize(self):
# pycurl Connection pool
self.m = pycurl.CurlMulti()
self.handles = []
for i in range(self.options.get("concurrent")):
curl_h = pycurl.Curl()
self.handles.append(curl_h)
self.curlh_freelist.append(curl_h)
# create threads
self.ths = []
for fn in ("_read_multi_stack",):
th = Thread(target=getattr(self, fn))
th.setName(fn)
self.ths.append(th)
th.start()
def job_stats(self):
with self.mutex_stats:
dic = {
"http_processed": self.processed,
"http_registered": self._registered,
}
return dic
# internal http pool control
def iter_results(self, poolid):
item = self.pool_map[poolid]["queue"].get()
if not item:
return
yield item
def _new_pool(self):
poolid = next(self.newid)
self.pool_map[poolid] = {}
self.pool_map[poolid]["queue"] = Queue()
self.pool_map[poolid]["proxy"] = None
if self.options.get("proxies"):
self.pool_map[poolid]["proxy"] = self._get_next_proxy(
self.options.get("proxies")
)
return poolid
def _prepare_curl_h(self, curl_h, fuzzres, poolid):
new_curl_h = ReqRespRequestFactory.to_http_object(
self.options, fuzzres.history, curl_h
)
new_curl_h = self._set_extra_options(new_curl_h, fuzzres, poolid)
new_curl_h.response_queue = (BytesIO(), BytesIO(), fuzzres, poolid)
new_curl_h.setopt(pycurl.WRITEFUNCTION, new_curl_h.response_queue[0].write)
new_curl_h.setopt(pycurl.HEADERFUNCTION, new_curl_h.response_queue[1].write)
return new_curl_h
def enqueue(self, fuzzres, poolid):
if self.exit_job:
return
self._request_list.append((fuzzres, poolid))
def _stop_to_pools(self):
for p in list(self.pool_map.keys()):
self.pool_map[p]["queue"].put(None)
def cleanup(self):
self.exit_job = True
for th in self.ths:
th.join()
def register(self):
with self.mutex_stats:
self._registered += 1
if not self.pool_map:
self._initialize()
return self._new_pool()
def deregister(self):
with self.mutex_stats:
self._registered -= 1
if self._registered <= 0:
self.cleanup()
def _get_next_proxy(self, proxy_list):
i = 0
while 1:
yield proxy_list[i]
i += 1
i = i % len(proxy_list)
def _set_extra_options(self, c, fuzzres, poolid):
if self.pool_map[poolid]["proxy"]:
ip, port, ptype = next(self.pool_map[poolid]["proxy"])
fuzzres.history.wf_proxy = (("%s:%s" % (ip, port)), ptype)
if ptype == "SOCKS5":
c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5)
c.setopt(pycurl.PROXY, "%s:%s" % (ip, port))
elif ptype == "SOCKS4":
c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS4)
c.setopt(pycurl.PROXY, "%s:%s" % (ip, port))
elif ptype == "HTTP":
c.setopt(pycurl.PROXY, "%s:%s" % (ip, port))
else:
raise FuzzExceptBadOptions(
"Bad proxy type specified, correct values are HTTP, SOCKS4 or SOCKS5."
)
else:
c.setopt(pycurl.PROXY, "")
mdelay = self.options.get("req_delay")
if mdelay is not None:
c.setopt(pycurl.TIMEOUT, mdelay)
cdelay = self.options.get("conn_delay")
if cdelay is not None:
c.setopt(pycurl.CONNECTTIMEOUT, cdelay)
return c
def _process_curl_handle(self, curl_h):
buff_body, buff_header, res, poolid = curl_h.response_queue
try:
ReqRespRequestFactory.from_http_object(
self.options,
res.history,
curl_h,
buff_header.getvalue(),
buff_body.getvalue(),
)
except Exception as e:
self.pool_map[poolid]["queue"].put(res.update(exception=e))
else:
# reset type to result otherwise backfeed items will enter an infinite loop
self.pool_map[poolid]["queue"].put(res.update())
with self.mutex_stats:
self.processed += 1
def _process_curl_should_retry(self, res, errno, poolid):
if errno not in UNRECOVERABLE_PYCURL_EXCEPTIONS:
res.history.wf_retries += 1
if res.history.wf_retries < self.options.get("retries"):
self._request_list.append((res, poolid))
return True
return False
def _process_curl_handle_error(self, res, errno, errmsg, poolid):
e = FuzzExceptNetError("Pycurl error %d: %s" % (errno, errmsg))
res.history.totaltime = 0
self.pool_map[poolid]["queue"].put(res.update(exception=e))
with self.mutex_stats:
self.processed += 1
def _read_multi_stack(self):
# Check for curl objects which have terminated, and add them to the curlh_freelist
while not self.exit_job:
while not self.exit_job:
ret, num_handles = self.m.perform()
if ret != pycurl.E_CALL_MULTI_PERFORM:
break
num_q, ok_list, err_list = self.m.info_read()
for curl_h in ok_list:
self._process_curl_handle(curl_h)
self.m.remove_handle(curl_h)
self.curlh_freelist.append(curl_h)
for curl_h, errno, errmsg in err_list:
buff_body, buff_header, res, poolid = curl_h.response_queue
if not self._process_curl_should_retry(res, errno, poolid):
self._process_curl_handle_error(res, errno, errmsg, poolid)
self.m.remove_handle(curl_h)
self.curlh_freelist.append(curl_h)
while self.curlh_freelist and self._request_list:
curl_h = self.curlh_freelist.pop()
fuzzres, poolid = self._request_list.popleft()
self.m.add_handle(self._prepare_curl_h(curl_h, fuzzres, poolid))
self._stop_to_pools()
# cleanup multi stack
for c in self.handles:
c.close()
self.curlh_freelist.append(c)
self.m.close()
================================================
FILE: src/wfuzz/myqueues.py
================================================
import collections
# python 2 and 3
try:
from itertools import zip_longest
except ImportError:
from itertools import izip_longest as zip_longest
from queue import PriorityQueue
from threading import Thread, RLock
from .fuzzobjects import FuzzError, FuzzType, FuzzItem
from .exception import FuzzException, FuzzExceptInternalError
class MyPriorityQueue(PriorityQueue):
def __init__(self, limit=0):
PriorityQueue.__init__(self, limit)
self.max_prio = 0
def _put_priority(self, prio, item, wait):
self.max_prio = max(prio, self.max_prio)
PriorityQueue.put(self, (prio, item), wait)
def put(self, item, wait=True):
self._put_priority(item.rlevel, item, wait)
def put_first(self, item, wait=True):
self._put_priority(0, item, wait)
def put_last(self, item, wait=True):
self._put_priority(self.max_prio + 1, item, wait)
def get(self):
prio, item = PriorityQueue.get(self, True)
return item
class FuzzQueue(MyPriorityQueue, Thread):
def __init__(self, options, queue_out=None, limit=0):
MyPriorityQueue.__init__(self, limit)
self.queue_out = queue_out
self.duplicated = False
self.syncq = None
self.stats = options.get("compiled_stats")
self.options = options
Thread.__init__(self)
self.setName(self.get_name())
def next_queue(self, q):
self.queue_out = q
def process(self, item):
raise NotImplementedError
def get_name(self):
raise NotImplementedError
def process_discarded(self):
return False
def items_to_process(self):
return [FuzzType.RESULT]
# Override this method if needed. This will be called just before cancelling the job.
def cancel(self):
pass
# Override this method if needed. This will be called just before starting the job.
def mystart(self):
pass
def set_syncq(self, q):
self.syncq = q
def qstart(self):
self.mystart()
self.start()
def send_first(self, item):
self.queue_out.put_first(item)
def send_last(self, item):
self.queue_out.put_last(item)
def qout_join(self):
self.queue_out.join()
def send(self, item):
self.queue_out.put(item)
def discard(self, item):
item.discarded = True
self.send(item)
def join(self):
MyPriorityQueue.join(self)
def tjoin(self):
Thread.join(self)
# Override this method if needed. This will be called after job's thread dies.
def _cleanup(self):
pass
def _throw(self, e):
self.syncq.put_first(FuzzError(e))
def get_stats(self):
return {self.get_name(): self.qsize()}
def run(self):
cancelling = False
while 1:
item = self.get()
try:
if item is None:
if not self.duplicated:
self.send_last(None)
self.task_done()
break
elif cancelling:
self.task_done()
continue
elif item.item_type == FuzzType.STARTSEED:
self.stats.mark_start()
elif item.item_type == FuzzType.ENDSEED:
if not self.duplicated:
self.send_last(item)
self.task_done()
continue
elif item.item_type == FuzzType.CANCEL:
cancelling = True
self.send_first(item)
self.task_done()
continue
if (
not item.discarded or (item.discarded and self.process_discarded())
) and item.item_type in self.items_to_process():
self.process(item)
else:
self.send(item)
self.task_done()
except Exception as e:
self.task_done()
self._throw(e)
self._cleanup()
class LastFuzzQueue(FuzzQueue):
def __init__(self, options, queue_out=None, limit=0):
FuzzQueue.__init__(self, options, queue_out, limit)
def get_name(self):
return "LastFuzzQueue"
def process(self):
pass
def _cleanup(self):
pass
def _throw(self, e):
self.queue_out.put_first(FuzzError(e))
def run(self):
cancelling = False
while 1:
item = self.get()
try:
self.task_done()
if item is None:
break
elif cancelling:
continue
elif item.item_type == FuzzType.ERROR:
self.qmanager.cancel()
self.send_first(item)
continue
elif item.item_type == FuzzType.CANCEL:
cancelling = True
continue
if item.item_type == FuzzType.RESULT and not item.discarded:
self.send(item)
if item.item_type == FuzzType.ENDSEED:
self.stats.pending_seeds.dec()
elif item.item_type == FuzzType.RESULT:
self.stats.processed.inc()
self.stats.pending_fuzz.dec()
if item.discarded:
self.stats.filtered.inc()
if self.stats.pending_fuzz() == 0 and self.stats.pending_seeds() == 0:
self.qmanager.cleanup()
except Exception as e:
self._throw(e)
self.qmanager.cancel()
self._cleanup()
class FuzzListQueue(FuzzQueue):
def __init__(self, options, queues_out, limit=0):
FuzzQueue.__init__(self, options, queues_out, limit)
# not to propagate a None/Exception to various queueas at the same level, only propagate through one queue
for q in self.queue_out[1:]:
q.duplicated = True
def set_syncq(self, q):
for q in self.queue_out:
q.syncq = q
def qstart(self):
for q in self.queue_out:
q.mystart()
q.start()
self.start()
def send_first(self, item):
for q in self.queue_out:
q.put_first(item)
def send_last(self, item):
for q in self.queue_out:
q.put_last(item)
def send(self, item):
for q in self.queue_out:
q.put(item)
def qout_join(self):
for q in self.queue_out:
q.join()
def join(self):
self.qout_join()
MyPriorityQueue.join(self)
def next_queue(self, nextq):
for qq in self.queue_out:
qq.next_queue(nextq)
def get_stats(self):
stat_list = []
for qq in self.queue_out:
stat_list = stat_list + list(qq.get_stats().items())
stat_list = stat_list + list(FuzzQueue.get_stats(self).items())
return dict(stat_list)
class FuzzRRQueue(FuzzListQueue):
def __init__(self, options, queues_out, limit=0):
FuzzListQueue.__init__(self, options, queues_out, limit)
self._next_queue = self._get_next_route()
def send(self, item):
next(self._next_queue).put(item)
def _get_next_route(self):
i = 0
while 1:
yield self.queue_out[i]
i += 1
i = i % len(self.queue_out)
class QueueManager:
def __init__(self, options):
self._queues = collections.OrderedDict()
self._lastq = None
self._syncq = None
self._mutex = RLock()
self.options = options
def add(self, name, q):
self._queues[name] = q
def bind(self, lastq):
with self._mutex:
queue_list = list(self._queues.values())
self._lastq = lastq
self._syncq = LastFuzzQueue(self.options, lastq)
self._syncq.qmanager = self
for first, second in zip_longest(queue_list[0:-1:1], queue_list[1::1]):
first.next_queue(second)
first.set_syncq(self._syncq)
queue_list[-1].next_queue(self._syncq)
queue_list[-1].set_syncq(self._syncq)
def __getitem__(self, key):
return self._queues[key]
def join(self, remove=False):
with self._mutex:
for k, q in list(self._queues.items()):
q.join()
if remove:
del self._queues[k]
def start(self):
with self._mutex:
if self._queues:
self._syncq.qstart()
for q in list(self._queues.values()):
q.qstart()
list(self._queues.values())[0].put_first(FuzzItem(FuzzType.STARTSEED))
def cleanup(self):
with self._mutex:
if self._queues:
list(self._queues.values())[0].put_last(None)
self.join(remove=True)
self.options.get("compiled_stats").mark_end()
self._lastq.put_last(None, wait=False)
self._queues = collections.OrderedDict()
self._lastq = None
def cancel(self):
with self._mutex:
if self._queues:
# stop processing pending items
for q in list(self._queues.values()):
q.cancel()
q.put_first(FuzzItem(FuzzType.CANCEL))
# wait for cancel to be processed
self.join()
# send None to stop (almost nicely)
self.cleanup()
def get_stats(self):
stat_list = []
for q in list(self._queues.values()):
stat_list = stat_list + list(q.get_stats().items())
return dict(stat_list)
================================================
FILE: src/wfuzz/options.py
================================================
from .exception import (
FuzzExceptBadRecipe,
FuzzExceptBadOptions,
FuzzExceptBadFile,
)
from .facade import (
Facade,
ERROR_CODE,
BASELINE_CODE,
)
from .factories.fuzzresfactory import resfactory
from .factories.dictfactory import dictionary_factory
from .fuzzobjects import FuzzStats
from .filters.ppfilter import FuzzResFilter
from .filters.simplefilter import FuzzResSimpleFilter
from .helpers.str_func import (
json_minify,
python2_3_convert_from_unicode,
)
from .core import Fuzzer
from .myhttp import HttpPool
from .externals.reqresp.cache import HttpCache
from collections import defaultdict
# python 2 and 3
try:
from collections import UserDict
except ImportError:
from UserDict import UserDict
import json
class FuzzSession(UserDict):
def __init__(self, **kwargs):
self.data = self._defaults()
self.keys_not_to_dump = [
"interactive",
"recipe",
"seed_payload",
"compiled_stats",
"compiled_dictio",
"compiled_simple_filter",
"compiled_filter",
"compiled_prefilter",
"compiled_printer",
"description",
"show_field",
"transport",
]
# recipe must be superseded by options
if "recipe" in kwargs and kwargs["recipe"]:
for recipe in kwargs["recipe"]:
self.import_from_file(recipe)
self.update(kwargs)
self.cache = HttpCache()
self.http_pool = None
self.stats = FuzzStats()
def _defaults(self):
return dict(
console_printer="",
hs=None,
hc=[],
hw=[],
hl=[],
hh=[],
ss=None,
sc=[],
sw=[],
sl=[],
sh=[],
payloads=None,
iterator=None,
printer=(None, None),
colour=False,
previous=False,
verbose=False,
interactive=False,
transport="http",
recipe=[],
save="",
proxies=None,
conn_delay=int(Facade().sett.get("connection", "conn_delay")),
req_delay=int(Facade().sett.get("connection", "req_delay")),
retries=int(Facade().sett.get("connection", "retries")),
rlevel=0,
dlevel=4,
scanmode=False,
delay=None,
concurrent=int(Facade().sett.get("connection", "concurrent")),
url="",
method=None,
auth={},
follow=False,
postdata=None,
headers=[],
cookie=[],
allvars=None,
script="",
script_args={},
connect_to_ip=None,
fields=[],
no_cache=False,
show_field=None,
# this is equivalent to payloads but in a different format
dictio=None,
# these will be compiled
seed_payload=False,
filter="",
prefilter=[],
compiled_filter=None,
compiled_prefilter=[],
compiled_printer=None,
compiled_seed=None,
compiled_baseline=None,
compiled_stats=None,
compiled_dictio=None,
exec_mode="api",
)
def update(self, options):
self.data.update(options)
def validate(self):
error_list = []
if self.data["dictio"] and self.data["payloads"]:
raise FuzzExceptBadOptions(
"Bad usage: Dictio and payloads options are mutually exclusive. Only one could be specified."
)
if self.data["rlevel"] > 0 and self.data["transport"] == "dryrun":
error_list.append(
"Bad usage: Recursion cannot work without making any HTTP request."
)
if self.data["script"] and self.data["transport"] == "dryrun":
error_list.append(
"Bad usage: Plugins cannot work without making any HTTP request."
)
if self.data["no_cache"] not in [True, False]:
raise FuzzExceptBadOptions("Bad usage: No-cache is a boolean value")
if not self.data["url"]:
error_list.append("Bad usage: You must specify an URL.")
if not self.data["payloads"] and not self.data["dictio"]:
error_list.append("Bad usage: You must specify a payload.")
if self.data["hs"] and self.data["ss"]:
raise FuzzExceptBadOptions(
"Bad usage: Hide and show regex filters flags are mutually exclusive. Only one could be specified."
)
if self.data["rlevel"] < 0:
raise FuzzExceptBadOptions(
"Bad usage: Recursion level must be a positive int."
)
if self.data["allvars"] not in [None, "allvars", "allpost", "allheaders"]:
raise FuzzExceptBadOptions(
"Bad options: Incorrect all parameters brute forcing type specified, correct values are allvars,allpost or allheaders."
)
if self.data["proxies"]:
for ip, port, ttype in self.data["proxies"]:
if ttype not in ("SOCKS5", "SOCKS4", "HTTP"):
raise FuzzExceptBadOptions(
"Bad proxy type specified, correct values are HTTP, SOCKS4 or SOCKS5."
)
return error_list
def export_to_file(self, filename):
try:
with open(filename, "w") as f:
f.write(self.export_json())
except IOError:
raise FuzzExceptBadFile("Error writing recipe file.")
def import_from_file(self, filename):
try:
with open(filename, "r") as f:
self.import_json(f.read())
except IOError:
raise FuzzExceptBadFile("Error loading recipe file {}.".format(filename))
except json.decoder.JSONDecodeError as e:
raise FuzzExceptBadRecipe(
"Incorrect JSON recipe {} format: {}".format(filename, str(e))
)
def import_json(self, data):
js = json.loads(json_minify(data))
try:
if js["version"] == "0.2" and "wfuzz_recipe" in js:
for k, v in js["wfuzz_recipe"].items():
if k not in self.keys_not_to_dump:
# python 2 and 3 hack
if k in self.data and isinstance(self.data[k], list):
self.data[k] += python2_3_convert_from_unicode(v)
else:
self.data[k] = python2_3_convert_from_unicode(v)
else:
raise FuzzExceptBadRecipe("Unsupported recipe version.")
except KeyError:
raise FuzzExceptBadRecipe("Incorrect recipe format.")
def export_json(self):
tmp = dict(version="0.2", wfuzz_recipe=defaultdict(dict))
defaults = self._defaults()
# Only dump the non-default options
for k, v in self.data.items():
if v != defaults[k] and k not in self.keys_not_to_dump:
tmp["wfuzz_recipe"][k] = self.data[k]
return json.dumps(tmp, sort_keys=True, indent=4, separators=(",", ": "))
def payload(self, **kwargs):
try:
self.data.update(kwargs)
self.compile_seeds()
self.compile_dictio()
for r in self.data["compiled_dictio"]:
yield tuple((fuzz_word.content for fuzz_word in r))
finally:
self.data["compiled_dictio"].cleanup()
def fuzz(self, **kwargs):
self.data.update(kwargs)
fz = None
try:
fz = Fuzzer(self.compile())
for f in fz:
yield f
finally:
if fz:
fz.cancel_job()
self.stats.update(self.data["compiled_stats"])
if self.http_pool:
self.http_pool.deregister()
self.http_pool = None
def get_payloads(self, iterator):
self.data["dictio"] = iterator
return self
def get_payload(self, iterator):
return self.get_payloads([iterator])
def __enter__(self):
self.http_pool = HttpPool(self)
self.http_pool.register()
return self
def __exit__(self, *args):
self.close()
def get_fuzz_words(self):
fuzz_words = self.data["compiled_filter"].get_fuzz_words()
for comp_obj in ["compiled_seed", "compiled_baseline"]:
if self.data[comp_obj]:
fuzz_words += self.data[comp_obj].payload_man.get_fuzz_words()
for prefilter in self.data["compiled_prefilter"]:
fuzz_words += prefilter.get_fuzz_words()
if self.data["url"] == "FUZZ":
fuzz_words.append("FUZZ")
return set(fuzz_words)
def compile_dictio(self):
if self.data["allvars"]:
self.data["compiled_dictio"] = dictionary_factory.create(
"dictio_from_allvar", self
)
else:
self.data["compiled_dictio"] = dictionary_factory.create(
"dictio_from_options", self
)
def compile_seeds(self):
self.data["compiled_seed"] = resfactory.create("seed_from_options", self)
self.data["compiled_baseline"] = resfactory.create(
"baseline_from_options", self
)
def compile(self):
# Validate options
error = self.validate()
if error:
raise FuzzExceptBadOptions(error[0])
self.data["seed_payload"] = True if self.data["url"] == "FUZZ" else False
# printer
try:
filename, printer = self.data["printer"]
except ValueError:
raise FuzzExceptBadOptions(
"Bad options: Printer must be specified in the form of ('filename', 'printer')"
)
if filename:
if printer == "default" or not printer:
printer = Facade().sett.get("general", "default_printer")
self.data["compiled_printer"] = Facade().printers.get_plugin(printer)(
filename
)
try:
for filter_option in ["hc", "hw", "hl", "hh", "sc", "sw", "sl", "sh"]:
self.data[filter_option] = [
BASELINE_CODE
if i == "BBB"
else ERROR_CODE
if i == "XXX"
else int(i)
for i in self.data[filter_option]
]
except ValueError:
raise FuzzExceptBadOptions(
"Bad options: Filter must be specified in the form of [int, ... , int, BBB, XXX]."
)
self.compile_seeds()
self.compile_dictio()
# filter options
self.data["compiled_simple_filter"] = FuzzResSimpleFilter.from_options(self)
self.data["compiled_filter"] = FuzzResFilter(self.data["filter"])
for prefilter in self.data["prefilter"]:
self.data["compiled_prefilter"].append(
FuzzResFilter(filter_string=prefilter)
)
self.data["compiled_stats"] = FuzzStats.from_options(self)
# Check payload num
fuzz_words = self.get_fuzz_words()
if self.data["compiled_dictio"].width() != len(fuzz_words):
raise FuzzExceptBadOptions(
"FUZZ words and number of payloads do not match!"
)
if self.data["allvars"] is None and len(fuzz_words) == 0:
raise FuzzExceptBadOptions("You must specify at least a FUZZ word!")
if self.data["compiled_baseline"] is None and (
BASELINE_CODE in self.data["hc"]
or BASELINE_CODE in self.data["hl"]
or BASELINE_CODE in self.data["hw"]
or BASELINE_CODE in self.data["hh"]
):
raise FuzzExceptBadOptions(
"Bad options: specify a baseline value when using BBB"
)
if self.data["script"]:
Facade().scripts.kbase.update(self.data["script_args"])
for k, v in Facade().sett.get_section("kbase"):
if k not in self.data["script_args"]:
Facade().scripts.kbase[k] = v
if not self.http_pool:
self.http_pool = HttpPool(self)
self.http_pool.register()
if self.data["colour"]:
Facade().printers.kbase["colour"] = True
if self.data["verbose"]:
Facade().printers.kbase["verbose"] = True
return self
def close(self):
if self.data["compiled_dictio"]:
self.data["compiled_dictio"].cleanup()
if self.http_pool:
self.http_pool.deregister()
self.http_pool = None
================================================
FILE: src/wfuzz/plugin_api/__init__.py
================================================
================================================
FILE: src/wfuzz/plugin_api/base.py
================================================
from wfuzz.fuzzobjects import FuzzWord, FuzzPlugin
from wfuzz.exception import (
FuzzExceptBadFile,
FuzzExceptBadOptions,
FuzzExceptPluginError,
)
from wfuzz.facade import Facade
from wfuzz.factories.plugin_factory import plugin_factory
from wfuzz.helpers.file_func import find_file_in_paths
import sys
import os
from distutils import util
# python 2 and 3: iterator
from builtins import object
# Util methods for accessing search results
class BasePlugin:
def __init__(self):
self.results_queue = None
self.base_fuzz_res = None
# check mandatory params, assign default values
for name, default_value, required, description in self.parameters:
param_name = "{}.{}".format(self.name, name)
if required and param_name not in list(self.kbase.keys()):
raise FuzzExceptBadOptions(
"Plugins, missing parameter %s!" % (param_name,)
)
if param_name not in list(self.kbase.keys()):
self.kbase[param_name] = default_value
def run(self, fuzzresult, control_queue, results_queue):
try:
self.results_queue = results_queue
self.base_fuzz_res = fuzzresult
self.process(fuzzresult)
except Exception as e:
results_queue.put(plugin_factory.create("plugin_from_error", self.name, e))
finally:
control_queue.get()
control_queue.task_done()
return
def process(self, fuzzresult):
"""
This is were the plugin processing is done. Any wfuzz plugin must implement this method, do its job with the fuzzresult received and:
- queue_url: if it is a discovery plugin enqueing more HTTP request that at some point will generate more results
- add_result: Add information about the obtained results after the processing with an accurate description
A kbase (get_kbase, has_kbase, add_kbase) is shared between all plugins. this can be used to store and retrieve relevant "collaborative" information.
"""
raise NotImplementedError
def validate(self):
raise FuzzExceptPluginError("Method count not implemented")
def add_result(self, itype, issue, data, severity=FuzzPlugin.INFO):
self.results_queue.put(
plugin_factory.create(
"plugin_from_finding", self.name, itype, issue, data, severity
)
)
def queue_url(self, url):
self.results_queue.put(
plugin_factory.create(
"plugin_from_recursion", self.name, self.base_fuzz_res, url
)
)
def _bool(self, value):
return bool(util.strtobool(value))
class BasePrinter:
def __init__(self, output):
self.f = None
if output:
try:
self.f = open(output, "w")
except IOError as e:
raise FuzzExceptBadFile("Error opening file. %s" % str(e))
else:
self.f = sys.stdout
self.verbose = Facade().printers.kbase["verbose"]
def header(self):
raise FuzzExceptPluginError("Method header not implemented")
def footer(self):
raise FuzzExceptPluginError("Method footer not implemented")
def result(self):
raise FuzzExceptPluginError("Method result not implemented")
class BasePayload(object):
def __init__(self, params):
self.params = params
# default params
if "default" in self.params:
self.params[self.default_parameter] = self.params["default"]
if not self.default_parameter:
raise FuzzExceptBadOptions("Too many plugin parameters specified")
# Check for allowed parameters
if [
k
for k in list(self.params.keys())
if k not in [x[0] for x in self.parameters]
and k not in ["encoder", "default"]
]:
raise FuzzExceptBadOptions(
"Plugin %s, unknown parameter specified!" % (self.name)
)
# check mandatory params, assign default values
for name, default_value, required, description in self.parameters:
if required and name not in self.params:
raise FuzzExceptBadOptions(
"Plugin %s, missing parameter %s!" % (self.name, name)
)
if name not in self.params:
self.params[name] = default_value
def get_type(self):
raise FuzzExceptPluginError("Method get_type not implemented")
def get_next(self):
raise FuzzExceptPluginError("Method get_next not implemented")
def __next__(self):
return FuzzWord(self.get_next(), self.get_type())
def count(self):
raise FuzzExceptPluginError("Method count not implemented")
def __iter__(self):
return self
def close(self):
pass
def find_file(self, name):
if os.path.exists(name):
return name
for pa in Facade().sett.get("general", "lookup_dirs").split(","):
fn = find_file_in_paths(name, pa)
if fn is not None:
return fn
return name
================================================
FILE: src/wfuzz/plugin_api/mixins.py
================================================
# Plugins specializations with common methods useful for their own type
from wfuzz.plugin_api.urlutils import parse_url
from .base import BasePlugin
class DiscoveryPluginMixin:
def queue_url(self, url):
if not parse_url(url).isbllist:
BasePlugin.queue_url(self, url)
return True
return False
================================================
FILE: src/wfuzz/plugin_api/payloadtools.py
================================================
from wfuzz.exception import (
FuzzExceptMissingAPIKey,
FuzzExceptResourceParseError,
FuzzExceptPluginLoadError,
)
from wfuzz.facade import Facade
from wfuzz.helpers.utils import MyCounter
# Python 2 and 3: alternative 4
try:
from urllib.request import Request
from urllib.request import build_opener
except ImportError:
from urllib2 import Request
from urllib2 import build_opener
import json
# python 2 and 3: iterator
from builtins import object
from threading import Thread
from queue import Queue
IMPORTED_SHODAN = True
try:
import shodan
except ImportError:
IMPORTED_SHODAN = False
m = {
"matches": [
{
"_shodan": {
"id": "54e0ae62-9e22-404b-91b4-92f99e89c987",
"options": {},
"ptr": True,
"module": "auto",
"crawler": "62861a86c4e4b71dceed5113ce9593b98431f89a",
},
"hash": -1355923443,
"os": None,
"ip": 1240853908,
"isp": "Comcast Cable",
"http": {
"html_hash": -2142469325,
"robots_hash": None,
"redirects": [],
"securitytxt": None,
"title": "400 Bad Request",
"sitemap_hash": None,
"robots": None,
"favicon": None,
"host": "73.245.237.148",
"html": '\n\n400 Bad Request\n\n
Bad Request
\n
Your browser sent a request that this server could not understand. \nReason: You\'re speaking plain HTTP to an SSL-enabled server port. \n Instead use the HTTPS scheme to access this URL, please. \n
\n
Additionally, a 404 Not Found\nerror was encountered while trying to use an ErrorDocument to handle the request.
\nBrowser not authentication-capable or authentication failed.\n\n",
"location": "/",
"components": {},
"server": "Apache",
"sitemap": None,
"securitytxt_hash": None,
},
"cpe": ["cpe:/a:apache:http_server"],
"port": 8085,
"hostnames": [],
"location": {
"city": "$ helmsford",
"region_code": "E4",
"area_code": None,
"longitude": 0.48330000000001405,
"country_code3": "GBR",
"country_name": "United Kingdom",
"postal_code": "CM2",
"dma_code": None,
"country_code": "GB",
"latitude": 51.733300000000014,
},
"timestamp": "2019-04-10T11:03:59.955967",
"$ omains": [],
"org": "EE High Speed Internet",
"data": 'HTTP/1.1 401 Unauthorized\r\nServer: Apache\r\nConnection: Close\r\nContent-type: text/html\r\nWWW-Authenticate: Digest realm="DSLForum CPE Management", algorithm=MD5, qop=auth, stale=FALSE, nonce="3d7a3f71e72e095dba31fd77d4db74$5", opaque="5ccc069c403ebaf9f0171e9517f40e41"\r\n\r\n',
"asn": "AS12576",
"transport": "tcp",
"ip_str": "2.25.131.132",
},
]
}
class BingIter(object):
def __init__(self, dork, offset=0, limit=0, key=None):
if key is None:
key = Facade().sett.get("plugins", "bing_apikey")
if not key:
raise FuzzExceptMissingAPIKey(
"An api Bing key is needed. Please chek wfuzz.ini."
)
self._key = key
self._dork = dork
self.max_count = 0
self.current = 0
self._index = 0
self._retrieved = 0
self._results = []
# first bing request to get estimated total count (it does not take into consideration offset).
if limit > 0 and limit < 50:
total_results, self._retrieved, self._results = self._do_search(
offset, limit
)
else:
total_results, self._retrieved, self._results = self._do_search(offset)
# offset not over the results
if offset > total_results:
self._offset = total_results
else:
self._offset = offset
self.max_count = total_results - self._offset
# no more than limit results
if self.max_count > limit and limit > 0:
self.max_count = limit
def _do_search(self, offset=0, limit=50):
# some code taken from http://www.securitybydefault.com/2014/07/search2auditpy-deja-que-bing-haga-el.html?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+SecurityByDefault+%28Security+By+Default%29
# api doc http://go.microsoft.com/fwlink/?LinkID=248077
user_agent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; FDM; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 1.1.4322)"
creds = (":%s" % self._key).encode("base64")[:-1]
auth = "Basic %s" % creds
result = None
try:
urlstr = (
"https://api.datamarket.azure.com/Data.ashx/Bing/Search/Composite?Sources=%27web%27&Query=%27"
+ self._dork
+ "%27&$format=json"
)
if limit != 50:
urlstr += "&$top=%d" % limit
if offset != 0:
urlstr += "&$skip=%d" % offset
request = Request(urlstr)
request.add_header("Authorization", auth)
request.add_header("User-Agent", user_agent)
requestor = build_opener()
result = requestor.open(request)
except Exception as e:
raise FuzzExceptResourceParseError(
"Error when retrieving Bing API results: %s." % str(e)
)
results = json.loads(result.read())
# WebTotal is not reliable, it is usually much bigger than the actual results, therefore
# if your offset increases over the real number of results, you get a dict
# without values and counters to ''. It gets updated when you are close to that limit though.
if results["d"]["results"][0]["WebTotal"]:
res_total = int(results["d"]["results"][0]["WebTotal"])
res_list = results["d"]["results"][0]["Web"]
return res_total, len(res_list), res_list
else:
return 0, 0, 0
def __iter__(self):
return self
def __next__(self):
if self.current >= self.max_count:
raise StopIteration
# Result buffer already consumed
if self._index >= self._retrieved:
realcount, self._retrieved, self._results = self._do_search(
self.current + self._offset
)
self._index = 0
# update real count
if self.max_count > realcount:
self.max_count = realcount
elem = self._results[self._index]["Url"].strip()
self.current += 1
self._index += 1
# pycurl does not like unicode
if isinstance(elem, str):
return elem.encode("utf-8")
else:
return elem
class ShodanIter:
SHODAN_RES_PER_PAGE = 100
MAX_ENQUEUED_RES = SHODAN_RES_PER_PAGE + 1
NUM_OF_WORKERS = 1
SLOW_START = True
def __init__(self, dork, page, limit):
if IMPORTED_SHODAN is False:
raise FuzzExceptPluginLoadError(
"shodan module not imported. Please, install shodan using pip"
)
key = Facade().sett.get("plugins", "shodan_apikey")
if not key:
raise FuzzExceptMissingAPIKey(
"A Shodan api key is needed. Please check ~/.wfuzz/wfuzz.ini"
)
self.api = shodan.Shodan(key)
self._dork = dork
self._page = MyCounter(page)
self._page_limit = self._page() + limit if limit > 0 else -1
self.results_queue = Queue(self.MAX_ENQUEUED_RES)
self.page_queue = Queue()
self._threads = []
self._started = False
self._cancel_job = False
def _do_search(self):
while 1:
page = self.page_queue.get()
if page is None:
self.page_queue.task_done()
break
if self._cancel_job:
self.page_queue.task_done()
continue
if self._page_limit > 0 and page >= self._page_limit:
self.page_queue.task_done()
self.results_queue.put(None)
continue
try:
results = self.api.search(self._dork, page=page)
for item in results["matches"]:
if not self._cancel_job:
self.results_queue.put(item)
self.page_queue.task_done()
if not self._cancel_job:
self.page_queue.put(self._page.inc())
except shodan.APIError as e:
self.page_queue.task_done()
if "Invalid page size" in str(e):
self.results_queue.put(None)
elif "Insufficient query credits" in str(e):
self.results_queue.put(None)
else:
self.results_queue.put(e)
continue
def __iter__(self):
return self
def _start(self):
for th_n in range(self.NUM_OF_WORKERS):
worker = Thread(target=self._do_search)
worker.setName("_do_search_{}".format(str(th_n)))
self._threads.append(worker)
worker.start()
self.page_queue.put(self._page())
if not self.SLOW_START:
for _ in range(self.NUM_OF_WORKERS - 1):
self.page_queue.put(self._page.inc())
def _stop(self):
self._cancel_job = True
for th in self._threads:
self.page_queue.put(None)
self.page_queue.join()
for th in self._threads:
th.join()
self._threads = []
self.results_queue.put(None)
def __next__(self):
if not self._started:
self._start()
self._started = True
res = self.results_queue.get()
self.results_queue.task_done()
if res is None:
self._stop()
self._cancel_job = False
self._started = False
raise StopIteration
elif isinstance(res, Exception):
self._stop()
raise res
return res
================================================
FILE: src/wfuzz/plugin_api/urlutils.py
================================================
import os
# Python 2 and 3
import sys
if sys.version_info >= (3, 0):
from urllib.parse import ParseResult
from urllib.parse import urlparse
from urllib.parse import parse_qs
else:
from urlparse import ParseResult
from urlparse import urlparse
from urlparse import parse_qs
from wfuzz.facade import Facade
from wfuzz.exception import FuzzExceptBadAPI
class FuzzRequestParse(ParseResult):
@property
def ffname(self):
"""
Returns script plus extension from an URL. ie. http://www.localhost.com/kk/index.html?id=3
will return index.html
"""
u = self.path.split("/")[-1:][0]
return u
@property
def fext(self):
"""
Returns script extension from an URL. ie. http://www.localhost.com/kk/index.html?id=3
will return .html
"""
return os.path.splitext(self.ffname)[1]
@property
def fname(self):
"""
Returns script name from an URL. ie. http://www.localhost.com/kk/index.html?id=3
will return index
"""
return os.path.splitext(self.ffname)[0]
@property
def isbllist(self):
fext = self.fext
return fext != "." and fext in Facade().sett.get(
"kbase", "discovery.blacklist"
).split("-")
@property
def hasquery(self):
return self.query != ""
def cache_key(self, base_urlp=None):
scheme = self.scheme
netloc = self.netloc
if base_urlp:
scheme = self.scheme if self.scheme else base_urlp.scheme
netloc = self.netloc if self.netloc else base_urlp.netloc
key = "{}-{}-{}-{}".format(scheme, netloc, self.path, self.params)
dicc = {"g{}".format(key): True for key in parse_qs(self.query).keys()}
# take URL parameters into consideration
url_params = list(dicc.keys())
url_params.sort()
key += "-" + "-".join(url_params)
return key
def parse_url(url):
# >>> urlparse.urlparse("http://some.page.pl/nothing.py;someparam=some;otherparam=other?query1=val1&query2=val2#frag")
# ParseResult(scheme='http', netloc='some.page.pl', path='/nothing.py', params='someparam=some;otherparam=other', query='query1=val1&query2=val2', fragment='frag')
scheme, netloc, path, params, query, fragment = urlparse(url)
return FuzzRequestParse(scheme, netloc, path, params, query, fragment)
def check_content_type(fuzzresult, which):
ctype = None
if "Content-Type" in fuzzresult.history.headers.response:
ctype = fuzzresult.history.headers.response["Content-Type"]
if which == "text":
return not ctype or (
ctype and any([ctype.find(x) >= 0 for x in ["text/plain"]])
)
else:
raise FuzzExceptBadAPI("Unknown content type")
================================================
FILE: src/wfuzz/plugins/__init__.py
================================================
================================================
FILE: src/wfuzz/plugins/encoders/__init__.py
================================================
================================================
FILE: src/wfuzz/plugins/encoders/encoders.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
# Python 2 and 3
try:
from urllib.parse import quote
from urllib.parse import unquote
except ImportError:
from urllib import quote
from urllib import unquote
# Python 2 and 3
try:
from base64 import decodebytes as b64decode
from base64 import standard_b64encode
except ImportError:
from base64 import decodestring as b64decode
from base64 import standard_b64encode
import re
import binascii
import random
import hashlib
import html
@moduleman_plugin("encode")
class none:
name = "none"
author = ("Xavi Mendez (@xmendez)",)
version = "0.1"
summary = "Returns string without changes"
category = ["default"]
priority = 99
def encode(self, string):
return string
def decode(self, string):
return string
@moduleman_plugin("encode")
class urlencode:
name = "urlencode"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Replace special characters in string using the %xx escape. Letters, digits, and the characters '_.-' are never quoted."
category = ["url_safe", "url"]
priority = 99
def encode(self, string):
return quote(string)
def decode(self, string):
return unquote(string)
@moduleman_plugin("encode")
class double_urlencode:
name = "double_urlencode"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Applies a double encode to special characters in string using the %25xx escape. Letters, digits, and the characters '_.-' are never quoted."
category = ["url_safe", "url"]
priority = 99
def encode(self, string):
return quote(quote(string))
def decode(self, string):
return unquote(unquote(string))
@moduleman_plugin("encode")
class base64:
name = "base64"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Encodes the given string using base64"
category = ["hashes"]
priority = 99
def encode(self, string):
return standard_b64encode(string.encode("utf-8")).decode("utf-8")
def decode(self, string):
return b64decode(string.encode("utf-8")).decode("utf-8")
@moduleman_plugin("encode")
class uri_triple_hex:
name = "uri_triple_hex"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Encodes ALL charachers using the %25%xx%xx escape."
category = ["url"]
priority = 99
def encode(self, string):
strt = ""
s = re.compile(r"/|;|=|:|&|@|\\|\?")
for c in string:
if s.search(c):
strt += c
continue
temp = hex(ord(c))[2:]
strt += "%%25%%%02x%%%02x" % (ord(temp[:1]), ord(temp[1:]))
return strt
@moduleman_plugin("encode")
class uri_double_hex:
name = "uri_double_hex"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Encodes ALL charachers using the %25xx escape."
category = ["url"]
priority = 99
def encode(self, string):
strt = ""
con = "%%25%02x"
s = re.compile(r"/|;|=|:|&|@|\\|\?")
for c in string:
if s.search(c):
strt += c
continue
strt += con % ord(c)
return strt
@moduleman_plugin("encode")
class uri_hex:
name = "uri_hex"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Encodes ALL charachers using the %xx escape."
category = ["url"]
priority = 99
def encode(self, string):
strt = ""
con = "%%%02x"
s = re.compile(r"/|;|=|:|&|@|\\|\?")
for c in string:
if s.search(c):
strt += c
continue
strt += con % ord(c)
return strt
@moduleman_plugin("encode")
class random_upper:
name = "random_upper"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Replaces random characters in string with its capitals letters"
category = ["default"]
priority = 99
def encode(self, string):
strt = ""
for c in string:
x = int(random.uniform(0, 10))
x = x % 2
if x == 1:
strt += c.upper()
else:
strt += c
return strt
@moduleman_plugin("encode")
class second_nibble_hex:
name = "second_nibble_hex"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Replaces ALL characters in string using the %?%dd escape"
category = ["url"]
priority = 99
def encode(self, string):
strt = ""
s = re.compile(r"/|;|=|:|&|@|\\|\?")
for c in string:
if s.search(c):
strt += c
continue
temp = hex(ord(c))[2:]
strt += "%%%s%%%02x" % (str(temp[:1]), ord(temp[1:]))
return strt
@moduleman_plugin("encode")
class first_nibble_hex:
name = "first_nibble_hex"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Replaces ALL characters in string using the %%dd? escape"
category = ["url"]
priority = 99
def encode(self, string):
strt = ""
s = re.compile(r"/|;|=|:|&|@|\\|\?")
for c in string:
if s.search(c):
strt += c
continue
temp = hex(ord(c))[2:]
strt += "%%%%%02x%s" % (ord(temp[:1]), str(temp[1:]))
return strt
@moduleman_plugin("encode")
class doble_nibble_hex:
name = "doble_nibble_hex"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Replaces ALL characters in string using the %%dd%dd escape"
category = ["url"]
priority = 99
def encode(self, string):
strt = ""
fin = ""
con = "%%%02x"
s = re.compile(r"/|;|=|:|&|@|\\|\?")
enc = uri_hex()
strt = enc.encode(string)
for c in strt:
if not c == "%":
if s.search(c):
fin += c
continue
fin += con % ord(c)
else:
fin += c
return fin
@moduleman_plugin("encode")
class sha1:
name = "sha1"
summary = "Applies a sha1 hash to the given string"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
category = ["hashes"]
priority = 99
def encode(self, string):
s = hashlib.sha1()
s.update(string.encode("utf-8"))
res = s.hexdigest()
return res
@moduleman_plugin("encode")
class sha256:
name = "sha256"
summary = "Applies a sha256 hash to the given string"
author = ("Dustin Evans (@dustinaevans)",)
version = "0.1"
category = ["hashes"]
priority = 99
def encode(self, string):
s = hashlib.sha256()
s.update(string.encode("utf-8"))
res = s.hexdigest()
return res
@moduleman_plugin("encode")
class sha512:
name = "sha512"
summary = "Applies a sha512 hash to the given string"
author = ("Dustin Evans (@dustinaevans)",)
version = "0.1"
category = ["hashes"]
priority = 99
def encode(self, string):
s = hashlib.sha512()
s.update(string.encode("utf-8"))
res = s.hexdigest()
return res
@moduleman_plugin("encode")
class md5:
name = "md5"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Applies a md5 hash to the given string"
category = ["hashes"]
priority = 99
def encode(self, string):
m = hashlib.new("md5")
m.update(string.encode("utf-8"))
res = m.hexdigest()
return res
@moduleman_plugin("encode")
class hexlify:
name = "hexlify"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Every byte of data is converted into the corresponding 2-digit hex representation."
category = ["default"]
priority = 99
def encode(self, string):
return binascii.hexlify(string.encode("utf-8")).decode("utf-8")
def decode(self, string):
return binascii.unhexlify(string.encode("utf-8")).decode("utf-8")
@moduleman_plugin("encode")
class html_escape:
name = "html_escape"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = 'Convert the characters &<>" in string to HTML-safe sequences.'
category = ["html"]
priority = 99
def encode(self, string):
return html.escape(string, quote=True)
@moduleman_plugin("encode")
class html_decimal:
name = "html_decimal"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Replaces ALL characters in string using the dd; escape"
category = ["html"]
priority = 99
def encode(self, string):
new = ""
for x in string:
new += "" + str(ord(x)) + ";"
return new
@moduleman_plugin("encode")
class html_hexadecimal:
name = "html_hexadecimal"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Replaces ALL characters in string using the x; escape"
category = ["html"]
priority = 99
def encode(self, string):
new = ""
for x in string:
val = "%02x" % ord(x)
new += "" + str(val) + ";"
return new
@moduleman_plugin("encode")
class utf8_binary:
name = "utf8_binary"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Replaces ALL characters in string using the \\uxx escape"
category = ["url"]
priority = 99
def encode(self, string):
new = ""
for x in string:
val = "%02x" % ord(x)
new += "\\x" + str(val)
return new
@moduleman_plugin("encode")
class utf8:
name = "utf8"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Replaces ALL characters in string using the \\u00xx escape"
category = ["url"]
priority = 99
def encode(self, string):
new = ""
for x in string:
val = "%02x" % ord(x)
if len(val) == 2:
new += "\\u00" + str(val)
else:
new += "\\u" + str(val)
return new
@moduleman_plugin("encode")
class uri_unicode:
name = "uri_unicode"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Replaces ALL characters in string using the %u00xx escape"
category = ["url"]
priority = 99
def encode(self, string):
new = ""
for x in string:
val = "%02x" % ord(x)
if len(val) == 2:
new += "%u00" + str(val)
else:
new += "%u" + str(val)
return new
@moduleman_plugin("encode")
class mysql_char:
name = "mysql_char"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Converts ALL characters to MySQL's char(xx)"
category = ["db"]
priority = 99
def encode(self, string):
new = "CHAR("
for x in string:
val = str(ord(x))
new += str(val) + ","
new = new.strip(",")
new += ")"
return new
def decode(self, string):
temp = string.strip("CHAR").strip("(").strip(")").split(",")
new = ""
for x in temp:
new += chr(int(x))
return new
@moduleman_plugin("encode")
class mssql_char:
name = "mssql_char"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Converts ALL characters to MsSQL's char(xx)"
category = ["db"]
priority = 99
def encode(self, string):
new = ""
for x in string:
val = str(ord(x))
new += "CHAR(" + str(val) + ")+"
new = new.strip("+")
return new
def decode(self, string):
new = ""
temp = string.split("+")
for x in temp:
x = x.strip("CHAR").strip(")").strip("(")
new += chr(int(x))
return new
@moduleman_plugin("encode")
class oracle_char:
name = "oracle_char"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Converts ALL characters to Oracle's chr(xx)"
category = ["db"]
priority = 99
def encode(self, string):
new = ""
for x in string:
val = str(ord(x))
new += "chr(" + val + ")||"
new = new.strip("||")
return new
def decode(self, string):
new = ""
temp = string.split("||")
for x in temp:
x = x.strip("chr").strip(")").strip("(")
new += chr(int(x))
return new
================================================
FILE: src/wfuzz/plugins/iterators/__init__.py
================================================
================================================
FILE: src/wfuzz/plugins/iterators/iterations.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.dictionaries import BaseIterator
import itertools
from functools import reduce
from builtins import zip as builtinzip
@moduleman_plugin
class zip(BaseIterator):
name = "zip"
author = ("Xavi Mendez (@xmendez)",)
version = "0.1"
summary = "Returns an iterator that aggregates elements from each of the iterables."
category = ["default"]
priority = 99
def __init__(self, *i):
self._payload_list = i
self.__width = len(i)
self.__count = min([x.count() for x in i])
self.it = builtinzip(*i)
def count(self):
return self.__count
def width(self):
return self.__width
def payloads(self):
return self._payload_list
def __next__(self):
return next(self.it)
def __iter__(self):
return self
@moduleman_plugin
class product(BaseIterator):
name = "product"
author = ("Xavi Mendez (@xmendez)",)
version = "0.1"
summary = "Returns an iterator cartesian product of input iterables."
category = ["default"]
priority = 99
def __init__(self, *i):
self._payload_list = i
self.__width = len(i)
self.__count = reduce(lambda x, y: x * y.count(), i[1:], i[0].count())
self.it = itertools.product(*i)
def count(self):
return self.__count
def width(self):
return self.__width
def payloads(self):
return self._payload_list
def __next__(self):
return next(self.it)
def __iter__(self):
return self
@moduleman_plugin
class chain(BaseIterator):
name = "chain"
author = ("Xavi Mendez (@xmendez)",)
version = "0.1"
summary = "Returns an iterator returns elements from the first iterable until it is exhausted, then proceeds to the next iterable, until all of the iterables are exhausted."
category = ["default"]
priority = 99
def __init__(self, *i):
self._payload_list = i
self.__count = sum([x.count() for x in i])
self.it = itertools.chain(*i)
def count(self):
return self.__count
def width(self):
return 1
def payloads(self):
return self._payload_list
def __next__(self):
return (next(self.it),)
def __iter__(self):
return self
================================================
FILE: src/wfuzz/plugins/payloads/__init__.py
================================================
================================================
FILE: src/wfuzz/plugins/payloads/autorize.py
================================================
import re
import base64
from wfuzz.exception import FuzzExceptBadFile
from wfuzz.fuzzobjects import FuzzResult, FuzzWordType
from wfuzz.fuzzrequest import FuzzRequest
from wfuzz.plugin_api.base import BasePayload
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.helpers.obj_dyn import rgetattr
@moduleman_plugin
class autorize(BasePayload):
name = "autorize"
author = ("Xavi Mendez (@xmendez)",)
version = "0.2"
description = ("Reads burp extension autorize states",)
summary = "Returns fuzz results' from autorize."
category = ["default"]
priority = 99
parameters = (
("fn", "", True, "Filename of a valid autorize state file."),
(
"attr",
None,
False,
"Attribute of fuzzresult to return. If not specified the whole object is returned.",
),
)
default_parameter = "fn"
def __init__(self, params):
BasePayload.__init__(self, params)
self.__max = -1
self.attr = self.params["attr"]
self._it = self._gen_wfuzz(self.params["fn"])
def count(self):
return self.__max
def get_next(self):
next_item = next(self._it)
return next_item if not self.attr else rgetattr(next_item, self.attr)
def get_type(self):
return FuzzWordType.WORD
def _gen_wfuzz(self, output_fn):
try:
with open(self.find_file(output_fn), "r") as f:
for (
url1,
port1,
schema1,
req1,
resp1,
url2,
port2,
schema2,
req2,
resp2,
url3,
port3,
schema3,
req3,
resp3,
res1,
res2,
) in [re.split(r"\t+", x) for x in f.readlines()]:
raw_req1 = base64.decodestring(req2)
# raw_res1 = base64.decodestring(res2)
item = FuzzResult()
item.history = FuzzRequest()
item.history.update_from_raw_http(raw_req1, schema1)
yield item
except IOError as e:
raise FuzzExceptBadFile("Error opening wfuzz payload file. %s" % str(e))
except EOFError:
raise StopIteration
================================================
FILE: src/wfuzz/plugins/payloads/bing.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.plugin_api.payloadtools import BingIter
from wfuzz.plugin_api.base import BasePayload
from wfuzz.fuzzobjects import FuzzWordType
@moduleman_plugin
class bing(BasePayload):
name = "bing"
author = ("Xavi Mendez (@xmendez)",)
version = "0.2"
description = (
'intitle:"JBoss JMX Management Console"',
"Some examples of bing hacking:",
"http://www.elladodelmal.com/2010/02/un-poco-de-bing-hacking-i-de-iii.html",
)
summary = "Returns URL results of a given bing API search (needs api key)."
category = ["default"]
priority = 99
parameters = (
("dork", "", True, "Google dork search string."),
("offset", "0", False, "Offset index, starting at zero."),
("limit", "0", False, "Number of results. Zero for all."),
)
default_parameter = "dork"
def __init__(self, params):
BasePayload.__init__(self, params)
offset = int(params["offset"])
limit = int(params["limit"])
self._it = BingIter(params["dork"], offset, limit)
def count(self):
return self._it.max_count
def get_next(self):
return next(self._it)
def get_type(self):
return FuzzWordType.WORD
================================================
FILE: src/wfuzz/plugins/payloads/buffer_overflow.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.plugin_api.base import BasePayload
from wfuzz.fuzzobjects import FuzzWordType
@moduleman_plugin
class buffer_overflow(BasePayload):
name = "buffer_overflow"
author = ("Xavi Mendez (@xmendez)",)
version = "0.2"
description = ()
summary = "Returns a string using the following pattern A * given number."
category = ["default"]
priority = 99
parameters = (("size", "", True, "Size of the overflow string."),)
default_parameter = "size"
def __init__(self, params):
BasePayload.__init__(self, params)
self.bov_list = ["A" * int(self.params["size"])]
self.current = 0
def count(self):
return 1
def get_next(self):
if self.current == 0:
elem = self.bov_list[self.current]
self.current += 1
return elem
else:
raise StopIteration
def get_type(self):
return FuzzWordType.WORD
================================================
FILE: src/wfuzz/plugins/payloads/burpitem.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.exception import FuzzExceptBadFile
from wfuzz.fuzzobjects import FuzzResult, FuzzWordType
from wfuzz.fuzzrequest import FuzzRequest
from wfuzz.plugin_api.base import BasePayload
from wfuzz.helpers.obj_dyn import rgetattr
import xml.etree.cElementTree as ET
from base64 import b64decode
@moduleman_plugin
class burpitem(BasePayload):
name = "burpitem"
author = ("Bendegúz Nagy (@PaperTsar)",)
version = "0.1"
description = (
"This payload loads request/response from items saved from Burpsuite.",
)
summary = "This payload loads request/response from items saved from Burpsuite."
category = ["default"]
priority = 99
parameters = (
("fn", "", True, "Filename of a valid Burp item file."),
(
"attr",
None,
False,
"Attribute of fuzzresult to return. If not specified the whole object is returned.",
),
)
default_parameter = "fn"
def __init__(self, params):
BasePayload.__init__(self, params)
self.__max = -1
self.attr = self.params["attr"]
self._it = self._gen_burpitem(self.params["fn"])
def count(self):
return self.__max
def get_next(self):
next_item = next(self._it)
return next_item if not self.attr else rgetattr(next_item, self.attr)
def get_type(self):
return FuzzWordType.FUZZRES if not self.attr else FuzzWordType.WORD
def _gen_burpitem(self, output_fn):
try:
tree = ET.parse(self.find_file(output_fn))
for item in tree.getroot().iter("item"):
fr = FuzzRequest()
fr.update_from_raw_http(
raw=b64decode(item.find("request").text or "").decode("utf-8"),
scheme=item.find("protocol").text,
raw_response=b64decode(item.find("response").text or ""),
)
fr.wf_ip = {
"ip": item.find("host").attrib.get("ip", None)
or item.find("host").text,
"port": item.find("port").text,
}
frr = FuzzResult(history=fr)
yield frr.update()
return
except IOError as e:
raise FuzzExceptBadFile(
"Error opening Burp items payload file. %s" % str(e)
)
except EOFError:
return
================================================
FILE: src/wfuzz/plugins/payloads/burplog.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.exception import FuzzExceptBadFile
from wfuzz.fuzzobjects import FuzzResult
from wfuzz.fuzzrequest import FuzzRequest
from wfuzz.plugin_api.base import BasePayload
from wfuzz.helpers.obj_dyn import rgetattr
from wfuzz.fuzzobjects import FuzzWordType
import re
import sys
if sys.version_info < (3, 0):
from io import open
CRLF = "\n"
DELIMITER = "%s%s" % ("=" * 54, CRLF)
CRLF_DELIMITER = CRLF + DELIMITER
HEADER = re.compile(
r"(\d{1,2}:\d{2}:\d{2} (AM|PM|))[ \t]+(\S+)([ \t]+\[(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|unknown host)\])?"
)
@moduleman_plugin
class burplog(BasePayload):
name = "burplog"
author = ("Xavi Mendez (@xmendez)",)
version = "0.1"
description = ("Returns fuzz results' URL from a Burp log.",)
summary = "Returns fuzz results from a Burp log."
category = ["default"]
priority = 99
parameters = (
("fn", "", True, "Filename of a valid Burp log file."),
(
"attr",
None,
False,
"Attribute of fuzzresult to return. If not specified the whole object is returned.",
),
)
default_parameter = "fn"
def __init__(self, params):
BasePayload.__init__(self, params)
self.__max = -1
self.attr = self.params["attr"]
self._it = self.parse_burp_log(self.params["fn"])
def count(self):
return self.__max
def get_type(self):
return FuzzWordType.FUZZRES if not self.attr else FuzzWordType.WORD
def get_next(self):
next_item = next(self._it)
return next_item if not self.attr else rgetattr(next_item, self.attr)
def parse_burp_log(self, burp_log):
burp_file = None
try:
burp_file = open(
self.find_file(burp_log),
"r",
encoding="utf-8",
errors="surrogateescape",
)
history = "START"
rl = burp_file.readline()
while rl != "":
if history == "START":
if rl == DELIMITER:
history = "HEADER"
elif history == "HEADER":
if rl == DELIMITER:
raw_request = ""
history = "REQUEST"
else:
matched = HEADER.match(rl)
ctime, host, ip_address = matched.group(1, 3, 5)
elif history == "REQUEST":
if rl == DELIMITER:
history = "DELIM1"
else:
raw_request += rl
elif history == "DELIM1":
if rl == CRLF:
raw_response = ""
history = "DELIM3"
else:
raw_response = rl
history = "RESPONSE"
elif history == "RESPONSE":
if rl == DELIMITER:
history = "DELIM2"
else:
raw_response += rl
elif history == "DELIM2":
if rl == CRLF:
history = "DELIM3"
elif history == "DELIM3":
if rl == CRLF:
history = "DELIM4"
elif history == "DELIM4":
if rl == CRLF:
fr = FuzzRequest()
# last read line contains an extra CRLF
fr.update_from_raw_http(
raw_request, host[: host.find("://")], raw_response[:-1]
)
frr = FuzzResult(history=fr)
yield frr.update()
history = "START"
rl = burp_file.readline()
except IOError as e:
raise FuzzExceptBadFile("Error opening burp log file. %s" % str(e))
finally:
if burp_file is not None:
burp_file.close()
================================================
FILE: src/wfuzz/plugins/payloads/burpstate.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.exception import FuzzExceptBadFile, FuzzExceptBadOptions
from wfuzz.fuzzobjects import FuzzResult
from wfuzz.fuzzrequest import FuzzRequest
from wfuzz.plugin_api.base import BasePayload
from wfuzz.helpers.obj_dyn import rgetattr
from wfuzz.fuzzobjects import FuzzWordType
import datetime
import string
import re
import struct
import zipfile
TAG = re.compile(r"?(\w*)>", re.M) # Match a XML tag
nvprint = string.printable.replace("\x0b", "").replace("\x0c", "") # Printables
@moduleman_plugin
class burpstate(BasePayload):
name = "burpstate"
author = ("Xavi Mendez (@xmendez)",)
version = "0.1"
description = (
"*ALERT*: https://portswigger.net/blog/goodbye-state-files-we-wont-miss-you",
"",
"Returns fuzz results' from a Burp saved state file. This payload's code is based on burp2xml.py:",
"Developed by Paul Haas, under Redspin. Inc.",
"Licensed under the GNU Public License version 3.0 (2008-2009)",
"Process Burp Suite Professional's output into a well-formed XML document.",
"",
"Currently, the whole burp state file is read, in the future this needs to be changed to be more memory efficient.",
)
summary = "Returns fuzz results from a Burp state."
category = ["default"]
priority = 99
parameters = (
("fn", "", True, "Filename of a valid Burp state file."),
(
"attr",
None,
False,
"Fuzzresult attribute to return. If not specified the whole object is returned.",
),
(
"source",
"proxy, target",
False,
"A list of separated Burp sources to get the HTTP requests and responses from. It could be proxy or target tool.",
),
(
"checkversion",
False,
False,
"If the Burp log file version is unknown an exception will be raised and execution will fail. Checked with burp state file version 65, 67.",
),
)
default_parameter = "fn"
def __init__(self, params):
BasePayload.__init__(self, params)
self.__max = -1
self.attr = self.params["attr"]
self._it = self.burp_to_xml(self.params["fn"])
if any(i not in ["proxy", "target"] for i in self.params["source"].split(",")):
raise FuzzExceptBadOptions("Unknown burp source parameter")
self.request_tags = []
self.response_tags = []
if "proxy" in self.params["source"]:
self.request_tags.append("")
self.response_tags.append("")
if "target" in self.params["source"]:
self.request_tags.append("")
self.response_tags.append("")
def __iter__(self):
return self
def count(self):
return self.__max
def get_type(self):
return FuzzWordType.FUZZRES if not self.attr else FuzzWordType.WORD
def get_next(self):
next_item = next(self._it)
return next_item if not self.attr else rgetattr(next_item, self.attr)
def milliseconds_to_date(self, milliseconds):
"""Convert milliseconds since Epoch (from Java) to Python date structure:
See: http://java.sun.com/j2se/1.4.2/docs/api/java/util/Date.html
There is no direct way to convert milliseconds since Epoch to Python object
So we convert the milliseconds to seconds first as a POSIX timestamp which
can be used to get a valid date, and then use the parsed values from that
object along with converting mili -> micro seconds in a new date object."""
try:
d = datetime.datetime.fromtimestamp(milliseconds / 1000)
date = datetime.datetime(
d.year,
d.month,
d.day,
d.hour,
d.minute,
d.second,
(milliseconds % 1000) * 1000,
)
except ValueError: # Bad date, just return the milliseconds
date = str(milliseconds)
return None
return date
def burp_binary_field(self, field, i):
"""Strip Burp Suite's binary format characters types from our data.
The first character after the leading tag describes the type of the data."""
if len(field) <= i:
return None, -1
elif field[i] == "\x00": # 4 byte integer value
return str(struct.unpack(">I", field[i + 1 : i + 5])[0]), 5
elif field[i] == "\x01": # Two possible unsigned long long types
if field[i + 1] == "\x00": # (64bit) 8 Byte Java Date
ms = struct.unpack(">Q", field[i + 1 : i + 9])[0]
date = self.milliseconds_to_date(ms)
value = (
date.ctime() if date else 0
) # Use the ctime string format for date
else: # Serial Number only used ocasionally in Burp
value = str(struct.unpack(">Q", field[i + 1 : i + 9])[0])
return value, 9
elif field[i] == "\x02": # Boolean Object True/False
return str(struct.unpack("?", field[i + 1 : i + 2])[0]), 2
elif field[i] == "\x03" or field[i] == "\x04": # 4 byte length + string
length = struct.unpack(">I", field[i + 1 : i + 5])[0]
# print "Saw string of length", length, "at", i + 5, i + 5+length
value = field[i + 5 : i + 5 + length]
if "<" in value or ">" in value or "&" in value: # Sanatize HTML w/CDATA
value = "", "]]>"
value = "".join(c for c in value if c in nvprint) # Remove nonprintables
return value, 5 + length # ** TODO: Verify length by matching end tag **
print("Unknown binary format", repr(field[i]))
return None, -1
def strip_cdata(self, data):
if data.startswith(""):
data = data[:-3]
return data
def burp_to_xml(self, filename):
"""Unzip Burp's file, remove non-printable characters, CDATA any HTML,
include a valid XML header and trailer, and return a valid XML string."""
z = zipfile.ZipFile(self.find_file(filename)) # Open Burp's zip file
burp = z.read("burp", "rb") # Read-in the main burp file
m = TAG.match(burp, 0) # Match a tag at the start of the string
while m:
index = m.end()
etag = m.group().replace("<", "") # Matching tag
m = TAG.match(burp, index) # Attempt to get the next tag
if not m: # Data folows
# Read the type of data using Burp's binary data headers
value, length = self.burp_binary_field(burp, index)
if value is None:
break
index += length + len(etag) # Point our index to the next tag
m = TAG.match(burp, index) # And retrieve it
if (
self.params["checkversion"]
and etag == ""
and value not in ["65", "67"]
):
raise FuzzExceptBadFile("Unknown burp log version %s" % value)
if etag == "":
https_tag = value == "True"
if etag in self.request_tags:
raw_request = self.strip_cdata(value)
if etag in self.response_tags:
fr = FuzzRequest()
fr.update_from_raw_http(
raw_request,
"http" if not https_tag else "https",
self.strip_cdata(value),
)
frr = FuzzResult(history=fr)
raw_request = ""
https_tag = ""
yield frr.update()
================================================
FILE: src/wfuzz/plugins/payloads/dirwalk.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.plugin_api.base import BasePayload
from wfuzz.fuzzobjects import FuzzWordType
import os
# Python 2 and 3: alternative 4
try:
from urllib.parse import quote
except ImportError:
from urllib import quote
@moduleman_plugin
class dirwalk(BasePayload):
name = "dirwalk"
author = ("Xavi Mendez (@xmendez)",)
version = "0.1"
description = (
"Returns all the file paths found in the specified directory.",
"Handy if you want to check a directory structure against a webserver,",
"for example, because you have previously downloaded a specific version",
"of what is supposed to be on-line.",
)
summary = "Returns filename's recursively from a local directory."
category = ["default"]
priority = 99
parameters = (
("dir", "", True, "Directory path to walk and generate payload from."),
)
default_parameter = "dir"
def __init__(self, params):
BasePayload.__init__(self, params)
self.g = self._my_gen(self.params["dir"])
def _my_gen(self, directory):
for root, dirs, fnames in os.walk(directory):
for f in fnames:
relative_path = os.path.relpath(os.path.join(root, f), directory)
yield quote(relative_path)
def get_next(self):
return next(self.g)
def get_type(self):
return FuzzWordType.WORD
def count(self):
return -1
================================================
FILE: src/wfuzz/plugins/payloads/file.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.exception import FuzzExceptBadFile
from wfuzz.plugin_api.base import BasePayload
from wfuzz.helpers.file_func import FileDetOpener
from wfuzz.fuzzobjects import FuzzWordType
@moduleman_plugin
class file(BasePayload):
name = "file"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.2"
description = ("Returns the contents of a dictionary file line by line.",)
summary = "Returns each word from a file."
category = ["default"]
priority = 99
parameters = (
("fn", "", True, "Filename of a valid dictionary"),
(
"count",
"True",
False,
"Indicates if the number of words in the file should be counted.",
),
("encoding", "Auto", False, "Indicates the file encoding."),
)
default_parameter = "fn"
def __init__(self, params):
BasePayload.__init__(self, params)
try:
encoding = (
self.params["encoding"]
if self.params["encoding"].lower() != "auto"
else None
)
self.f = FileDetOpener(self.find_file(self.params["fn"]), encoding)
except IOError as e:
raise FuzzExceptBadFile("Error opening file. %s" % str(e))
self.__count = None
def get_type(self):
return FuzzWordType.WORD
def get_next(self):
line = next(self.f)
if not line:
self.f.close()
raise StopIteration
return line.strip()
def count(self):
if self.params["count"].lower() == "false":
return -1
if self.__count is None:
self.__count = len(list(self.f))
self.f.reset()
return self.__count
================================================
FILE: src/wfuzz/plugins/payloads/guitab.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.plugin_api.base import BasePayload
from wfuzz.fuzzobjects import FuzzWordType
from wfuzz.facade import Facade
@moduleman_plugin
class guitab(BasePayload):
name = "guitab"
author = ("Xavi Mendez (@xmendez)",)
version = "0.1"
description = (
"** This is a beta plugin for the GUI under construction.",
"This payload reads requests from a tab in the GUI",
)
summary = "This payload reads requests from a tab in the GUI"
category = ["default"]
priority = 99
parameters = (
("tab", "", True, "Name of a valid GUI tab."),
(
"attr",
None,
False,
"Attribute of fuzzresult to return. If not specified the whole object is returned.",
),
)
default_parameter = "tab"
def __init__(self, params):
BasePayload.__init__(self, params)
self.attr = self.params["attr"]
self._it = iter(Facade().data[self.params["tab"]])
def count(self):
return len(Facade().data[self.params["tab"]])
def get_type(self):
return FuzzWordType.WORD
def get_next(self):
next_item = next(self._it)
return next_item if not self.attr else next_item.get_field(self.attr)
================================================
FILE: src/wfuzz/plugins/payloads/hexrand.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.plugin_api.base import BasePayload
from wfuzz.exception import FuzzExceptPluginBadParams
from wfuzz.fuzzobjects import FuzzWordType
import random
@moduleman_plugin
class hexrand(BasePayload):
name = "hexrand"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
description = ()
summary = "Returns random hex numbers from the given range."
category = ["default"]
priority = 99
parameters = (
(
"range",
"",
True,
"Range of hex numbers to randomly generate in the form of 00-ff.",
),
)
default_parameter = "range"
def __init__(self, params):
BasePayload.__init__(self, params)
try:
ran = self.params["range"].split("-")
self.minimum = int(ran[0], 16)
self.maximum = int(ran[1], 16)
self.__count = -1
except ValueError:
raise FuzzExceptPluginBadParams('Bad range format (eg. "0-ffa")')
def __iter__(self):
return self
def count(self):
return self.__count
def get_type(self):
return FuzzWordType.WORD
def get_next(self):
self.current = random.SystemRandom().randint(self.minimum, self.maximum)
lgth = len(hex(self.maximum).replace("0x", ""))
pl = "%" + str(lgth) + "s"
num = hex(self.current).replace("0x", "")
pl = pl % (num)
payl = pl.replace(" ", "0")
return payl
================================================
FILE: src/wfuzz/plugins/payloads/hexrange.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.plugin_api.base import BasePayload
from wfuzz.exception import FuzzExceptBadOptions
from wfuzz.fuzzobjects import FuzzWordType
@moduleman_plugin
class hexrange(BasePayload):
name = "hexrange"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
description = ()
summary = "Returns each hex number of the given hex range."
category = ["default"]
priority = 99
parameters = (
("range", "", True, "Range of hex numbers to generate in the form of 00-ff."),
)
default_parameter = "range"
def __init__(self, params):
BasePayload.__init__(self, params)
try:
ran = self.params["range"].split("-")
self.minimum = int(ran[0], 16)
self.maximum = int(ran[1], 16)
self.__count = self.maximum - self.minimum + 1
self.current = self.minimum
self.lgth = max(
len(ran[0]), len(ran[1]), len(hex(self.maximum).replace("0x", ""))
)
except ValueError:
raise FuzzExceptBadOptions('Bad range format (eg. "0-ffa")')
def count(self):
return self.__count
def get_type(self):
return FuzzWordType.WORD
def get_next(self):
if self.current > self.maximum:
raise StopIteration
pl = "%" + str(self.lgth) + "s"
num = hex(self.current).replace("0x", "")
pl = pl % (num)
payl = pl.replace(" ", "0")
self.current += 1
return payl
================================================
FILE: src/wfuzz/plugins/payloads/ipnet.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.exception import FuzzExceptPluginBadParams, FuzzExceptBadInstall
from wfuzz.plugin_api.base import BasePayload
from wfuzz.fuzzobjects import FuzzWordType
@moduleman_plugin
class ipnet(BasePayload):
name = "ipnet"
author = ("Xavi Mendez (@xmendez)",)
version = "0.1"
description = ("ie. 192.168.1.0/24", "Requires: netaddr module")
summary = "Returns list of IP addresses of a network."
category = ["default"]
priority = 99
parameters = (("net", "", True, "Network range in the form ip/mask."),)
default_parameter = "net"
def __init__(self, params):
BasePayload.__init__(self, params)
try:
from netaddr import IPNetwork
from netaddr.core import AddrFormatError
net = IPNetwork("%s" % self.params["net"])
self.f = net.iter_hosts()
self.__count = net.size - 2
if self.__count <= 0:
raise FuzzExceptPluginBadParams(
"There are not hosts in the specified network"
)
except ValueError:
raise FuzzExceptPluginBadParams(
"The specified network has an incorrect format."
)
except ImportError:
raise FuzzExceptBadInstall(
"ipnet plugin requires netaddr module. Please install it using pip."
)
except AddrFormatError:
raise FuzzExceptPluginBadParams(
"The specified network has an incorrect format."
)
def get_type(self):
return FuzzWordType.WORD
def get_next(self):
return str(next(self.f))
def count(self):
return self.__count
================================================
FILE: src/wfuzz/plugins/payloads/iprange.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.exception import FuzzExceptPluginBadParams, FuzzExceptBadInstall
from wfuzz.plugin_api.base import BasePayload
from wfuzz.fuzzobjects import FuzzWordType
@moduleman_plugin
class iprange(BasePayload):
name = "iprange"
author = ("Xavi Mendez (@xmendez)",)
version = "0.1"
description = (
"ie. 192.168.1.0-192.168.1.12",
"Requires: netaddr module",
)
summary = "Returns list of IP addresses of a given IP range."
category = ["default"]
priority = 99
parameters = (
("iprange", "", True, "IP address range int the form 192.168.1.0-192.168.1.12"),
)
default_parameter = "iprange"
def __init__(self, params):
BasePayload.__init__(self, params)
try:
from netaddr import IPRange
from netaddr.core import AddrFormatError
ran = self.params["iprange"].split("-")
net = IPRange(ran[0], ran[1])
self.f = iter(net)
self.__count = net.size
except ImportError:
raise FuzzExceptBadInstall(
"ipnet plugin requires netaddr module. Please install it using pip."
)
except AddrFormatError:
raise FuzzExceptPluginBadParams(
"The specified network range has an incorrect format."
)
except IndexError:
raise FuzzExceptPluginBadParams(
"The specified network range has an incorrect format."
)
def get_type(self):
return FuzzWordType.WORD
def get_next(self):
return str(next(self.f))
def count(self):
return self.__count
================================================
FILE: src/wfuzz/plugins/payloads/list.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.plugin_api.base import BasePayload
from wfuzz.fuzzobjects import FuzzWordType
@moduleman_plugin
class list(BasePayload):
name = "list"
author = ("Xavi Mendez (@xmendez)",)
version = "0.1"
description = ("ie word1-word2",)
summary = "Returns each element of the given word list separated by -."
category = ["default"]
priority = 99
parameters = (
("values", "", True, "Values separated by - to return as a dictionary."),
)
default_parameter = "values"
def __init__(self, params):
BasePayload.__init__(self, params)
if self.params["values"].find("\\") >= 0:
self.params["values"] = self.params["values"].replace("\\-", "$SEP$")
self.params["values"] = self.params["values"].replace("\\\\", "$SCAP$")
self.value_list = self.params["values"].split("-")
for i in range(len(self.value_list)):
self.value_list[i] = self.value_list[i].replace("$SEP$", "-")
self.value_list[i] = self.value_list[i].replace("$SCAP$", "\\")
else:
self.value_list = self.params["values"].split("-")
self.__count = len(self.value_list)
self.current = 0
def count(self):
return self.__count
def get_type(self):
return FuzzWordType.WORD
def get_next(self):
if self.current >= self.__count:
raise StopIteration
else:
elem = self.value_list[self.current]
self.current += 1
return elem
================================================
FILE: src/wfuzz/plugins/payloads/names.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.plugin_api.base import BasePayload
from wfuzz.fuzzobjects import FuzzWordType
@moduleman_plugin
class names(BasePayload):
name = "names"
author = (
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
description = ("ie. jon-smith",)
summary = "Returns possible usernames by mixing the given words, separated by -, using known typical constructions."
category = ["default"]
priority = 99
parameters = (("name", "", True, "Name and surname in the form of name-surname."),)
default_parameter = "name"
def __init__(self, params):
BasePayload.__init__(self, params)
possibleusernames = []
name = ""
llist = self.params["name"].split("-")
for x in llist:
if name == "":
name = name + x
else:
name = name + " " + x
if " " in name:
parts = name.split()
possibleusernames.append(parts[0])
possibleusernames.append(parts[0] + "." + parts[1])
possibleusernames.append(parts[0] + parts[1])
possibleusernames.append(parts[0] + "." + parts[1][0])
possibleusernames.append(parts[0][0] + "." + parts[1])
possibleusernames.append(parts[0] + parts[1][0])
possibleusernames.append(parts[0][0] + parts[1])
str1 = ""
str2 = ""
str3 = ""
str4 = ""
for i in range(0, len(parts) - 1):
str1 = str1 + parts[i] + "."
str2 = str2 + parts[i]
str3 = str3 + parts[i][0] + "."
str4 = str4 + parts[i][0]
str5 = str1 + parts[-1]
str6 = str2 + parts[-1]
str7 = str4 + parts[-1]
str8 = str3 + parts[-1]
str9 = str2 + parts[-1][0]
str10 = str4 + parts[-1][0]
possibleusernames.append(str5)
possibleusernames.append(str6)
possibleusernames.append(str7)
possibleusernames.append(str8)
possibleusernames.append(str9)
possibleusernames.append(str10)
possibleusernames.append(parts[-1])
possibleusernames.append(parts[0] + "." + parts[-1])
possibleusernames.append(parts[0] + parts[-1])
possibleusernames.append(parts[0] + "." + parts[-1][0])
possibleusernames.append(parts[0][0] + "." + parts[-1])
possibleusernames.append(parts[0] + parts[-1][0])
possibleusernames.append(parts[0][0] + parts[-1])
else:
possibleusernames.append(name)
self.creatednames = possibleusernames
self.__count = len(possibleusernames)
def count(self):
return self.__count
def get_type(self):
return FuzzWordType.WORD
def get_next(self):
if self.creatednames:
payl = self.creatednames.pop()
return payl
else:
raise StopIteration
================================================
FILE: src/wfuzz/plugins/payloads/permutation.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.plugin_api.base import BasePayload
from wfuzz.exception import FuzzExceptBadOptions
from wfuzz.fuzzobjects import FuzzWordType
@moduleman_plugin
class permutation(BasePayload):
name = "permutation"
author = ("Xavi Mendez (@xmendez)",)
version = "0.1"
description = ()
summary = "Returns permutations of the given charset and length."
category = ["default"]
priority = 99
parameters = (("ch", "", True, "Charset and len to permute in the form of abc-2."),)
default_parameter = "ch"
def __init__(self, params):
BasePayload.__init__(self, params)
self.charset = []
try:
ran = self.params["ch"].split("-")
self.charset = ran[0]
self.width = int(ran[1])
except ValueError:
raise FuzzExceptBadOptions('Bad range format (eg. "0-ffa")')
pset = []
for x in self.charset:
pset.append(x)
words = self.xcombinations(pset, self.width)
self.lista = []
for x in words:
self.lista.append("".join(x))
self.__count = len(self.lista)
def count(self):
return self.__count
def get_type(self):
return FuzzWordType.WORD
def get_next(self):
if self.lista != []:
payl = self.lista.pop()
return payl
else:
raise StopIteration
def xcombinations(self, items, n):
if n == 0:
yield []
else:
for i in range(len(items)):
for cc in self.xcombinations(items[:i] + items[i:], n - 1):
yield [items[i]] + cc
================================================
FILE: src/wfuzz/plugins/payloads/range.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.exception import FuzzExceptPluginBadParams
from wfuzz.plugin_api.base import BasePayload
from wfuzz.fuzzobjects import FuzzWordType
@moduleman_plugin
class range(BasePayload):
name = "range"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
description = ("ie. 0-10",)
summary = "Returns each number of the given range."
category = ["default"]
priority = 99
parameters = (("range", "", True, "Range of numbers in the form 0-10."),)
default_parameter = "range"
def __init__(self, params):
BasePayload.__init__(self, params)
try:
ran = self.params["range"].split("-")
self.minimum = int(ran[0])
self.maximum = int(ran[1])
self.__count = self.maximum - self.minimum + 1
self.width = len(ran[0])
self.current = self.minimum
except ValueError:
raise FuzzExceptPluginBadParams('Bad range format (eg. "23-56")')
def get_type(self):
return FuzzWordType.WORD
def get_next(self):
if self.current > self.maximum:
raise StopIteration
else:
if self.width:
payl = "%0" + str(self.width) + "d"
payl = payl % (self.current)
else:
payl = str(self.current)
self.current += 1
return payl
def count(self):
return self.__count
def __iter__(self):
return self
================================================
FILE: src/wfuzz/plugins/payloads/shodanp.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.plugin_api.payloadtools import ShodanIter
from wfuzz.plugin_api.base import BasePayload
from wfuzz.fuzzobjects import FuzzWordType
@moduleman_plugin
class shodanp(BasePayload):
name = "shodanp"
author = ("Xavi Mendez (@xmendez)",)
version = "0.1"
description = ("Queries the Shodan API",)
summary = "Returns URLs of a given Shodan API search (needs api key)."
category = ["default"]
priority = 99
parameters = (
("search", "", True, "Shodan search string."),
("page", "0", False, "Offset page, starting at zero."),
(
"limit",
"0",
False,
"Number of pages (1 query credit = 100 results). Zero for all.",
),
)
default_parameter = "search"
def __init__(self, params):
BasePayload.__init__(self, params)
search = params["search"]
page = int(params["page"])
limit = int(params["limit"])
self._it = ShodanIter(search, page, limit)
def count(self):
return -1
def close(self):
self._it._stop()
def get_type(self):
return FuzzWordType.WORD
def get_next(self):
match = next(self._it)
port = match["port"]
scheme = "https" if "ssl" in match or port == 443 else "http"
if match["hostnames"]:
for hostname in match["hostnames"]:
return "{}://{}:{}".format(scheme, hostname, port)
else:
return "{}://{}:{}".format(scheme, match["ip_str"], port)
================================================
FILE: src/wfuzz/plugins/payloads/stdin.py
================================================
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.plugin_api.base import BasePayload
from wfuzz.fuzzobjects import FuzzWordType
import sys
@moduleman_plugin
class stdin(BasePayload):
name = "stdin"
author = ("Xavi Mendez (@xmendez)",)
version = "0.1"
description = ()
summary = "Returns each item read from stdin."
category = ["default"]
priority = 99
parameters = ()
default_parameter = ""
def __init__(self, params):
BasePayload.__init__(self, params)
self.__count = -1
def count(self):
return self.__count
def get_type(self):
return FuzzWordType.WORD
def get_next(self):
line = next(sys.stdin).strip()
return line
================================================
FILE: src/wfuzz/plugins/payloads/wfuzzp.py
================================================
import pickle as pickle
import gzip
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.exception import FuzzExceptBadFile
from wfuzz.fuzzobjects import FuzzResult, FuzzWordType
from wfuzz.plugin_api.base import BasePayload
from wfuzz.helpers.obj_dyn import rgetattr
@moduleman_plugin
class wfuzzp(BasePayload):
name = "wfuzzp"
author = ("Xavi Mendez (@xmendez)",)
version = "0.2"
description = (
"This payload uses pickle.",
"Warning: The pickle module is not intended to be secure against erroneous or maliciously constructed data.",
"Never unpickle data received from an untrusted or unauthenticated source.",
"See: https://blog.nelhage.com/2011/03/exploiting-pickle/",
)
summary = "Returns fuzz results' URL from a previous stored wfuzz session."
category = ["default"]
priority = 99
parameters = (
("fn", "", True, "Filename of a valid wfuzz result file."),
(
"attr",
None,
False,
"Attribute of fuzzresult to return. If not specified the whole object is returned.",
),
)
default_parameter = "fn"
def __init__(self, params):
BasePayload.__init__(self, params)
self.__max = -1
self.attr = self.params["attr"]
self._it = self._gen_wfuzz(self.params["fn"])
def count(self):
return self.__max
def get_next(self):
next_item = next(self._it)
return next_item if not self.attr else rgetattr(next_item, self.attr)
def get_type(self):
return FuzzWordType.FUZZRES if not self.attr else FuzzWordType.WORD
def _gen_wfuzz(self, output_fn):
try:
with gzip.open(self.find_file(output_fn), "r+b") as output:
while 1:
item = pickle.load(output)
if not isinstance(item, FuzzResult):
raise FuzzExceptBadFile(
"Wrong wfuzz payload format, the object read is not a valid fuzz result."
)
yield item
except IOError as e:
raise FuzzExceptBadFile("Error opening wfuzz payload file. %s" % str(e))
except EOFError:
return
================================================
FILE: src/wfuzz/plugins/printers/__init__.py
================================================
================================================
FILE: src/wfuzz/plugins/printers/printers.py
================================================
import socket
import csv as csvmod
import json as jjson
from xml.dom import minidom
from wfuzz.externals.moduleman.plugin import moduleman_plugin
from wfuzz.plugin_api.base import BasePrinter
from wfuzz.exception import FuzzExceptPluginBadParams
@moduleman_plugin
class magictree(BasePrinter):
name = "magictree"
author = ("Xavi Mendez (@xmendez)",)
version = "0.1"
summary = "Prints results in magictree format"
category = ["default"]
priority = 99
def __init__(self, output):
BasePrinter.__init__(self, output)
self.node_mt = None
self.node_service = None
def __create_xml_element(self, parent, caption, text):
# Create a element
doc = minidom.Document()
el = doc.createElement(caption)
parent.appendChild(el)
# Give the element some text
ptext = doc.createTextNode(text)
el.appendChild(ptext)
return el
def header(self, summary):
doc = minidom.Document()
#
self.node_mt = doc.createElement("magictree")
self.node_mt.setAttribute("class", "MtBranchObject")
#
node_td = doc.createElement("testdata")
node_td.setAttribute("class", "MtBranchObject")
self.node_mt.appendChild(node_td)
# 209.85.146.105
host = summary.seed.history.host
if host.find(":") > 0:
host, port = host.split(":")
else:
port = 80
if summary.seed.history.scheme.lower() == "https":
port = 443
try:
resolving = socket.gethostbyname(host)
node_h = self.__create_xml_element(node_td, "host", str(resolving))
except socket.gaierror:
node_h = self.__create_xml_element(node_td, "host", str(host))
# tcp
node_ipr = self.__create_xml_element(node_h, "ipproto", "tcp")
# 80openhttp
node_port = self.__create_xml_element(node_ipr, "port", str(port))
self.__create_xml_element(node_port, "state", "open")
if summary.seed.history.scheme.lower() == "https":
node_port = self.__create_xml_element(node_port, "tunnel", "ssl")
self.node_service = self.__create_xml_element(node_port, "service", "http")
def result(self, fuzz_result):
node_url = self.__create_xml_element(
self.node_service, "url", str(fuzz_result.url)
)
if "Server" in fuzz_result.history.headers.response:
self.__create_xml_element(
node_url, "HTTPServer", fuzz_result.history.headers.response["Server"]
)
location = ""
if "Location" in fuzz_result.history.headers.response:
location = fuzz_result.history.headers.response["Location"]
if fuzz_result.code == 301 or fuzz_result.code == 302 and location:
self.__create_xml_element(node_url, "RedirectLocation", location)
self.__create_xml_element(node_url, "ResponseCode", str(fuzz_result.code))
self.__create_xml_element(node_url, "source", "WFuzz")
def footer(self, summary):
self.f.write(self.node_mt.toxml())
@moduleman_plugin
class html(BasePrinter):
name = "html"
author = (
"Carlos del Ojo",
"Christian Martorella",
"Adapted to newer versions Xavi Mendez (@xmendez)",
)
version = "0.1"
summary = "Prints results in html format"
category = ["default"]
priority = 99
def __init__(self, output):
BasePrinter.__init__(self, output)
def header(self, summary):
url = summary.url
self.f.write(
'
Fuzzing %s
\r\n
\r\n
#request
Code
#lines
#words
Url
\r\n'
% (url)
)
def result(self, fuzz_result):
htmlc = ""
if fuzz_result.code >= 400 and fuzz_result.code < 500:
htmlc = ""
elif fuzz_result.code >= 300 and fuzz_result.code < 400:
htmlc = ""
elif fuzz_result.code >= 200 and fuzz_result.code < 300:
htmlc = ""
if fuzz_result.history.method.lower() == "post":
inputs = ""
for n, v in list(fuzz_result.history.params.post.items()):
inputs += '' % (n, v)
self.f.write(
'\r\n