Beispiel #1
0
    def test_render_with_unicode_control_chars(self):
        _id = 2

        desc = ('This is a long description that contains some special'
                ' unicode control characters such as \f and \x09')

        vuln = MockVuln(_id=_id)
        vuln.set_desc(desc)

        url = URL('http://w3af.com/a/b/c.php')
        hdr = Headers([('User-Agent', 'w3af')])
        request = HTTPRequest(url, data='a=1')
        request.set_headers(hdr)

        hdr = Headers([('Content-Type', 'text/html')])
        res = HTTPResponse(200, '<html>', hdr, url, url)

        h1 = HistoryItem()
        h1.request = request
        res.set_id(_id)
        h1.response = res
        h1.save()

        x = xml_file()

        finding = Finding(x._get_jinja2_env(), vuln)
        xml = finding.to_string()

        self.assertNotIn('unicode control characters such as \f and \x09', xml)
        self.assertIn('unicode control characters such as <character code="000c"/> and <character code="0009"/>', xml)
        self.assertValidXML(xml)
Beispiel #2
0
def get_traffic_details(scan_id, traffic_id):
    """
    The HTTP request and response associated with a vulnerability, usually the
    user will first get /scans/1/kb/3 and from there (if needed) browse to
    this resource where the HTTP traffic is available

    :param scan_id: The scan ID
    :param traffic_id: The ID of the request/response
    :return: HTTP request and response in base64 format
    """
    scan_info = get_scan_info_from_id(scan_id)
    if scan_info is None:
        abort(404, 'Scan not found')

    history_db = HistoryItem()

    try:
        details = history_db.read(traffic_id)
    except DBException:
        msg = 'Failed to retrieve request with id %s from DB.'
        abort(404, msg)
        return

    data = {'request': b64encode(details.request.dump()),
            'response': b64encode(details.response.dump())}

    return jsonify(data)
Beispiel #3
0
 def toggle_bookmark(self, cell, path, model):
     """Toggle bookmark."""
     model[path][1] = not model[path][1]
     historyItem = HistoryItem()
     historyItem.load(model[path][0])
     historyItem.toggle_mark(True)
     return
Beispiel #4
0
 def edit_tag(self, cell, path, new_text, model):
     """Edit tag."""
     model[path][4] = new_text
     historyItem = HistoryItem()
     historyItem.load(model[path][0])
     historyItem.update_tag(new_text, True)
     return
Beispiel #5
0
    def __init__(self, w3af, request_id, enableWidget=None, withManual=True,
                 withFuzzy=True, withCompare=True, withAudit=True, editableRequest=False,
                 editableResponse=False, widgname="default"):

        # Create the window
        RememberingWindow.__init__(self, w3af, "reqResWin",
                                   _("w3af - HTTP Request/Response"),
                                   "Browsing_the_Knowledge_Base")

        # Create the request response viewer
        rrViewer = reqResViewer(w3af, enableWidget, withManual, withFuzzy,
                                withCompare, withAudit, editableRequest,
                                editableResponse, widgname)

        # Search the id in the DB
        historyItem = HistoryItem()
        historyItem.load(request_id)

        # Set
        rrViewer.request.show_object(historyItem.request)
        rrViewer.response.show_object(historyItem.response)
        rrViewer.show()
        self.vbox.pack_start(rrViewer)

        # Show the window
        self.show()
Beispiel #6
0
    def _get_request_response_from_work_unit(self, work_unit):
        """
        In some cases the work unit is a tuple with request / response instances.

        In other cases it is an ID, which needs to be queried from the History DB
        to get the request / response.

        :param work_unit: One of the options explained above
        :return: A request / response tuple
        """
        if not isinstance(work_unit, int):
            request, response = work_unit
        else:
            # Before we sent requests and responses as work units,
            # but since we changed from Queue to CachedQueue for BaseConsumer
            # the database was growing really big (1GB) for storing that traffic
            # and I decided to migrate to using just the response.id and querying
            # the SQLite one extra time.
            history = HistoryItem()
            request, response = history.load_from_file(work_unit)

        # Create a fuzzable request based on the urllib2 request object
        headers_inst = Headers(request.header_items())
        request = FuzzableRequest.from_parts(request.url_object,
                                             request.get_method(),
                                             request.get_data() or '',
                                             headers_inst)

        return request, response
Beispiel #7
0
    def test_render_with_special_chars(self):
        _id = 2

        desc = ('This is a long description that contains some special'
                ' characters such as <, & and > which MUST be encoded'
                ' by jinja2.')

        vuln = MockVuln(_id=_id)
        vuln.set_desc(desc)

        url = URL('http://w3af.com/a/b/c.php')
        hdr = Headers([('User-Agent', 'w3af')])
        request = HTTPRequest(url, data='a=1')
        request.set_headers(hdr)

        hdr = Headers([('Content-Type', 'text/html')])
        res = HTTPResponse(200, '<html>', hdr, url, url)

        h1 = HistoryItem()
        h1.request = request
        res.set_id(_id)
        h1.response = res
        h1.save()

        x = xml_file()

        finding = Finding(x._get_jinja2_env(), vuln)
        xml = finding.to_string()

        self.assertNotIn('such as <, & and > which MUST', xml)
        self.assertIn('such as &lt;, &amp; and &gt; which MUST', xml)
        self.assertValidXML(xml)
Beispiel #8
0
    def setUp(self):
        super(TestHTMLRendering, self).setUp()
        self.plugin = self.w3afcore.plugins.get_plugin_inst('output',
                                                            'html_file')

        HistoryItem().init()

        url = URL('http://w3af.com/a/b/c.php')
        request = HTTPRequest(url, data='a=1')
        hdr = Headers([('Content-Type', 'text/html')])
        res = HTTPResponse(200, '<html>', hdr, url, url)
        h1 = HistoryItem()
        h1.request = request
        res.set_id(1)
        h1.response = res
        h1.save()

        url = URL('http://w3af.com/foo.py')
        request = HTTPRequest(url, data='text=xss')
        hdr = Headers([('Content-Type', 'text/html')])
        res = HTTPResponse(200, '<html>empty</html>', hdr, url, url)
        h1 = HistoryItem()
        h1.request = request
        res.set_id(4)
        h1.response = res
        h1.save()
Beispiel #9
0
    def test_render_attr_with_special_chars(self):
        _id = 2

        name = 'A long description with special characters: <&">'

        vuln = MockVuln(_id=_id)
        vuln.set_name(name)

        url = URL('http://w3af.com/a/b/c.php')
        hdr = Headers([('User-Agent', 'w3af')])
        request = HTTPRequest(url, data='a=1')
        request.set_headers(hdr)

        hdr = Headers([('Content-Type', 'text/html')])
        res = HTTPResponse(200, '<html>', hdr, url, url)

        h1 = HistoryItem()
        h1.request = request
        res.set_id(_id)
        h1.response = res
        h1.save()

        x = xml_file()

        finding = Finding(x._get_jinja2_env(), vuln)
        xml = finding.to_string()

        self.assertNotIn(name, xml)
        self.assertIn('A long description with special characters: &lt;&amp;&quot;&gt;', xml)
        self.assertValidXML(xml)
Beispiel #10
0
 def test_history_access(self):
     self.count_plugin.loops = 1
     self.w3afcore.start()
     
     history_item = HistoryItem() 
     self.assertTrue(history_item.load(1))
     self.assertEqual(history_item.id, 1)
     self.assertEqual(history_item.get_request().get_uri().url_string,
                      get_moth_http())
     self.assertEqual(history_item.get_response().get_uri().url_string,
                      get_moth_http())
Beispiel #11
0
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())
Beispiel #12
0
    def test_cache(self):
        url = URL('http://w3af.com/a/b/c.php')
        hdr = Headers([('User-Agent', 'w3af')])
        request = HTTPRequest(url, data='a=1')
        request.set_headers(hdr)

        hdr = Headers([('Content-Type', 'text/html')])
        res = HTTPResponse(200, '<html>', hdr, url, url)

        _id = 2

        h1 = HistoryItem()
        h1.request = request
        res.set_id(_id)
        h1.response = res
        h1.save()

        x = xml_file()
        http_transaction = HTTPTransaction(x._get_jinja2_env(), _id)

        self.assertIsNone(http_transaction.get_node_from_cache())

        # Writes to cache
        xml = http_transaction.to_string()

        expected = (u'<http-transaction id="2">\n\n'
                    u'    <http-request>\n'
                    u'        <status>POST http://w3af.com/a/b/c.php HTTP/1.1</status>\n'
                    u'        <headers>\n'
                    u'            <header field="User-agent" content="w3af" />\n'
                    u'        </headers>\n'
                    u'        <body content-encoding="base64">YT0x\n</body>\n'
                    u'    </http-request>\n\n'
                    u'    <http-response>\n'
                    u'        <status>HTTP/1.1 200 OK</status>\n'
                    u'        <headers>\n'
                    u'            <header field="Content-Type" content="text/html" />\n'
                    u'        </headers>\n'
                    u'        <body content-encoding="base64">PGh0bWw+\n</body>\n'
                    u'    </http-response>\n\n</http-transaction>')
        self.assertEqual(expected, xml)

        # Yup, we're cached
        self.assertIsNotNone(http_transaction.get_node_from_cache())

        # Make sure they are all the same
        cached_xml = http_transaction.get_node_from_cache()
        self.assertEqual(cached_xml, expected)

        xml = http_transaction.to_string()
        self.assertEqual(expected, xml)
Beispiel #13
0
    def test_render_simple(self):
        _id = 2

        vuln = MockVuln(_id=_id)

        url = URL('http://w3af.com/a/b/c.php')
        hdr = Headers([('User-Agent', 'w3af')])
        request = HTTPRequest(url, data='a=1')
        request.set_headers(hdr)

        hdr = Headers([('Content-Type', 'text/html')])
        res = HTTPResponse(200, '<html>', hdr, url, url)

        h1 = HistoryItem()
        h1.request = request
        res.set_id(_id)
        h1.response = res
        h1.save()

        x = xml_file()

        finding = Finding(x._get_jinja2_env(), vuln)
        xml = finding.to_string()

        expected = (u'<vulnerability id="[2]" method="GET" name="TestCase" plugin="plugin_name" severity="High" url="None" var="None">\n'
                    u'    <description>Foo bar spam eggsFoo bar spam eggsFoo bar spam eggsFoo bar spam eggsFoo bar spam eggsFoo bar spam eggsFoo bar spam eggsFoo bar spam eggsFoo bar spam eggsFoo bar spam eggs</description>\n\n\n'
                    u'    <http-transactions>\n'
                    u'            <http-transaction id="2">\n\n'
                    u'    <http-request>\n'
                    u'        <status>POST http://w3af.com/a/b/c.php HTTP/1.1</status>\n'
                    u'        <headers>\n'
                    u'            <header field="User-agent" content="w3af" />\n'
                    u'        </headers>\n'
                    u'        <body content-encoding="base64">YT0x\n</body>\n'
                    u'    </http-request>\n\n'
                    u'    <http-response>\n'
                    u'        <status>HTTP/1.1 200 OK</status>\n'
                    u'        <headers>\n'
                    u'            <header field="Content-Type" content="text/html" />\n'
                    u'        </headers>\n'
                    u'        <body content-encoding="base64">PGh0bWw+\n</body>\n'
                    u'    </http-response>\n\n'
                    u'</http-transaction>\n'
                    u'    </http-transactions>\n'
                    u'</vulnerability>')

        self.assertEqual(xml, expected)
        self.assertValidXML(xml)
Beispiel #14
0
    def test_no_duplicate_vuln_reports(self):
        # The xml_file plugin had a bug where vulnerabilities were written to
        # disk multiple times, this test makes sure I fixed that vulnerability

        # Write the HTTP request / response to the DB
        url = URL('http://w3af.com/a/b/c.php')
        hdr = Headers([('User-Agent', 'w3af')])
        request = HTTPRequest(url, data='a=1')
        request.set_headers(hdr)

        hdr = Headers([('Content-Type', 'text/html')])
        res = HTTPResponse(200, '<html>syntax error near', hdr, url, url)

        _id = 1

        h1 = HistoryItem()
        h1.request = request
        res.set_id(_id)
        h1.response = res
        h1.save()

        # Create one vulnerability in the KB pointing to the request-
        # response we just created
        desc = 'Just a test for the XML file output plugin.'
        v = Vuln('SQL injection', desc, severity.HIGH, _id, 'sqli')
        kb.kb.append('sqli', 'sqli', v)

        self.assertEqual(len(kb.kb.get_all_vulns()), 1)

        # Setup the plugin
        plugin_instance = xml_file()

        # Set the output file for the unittest
        ol = OptionList()
        d = 'Output file name where to write the XML data'
        o = opt_factory('output_file', self.FILENAME, d, OUTPUT_FILE)
        ol.add(o)

        # Then we flush() twice to disk, this reproduced the issue
        plugin_instance.set_options(ol)
        plugin_instance.flush()
        plugin_instance.flush()
        plugin_instance.flush()

        # Now we parse the vulnerabilities from disk and confirm only one
        # is there
        file_vulns = get_vulns_from_xml(self.FILENAME)
        self.assertEqual(len(file_vulns), 1, file_vulns)
Beispiel #15
0
 def __init__(self, w3af, kbbrowser, ifilter):
     super(FullKBTree, self).__init__(w3af, ifilter,
                                      'Knowledge Base', strict=False)
     self._historyItem = HistoryItem()
     self.kbbrowser = kbbrowser
     self.connect('cursor-changed', self._showDesc)
     self.show()
Beispiel #16
0
    def test_clear(self):
        
        url = URL('http://w3af.com/a/b/c.php')
        request = HTTPRequest(url, data='a=1')
        hdr = Headers([('Content-Type', 'text/html')])
        res = HTTPResponse(200, '<html>', hdr, url, url)
        
        h1 = HistoryItem()
        h1.request = request
        res.set_id(1)
        h1.response = res
        h1.save()

        table_name = h1.get_table_name()
        db = get_default_temp_db_instance()
        
        self.assertTrue(db.table_exists(table_name))
        
        clear_result = h1.clear()
        
        self.assertTrue(clear_result)
        self.assertFalse(os.path.exists(h1._session_dir),
                         '%s exists.' % h1._session_dir)
        
        # Changed the meaning of clear a little bit... now it simply removes
        # all rows from the table, not the table itself
        self.assertTrue(db.table_exists(table_name))        
Beispiel #17
0
    def test_tag(self):
        tag_id = random.randint(501, 999)
        tag_value = rand_alnum(10)
        url = URL('http://w3af.org/a/b/c.php')

        for i in xrange(501, 1000):
            request = HTTPRequest(url, data='a=1')
            hdr = Headers([('Content-Type', 'text/html')])
            res = HTTPResponse(200, '<html>', hdr, url, url)
            h1 = HistoryItem()
            h1.request = request
            res.set_id(i)
            h1.response = res
            if i == tag_id:
                h1.update_tag(tag_value)
            h1.save()

        h2 = HistoryItem()
        h2.load(tag_id)
        self.assertEqual(h2.tag, tag_value)
Beispiel #18
0
    def __init__(self, w3af):
        super(KBBrowser, self).__init__(w3af, "pane-kbbrowser", 250)

        # Internal variables:
        # Save the request and response ids to be used in the page control
        self.req_res_ids = []
        # This is to search the DB and print the different request and responses
        # as they are requested from the page control, "page_change" method.
        self._historyItem = HistoryItem()

        # the filter to the tree
        filterbox = gtk.HBox()
        self.filters = {}

        def make_but(label, signal, initial):
            but = gtk.CheckButton(label)
            but.set_active(initial)
            but.connect('clicked', self.type_filter, signal)
            self.filters[signal] = initial
            but.show()
            filterbox.pack_start(but, expand=False, fill=False, padding=2)
        make_but('Vulnerability', 'vuln', True)
        make_but('Information', 'info', True)
        filterbox.show()

        # the kb tree
        self.kbtree = FullKBTree(w3af, self, self.filters)

        # all in the first pane
        kbtree_scrollwin = gtk.ScrolledWindow()
        kbtree_scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        kbtree_scrollwin.add(self.kbtree)
        kbtree_scrollwin.show()

        # the filter and tree box
        treebox = gtk.VBox()
        treebox.pack_start(filterbox, expand=False, fill=False)
        treebox.pack_start(kbtree_scrollwin)
        treebox.show()

        # the vulnerability information
        summary = self.get_notebook_summary(w3af)
        description = self.get_notebook_description()

        self.vuln_notebook = gtk.Notebook()
        self.vuln_notebook.append_page(summary, gtk.Label('Summary'))
        self.vuln_notebook.append_page(description, gtk.Label('Description'))
        self.vuln_notebook.set_current_page(0)
        self.vuln_notebook.show()

        # pack & show
        self.pack1(treebox)
        self.pack2(self.vuln_notebook)
        self.show()
Beispiel #19
0
    def store_in_cache(request, response):
        # Create the http response object
        resp = HTTPResponse.from_httplib_resp(response,
                                              original_url=request.url_object)
        resp.set_id(response.id)
        resp.set_alias(gen_hash(request))

        hi = HistoryItem()
        hi.request = request
        hi.response = resp

        # Now save them
        try:
            hi.save()
        except sqlite3.Error, e:
            msg = 'A sqlite3 error was raised: "%s".' % e
            
            if 'disk' in str(e).lower():
                msg += ' Please check if your disk is full.'
                
            raise ScanMustStopException(msg)
Beispiel #20
0
    def test_render_simple(self):
        url = URL('http://w3af.com/a/b/c.php')
        hdr = Headers([('User-Agent', 'w3af')])
        request = HTTPRequest(url, data='a=1')
        request.set_headers(hdr)

        hdr = Headers([('Content-Type', 'text/html')])
        res = HTTPResponse(200, '<html>', hdr, url, url)

        _id = 1

        h1 = HistoryItem()
        h1.request = request
        res.set_id(_id)
        h1.response = res
        h1.save()

        x = xml_file()
        http_transaction = HTTPTransaction(x._get_jinja2_env(), _id)
        xml = http_transaction.to_string()

        expected = (u'<http-transaction id="1">\n\n'
                    u'    <http-request>\n'
                    u'        <status>POST http://w3af.com/a/b/c.php HTTP/1.1</status>\n'
                    u'        <headers>\n'
                    u'            <header field="User-agent" content="w3af" />\n'
                    u'        </headers>\n'
                    u'        <body content-encoding="base64">YT0x\n</body>\n'
                    u'    </http-request>\n\n'
                    u'    <http-response>\n'
                    u'        <status>HTTP/1.1 200 OK</status>\n'
                    u'        <headers>\n'
                    u'            <header field="Content-Type" content="text/html" />\n'
                    u'        </headers>\n'
                    u'        <body content-encoding="base64">PGh0bWw+\n</body>\n'
                    u'    </http-response>\n\n</http-transaction>')

        self.assertEqual(expected, xml)
        self.assertValidXML(xml)
