Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
w3af/w3af/plugins/output/html_file.py
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
331 lines (258 sloc)
11 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
""" | |
html_file.py | |
Copyright 2006 Andres Riancho | |
This file is part of w3af, http://w3af.org/ . | |
w3af 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 version 2 of the License. | |
w3af 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 w3af; if not, write to the Free Software | |
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
""" | |
import os | |
import time | |
import datetime | |
import functools | |
import markdown | |
from jinja2 import StrictUndefined, Environment, FileSystemLoader | |
import w3af.core.data.kb.knowledge_base as kb | |
import w3af.core.data.kb.config as cf | |
from w3af import ROOT_PATH | |
from w3af.core.controllers.exceptions import DBException | |
from w3af.core.controllers.plugins.output_plugin import OutputPlugin | |
from w3af.core.data.misc.encoding import smart_unicode | |
from w3af.core.data.db.history import HistoryItem | |
from w3af.core.data.db.disk_list import DiskList | |
from w3af.core.data.options.opt_factory import opt_factory | |
from w3af.core.data.options.option_types import OUTPUT_FILE, INPUT_FILE | |
from w3af.core.data.options.option_list import OptionList | |
class html_file(OutputPlugin): | |
""" | |
Generate HTML report with identified vulnerabilities and log messages. | |
:author: Andres Riancho ((andres.riancho@gmail.com)) | |
""" | |
def __init__(self): | |
OutputPlugin.__init__(self) | |
# Internal variables | |
self._initialized = False | |
self._additional_info = DiskList(table_prefix='html_file') | |
self._enabled_plugins = {} | |
self.template_root = os.path.join(ROOT_PATH, 'plugins', 'output', | |
'html_file', 'templates') | |
# User configured parameters | |
self._verbose = False | |
self._output_file_name = '~/report.html' | |
self._template = os.path.join(self.template_root, 'complete.html') | |
def debug(self, message, new_line=True): | |
""" | |
This method is called from the output object. The output object was | |
called from a plugin or from the framework. This method should take | |
an action for debug messages. | |
""" | |
if self._verbose: | |
to_print = self._clean_string(message) | |
self._append_additional_info(to_print, 'debug') | |
def do_nothing(self, *args, **kwargs): | |
pass | |
information = vulnerability = do_nothing | |
def error(self, message, new_line=True): | |
""" | |
This method is called from the output object. The output object was | |
called from a plugin or from the framework. This method should take | |
an action for error messages. | |
""" | |
to_print = self._clean_string(message) | |
self._append_additional_info(to_print, 'error') | |
def console(self, message, new_line=True): | |
""" | |
This method is used by the w3af console to print messages to the | |
outside. | |
""" | |
to_print = self._clean_string(message) | |
self._append_additional_info(to_print, 'console') | |
def _append_additional_info(self, message, msg_type): | |
""" | |
Add a message to the debug table. | |
:param message: The message to add to the table. It's in HTML. | |
:param msg_type: The type of message | |
""" | |
now = time.localtime(time.time()) | |
the_time = time.strftime("%c", now) | |
self._additional_info.append((the_time, msg_type, message)) | |
def set_options(self, option_list): | |
""" | |
Sets the Options given on the OptionList to self. The options are the | |
result of a user entering some data on a window that was constructed | |
using the XML Options that was retrieved from the plugin using | |
get_options() | |
This method MUST be implemented on every plugin. | |
:return: No value is returned. | |
""" | |
self._output_file_name = option_list['output_file'].get_value() | |
self._verbose = option_list['verbose'].get_value() | |
self._template = option_list['template'].get_value() | |
def get_options(self): | |
""" | |
:return: A list of option objects for this plugin. | |
""" | |
ol = OptionList() | |
d = 'The path to the HTML template used to render the report.' | |
o = opt_factory('template', self._template, d, INPUT_FILE) | |
ol.add(o) | |
d = 'File name where this plugin will write to' | |
o = opt_factory('output_file', self._output_file_name, d, OUTPUT_FILE) | |
ol.add(o) | |
d = 'True if debug information will be appended to the report.' | |
o = opt_factory('verbose', self._verbose, d, 'boolean') | |
ol.add(o) | |
return ol | |
def log_enabled_plugins(self, plugins_dict, options_dict): | |
""" | |
This method is called from the output manager object. This method | |
should take an action for the enabled plugins and their configuration. | |
Usually, write the info to a file or print it somewhere. | |
:param plugins_dict: A dict with all the plugin types and the | |
enabled plugins for that type of plugin. | |
:param options_dict: A dict with the options for every plugin. | |
""" | |
self._enabled_plugins = {} | |
# TODO: Improve so it contains the plugin configuration too | |
for plugin_type, enabled in plugins_dict.iteritems(): | |
self._enabled_plugins[plugin_type] = enabled | |
def end(self): | |
try: | |
self.flush() | |
finally: | |
self._additional_info.clear() | |
self._enabled_plugins = {} | |
def flush(self): | |
""" | |
This method is called when we want to write the data to the html, | |
performs these main tasks: | |
* Get the target URLs | |
* Get the enabled plugins | |
* Get the vulnerabilities and infos from the KB | |
* Get the debug data | |
* Send all the data to jinja2 for rendering the template | |
""" | |
target_urls = [t.url_string for t in cf.cf.get('targets')] | |
target_domain = 'unknown' | |
target_domains = cf.cf.get('target_domains') | |
if target_domains and len(target_domains) > 0: | |
target_domain = target_domains[0] | |
enabled_plugins = self._enabled_plugins | |
findings = kb.kb.get_all_findings_iter() | |
debug_log = ((t, l, smart_unicode(m)) for (t, l, m) in self._additional_info) | |
known_urls = kb.kb.get_all_known_urls() | |
context = {'target_urls': target_urls, | |
'target_domain': target_domain, | |
'enabled_plugins': enabled_plugins, | |
'findings': findings, | |
'debug_log': debug_log, | |
'known_urls': known_urls} | |
# The file was verified to exist when setting the plugin configuration | |
template_fh = file(os.path.expanduser(self._template), 'r') | |
output_fh = file(os.path.expanduser(self._output_file_name), 'w') | |
self._render_html_file(template_fh, context, output_fh) | |
def _render_html_file(self, template_fh, context, output_fh): | |
""" | |
Renders the HTML file using the configured template. Separated as a | |
method to be able to easily test. | |
:param context: A dict containing target urls, enabled plugins, etc. | |
:return: True on successful rendering | |
""" | |
severity_icon = functools.partial(get_severity_icon, self.template_root) | |
env_config = {'undefined': StrictUndefined, | |
'trim_blocks': True, | |
'autoescape': True, | |
'lstrip_blocks': True} | |
try: | |
jinja2_env = Environment(**env_config) | |
except TypeError: | |
# Kali uses a different jinja2 version, which doesn't have the same | |
# Environment kwargs, so we first try with the version we expect | |
# to have available, and then if it doesn't work apply this | |
# workaround for Kali | |
# | |
# https://github.com/andresriancho/w3af/issues/9552 | |
env_config.pop('lstrip_blocks') | |
jinja2_env = Environment(**env_config) | |
jinja2_env.filters['render_markdown'] = render_markdown | |
jinja2_env.filters['request'] = request_dump | |
jinja2_env.filters['response'] = response_dump | |
jinja2_env.filters['severity_icon'] = severity_icon | |
jinja2_env.filters['severity_text'] = get_severity_text | |
jinja2_env.globals['get_current_date'] = get_current_date | |
jinja2_env.loader = FileSystemLoader(self.template_root) | |
template = jinja2_env.from_string(template_fh.read()) | |
report_stream = template.stream(context) | |
report_stream.enable_buffering(5) | |
for report_section in report_stream: | |
output_fh.write(report_section.encode('utf-8')) | |
return True | |
def get_long_desc(self): | |
""" | |
:return: A DETAILED description of the plugin functions and features. | |
""" | |
return """ | |
This plugin writes the framework findings and messages to an HTML report | |
Three configurable parameters exist: | |
- template | |
- output_file | |
- verbose | |
It is possible to customize the output by changing the template which | |
is used to render the output file. | |
If you want to write every HTTP request/response to a text file, you | |
should use the text_file plugin. | |
""" | |
def render_markdown(markdown_text): | |
""" | |
Render the vulndb/data contents (which are in markdown) to HTML | |
:param markdown_text: The markdown from vulndb/data | |
:return: HTML | |
""" | |
return markdown.markdown(markdown_text) | |
def request_dump(_id): | |
""" | |
:param _id: The ID to query in the database | |
:return: The request as unicode | |
""" | |
_history = HistoryItem() | |
try: | |
details = _history.read(_id) | |
except DBException: | |
return None | |
return smart_unicode(details.request.dump().strip()) | |
def response_dump(_id): | |
""" | |
:param _id: The ID to query in the database | |
:return: The response as unicode | |
""" | |
_history = HistoryItem() | |
try: | |
details = _history.read(_id) | |
except DBException: | |
return None | |
return smart_unicode(details.response.dump().strip()) | |
def get_current_date(): | |
return datetime.date.today().strftime("%d.%m.%Y") | |
def get_severity_icon(template_root, severity): | |
icon_file = os.path.join(template_root, '%s.png' % severity.lower()) | |
fmt = u'data:image/png;base64,%s' | |
if os.path.exists(icon_file): | |
return fmt % file(icon_file).read().encode('base64') | |
return fmt | |
def get_severity_text(severity): | |
if severity.lower() == 'information': | |
severity = 'info' | |
color_map = {'high': 'danger', | |
'medium': 'warning', | |
'low': 'success', | |
'info': 'info'} | |
fmt = u'<h3 class="text-%s">%s</h3>' | |
return fmt % (color_map[severity.lower()], severity.upper()) |