- home
- dradis framework guides
Upload Plugins
An Upload plugin is a special type of server plugin (see Plugins Overview for other types).
In this guide you will learn how to create and use new Upload plugins.
Please note that the upload plugin generator was updated as a result of this documentation work. This means that your milage will be slightly different if you are not running the latest version of Dradis from our git repository: dradis/dradisframework.
If you are running the latest stable release of Dradis (2.8), you can follow the guide without fear. The only difference would be that certain auto-generated blocks of code will be slightly different. When in doubt go with the code in this guide.
1 Introduction
Upload plugins are used to process files created by other tools and extract useful information into your Dradis repository.
You can access the Import plugins through the Import from file...
toolbar button.
We are going to go through the steps to create a new upload plugin for the OWASP Zed Attack Proxy.
Creating a plugin for ZAP was a user-requested feature and writing an upload plugins guide was in the roadmap for some time, so lets get to it.
2 Define Plugin Goals
We want to be able to parse ZAP’s XML Reports (generated in ZAP through the Report > Generate XML Report… menu).
A quick look at a sample report gives us an idea of the structure of this document:
<?xml version="1.0" encoding="UTF-8"?> <report> Report generated at Fri, 28 Oct 2011 11:33:54. <alertitem> <pluginid>40000</pluginid> <alert>Cookie set without HttpOnly flag</alert> <riskcode>1</riskcode> <reliability>2</reliability> <riskdesc>Low (Warning)</riskdesc> <desc>A cookie has been [...]</desc> <uri>http://dradisframework.org/community/index.php</uri> <param>PHPSESSID=ap49e3vjsunvk1saei443r1mr5; path=/</param> <otherinfo/> <uri>http://dradisframework.org/community/index.php</uri> <param>PHPSESSID=ap49e3vjsunvk1saei443r1mr5; expires=Fri, 28-Oct-2011 11:20:31 GMT; path=/</param> <otherinfo/> <uri>http://dradisframework.org/community/</uri> <param>PHPSESSID=1maqvbmdf3h1p6sdb8sunv7v42; path=/</param> <otherinfo/> <uri>http://dradisframework.org/community/index.php</uri> <param>SMFCookie471=a%3A4%3A%7Bi[...]; expires=Fri, 28-Oct-2011 11:20:31 GMT; path=/</param> <otherinfo/> <solution>Ensure that the HttpOnly flag is set for all cookies. </solution> <reference>www.owasp.org/index.php/HttpOnly </reference> </alertitem> <alertitem> <!-- [...] --> </alertitem> </report>
Looks simple enough, a root report
tag with a nested alertitem
for every issue.
3 Generate the Plugin
The framework comes with a few handy plugin generators that will help you getting your plugin kickstarted.
While you develop your plugin, it will be easier to run Dradis in development mode, this means that a number of files will be auto-reloaded whenever you change them to make your life easier. You just need to initialize your development database with:
$ bundle exec thor dradis:reset
This should create a ./db/development.sqlite3
SQLite3 file.
Now we are ready to generate a new plugin and debug it in development mode. Go to the server folder and run:
$ bundle exec rails generate upload_plugin zap
That will generate all the files we are going to need for the plugin.
All the generated code have been put in: ./vendor/plugins/zap_upload/
.
4 Create the Test Cases
TODO: Add RSpec plugin testing notes.
5 Plugin Configuration
If you check the ./lib/zap_upload.rb
you will see the following code:
# ZapUpload require 'zap_upload/filters' require 'zap_upload/meta' module ZapUpload class Configuration < Core::Configurator configure :namespace => 'zap_upload' # setting :my_setting, :default => 'Something' # setting :another, :default => 'Something Else' end end # This includes the upload plugin module in the Dradis upload plugin repository module Plugins module Upload include ZapUpload end end
The plugin generator created a plugin Configuration class and included this code to use the framework’s configuration storage facility. All the settings configured through the Configuration class are accessible via the Configuration Manager (https://localhost:3004/configurations).
The generator also included this plugin in the framework’s Upload plugin collection (Plugins::Upload
).
We can customize our plugin settings:
# [...] module ZapUpload class Configuration < Core::Configurator configure :namespace => 'zap_upload' setting :category, :default => 'ZAP output' setting :author, :default => 'ZAP plugin' setting :parent_node, :default => 'plugin.zap' end end # [...]
- The category defines the note category to which plugin-generated notes will be assigned.
- The author defines who will appear as author of plugin-generated notes.
- And the parent_node is the name of the node that will be created in your repository tree that will be ancestor to all plugin-generated content.
6 Implement the functionality
Upload filters are defined in ./lib/zap_upload/filters.rb
.
Typically an upload plugin defines a single upload filter. However it is possible to define multiple filters if you need to. For instance, imagine that the next release of ZAP completely changes the report XML format. Instead of overwriting our existing filter to support the new changes, we could create a second filter to support both the old and the new formats. If you want to see sample code of a plugin defining two upload filters the ProjectManagement provides a neat example.
Each filters is defined inside its own module under ZapUpload
with the following structure:
module ZapUpload private @@logger=nil public # This method will be called by the framework when the user selects your # plugin from the drop down list of the 'Import from file' dialog def self.import(params={}) file_content = File.read( params[:file] ) @@logger = params.fetch(:logger, Rails.logger) # TODO: do something with the contents of the file! # if you want to print out something to the screen or to the uploader # interface use @@logger.info("Your message") end end
This default content is fairly self-explanatory. You will receive two params to your method:
- :file is the full path to the file uploaded by the user
- :logger is the logger instance to use to show debug traces
In our case the filter implementation is going to be fairly vanilla, parse the XML contents and then cycle through the report to generate nodes and notes with all the information we want to extract.
Pro tip: instead of jumping straight to plugin implementation we are going to do ourselves a favour and create a console task first so we can debug the parsing code using the console instead of the web interface.
7 Add command line tasks
LEts make our plugin’s functionality available through the command line.
This is done by virtue of Thor (simple ruby build program with capabilities similar to make/Rake).
This is a good idea also for debugging purposes. Rails plugins do not get reloaded unless you restart the server. So in order for a change in your code to become used by the framework, you need to restart Dradis (which is a slow process). Running your plugin through the console using the thor will make debugging easier.
The plugin generator defined a dummy thor task in ./tasks/thorfile.rb
:
class DradisTasks < Thor class Upload < Thor namespace "dradis:upload" # desc "zap FILE", "upload ZAP results" # long_desc "This will appear if the user runs 'thor help dradis:upload:zap'" # def zap(file_path) # require 'config/environment' # # logger = Logger.new(STDOUT) # logger.level = Logger::DEBUG # # unless File.exists?(file_path) # $stderr.puts "** the file [#{file_path}] does not exist" # exit -1 # end # # ZapUpload.import( # :file => file_path, # :logger => logger) # # logger.close # end end end
Uncomment the code and you should be good to go. You should now see your new upload task in the console:
$ bundle exec thor -T | grep upload thor dradis:upload:burp FILE # upload Burp scanner XML output thor dradis:upload:nessus FILE # upload nessus results thor dradis:upload:nexpose FILE # upload NeXpose results thor dradis:upload:nikto FILE # upload nikto results thor dradis:upload:nmap FILE # upload the results of an Nmap scan thor dradis:upload:openvas FILE # upload OpenVAS results thor dradis:upload:project:package FILE # import an entire repository package thor dradis:upload:project:template FILE # create a new repository structure... thor dradis:upload:surecheck FILE # Upload a SureCheck .sc file thor dradis:upload:typhon FILE # upload typhon results thor dradis:upload:w3af FILE # upload w3af results thor dradis:upload:wxf FILE # upload wXf results thor dradis:upload:zap FILE # upload ZAP results
So now, every time you want to test your implementation code you only need to run the Thor task passing as a parameter a path to the ZAP results file:
$ bundle exec thor dradis:upload:zap /tmp/ZAP_report_.xml
Lets work on the implementation next.
8 The filter implementation
We are going to use the excellent Nokogiri library for XML parsing.
module ZapUpload # [...] def self.import(params={}) file_content = File.read( params[:file] ) @@logger = params.fetch(:logger, Rails.logger) # create the parent node early so we can use it to provide feedback on errors parent = Node.find_or_create_by_label( Configuration.parent_node) # every note we create will be assigned to this author author = Configuration.author # get the note category instance or create it if it does not exist category = Category.find_or_create_by_name( Configuration.category ) @@logger.info{ 'Parsing ZAP output...' } doc = Nokogiri::XML(file_content) @@logger.info{ 'Done.' } # Add a note to the plugin root folder with the file name and report date file_name = File.basename(params[:file]) report_date = doc.root.children.first.text parent.notes.create( :author => author, :category => category, :text => "#[Title]#\nZAP upload: #{file_name}\n\n#[Report_date]##{report_date}") # Process the report contents doc.xpath('/report/alertitem').each do |alert| alert_name = alert.xpath('alert').text alert_text = alert.elements.collect{ |attribute| "#[#{attribute.name.capitalize}]#\n#{attribute.text}\n\n" }.join("\n") @@logger.info{ "Parsing alert item: #{alert_name}" } alert_node = parent.children.find_or_create_by_label(alert_name) alert_node.notes.create( :author => author, :category => category, :text => alert_text) end end end
Hopefully the code above is fairly easy to follow. For each alertitem
we create a node inside the plugins parent node and add a note to it.
If multiple instances of the same issue are found (i.e. multiple alertitems
with the same pluginid
), the use of find_or_create_by_label() ensures that only one child node is create. Subsequent alertitems
will just find it and append a new note to it.
As for the note content (i.e. alert_text
) we are just cycling through all the nested tags in the alertitem
and converting them to the Dradis standard note format. For example, the original item shown at the beginning of this guide:
<!-- [...] --> <alertitem> <pluginid>40000</pluginid> <alert>Cookie set without HttpOnly flag</alert> <riskcode>1</riskcode> <reliability>2</reliability> <riskdesc>Low (Warning)</riskdesc> <desc>A cookie has been [...]</desc> <uri>http://dradisframework.org/community/index.php</uri> <!-- [...] --> </alertitem> <!-- [...] -->
Will result in the following note:
#[Pluginid]# 40000 #[Alert]# Cookie set without HttpOnly flag #[Riskcode]# 1 #[Reliability]# 2 #[Riskdesc]# Low (Warning) #[Desc]# A cookie has been [...] #[Uri]# http://dradisframework.org/community/index.php
9 Running the plugin
Lets run the console task to make sure it everything works:
$ bundle exec thor dradis:upload:zap ~/ZAP.xml Parsing ZAP output... Done. Parsing alert item: Cookie set without HttpOnly flag Parsing alert item: Password Autocomplete in browser Parsing alert item: Cross site scripting Parsing alert item: Cross site scripting in SCRIPT section
And the results:
10 More Information
Upload plugins as any Dradis server plugin are just standard Ruby on Rails plugins with a specific structure and purpose.
You can learn more about Rails plugins at the Ruby on Rails plugins guide.