Beispiel #21
0
 def test_save_load(self):
     i = random.randint(1, 499)
     url = URL('http://w3af.com/a/b/c.php')
     request = HTTPRequest(url, data='a=1')
     hdr = Headers([('Content-Type', 'text/html')])
     res = HTTPResponse(200, '<html>', hdr, url, url)
     h1 = HistoryItem()
     h1.request = request
     res.set_id(i)
     h1.response = res
     h1.save()
     h2 = HistoryItem()
     h2.load(i)
     self.assertEqual(h1.request, h2.request)
     self.assertEqual(h1.response.body, h2.response.body)
Beispiel #22
0
    def _impact_done(self, event, impact):
        # Keep calling this from timeout_add until isSet
        if not event.isSet():
            return True
        # We stop the throbber, and hide it
        self.throbber.hide()
        self.throbber.running(False)
        # Analyze the impact
        if impact.ok:
            #   Lets check if we found any vulnerabilities
            #
            #   TODO: I should actually show ALL THE REQUESTS generated by audit plugins...
            #               not just the ones with vulnerabilities.
            #
            for result in impact.result:

                # TODO: I'm not sure when this is None bug it appeared in Trac bug #167736
                if result.get_id() is not None:
                    for itemId in result.get_id():
                        historyItem = HistoryItem()
                        historyItem.load(itemId)
                        historyItem.update_tag(historyItem.tag +
                                               result.plugin_name)
                        historyItem.info = result.get_desc()
                        historyItem.save()
        else:
            if impact.exception.__class__ == BaseFrameworkException:
                msg = str(impact.exception)
            elif impact.exception.__class__ == ScanMustStopException:
                msg = "Stopped sending requests because " + \
                    str(impact.exception)
            elif impact.exception.__class__ == ScanMustStopOnUrlError:
                msg = "Not sending requests because " + str(impact.exception)
            else:
                raise impact.exception
            # We stop the throbber, and hide it
            self.throbber.hide()
            self.throbber.running(False)
            gtk.gdk.threads_enter()
            helpers.FriendlyExceptionDlg(msg)
            gtk.gdk.threads_leave()
        return False
Beispiel #23
0
    def test_delete(self):
        i = random.randint(1, 499)

        url = URL('http://w3af.com/a/b/c.php')
        request = HTTPRequest(url, data='a=1')
        hdr = Headers([('Content-Type', 'text/html')])
        res = HTTPResponse(200, '<html>', hdr, url, url)
        res.set_id(i)

        h1 = HistoryItem()
        h1.request = request
        h1.response = res
        h1.save()

        fname = h1._get_trace_filename_for_id(i)
        self.assertTrue(os.path.exists(fname))

        h1.delete(i)

        self.assertRaises(DBException, h1.read, i)
        self.assertFalse(os.path.exists(fname))
Beispiel #24
0
    def _impact_done(self, event, impact):
        # Keep calling this from timeout_add until isSet
        if not event.isSet():
            return True
        # We stop the throbber, and hide it
        self.throbber.hide()
        self.throbber.running(False)

        # Analyze the impact
        if impact.ok:
            #   Lets check if we found any vulnerabilities
            #
            #   TODO: I should actually show ALL THE REQUESTS generated by
            #         audit plugins... not just the ones with vulnerabilities.
            #
            for result in impact.result:
                if result.get_id() is None:
                    continue

                for itemId in result.get_id():
                    history_item = HistoryItem()
                    history_item.load(itemId)
                    history_item.update_tag(history_item.tag + result.plugin_name)
                    history_item.info = result.get_desc()
                    history_item.save()
        else:
            if isinstance(impact.exception, HTTPRequestException):
                msg = 'Exception found while sending HTTP request. Original' \
                      ' exception is: "%s"' % impact.exception
            elif isinstance(impact.exception, ScanMustStopException):
                msg = 'Multiple exceptions found while sending HTTP requests.' \
                      ' Exception: "%s"' % impact.exception
            elif isinstance(impact.exception, BaseFrameworkException):
                msg = str(impact.exception)
            else:
                raise impact.exception

            # We stop the throbber, and hide it
            self.throbber.hide()
            self.throbber.running(False)
            gtk.gdk.threads_enter()
            helpers.FriendlyExceptionDlg(msg)
            gtk.gdk.threads_leave()

        return False
Beispiel #25
0
    def __init__(self, w3af, kbbrowser, ifilter):
        """A tree showing all the info.

        This also gives a long description of the element when clicked.

        :param kbbrowser: The KB Browser
        :param filter: The filter to show which elements

        :author: Facundo Batista <facundobatista =at= taniquetil.com.ar>
        """
        super(FullKBTree, self).__init__(w3af, ifilter,
                                         'Knowledge Base', strict=False)
        self._historyItem = HistoryItem()
        self.kbbrowser = kbbrowser
        self.connect('cursor-changed', self._showDesc)
        self.show()
Beispiel #26
0
 def test_clear_clear(self):
     url = URL('http://w3af.com/a/b/c.php')
     request = HTTPRequest(url, data='a=1')
     hdr = Headers([('Content-Type', 'text/html')])
     res = HTTPResponse(200, '<html>', hdr, url, url)
     
     h1 = HistoryItem()
     h1.request = request
     res.set_id(1)
     h1.response = res
     h1.save()
     
     h1.clear()
     h1.clear()
Beispiel #27
0
    def test_render_url_special_chars(self):
        self.maxDiff = None

        _id = 2
        vuln = MockVuln(_id=_id)

        url = URL(
            u'https://w3af.com/._basebind/node_modules/lodash._basecreate/'
            u'LICENSE.txt\x00=ڞ')
        hdr = Headers([('User-Agent', 'w3af')])
        request = HTTPRequest(url, data='a=1')
        request.set_headers(hdr)

        vuln.set_uri(url)

        hdr = Headers([('Content-Type', 'text/html')])
        res = HTTPResponse(200, '<html>', hdr, url, url)

        h1 = HistoryItem()
        h1.request = request
        res.set_id(_id)
        h1.response = res
        h1.save()

        x = xml_file()

        finding = Finding(x._get_jinja2_env(), vuln)
        xml = finding.to_string()

        expected = (
            u'<vulnerability id="[2]" method="GET" name="TestCase" plugin="plugin_name" severity="High" url="https://w3af.com/._basebind/node_modules/lodash._basecreate/LICENSE.txt&lt;character code=&quot;0000&quot;/&gt;=\u069e" var="None">\n'
            u'    <description>Foo bar spam eggsFoo bar spam eggsFoo bar spam eggsFoo bar spam eggsFoo bar spam eggsFoo bar spam eggsFoo bar spam eggsFoo bar spam eggsFoo bar spam eggsFoo bar spam eggs</description>\n\n\n'
            u'    <http-transactions>\n'
            u'            <http-transaction id="2">\n\n'
            u'    <http-request>\n'
            u'        <status>POST https://w3af.com/._basebind/node_modules/lodash._basecreate/LICENSE.txt%00=%DA%9E HTTP/1.1</status>\n'
            u'        <headers>\n'
            u'            <header field="User-agent" content="w3af" />\n'
            u'        </headers>\n'
            u'        <body content-encoding="base64">YT0x\n</body>\n'
            u'    </http-request>\n\n'
            u'    <http-response>\n'
            u'        <status>HTTP/1.1 200 OK</status>\n'
            u'        <headers>\n'
            u'            <header field="Content-Type" content="text/html" />\n'
            u'        </headers>\n'
            u'        <body content-encoding="base64">PGh0bWw+\n</body>\n'
            u'    </http-response>\n\n'
            u'</http-transaction>\n'
            u'    </http-transactions>\n'
            u'</vulnerability>')

        self.assertEqual(xml, expected)
        self.assertValidXML(xml)
Beispiel #28
0
    def test_save_load_unicode_decode_error(self):
        url = URL('http://w3af.com/a/b/é.php?x=á')
        request = HTTPRequest(url, data='a=1')
        headers = Headers([('Content-Type', 'text/html')])

        res = HTTPResponse(200, '<html>', headers, url, url)
        res.set_id(1)

        h1 = HistoryItem()
        h1.request = request
        h1.response = res
        h1.save()

        h2 = HistoryItem()
        h2.load(1)

        self.assertEqual(h1.request, h2.request)
        self.assertEqual(h1.response.body, h2.response.body)
        self.assertEqual(h1.request.url_object, h2.request.url_object)
