Skip to content

Instantly share code, notes, and snippets.

@Zapotek
Created June 20, 2012 15:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Zapotek/2960625 to your computer and use it in GitHub Desktop.
Save Zapotek/2960625 to your computer and use it in GitHub Desktop.
Scripting with Arachni
# Helps to see status messages when developing.
#require 'arachni/ui/cli/output'
# Only works with the latest code from the experimental branch.
require 'arachni'
# to avoid having to prefix every object's name with it
include Arachni
include Utilities
# global target URL
Options.url = 'http://testfire.net'
# This cancels out Arachni's performance edge but will make these examples
# easier to follow.
HTTP_REQUEST_OPTS = { async: false }
page = page_from_response HTTP.get( Options.url, HTTP_REQUEST_OPTS ).response
#
# Let's find the search form...
#
# OK, there's actually only one form in the page but this is a demo, heh...
#
search = page.forms.select{ |form| form.action.include?( 'search' ) }.first
#
# All elements inherit from Parser::Element::Base, which includes
# Parser::Element::Auditable (which in turn includes Parser::Element::Mutable).
#
# All these decorate our elements with a few helpful methods.
# For example, Mutable provides mutation generation, like so:
mutations = search.mutations_for 'fuzz seed'
# To see the mutations call:
#
# ap mutations
#
# For info about available options see the Parser::Element::Mutable module.
#
#
# From the Auditable module we get all available analysis techniques
# (see the modules in arachni/parser/element/analysis) and the ability to perform
# the appropriate HTTP requests so as to submit elements as well as audit them.
#
# For example, here's how you submit the form with its default values:
# ap response = search.submit( HTTP_REQUEST_OPTS )
# If you want to provide a search term you do:
#
# search.auditable = { 'txtSearch' => 'find this' }
# ap response = search.submit( HTTP_REQUEST_OPTS )
#
# careful though, you can't manipulate the #auditable inputs in place like so:
#
# search.auditable['txtSearch'] = 'find this'
#
# because it's frozen for optimization and some other esoteric reasons.
#
#
# So, the above can become this:
#
# mutations.each do |mutation|
# response = mutation.submit( HTTP_REQUEST_OPTS )
# end
#
# and so you can fuzz an element with a seed you want quite easily and get
# back the responses.
# Now, this is sort of the same thing that the #audit method does but it also
# avoids performing redundant requests and some other boring crap:
#
# search.audit( 'fuzz seed', HTTP_REQUEST_OPTS ) do |response, options, element|
# # HTTP response
# ap response
#
# # Options used to audit the element
# # (same as element.opts, still here for compatibility reasons)
# ap options
#
# # The mutation that was submitted
# ap element
# end
# Let's now see how we can use the analysis techniques to make our lives easier:
#search.taint_analysis( 'blah', HTTP_REQUEST_OPTS )
# ah, nothing happens.. and there's a good reason for that.
# The taint analysis technique does its own logging if it finds an issue but
# does it through the provided auditor, and we've provided none.
# Let's whip up a simple auditor
Auditor = Class.new do
include Arachni::Module::Auditor
#
# A method to intercept all issues as they're being logged,
# you can do whatever the hell you want with them after that.
#
# These are quite raw though, no deduplications in the form of variations
# so we'll just buffer them until we're ready to retrieve them.
#
def register_results( issues )
@issues ||= []
@issues |= issues
end
# Will deduplicate and return the logged issues.
def issues
AuditStore.new( options: Options.instance.to_hash, issues: @issues || [] ).issues
end
# this is mandatory
def self.info
{ name: 'My custom auditor' }
end
end
auditor = Auditor.new
# The element's auditor will be nil-ed out after the audit to help out the garbage collector...
search.auditor = auditor
# ...uncomment the following line to keep it.
#search.keep_auditor
# This will log inputs which cause the seed to be included in the response.
search.taint_analysis( 'blah', HTTP_REQUEST_OPTS )
puts auditor.issues.map { |i| "#{i.elem.capitalize} input '#{i.var}' submitted to '#{i.url}'." }.join( "\n" )
custom_search_issues = auditor.issues.dup
#
# There still are the rDiff and Timeout techniques too but you get the gist.
#
# Also, keep in mind that everything has already been documented as RSpec examples
# which can be found under /spec/arachni/parser/element/analysis/.
#
# Also, all elements can be manipulated, submitted and audited in the same way
# except for Cookie and Header -- these only have one auditable pair each,
# other than that you shouldn't notice any difference.
#
#
# If we want to take advantage of the existing modules then we need to move to a higher level.
#
# The modules operate on a single Page object so if you want to restrict the scan
# and have the modules do your job for you you'll have to only leave the elements
# you want in the Page object you want to audit.
#
# This we can do from a Framework perspective so there'll be no need to muck about
# with the modules themselves.
#
# First of all, we enable form and link audits (cookies and headers stay disabled).
Options.audit :forms, :links
# Then we disable the spider.
Options.do_not_crawl
# Now we only leave the elements we want to audit in the Page, like
# the search form we cherry picked earlier...
page.forms = [ search ]
# ...and some links with a URL param named 'content' -- which we know are
# vulnerable to path traversal.
page.links = page.links.select { |link| link.auditable.keys.include?( 'content' ) }
# We don't care about its cookies and headers because, as mentioned before,
# they won't be audited.
# Now, we need the framework...
framework = Framework.new
# ...and then we load the modules we want...
framework.modules.load :xss, :path_traversal
# ...and we push our customized Page to the appropriate audit queue.
framework.push_to_page_queue( page )
# Yeeeee-ha!
framework.run
ap '------------'
puts framework.auditstore.issues.
map { |i| "#{i.name.capitalize} in #{i.elem} input '#{i.var}' submitted to '#{i.url}'." }.join( "\n" )
#
# But what if you want something in the middle?
#
# Like auditing crap individually but buffer all the discovered issues
# until we are ready to retrieve them?
#
# We already know that the AuditStore can de-dup Issues and classify them as
# variations of one another so we know what to do with a bunch of raw Issues.
#
# But how do we tell the framework to somehow pass all the issues to us and
# not store them itself?
#
# Quite easily actually:
#
#
# OK, confession time, the Framework was *never* meant to be re-usable.
#
# It has a boat load of class hierarchy vars (@@ vars) to keep track
# of which elements have been audited and caches and crap like that so just
# initializing a new framework doesn't mean that the old one will be garbage
# collected and that you'll start with a clean slate.
#
# And this is why we call reset, keep in mind though that this is for men
# with hair on their chest and balls the size of boulders -- *DO NOT* use this
# in a production environment.
#
# The reason I call this here is to undo whatever the previous demos have done.
framework.reset
# Guess what this does.
issue_buffer = []
# Tell the module manager not to store the issues that the modules will log...
framework.modules.do_not_store
# ...but instead pass them to us.
framework.modules.on_register_results { |issues| issue_buffer |= issues }
# Then it's business as usual:
framework.modules.load :xss, :path_traversal
framework.push_to_page_queue( page )
framework.run
ap '------------'
puts framework.auditstore.issues.
map { |i| "#{i.name.capitalize} in #{i.elem} input '#{i.var}' submitted to '#{i.url}'." }.join( "\n" )
# OH! Nothing was echoed! Well, we told the manager not to store anything but
# instead pass the issues to *us*, did he?
ap '------------'
puts AuditStore.new( options: Options.to_hash, issues: issue_buffer ).issues.
map { |i| "#{i.name.capitalize} in #{i.elem} input '#{i.var}' submitted to '#{i.url}'." }.join( "\n" )
# Who's a good boy!? Who's a gooooood booooy!?
# That's right, the module manager is!
#
# OR
#
# If you don't want complete and utter control let the manager keep track of
# the issues logged by modules and you can later retrieve them and merge them
# with the issues you obtained in some other way -- like auditing individual elements.
#
framework.reset
# Then it's business as usual:
framework.modules.load :xss, :path_traversal
framework.push_to_page_queue( page )
framework.run
merged_issues = custom_search_issues | framework.modules.results
ap '------------'
puts AuditStore.new( options: Options.to_hash, issues: merged_issues ).issues.
map { |i| "#{i.name.capitalize} in #{i.elem} input '#{i.var}' submitted to '#{i.url}'." }.join( "\n" )
#
# You can go mainstream and do this and get the hell out of the way:
#
# Pass your custom issues back to the module manager and...
framework.modules.register_results( custom_search_issues )
ap '------------'
# just retrieve them through the framework.
puts framework.auditstore.issues.
map { |i| "#{i.name.capitalize} in #{i.elem} input '#{i.var}' submitted to '#{i.url}'." }.join( "\n" )
#
# This is one of the design goals of Arachni, to allow the user to work at
# whichever level he feels is more suitable to meet his needs.
#
# From deep down and dirty to high and clean or even a combination of both.
#
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment