Every pen-tester - I am using this term both for people who really do pen-tests as for people who do vulnerability assessments- has the same problem: after some days of twiddling with tools, home-grown scripts, diving into attack techniques and research into specific vulnerabilities, you have to write a report.
For those doing this for a living, this report is what what your customer is paying you for and at the end, that’s what keeps your customer coming back. Your customer does not pay you for great out-of-the-box thinking, fantastic mastery of the darker corners of Perl scripting nor your ability to perform fast Google or Baidu searches on the strengths and capabilities of little-known exotic hacking tools.
No, your customer wants a quality report, telling him what is good, what is wrong and what he needs to do to improve or to prevent bad things from happening. This report typically has at least two sections: the management section and a technical section. It is the latter section that usually results in endless hours of mindlessly copying stuff from everywhere.
At Astyran, we try to streamline the reporting activity as much as possible. Our strategy is two-fold:
- Limit the number of different applications, tools, scripts as much as possible and choose tools with a standardised, easily usable and adaptable output (e.g. XML);
- Automate the report-creating process, let your consultants concentrate on what they do best or like the most (I never met a pen-tester who liked reporting).
For the second part, we did a quick search and found two interesting options: MagicTree and Dradis. As detailed here, these tools are quite different in vision and design. In a later post we will describe our experiences with Dradis, but let us first start with MagicTree.
What is MagicTree?
Gremwell calls it a penetration tester productivity tool, designed to allow easy and straightforward data consolidation, querying, external command execution and report generation.
In layman’s terms, MagicTree
- Can import external report data generated by tools often used in pen-testing (supported out-of-the-box at this time are Burp, Nmap, Nikto, Nessus, OpenVAS, Qualys and Imperva Scuba). Some tools (e.g. WhatWeb) generate files that are compatible with the MagicTree XML format;
- Can perform searches on the imported data;
- Can launch external tools;
- Can generate a consolidated report.
Arachni Data
Our first main objective of our review was to see if we could use MagicTree as a reporting tool for web application vulnerability assessments. So we did not really go into the capabilities of the tool to perform searches and launch external tools.
Since we use Arachni in our assessments, and Arachni 0.4 had just been released, we decided to see if we could import the Arachni XML report data into MagicTree.
Alas, Arachni is not supported out of the box, but the online documentation of MagicTree explains how to use XSLT to transform the Arachni XML data into something MagicTree can work with.
Warning: our XSLT transform will only work with the current experimental (0.4.1) version of Arachni, due to some new changes in the XML format since the release. You have to install the experimental version first!
MagicTree Modifications
First, modify the file transforms.properties by adding the following lines:
1: # arachni 2: arachni_report arachni2mt.xslCreate (in the ‘xslt’ folder) the file arachni2mt.xsl with the following content:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="my_report_signature">
<xsl:text>Arachni </xsl:text>
<xsl:value-of select="/arachni_report/generated_on" />
<xsl:value-of select="/arachni_report/system/start_datetime" />
<xsl:value-of select="/arachni_report/system/finish_datetime" />
</xsl:variable>
<xsl:template match="/arachni_report">
<magictree class="MtBranchObject" xmlns:mt="http://www.gremwell.com/magictree">
<testdata class="MtBranchObject">
<!-- a hack to get the port, gamble that domain/subdomain use the same --><xsl:variable name="my_port">
<xsl:call-template name="get-port">
<xsl:with-param name="host" select="system/url"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="my_proto">
<xsl:call-template name="get-proto" >
<xsl:with-param name="host" select="system/url" />
</xsl:call-template>
</xsl:variable>
<!-- Loop through all possible IP addresses of the Arachni XML report --><xsl:for-each select="plugins/resolver/results/hostname" >
<host> <xsl:value-of select="@ipaddress"/>
<hostname>
<xsl:value-of select="@value"/>
</hostname>
<ipproto>tcp
<port>
<xsl:value-of select="$my_port"/>
<xsl:choose>
<xsl:when test="$my_proto='https'">
<tunnel>ssl
<xsl:call-template name="report-issues"/>
</tunnel>
</xsl:when>
<xsl:otherwise> <!-- we assume http -->
<xsl:call-template name="report-issues"/>
</xsl:otherwise>
</xsl:choose>
</port>
</ipproto>
</host>
</xsl:for-each> <!-- The end of our loop through all possible IP addresses -->
</testdata> <!-- The end of the testdata -->
<scaninfo class="MtBranchObject"> <!-- added this to have information about all scans -->
<scan status="new" class="MtTextObject">
<xsl:attribute name="title">
<xsl:text>Arachni </xsl:text>
<xsl:value-of select="/arachni_report/generated_on" />
</xsl:attribute>
<xsl:text>Arachni </xsl:text>
<xsl:value-of select="/arachni_report/generated_on" />
<version>
<xsl:value-of select="/arachni_report/system/version" />
</version>version
<revision>
<xsl:value-of select="/arachni_report/system/revision" />
</revision>
<start_datetime>
<xsl:value-of select="/arachni_report/system/start_datetime" />
</start_datetime>
<finish_datetime>
<xsl:value-of select="/arachni_report/system/finish_datetime" />
</finish_datetime>
<delta_time>
<xsl:value-of select="/arachni_report/system/delta_time" />
</delta_time>
<url>
<xsl:value-of select="/arachni_report/system/url" />
</url>
<user_agent>
<xsl:value-of select="/arachni_report/system/user_agent" />
</user_agent>
<audited_elements>
<xsl:for-each select="/arachni_report/system/audited_elements/element">
<element>
<xsl:value-of select="/arachni_report/system/audited_elements/element" />
</element>
</xsl:for-each>
</audited_elements>
</scan>
</scaninfo> <!-- The end of our information about the scans-->
</magictree> <!-- The end of the output MagicTree report -->
</xsl:template>
<!-- Convert severity ratings from Arachni to Gremwell. No, I have no clue why the 'numeric' is required. --><xsl:template name="convert-severity">
<xsl:param name="severity" />
<source-severity>
<xsl:choose>
<xsl:when test="lower-case($severity)='high'">
high<numeric>3</numeric>
</xsl:when>
<xsl:when test="lower-case($severity)='medium'">
medium<numeric>2</numeric>
</xsl:when>
<xsl:when test="lower-case($severity)='Low'">
low<numeric>1</numeric>
</xsl:when>
<xsl:when test="lower-case($severity)='Informational'">
info<numeric>0</numeric>
</xsl:when>
<xsl:otherwise>
unknown<numeric>-1</numeric>
</xsl:otherwise>
</xsl:choose>
</source-severity>
</xsl:template>
<!-- Retrieves the port from the URL or used the default one Should work unless Arachni allows user-id/password in the hostname --> <xsl:template name="get-port">
<xsl:param name="host" />
<xsl:variable name="host-port" select="substring-after($host, '://')"/>
<xsl:variable name="proto" select="substring-before($host, ':')"/>
<xsl:choose>
<xsl:when test="string-length(substring-after($host-port,':'))>0">
<!-- The port is explicitly specified--><xsl:value-of select="substring-after($host-port,':')" />
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$proto=lower-case('https')">
443
</xsl:when>
<xsl:otherwise>
80
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Retrieves the protocol from the URL or use HTTP as default --> <xsl:template name="get-proto">
<xsl:param name="host" />
<xsl:variable name="proto" select="substring-before($host, ':')"/>
<xsl:choose>
<xsl:when test="$proto=lower-case('https')">
https
</xsl:when>
<xsl:otherwise>
http
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Generate all the issues according to the MagicTree standard --><xsl:template name="report-issues">
<service>http
<!-- loop through each issue --><xsl:for-each select="/arachni_report/issues/issue">
<finding class="MtTextObject" status="new" >
<xsl:attribute name="title">
<xsl:value-of select="name" />
</xsl:attribute>
<xsl:attribute name="mergeID" >
<xsl:value-of select="name" /> <!-- we group all findings -->
</xsl:attribute>
<xsl:if test="string-length(severity)>0">
<xsl:call-template name="convert-severity">
<xsl:with-param name="severity" select="severity"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="string-length(var)>0">
<var>
<xsl:value-of select="var" />
</var>
</xsl:if>
<xsl:if test="string-length(method)>0">
<method>
<xsl:value-of select="method" />
</method>
</xsl:if>
<xsl:if test="string-length(cvssv2)>0">
<cvss_base_score>
<xsl:value-of select="cvssv2" />
</cvss_base_score>
</xsl:if>
<synopsis class="MtTextObject" title="Description">
<xsl:value-of select="description" />
</synopsis>
<xsl:if test="string-length(remedy_guidance)>0">
<solution title="Solution" class="MtTextObject">
<xsl:value-of select="remedy_guidance" />
<xsl:value-of select="remedy_code" />
</solution>
</xsl:if>
<!--output class="MtTextObject" title="Details"></output--><source>
<xsl:text>Arachni </xsl:text>
<xsl:value-of select="/arachni_report/generated_on" />
<!--xref status="none" class="MtXrefObject"> <xsl:attribute name="id"> <xsl:value-of select="translate($my_report_signature,' 	
+-:','')"/> </xsl:attribute> </xref--></source>
<!--cve></cve--> <!--bid></bid--> <!--osvdb></osvdb--><xsl:if test="string-length(cwe)>0">
<reference>CWE
<xsl:text>-</xsl:text>
<xsl:value-of select="cwe" />
<xsl:text>#x20</xsl:text>
<xsl:value-of select="cwe_url" />
</reference>
</xsl:if>
<xsl:for-each select="references/reference">
<reference>
<xsl:value-of select="@name" />
<xsl:text>#x20</xsl:text>
<xsl:value-of select="@url" />
</reference>
</xsl:for-each>
<xsl:for-each select="variations/variation">
<affects>
<xsl:attribute name="mergeID" >
<xsl:value-of select="url" />
</xsl:attribute>
<url>
<xsl:value-of select="url" />
</url>
<injected>
<xsl:value-of select="injected" />
</injected>
<xsl:if test="string-length(regexp)>0">
<regexp>
<xsl:value-of select="reqexp" />
</regexp>
</xsl:if>
<xsl:if test="string-length(regexp_match)>0">
<regexp_match>
<xsl:value-of select="reqexp_match" />
</regexp_match>
</xsl:if>
<headers>
<request>
<xsl:for-each select="headers/request/field">
<field>
<xsl:value-of select="concat(@name,': ',@value)"/>
</field>
</xsl:for-each>
</request>
<response>
<xsl:for-each select="headers/response/field">
<field>
<xsl:value-of select="concat(@name,': ',@value)"/>
</field>
</xsl:for-each>
</response>
</headers>
<response title="Response" class="MtTextObject" >
<xsl:value-of select="html" />
</response>
</affects>
</xsl:for-each>
</finding>
</xsl:for-each> <!-- end the issues -->
</service>
</xsl:template>
</xsl:stylesheet>
Please note that this XSLT file is still considered alpha, but already very usable! No points for style, though. And be reminded that there are some rough edges. Further testing and fine-tuning is necessary.
After this, start up MagicTree and open an Arachni XML report. After import, every data is available through the GUI!
Conclusion
Basically, this was not a straight forwarded process and took many sleepless nights. MagicTree has excellent documentation, but fails to provide a XML Schema file, that would have made this endeavour a lot easier. Similar remark for Arachni, the structure of the XML file had to be looked up from the source code.
Our tests will continue, and I intend to update this post with our latest experiments. Meanwhile, have fun exploring MagicTree and Arachni, and do give feedback!
History
2012-01-12 Fixed small typo in blog.
2012-01-09 First release
Wow, this is cool! How long did it take you to make the XSLT?
ReplyDeleteRegrading numeric severities - they are to make sorting findings by severity easier.
How did you solve the IP address problem?
I'd like to ask your permission to include your Arachni XSLT with future versions of MagicTree.
Regards,
Alla
Tasos Laskos (author of Arachni) was so kind to include the IP address in the different Arachni report formats.
ReplyDeletePermission granted to include this with MagicTree!
Creating the XSL file took me about 2 days, but most of that time was spent trying to understand the thinking process behind MagicTree and to make sense of the Arachni and MagicTree XML formats.
Note: the current XSL file only works with the new experimental Arachni version (there were some minor bugs in the Arachni XML file creating routines that are now fixed), not with the current release version (0.4). Or maybe it does work, but some output fields will be blank.
Still more tests to execute and some cleaning up, but most of the functionality is already there.
Herman