Beispiel #29
0
    def __init__(self):
        OutputPlugin.__init__(self)

        # These attributes hold the file pointers
        self._file = None

        # User configured parameters
        self._file_name = '/tmp/f5_asm_import.xml'
        self._timeFormat = '%a %b %d %H:%M:%S %Y'
        self._longTimestampString = str(
            time.strftime(self._timeFormat, time.localtime()))
        self._timestampString = str(int(time.time()))

        # List with additional xml elements
        # xml
        # HistoryItem to get requests/responses
        self._history = HistoryItem()
        self._attack_type = {}
        # attack type matrix
        self._attack_type["US Social Security"] = "Information Leakage - SSN"
        self._attack_type["XPATH"] = "XPath Injection"
        self._attack_type["Response splitting"] = "HTTP Response Splitting"
        self._attack_type["path disclosure"] = "Path Traversal"
        self._attack_type[
            "Cross Site Request Forgery"] = "Cross-site Request Forgery"
        self._attack_type["SQL injection"] = "SQL-Injection"
        self._attack_type[
            "credit card number"] = "Information Leakage - Credit Card"
        self._attack_type[
            "Cross Site Scripting"] = "Cross Site Scripting (XSS)"
        self._attack_type["OS Commanding"] = "Command Execution"
        self._attack_type["SSI"] = "Server Side Code Injection"
        self._attack_type["input injection"] = "Injection Attempt"
        self._attack_type["LDAP injection"] = "LDAP Injection"
        self._attack_type["remote file inclusion"] = "Remote File Include"
        self._attack_type["file upload"] = "Malicious File Upload"
        self._attack_type["authentication cred"] = "Brute Force Attack"
        self._attack_type[
            "requires authentication"] = "Authentication/Authorization Attacks"
        self._attack_type["buffer-overflow"] = "Buffer Overflow"
        # start xml file
        self._asmfile = xml.dom.minidom.Document()
        self._topElement = self._asmfile.createElement(
            "scanner_vulnerabilities")
        self._topElement.setAttribute("version", self._timestampString)
Beispiel #30
0
    def _impact_done(self, event, impact):
        # Keep calling this from timeout_add until isSet
        if not event.isSet():
            return True
        # We stop the throbber, and hide it
        self.throbber.hide()
        self.throbber.running(False)
        # Analyze the impact
        if impact.ok:
            #   Lets check if we found any vulnerabilities
            #
            #   TODO: I should actually show ALL THE REQUESTS generated by audit plugins...
            #               not just the ones with vulnerabilities.
            #
            for result in impact.result:

                # TODO: I'm not sure when this is None bug it appeared in Trac bug #167736
                if result.get_id() is not None:
                    for itemId in result.get_id():
                        historyItem = HistoryItem()
                        historyItem.load(itemId)
                        historyItem.update_tag(
                            historyItem.tag + result.plugin_name)
                        historyItem.info = result.get_desc()
                        historyItem.save()
        else:
            if impact.exception.__class__ == BaseFrameworkException:
                msg = str(impact.exception)
            elif impact.exception.__class__ == ScanMustStopException:
                msg = "Stopped sending requests because " + \
                    str(impact.exception)
            elif impact.exception.__class__ == ScanMustStopOnUrlError:
                msg = "Not sending requests because " + str(impact.exception)
            else:
                raise impact.exception
            # We stop the throbber, and hide it
            self.throbber.hide()
            self.throbber.running(False)
            gtk.gdk.threads_enter()
            helpers.FriendlyExceptionDlg(msg)
            gtk.gdk.threads_leave()
        return False
Beispiel #31
0
 def test_delete(self):
     i = random.randint(1, 499)
     
     url = URL('http://w3af.com/a/b/c.php')
     request = HTTPRequest(url, data='a=1')
     hdr = Headers([('Content-Type', 'text/html')])
     res = HTTPResponse(200, '<html>', hdr, url, url)
     res.set_id(i)
     
     h1 = HistoryItem()
     h1.request = request
     h1.response = res
     h1.save()
     
     fname = h1._get_fname_for_id(i)
     self.assertTrue(os.path.exists(fname))
     
     h1.delete(i)
     
     self.assertRaises(DBException, h1.read, i)
     self.assertFalse(os.path.exists(fname))
Beispiel #32
0
 def __init__(self, w3af, padding=10, time_refresh=False):
     """Init object."""
     super(httpLogTab, self).__init__(w3af, "pane-httplogtab", 300)
     self.w3af = w3af
     self._padding = padding
     self._lastId = 0
     self._historyItem = HistoryItem()
     if time_refresh:
         gobject.timeout_add(1000, self.refresh_results)
     # Create the main container
     mainvbox = gtk.VBox()
     mainvbox.set_spacing(self._padding)
     # Add the menuHbox, Req/Res viewer and the R/R selector on the bottom
     self._initSearchBox(mainvbox)
     self._initFilterBox(mainvbox)
     self._initReqResViewer(mainvbox)
     mainvbox.show()
     # Add everything
     self.add(mainvbox)
     self.show()
Beispiel #33
0
 def test_load_not_exists(self):
     h = HistoryItem()
     self.assertRaises(DBException, h.load, 1)
Beispiel #34
0
 def _get_hist_obj(self):
     hist_obj = self._hist_obj
     if hist_obj is None:
         historyobjs = HistoryItem().find([('alias', self._hash_id, "=")])
         self._hist_obj = hist_obj = historyobjs[0] if historyobjs else None
     return hist_obj
Beispiel #35
0
 def test_single_db(self):
     h1 = HistoryItem()
     h2 = HistoryItem()
     self.assertEqual(h1._db, h2._db)
Beispiel #36
0
 def setUp(self):
     kb.kb.cleanup()
     create_temp_dir()
     HistoryItem().init()
Beispiel #37
0
    def test_tag(self):
        tag_id = random.randint(501, 999)
        tag_value = rand_alnum(10)
        url = URL('http://w3af.org/a/b/c.php')

        for i in xrange(501, 1000):
            request = HTTPRequest(url, data='a=1')
            hdr = Headers([('Content-Type', 'text/html')])
            res = HTTPResponse(200, '<html>', hdr, url, url)
            h1 = HistoryItem()
            h1.request = request
            res.set_id(i)
            h1.response = res
            if i == tag_id:
                h1.update_tag(tag_value)
            h1.save()

        h2 = HistoryItem()
        h2.load(tag_id)
        self.assertEqual(h2.tag, tag_value)
Beispiel #38
0
 def test_init_init(self):
     # No exceptions should be raised
     HistoryItem().init()
     HistoryItem().init()
Beispiel #39
0
    def test_save_load_compressed(self):
        force_compression_count = HistoryItem._UNCOMPRESSED_FILES + HistoryItem._COMPRESSED_FILE_BATCH
        force_compression_count += 150

        url = URL('http://w3af.com/a/b/c.php')
        headers = Headers([('Content-Type', 'text/html')])
        body = '<html>' + LOREM * 20

        for i in xrange(1, force_compression_count):
            request = HTTPRequest(url, data='a=%s' % i)

            response = HTTPResponse(200, body, headers, url, url)
            response.set_id(i)

            h = HistoryItem()
            h.request = request
            h.response = response
            h.save()

        compressed_file = os.path.join(h.get_session_dir(), '1-150.zip')
        self.assertTrue(os.path.exists(compressed_file))

        expected_files = [
            '%s.trace' % i
            for i in range(1, HistoryItem._COMPRESSED_FILE_BATCH + 1)
        ]

        _zip = zipfile.ZipFile(compressed_file, mode='r')
        self.assertEqual(_zip.namelist(), expected_files)

        for i in xrange(1, 100):
            h = HistoryItem()
            h.load(i)

            self.assertEqual(h.request.get_uri(), url)
            self.assertEqual(h.response.get_headers(), headers)
            self.assertEqual(h.response.get_body(), body)
Beispiel #40
0
 def init():
     create_temp_dir()
     HistoryItem().init()
