Created
June 20, 2012 15:54
-
-
Save Zapotek/2960625 to your computer and use it in GitHub Desktop.
Scripting with Arachni
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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