Beispiel #41
0
class FuzzyRequests(entries.RememberingWindow):
    """Infrastructure to generate fuzzy HTTP requests.

    :author: Facundo Batista <facundobatista =at= taniquetil.com.ar>
    """
    def __init__(self, w3af, initial_request=None):
        super(FuzzyRequests, self).__init__(w3af, "fuzzyreq",
                                            "w3af - Fuzzy Requests",
                                            "Fuzzy_Requests")
        self.w3af = w3af
        self.historyItem = HistoryItem()
        mainhbox = gtk.HBox()

        # To store the responses
        self.responses = []

        # ---- left pane ----
        vbox = gtk.VBox()
        mainhbox.pack_start(vbox, False, False)

        # we create the buttons first, to pass them
        analyzBut = gtk.Button("Analyze")
        self.sendPlayBut = entries.SemiStockButton(
            "", gtk.STOCK_MEDIA_PLAY, "Sends the pending requests")
        self.sendStopBut = entries.SemiStockButton(
            "", gtk.STOCK_MEDIA_STOP, "Stops the request being sent")
        self.sSB_state = helpers.PropagateBuffer(
            self.sendStopBut.set_sensitive)
        self.sSB_state.change(self, False)

        # Fix content length checkbox
        self._fix_content_lengthCB = gtk.CheckButton('Fix content length header')
        self._fix_content_lengthCB.set_active(True)
        self._fix_content_lengthCB.show()

        # request
        self.originalReq = RequestPart(self, w3af,
                                       [analyzBut.set_sensitive,
                                        self.sendPlayBut.set_sensitive,
                                        functools.partial(self.sSB_state.change, "rRV")],
                                       editable=True,
                                       widgname="fuzzyrequest")

        if initial_request is None:
            self.originalReq.show_raw(FUZZY_REQUEST_EXAMPLE, '')
        else:
            (initialUp, initialDn) = initial_request
            self.originalReq.show_raw(initialUp, initialDn)

        # Add the right button popup menu to the text widgets
        rawTextView = self.originalReq.get_view_by_id('HttpRawView')
        rawTextView.textView.connect("populate-popup", self._populate_popup)

        # help
        helplabel = gtk.Label()
        helplabel.set_selectable(True)
        helplabel.set_markup(FUZZYHELP)
        self.originalReq.append_page(helplabel, gtk.Label("Syntax help"))
        helplabel.show()
        self.originalReq.show()
        vbox.pack_start(self.originalReq, True, True, padding=5)
        vbox.show()

        # the commands
        t = gtk.Table(2, 4)
        analyzBut.connect("clicked", self._analyze)
        t.attach(analyzBut, 0, 2, 0, 1)
        self.analyzefb = gtk.Label("0 requests")
        self.analyzefb.set_sensitive(False)
        t.attach(self.analyzefb, 2, 3, 0, 1)
        self.preview = gtk.CheckButton("Preview")
        t.attach(self.preview, 3, 4, 0, 1)
        self.sPB_signal = self.sendPlayBut.connect("clicked", self._send_start)
        t.attach(self.sendPlayBut, 0, 1, 1, 2)
        self.sendStopBut.connect("clicked", self._send_stop)
        t.attach(self.sendStopBut, 1, 2, 1, 2)
        self.sendfb = gtk.Label("0 ok, 0 errors")
        self.sendfb.set_sensitive(False)
        t.attach(self.sendfb, 2, 3, 1, 2)
        t.attach(self._fix_content_lengthCB, 3, 4, 1, 2)
        t.show_all()

        vbox.pack_start(t, False, False, padding=5)

        # ---- throbber pane ----
        vbox = gtk.VBox()
        self.throbber = helpers.Throbber()
        self.throbber.set_sensitive(False)
        vbox.pack_start(self.throbber, False, False)
        vbox.show()
        mainhbox.pack_start(vbox, False, False)

        # ---- right pane ----
        vbox = gtk.VBox()
        mainhbox.pack_start(vbox)

        # A label to show the id of the response
        self.title0 = gtk.Label()
        self.title0.show()
        vbox.pack_start(self.title0, False, True)

        # result itself
        self.resultReqResp = ReqResViewer(w3af, withFuzzy=False,
                                          editableRequest=False,
                                          editableResponse=False)
        self.resultReqResp.set_sensitive(False)
        vbox.pack_start(self.resultReqResp, True, True, padding=5)
        vbox.show()

        # result control
        centerbox = gtk.HBox()
        self.pagesControl = entries.PagesControl(w3af, self._pageChange)
        centerbox.pack_start(self.pagesControl, True, False)
        centerbox.show()

        # cluster responses button
        image = gtk.Image()
        image.set_from_file(os.path.join(ROOT_PATH, 'core', 'ui', 'gui',
                                         'data', 'cluster_data.png'))
        image.show()
        self.clusterButton = gtk.Button(label='Cluster responses')
        self.clusterButton.connect("clicked", self._clusterData)
        self.clusterButton.set_sensitive(False)
        self.clusterButton.set_image(image)
        self.clusterButton.show()
        centerbox.pack_start(self.clusterButton, True, False)

        # clear responses button
        self.clearButton = entries.SemiStockButton(
            'Clear Responses', gtk.STOCK_CLEAR,
            tooltip='Clear all HTTP responses from fuzzer window')
        self.clearButton.connect("clicked", self._clearResponses)
        self.clearButton.set_sensitive(False)
        self.clearButton.show()
        centerbox.pack_start(self.clearButton, True, False)

        vbox.pack_start(centerbox, False, False, padding=5)

        # Show all!
        self._sendPaused = True
        self.vbox.pack_start(mainhbox)
        self.vbox.show()
        mainhbox.show()
        self.show()

    def _populate_popup(self, textview, menu):
        """Populates the menu with the fuzzing items."""
        menu.append(gtk.SeparatorMenuItem())
        main_generator_menu = gtk.MenuItem(_("Generators"))
        main_generator_menu.set_submenu(create_generator_menu(self))
        menu.append(main_generator_menu)
        menu.show_all()

    def _clearResponses(self, widg):
        """Clears all the responses from the fuzzy window."""
        self.responses = []
        self.resultReqResp.request.clear_panes()
        self.resultReqResp.response.clear_panes()
        self.resultReqResp.set_sensitive(False)
        self.clusterButton.set_sensitive(False)
        self.clearButton.set_sensitive(False)
        self.pagesControl.deactivate()

    def _clusterData(self, widg):
        """Analyze if we can cluster the responses and do it."""
        data = []
        for resp in self.responses:
            if resp[0]:
                reqid = resp[1]
                historyItem = self.historyItem.read(reqid)
                data.append(historyItem.response)

        if data:
            distance_function_selector(self.w3af, data)
        else:
            # Let the user know ahout the problem
            msg = "There are no HTTP responses available to cluster."
            dlg = gtk.MessageDialog(None, gtk.DIALOG_MODAL,
                                    gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, msg)
            opt = dlg.run()
            dlg.destroy()

    def _analyze(self, widg):
        """Handles the Analyze part."""
        (request, postbody) = self.originalReq.get_both_texts_raw()
        try:
            fg = helpers.coreWrap(fuzzygen.FuzzyGenerator, request, postbody)
        except fuzzygen.FuzzyError:
            return

        self.analyzefb.set_text("%d requests" % fg.calculate_quantity())
        self.analyzefb.set_sensitive(True)

        # raise the window only if preview is active
        if self.preview.get_active():
            PreviewWindow(self.w3af, self, fg)

    def _send_stop(self, widg=None):
        """Stop the requests being sent."""
        self._sendStopped = True
        self.sendPlayBut.change_internals(
            "", gtk.STOCK_MEDIA_PLAY, "Sends the pending requests")
        self.sendPlayBut.disconnect(self.sPB_signal)
        self.sPB_signal = self.sendPlayBut.connect("clicked", self._send_start)
        self.sSB_state.change(self, False)
        self.throbber.running(False)

    def _send_pause(self, widg):
        """Pause the requests being sent."""
        self._sendPaused = True
        self.sendPlayBut.change_internals("", gtk.STOCK_MEDIA_PLAY,
                                          "Sends the pending requests")
        self.sendPlayBut.disconnect(self.sPB_signal)
        self.sPB_signal = self.sendPlayBut.connect("clicked", self._send_play)
        self.throbber.running(False)

    def _send_play(self, widg):
        """Continue sending the requests."""
        self._sendPaused = False
        self.sendPlayBut.change_internals("", gtk.STOCK_MEDIA_PAUSE,
                                          "Sends the pending requests")
        self.sendPlayBut.disconnect(self.sPB_signal)
        self.sPB_signal = self.sendPlayBut.connect("clicked", self._send_pause)
        self.throbber.running(True)

    def _send_start(self, widg):
        """Start sending the requests."""
        (request, postbody) = self.originalReq.get_both_texts_raw()
        
        try:
            fg = helpers.coreWrap(fuzzygen.FuzzyGenerator, request, postbody)
        except fuzzygen.FuzzyError:
            return

        quant = fg.calculate_quantity()
        if quant > 20:
            msg = "Are you sure you want to send %d requests?" % quant
            dlg = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_WARNING,
                                    gtk.BUTTONS_YES_NO, msg)
            opt = dlg.run()
            dlg.destroy()
            if opt != gtk.RESPONSE_YES:
                return

        # Get the fix content length value
        fixContentLength = self._fix_content_lengthCB.get_active()

        # initial state
        self.result_ok = 0
        self.result_err = 0
        self._sendPaused = False
        self._sendStopped = False
        requestGenerator = fg.generate()

        # change the buttons
        self.sendPlayBut.change_internals("", gtk.STOCK_MEDIA_PAUSE,
                                          "Pauses the requests sending")
        self.sendPlayBut.disconnect(self.sPB_signal)
        self.sPB_signal = self.sendPlayBut.connect("clicked", self._send_pause)
        self.sSB_state.change(self, True)
        self.throbber.running(True)

        # let's send the requests!
        gobject.timeout_add(100, self._real_send, fixContentLength,
                            requestGenerator)

    def _real_send(self, fixContentLength, requestGenerator):
        """This is the one that actually sends the requests, if corresponds.

        :param fixContentLength: if the lenght should be fixed by the core.
        :param requestGenerator: where to ask for the requests
        """
        if self._sendStopped:
            return False
        if self._sendPaused:
            return True

        try:
            realreq, realbody = requestGenerator.next()
        except StopIteration:
            # finished with all the requests!
            self._send_stop()
            return False

        try:
            http_resp = self.w3af.uri_opener.send_raw_request(realreq, realbody,
                                                              fixContentLength)
            error_msg = None
            self.result_ok += 1
        except HTTPRequestException, e:
            # One HTTP request failed
            error_msg = str(e)
            http_resp = None
            self.result_err += 1
        except ScanMustStopException, e:
            # Many HTTP requests failed and the URL library wants to stop
            error_msg = str(e)
            self.result_err += 1

            # Let the user know about the problem
            msg = "Stopped sending requests because of the following"\
                  " unexpected error:\n\n%s"

            helpers.FriendlyExceptionDlg(msg % error_msg)
            return False
Beispiel #42
0
 def clear():
     """
     Clear the cache (remove all files and directories associated with it).
     """
     return HistoryItem().clear()
Beispiel #43
0
    def _write_findings_to_xml(self):
        """
        Write all the findings to the XML file
        :return: None, we write the data to the XML
        """
        # HistoryItem to get requests/responses
        req_history = HistoryItem()

        for i in kb.kb.get_all_findings():
            message_node = self._xml.createElement('vulnerability')

            message_node.setAttribute('severity', xml_str(i.get_severity()))
            message_node.setAttribute('method', xml_str(i.get_method()))
            message_node.setAttribute('url', xml_str(i.get_url()))
            message_node.setAttribute('var', xml_str(i.get_token_name()))
            message_node.setAttribute('name', xml_str(i.get_name()))
            message_node.setAttribute('plugin', xml_str(i.get_plugin_name()))

            # Wrap description in a <description> element and put it above the
            # request/response elements
            desc_str = xml_str(i.get_desc(with_id=False))
            description_node = self._xml.createElement('description')
            description = self._xml.createTextNode(desc_str)
            description_node.appendChild(description)
            message_node.appendChild(description_node)

            # If there is information from the vulndb, then we should write it
            if i.has_db_details():
                desc_str = xml_str(i.get_long_description())
                description_node = self._xml.createElement('long-description')
                description = self._xml.createTextNode(desc_str)
                description_node.appendChild(description)
                message_node.appendChild(description_node)

                fix_str = xml_str(i.get_fix_guidance())
                fix_node = self._xml.createElement('fix-guidance')
                fix = self._xml.createTextNode(fix_str)
                fix_node.appendChild(fix)
                message_node.appendChild(fix_node)

                fix_effort_str = xml_str(i.get_fix_effort())
                fix_node = self._xml.createElement('fix-effort')
                fix = self._xml.createTextNode(fix_effort_str)
                fix_node.appendChild(fix)
                message_node.appendChild(fix_node)

                if i.get_references():
                    references_node = self._xml.createElement('references')

                    for ref in i.get_references():
                        ref_node = self._xml.createElement('reference')
                        ref_node.setAttribute('title', xml_str(ref.title))
                        ref_node.setAttribute('url', xml_str(ref.url))
                        references_node.appendChild(ref_node)

                    message_node.appendChild(references_node)

            if i.get_id():
                message_node.setAttribute('id', str(i.get_id()))
                # Wrap all transactions in a http-transactions node
                transaction_set = self._xml.createElement('http-transactions')
                message_node.appendChild(transaction_set)

                for request_id in i.get_id():
                    try:
                        details = req_history.read(request_id)
                    except DBException:
                        msg = 'Failed to retrieve request with id %s from DB.'
                        print(msg % request_id)
                        continue

                    # Wrap the entire http transaction in a single block
                    action_set = self._xml.createElement('http-transaction')
                    action_set.setAttribute('id', str(request_id))
                    transaction_set.appendChild(action_set)

                    request_node = self._xml.createElement('http-request')
                    self.report_http_action(request_node, details.request)
                    action_set.appendChild(request_node)

                    response_node = self._xml.createElement('http-response')
                    self.report_http_action(response_node, details.response)
                    action_set.appendChild(response_node)

            self._top_elem.appendChild(message_node)
Beispiel #44
0
    def test_find(self):
        find_id = random.randint(1, 499)
        url = URL('http://w3af.org/a/b/foobar.php?foo=123')
        tag_value = rand_alnum(10)

        for i in xrange(0, 500):
            request = HTTPRequest(url, data='a=1')
            code = 200
            if i == find_id:
                code = 302

            hdr = Headers([('Content-Type', 'text/html')])
            res = HTTPResponse(code, '<html>', hdr, url, url)
            h1 = HistoryItem()
            h1.request = request
            res.set_id(i)
            h1.response = res

            if i == find_id:
                h1.toggle_mark()
                h1.update_tag(tag_value)
            h1.save()

        h2 = HistoryItem()
        self.assertEqual(
            len(h2.find([('tag', "%" + tag_value + "%", 'like')])), 1)
        self.assertEqual(len(h2.find([('code', 302, '=')])), 1)
        self.assertEqual(len(h2.find([('mark', 1, '=')])), 1)
        self.assertEqual(len(h2.find([('has_qs', 1, '=')])), 500)
        self.assertEqual(len(h2.find([('has_qs', 1, '=')], result_limit=10)),
                         10)
        results = h2.find([('has_qs', 1, '=')],
                          result_limit=1,
                          order_data=[('id', 'desc')])
        self.assertEqual(results[0].id, 499)
        search_data = [('id', find_id + 1, "<"), ('id', find_id - 1, ">")]
        self.assertEqual(len(h2.find(search_data)), 1)
Beispiel #45
0
class httpLogTab(RememberingHPaned):
    """
    A tab that shows all HTTP requests and responses made by the framework.

    :author: Andres Riancho ([email protected])
    """
    def __init__(self, w3af, padding=10, time_refresh=False):
        """Init object."""
        super(httpLogTab, self).__init__(w3af, "pane-httplogtab", 300)
        self.w3af = w3af
        self._padding = padding
        self._lastId = 0
        self._historyItem = HistoryItem()
        if time_refresh:
            gobject.timeout_add(1000, self.refresh_results)
        # Create the main container
        mainvbox = gtk.VBox()
        mainvbox.set_spacing(self._padding)
        # Add the menuHbox, Req/Res viewer and the R/R selector on the bottom
        self._initSearchBox(mainvbox)
        self._initFilterBox(mainvbox)
        self._initReqResViewer(mainvbox)
        mainvbox.show()
        # Add everything
        self.add(mainvbox)
        self.show()

    def _initReqResViewer(self, mainvbox):
        """Create the req/res viewer."""
        self._req_res_viewer = ReqResViewer(self.w3af,
                                            editableRequest=False,
                                            editableResponse=False)
        self._req_res_viewer.set_sensitive(False)
        # Create the req/res selector (when a search with more
        # than one result is done, this window appears)
        self._sw = gtk.ScrolledWindow()
        self._sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
        self._sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self._lstore = gtk.ListStore(gobject.TYPE_UINT, gobject.TYPE_BOOLEAN,
                                     gobject.TYPE_STRING, gobject.TYPE_STRING,
                                     gobject.TYPE_STRING, gobject.TYPE_UINT,
                                     gobject.TYPE_STRING, gobject.TYPE_UINT,
                                     gobject.TYPE_STRING, gobject.TYPE_FLOAT)
        # Create tree view
        self._lstoreTreeview = gtk.TreeView(self._lstore)
        self._lstoreTreeview.set_rules_hint(True)
        self._lstoreTreeview.set_search_column(0)
        self.__add_columns(self._lstoreTreeview)
        self._lstoreTreeview.show()
        self._lstoreTreeview.connect('cursor-changed',
                                     self._view_in_req_res_viewer)
        # Popup menu
        self._rightButtonMenu = None
        self._lstoreTreeview.connect('button-press-event', self._popupMenu)
        #
        #
        # Selection
        #
        treeselection = self._lstoreTreeview.get_selection()
        treeselection.set_mode(gtk.SELECTION_MULTIPLE)

        self._sw.add(self._lstoreTreeview)
        #self._sw.set_sensitive(False)
        self._sw.show_all()
        # I want all sections to be resizable
        self._vpan = RememberingVPaned(self.w3af, "pane-swandrRV", 100)
        self._vpan.pack1(self._sw)
        self._vpan.pack2(self._req_res_viewer)
        self._vpan.show()
        mainvbox.pack_start(self._vpan)

    def _popupMenu(self, tv, event):
        """Generate and show popup menu."""
        if event.button != 3:
            return
        # creates the whole menu only once
        if self._rightButtonMenu is None:
            gm = gtk.Menu()
            self._rightButtonMenu = gm
            # the items
            e = gtk.MenuItem(_("Delete selected items"))
            e.connect('activate', self._deleteSelected)
            gm.append(e)
            gm.show_all()
        else:
            gm = self._rightButtonMenu
        gm.popup(None, None, None, event.button, event.time)
        return True

    def _deleteSelected(self, widg=None):
        """Delete selected transactions."""
        ids = []
        iters = []
        sel = self._lstoreTreeview.get_selection()
        (model, pathlist) = sel.get_selected_rows()
        for path in pathlist:
            iters.append(self._lstore.get_iter(path))
            itemNumber = path[0]
            iid = self._lstore[itemNumber][0]
            ids.append(iid)
        for i in iters:
            self._lstore.remove(i)
        #  TODO Move this action to separate thread
        for iid in ids:
            self._historyItem.delete(iid)

    def _initSearchBox(self, mainvbox):
        """Init Search box."""
        # The search entry
        self._searchText = gtk.Entry()
        self._searchText.connect("activate", self.find_request_response)
        # The button that is used to advanced search
        filterBtn = gtk.ToggleButton(label=_("_Filter Options"))
        filterBtn.connect("toggled", self._showHideFilterBox)
        filterImg = gtk.Image()
        filterImg.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
        filterBtn.set_image(filterImg)
        # Clear button
        close = gtk.Image()
        close.set_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_MENU)
        clearBox = gtk.EventBox()
        clearBox.add(close)
        clearBox.connect("button-release-event", self._showAllRequestResponses)
        # Create the container that has the menu
        menuHbox = gtk.HBox()
        menuHbox.set_spacing(self._padding)
        menuHbox.pack_start(gtk.Label(_("Search:")), False)
        menuHbox.pack_start(self._searchText)
        menuHbox.pack_start(clearBox, False)
        menuHbox.pack_start(filterBtn, False)
        menuHbox.show_all()
        mainvbox.pack_start(menuHbox, False, True)

    def _initFilterBox(self, mainvbox):
        """Init advanced search options."""
        self._advSearchBox = gtk.HBox()
        self._advSearchBox.set_spacing(self._padding)
        self.pref = FilterOptions(self)
        # Filter options
        self._filterMethods = [
            ('GET', 'GET', False),
            ('POST', 'POST', False),
        ]
        filterMethods = OptionList()
        for method in self._filterMethods:
            filterMethods.add(
                opt_factory(method[0], method[2], method[1], "boolean"))
        self.pref.add_section('methods', _('Request Method'), filterMethods)
        filterId = OptionList()
        filterId.add(opt_factory("min", "0", "Min ID", "string"))
        filterId.add(opt_factory("max", "0", "Max ID", "string"))
        self.pref.add_section('trans_id', _('Transaction ID'), filterId)
        filterCodes = OptionList()
        codes = [
            ("1xx", "1xx", False),
            ("2xx", "2xx", False),
            ("3xx", "3xx", False),
            ("4xx", "4xx", False),
            ("5xx", "5xx", False),
        ]
        for code in codes:
            filterCodes.add(opt_factory(code[0], code[2], code[1], "boolean"))
        self.pref.add_section('codes', _('Response Code'), filterCodes)
        filterMisc = OptionList()
        filterMisc.add(opt_factory("tag", False, "Tag", "boolean"))
        filterMisc.add(
            opt_factory("has_qs", False, "Request has Query String",
                        "boolean"))
        self.pref.add_section('misc', _('Misc'), filterMisc)
        filterTypes = OptionList()
        self._filterTypes = [
            ('html', 'HTML', False),
            ('javascript', 'JavaScript', False),
            ('image', 'Images', False),
            ('flash', 'Flash', False),
            ('css', 'CSS', False),
            ('text', 'Text', False),
        ]
        for filterType in self._filterTypes:
            filterTypes.add(
                opt_factory(filterType[0], filterType[2], filterType[1],
                            "boolean"))
        self.pref.add_section('types', _('Response Content Type'), filterTypes)
        filterSize = OptionList()
        filterSize.add(opt_factory("resp_size", False, "Not Null", "boolean"))
        self.pref.add_section('sizes', _('Response Size'), filterSize)
        self.pref.show()
        self._advSearchBox.pack_start(self.pref, False, False)
        self._advSearchBox.hide_all()
        mainvbox.pack_start(self._advSearchBox, False, False)

    def __add_columns(self, treeview):
        """Add columns to main log table."""
        model = treeview.get_model()
        # Column for id's
        column = gtk.TreeViewColumn(_('ID'), gtk.CellRendererText(), text=0)
        column.set_sort_column_id(0)
        treeview.append_column(column)

        # Column for bookmark
        #TODO: Find a better way to do this. The "B" and the checkbox aren't nice
        #what we aim for is something like the stars in gmail.
        """
        renderer = gtk.CellRendererToggle()
        renderer.set_property('activatable', True)
        renderer.connect('toggled', self.toggle_bookmark, model)
        column = gtk.TreeViewColumn(_('B'), renderer)
        column.add_attribute(renderer, "active", 1)
        column.set_sort_column_id(1)
        treeview.append_column(column)
        """

        # Column for METHOD
        column = gtk.TreeViewColumn(_('Method'),
                                    gtk.CellRendererText(),
                                    text=2)
        column.set_sort_column_id(2)
        treeview.append_column(column)
        # Column for URI
        renderer = gtk.CellRendererText()
        renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
        column = gtk.TreeViewColumn('URI', renderer, text=3)
        column.set_sort_column_id(3)
        column.set_expand(True)
        column.set_resizable(True)
        treeview.append_column(column)
        # Column for Tag
        renderer = gtk.CellRendererText()
        #renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
        renderer.set_property('editable', True)
        renderer.connect('edited', self.edit_tag, model)
        column = gtk.TreeViewColumn(_('Tag'), renderer, text=4)
        column.set_sort_column_id(4)
        column.set_resizable(True)
        column.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
        treeview.append_column(column)
        extColumns = [
            (5, _('Code')),
            (6, _('Message')),
            (7, _('Content-Length')),
            (8, _('Content-Type')),
            (9, _('Time (ms)')),
        ]
        for n, title in extColumns:
            column = gtk.TreeViewColumn(title, gtk.CellRendererText(), text=n)
            column.set_sort_column_id(n)
            treeview.append_column(column)

    def toggle_bookmark(self, cell, path, model):
        """Toggle bookmark."""
        model[path][1] = not model[path][1]
        historyItem = HistoryItem()
        historyItem.load(model[path][0])
        historyItem.toggle_mark(True)
        return

    def edit_tag(self, cell, path, new_text, model):
        """Edit tag."""
        model[path][4] = new_text
        historyItem = HistoryItem()
        historyItem.load(model[path][0])
        historyItem.update_tag(new_text, True)
        return

    def _showHideFilterBox(self, widget):
        """Show/hide advanced options."""
        if not widget.get_active():
            self._advSearchBox.hide_all()
        else:
            self._advSearchBox.show_all()

    def _showAllRequestResponses(self, widget=None, event=None):
        """Show all results."""
        self._searchText.set_text("")
        try:
            self.find_request_response()
        except BaseFrameworkException as w3:
            self._empty_results()
        return

    def refresh_results(self):
        """
        TODO: IMPROVEMENT: The find_request_response will read all items from
                           the DB again. If there are no new requests BUT we're
                           already showing 1K of them, all will be read. Not
                           good for performance.
        """
        self.find_request_response(refresh=True)
        return True

    def find_request_response(self, widget=None, refresh=False):
        """Find entries (req/res)."""
        searchText = self._searchText.get_text()
        searchText = searchText.strip()
        search_data = []
        #
        #  Search part
        #
        if searchText:
            likePieces = [('url', "%" + searchText + "%", 'like'),
                          ('tag', "%" + searchText + "%", 'like')]
            search_data.append((likePieces, 'OR'))
        #
        # Filter part
        #
        # Codes
        codes = self.pref.get_options('codes')
        filterCodes = []
        for opt in codes:
            if opt.get_value():
                codef = opt.get_name()
                filterCodes.append(('codef', int(codef[0]), '='))
        search_data.append((filterCodes, 'OR'))
        # IDs
        try:
            minId = int(self.pref.get_value('trans_id', 'min'))
        except:
            minId = 0
        try:
            maxId = int(self.pref.get_value('trans_id', 'max'))
        except:
            maxId = 0
        if maxId > 0:
            search_data.append(('id', maxId, "<"))
        if minId > 0:
            search_data.append(('id', minId, ">"))
        if refresh:
            search_data.append(('id', self._lastId, ">"))
        # Sizes
        if self.pref.get_value('sizes', 'resp_size'):
            search_data.append(('response_size', 0, ">"))
        # Tags
        if self.pref.get_value('misc', 'tag'):
            search_data.append(('tag', '', "!="))
        # has_query_string
        if self.pref.get_value('misc', 'has_qs'):
            search_data.append(('has_qs', 0, ">"))
        # Content type
        filterTypes = []
        for filterType in self._filterTypes:
            if self.pref.get_value('types', filterType[0]):
                filterTypes.append(
                    ('content_type', "%" + filterType[0] + "%", 'like'))
        search_data.append((filterTypes, 'OR'))
        # Method
        filterMethods = []
        for method in self._filterMethods:
            if self.pref.get_value('methods', method[0]):
                filterTypes.append(('method', method[0], '='))
        search_data.append((filterMethods, 'OR'))

        try:
            # Please see the 5000 below
            searchResultObjects = self._historyItem.find(search_data,
                                                         result_limit=5001,
                                                         order_data=[("id", "")
                                                                     ])
        except BaseFrameworkException, w3:

            self._empty_results()
            return
        if len(searchResultObjects) == 0:
            if not refresh:
                self._empty_results()
            return
        # Please see the 5001 above
        elif len(searchResultObjects) > 5000:
            self._empty_results()
            msg = _('The search you performed returned too many results (') +\
                str(len(searchResultObjects)) + ').\n'
            msg += _('Please refine your search and try again.')
            self._show_message('Too many results', msg)
            return
        else:
            # show the results in the list view (when first row is selected
            # that just triggers the req/resp filling.
            lastItem = searchResultObjects[-1]
            self._lastId = int(lastItem.id)
            self._show_list_view(searchResultObjects, appendMode=refresh)

            self._sw.set_sensitive(True)
            self._req_res_viewer.set_sensitive(True)

            if not refresh:
                self._lstoreTreeview.set_cursor((0, ))

            return
Beispiel #46
0
    def test_mark(self):
        mark_id = 3
        url = URL('http://w3af.org/a/b/c.php')

        for i in xrange(0, 500):
            request = HTTPRequest(url, data='a=1')
            hdr = Headers([('Content-Type', 'text/html')])
            res = HTTPResponse(200, '<html>', hdr, url, url)
            h1 = HistoryItem()
            h1.request = request
            res.set_id(i)
            h1.response = res
            if i == mark_id:
                h1.toggle_mark()
            h1.save()

        h2 = HistoryItem()
        h2.load(mark_id)
        self.assertTrue(h2.mark)

        h3 = HistoryItem()
        h3.load(mark_id - 1)
        self.assertFalse(h3.mark)
Beispiel #47
0
class FullKBTree(KBTree):
    def __init__(self, w3af, kbbrowser, ifilter):
        """A tree showing all the info.

        This also gives a long description of the element when clicked.

        :param kbbrowser: The KB Browser
        :param filter: The filter to show which elements

        :author: Facundo Batista <facundobatista =at= taniquetil.com.ar>
        """
        super(FullKBTree, self).__init__(w3af,
                                         ifilter,
                                         'Knowledge Base',
                                         strict=False)
        self._historyItem = HistoryItem()
        self.kbbrowser = kbbrowser
        self.connect('cursor-changed', self._showDesc)
        self.show()

    def _showDesc(self, tv):
        """Shows the description at the right

        :param tv: the treeview.
        """
        (path, column) = tv.get_cursor()
        if path is None:
            return

        instance = self.get_instance(path)
        if not isinstance(instance, Info):
            return

        longdesc = instance.get_desc()
        self.kbbrowser.explanation.set_text(longdesc)

        if not instance.get_id():
            self.clear_request_response_viewer()
            return

        #
        # We have two different cases:
        #
        # 1) The object is related to ONLY ONE request / response
        # 2) The object is related to MORE THAN ONE request / response
        #
        # For 1), we show the classic view, and for 2) we show the classic
        # view with a "page control"
        #
        # Work:
        #
        if len(instance.get_id()) == 1:
            # There is ONLY ONE id related to the object
            # This is 1)
            self.kbbrowser.pagesControl.deactivate()
            self.kbbrowser._pageChange(0)
            self.kbbrowser.pagesControl.hide()
            self.kbbrowser.title0.hide()

            search_id = instance.get_id()[0]
            try:
                history_item = self._historyItem.read(search_id)
            except DBException:
                msg = _('The HTTP data with id %s is not inside the database.')
                self._show_message(_('Error'), msg % search_id)
                self.clear_request_response_viewer()
                return

            # Error handling for .trace file problems
            # https://github.com/andresriancho/w3af/issues/1174
            try:
                # These lines will trigger the code that reads the .trace file
                # from disk and if they aren't there an exception will rise
                history_item.request
                history_item.response
            except IOError, ioe:
                self._show_message(_('Error'), str(ioe))
                return

            # Now we know that these two lines will work and we won't trigger
            # https://github.com/andresriancho/w3af/issues/1174
            self.kbbrowser.rrV.request.show_object(history_item.request)
            self.kbbrowser.rrV.response.show_object(history_item.response)

            # Don't forget to highlight if necessary
            severity = instance.get_severity()
            for s in instance.get_to_highlight():
                self.kbbrowser.rrV.response.highlight(s, severity)

        else:
Beispiel #48
0
    def __init__(self, w3af, initial_request=None):
        super(FuzzyRequests, self).__init__(w3af, "fuzzyreq",
                                            "w3af - Fuzzy Requests",
                                            "Fuzzy_Requests")
        self.w3af = w3af
        self.historyItem = HistoryItem()
        mainhbox = gtk.HBox()

        # To store the responses
        self.responses = []

        # ---- left pane ----
        vbox = gtk.VBox()
        mainhbox.pack_start(vbox, False, False)

        # we create the buttons first, to pass them
        analyzBut = gtk.Button("Analyze")
        self.sendPlayBut = entries.SemiStockButton(
            "", gtk.STOCK_MEDIA_PLAY, "Sends the pending requests")
        self.sendStopBut = entries.SemiStockButton(
            "", gtk.STOCK_MEDIA_STOP, "Stops the request being sent")
        self.sSB_state = helpers.PropagateBuffer(
            self.sendStopBut.set_sensitive)
        self.sSB_state.change(self, False)

        # Fix content length checkbox
        self._fix_content_lengthCB = gtk.CheckButton('Fix content length header')
        self._fix_content_lengthCB.set_active(True)
        self._fix_content_lengthCB.show()

        # request
        self.originalReq = RequestPart(self, w3af,
                                       [analyzBut.set_sensitive,
                                        self.sendPlayBut.set_sensitive,
                                        functools.partial(self.sSB_state.change, "rRV")],
                                       editable=True,
                                       widgname="fuzzyrequest")

        if initial_request is None:
            self.originalReq.show_raw(FUZZY_REQUEST_EXAMPLE, '')
        else:
            (initialUp, initialDn) = initial_request
            self.originalReq.show_raw(initialUp, initialDn)

        # Add the right button popup menu to the text widgets
        rawTextView = self.originalReq.get_view_by_id('HttpRawView')
        rawTextView.textView.connect("populate-popup", self._populate_popup)

        # help
        helplabel = gtk.Label()
        helplabel.set_selectable(True)
        helplabel.set_markup(FUZZYHELP)
        self.originalReq.append_page(helplabel, gtk.Label("Syntax help"))
        helplabel.show()
        self.originalReq.show()
        vbox.pack_start(self.originalReq, True, True, padding=5)
        vbox.show()

        # the commands
        t = gtk.Table(2, 4)
        analyzBut.connect("clicked", self._analyze)
        t.attach(analyzBut, 0, 2, 0, 1)
        self.analyzefb = gtk.Label("0 requests")
        self.analyzefb.set_sensitive(False)
        t.attach(self.analyzefb, 2, 3, 0, 1)
        self.preview = gtk.CheckButton("Preview")
        t.attach(self.preview, 3, 4, 0, 1)
        self.sPB_signal = self.sendPlayBut.connect("clicked", self._send_start)
        t.attach(self.sendPlayBut, 0, 1, 1, 2)
        self.sendStopBut.connect("clicked", self._send_stop)
        t.attach(self.sendStopBut, 1, 2, 1, 2)
        self.sendfb = gtk.Label("0 ok, 0 errors")
        self.sendfb.set_sensitive(False)
        t.attach(self.sendfb, 2, 3, 1, 2)
        t.attach(self._fix_content_lengthCB, 3, 4, 1, 2)
        t.show_all()

        vbox.pack_start(t, False, False, padding=5)

        # ---- throbber pane ----
        vbox = gtk.VBox()
        self.throbber = helpers.Throbber()
        self.throbber.set_sensitive(False)
        vbox.pack_start(self.throbber, False, False)
        vbox.show()
        mainhbox.pack_start(vbox, False, False)

        # ---- right pane ----
        vbox = gtk.VBox()
        mainhbox.pack_start(vbox)

        # A label to show the id of the response
        self.title0 = gtk.Label()
        self.title0.show()
        vbox.pack_start(self.title0, False, True)

        # result itself
        self.resultReqResp = ReqResViewer(w3af, withFuzzy=False,
                                          editableRequest=False,
                                          editableResponse=False)
        self.resultReqResp.set_sensitive(False)
        vbox.pack_start(self.resultReqResp, True, True, padding=5)
        vbox.show()

        # result control
        centerbox = gtk.HBox()
        self.pagesControl = entries.PagesControl(w3af, self._pageChange)
        centerbox.pack_start(self.pagesControl, True, False)
        centerbox.show()

        # cluster responses button
        image = gtk.Image()
        image.set_from_file(os.path.join(ROOT_PATH, 'core', 'ui', 'gui',
                                         'data', 'cluster_data.png'))
        image.show()
        self.clusterButton = gtk.Button(label='Cluster responses')
        self.clusterButton.connect("clicked", self._clusterData)
        self.clusterButton.set_sensitive(False)
        self.clusterButton.set_image(image)
        self.clusterButton.show()
        centerbox.pack_start(self.clusterButton, True, False)

        # clear responses button
        self.clearButton = entries.SemiStockButton(
            'Clear Responses', gtk.STOCK_CLEAR,
            tooltip='Clear all HTTP responses from fuzzer window')
        self.clearButton.connect("clicked", self._clearResponses)
        self.clearButton.set_sensitive(False)
        self.clearButton.show()
        centerbox.pack_start(self.clearButton, True, False)

        vbox.pack_start(centerbox, False, False, padding=5)

        # Show all!
        self._sendPaused = True
        self.vbox.pack_start(mainhbox)
        self.vbox.show()
        mainhbox.show()
        self.show()
Beispiel #49
0
    def to_string(self):
        """
        :return: An xml node (as a string) representing the HTTP request / response.

        <http-transaction id="...">
            <http-request>
                <status></status>
                <headers>
                    <header>
                        <field></field>
                        <content></content>
                    </header>
                </headers>
                <body content-encoding="base64"></body>
            </http-request>

            <http-response>
                <status></status>
                <headers>
                    <header>
                        <field></field>
                        <content></content>
                    </header>
                </headers>
                <body content-encoding="base64"></body>
            </http-response>
        </http-transaction>

        One of the differences this class has with the previous implementation is
        that the body is always encoded, no matter the content-type. This helps
        prevent encoding issues.
        """
        # Get the data from the cache
        node = self.get_node_from_cache()
        if node is not None:
            return node

        # HistoryItem to get requests/responses
        req_history = HistoryItem()

        # This might raise a DBException in some cases (which I still
        # need to identify and fix). When an exception is raised here
        # the caller needs to handle it by ignoring this part of the
        # HTTP transaction
        request, response = req_history.load_from_file(self._id)

        data = request.get_data() or ''
        b64_encoded_request_body = base64.encodestring(smart_str_ignore(data))

        body = response.get_body() or ''
        b64_encoded_response_body = base64.encodestring(smart_str_ignore(body))

        context = {
            'id': self._id,
            'request': {
                'status': request.get_request_line().strip(),
                'headers': request.get_headers(),
                'body': b64_encoded_request_body
            },
            'response': {
                'status': response.get_status_line().strip(),
                'headers': response.get_headers(),
                'body': b64_encoded_response_body
            }
        }

        context = dotdict(context)

        template = self.get_template(self.TEMPLATE)
        transaction = template.render(context)
        self.save_node_to_cache(transaction)

        return transaction