Programatically scanning using Arachni (Part 5)
As promised, part 5.
Not that anyone’s reading this crap, once I’m done with the series though I’ll be able to gather them into a nice developer’s guide so I might as well keep going.
As always, keep your installation to up date with the experimental branch before continuing.
These articles have forced me to see Arachni from a completely different perspective and so I keep improving the API to make it more developer friendly.
This time we’ll focus on auditing individual elements and also work on a per page scope.
Let me paint you a picture:
You have a Rails (or some other such framework) web application.
You need to audit it in a consistent manner.
You wish you could simply add security tests to your existing test suite next to your units/functional/integration/etc. tests.
Your webapp framework already keeps track of pretty much all inputs (and if not you can override helper methods like link_to and form_for to keep track of them).
The only thing missing is a system to which you can feed that data and audit those inputs.
You see where I’m going with this, right?
The Arachni framework can easily handle this in a number of ways, some of which I’ll demonstrate here.
Scripted element audits
Let’s start from the outside and move inwards.
The framework feeds pages to modules –> modules audit pages –> pages include elements –> modules inherit from Arachni::Module::Base –> Arachni::Module::Base includes Arachni::Module::Auditor and Arachni::Module::Output.
And everything’s at its place, modules live under “/modules” and everyone’s happy.
I’ve already showed how to script an audit but what if you want a narrower scope?
What if you have a list of elements you want to audit?
What if you want to pass a custom page to be audited?
And more importantly, how can you do that without needing to work with the framework, the module manager and without needing to write a full blown module?
What if you just want the functionality without the hassle?
Obviously that’s possible, otherwise I wouldn’t have gone through that sales pitch.
What we need to do is create our own Auditor at runtime… better yet, let’s create our own Module so that way we’ll have access to some utilities and other toys as well.
Through that Auditor we’ll be able to submit, fuzz, audit, analyze elements and server responses.
Time to go through the 2 cleanest ways to do that.
First of all, this will be our Auditor, it’ll serve as a proxy to the functionality enjoyed by regular framework modules.
auditor.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | require 'arachni/ui/cli/output' require 'arachni' # # This class is capable of providing module-like functionality to 3rd party objects. # # This is the same as a regular module but instead of including callbacks like: # * prepare # * run # * clean_up # # to perform its job, its functionality is exploited by other classes. # class Auditor < Arachni::Module::Base # # If the parameter is a Arachni::Parser::Page that's going to be the # reference page of the auditor. # # If the param is a string it'll be treated like a URL and a new page # will be created for it. # def initialize( page_or_url ) # setup a minimal page to have as a reference if !page_or_url.is_a? Arachni::Parser::Page super Arachni::Parser::Page.new( :url => page_or_url.to_s ) else super page_or_url end end def self.info; { :name => 'My auditor' } end end |
Each Auditor must have a page as a reference if only to know the current URL, so a page with at least a URL assigned must be available at all times.
Per element
This approach deals with auditing individual, user created, elements, which is the finest grained of controls possible.
Talk is cheap though, let’s test a few examples.
Pattern matching
Simple enough, inject a string and see if it appears in the response:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | require_relative 'auditor' opts = Arachni::Options.instance opts.audit_links = true opts.url = 'http://testfire.net' # the owner is supposed to be the URL of the page containing the element owner = opts.url.to_s # let's create a link element to audit link = Arachni::Parser::Element::Link.new( owner, { # action, to which URL to send the data :action => owner + '/notfound.aspx', # auditable inputs, these will be fuzzed/audited/whatever :inputs => { 'aspxerrorpath' => '' } }) # seed to inject injection_str = '<xss />' # some audit options audit_opts = { # the existence of this substring in the response will verify the issue # :substring => injection_str, # is this is a string it'll be converted to a regexp :regexp => injection_str, # controls the format of the injected string, # this one means: inject as is :format => [ Arachni::Module::Auditor::Format::STRAIGHT ] } # get an auditor and pass it the seed URL, it'll also be used as the page URL auditor = Auditor.new( opts.url.to_s ) # the name is a dead give-away # # the 1st param is an array of elements to audit # the 2nd one is the string to inject # the 3rd one is a hash with audit options # # if a server response contains the :substring (or matches a :regexp) # then an issue will be logged automatically # auditor.audit_elems( [link], injection_str, audit_opts ) # this fires the queued requests and blocks until all responses have arrived auditor.http.run # grab any logged issues/results results = Arachni::Module::Manager.results auditor.print_info "Found " + results.count.to_s + " matches." |
This time we don’t mute the output, and why should we? Let’s admire our handy work.
But what if you don’t want the auditor to log the the issue automatically based on an expression or substring?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | audit_opts = { :format => [ Arachni::Module::Auditor::Format::STRAIGHT ] } # if you pass a block you get full control over how to analyze the audit responses auditor.audit_elems( [link], injection_str, audit_opts ) { |response, options, element| # full HTTP response # pp response # expanded options # ap options # updated element # pp element # perform your own analysis and logging auditor.log( options, response ) if response.body.include?( injection_str ) } |
Differential analysis
The Auditor module contains helpers that perform differential analysis for you using Arachni’s own rDiff algorithm.
See this SQL injection example for instance:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | require_relative 'auditor' opts = Arachni::Options.instance opts.audit_links = true opts.url = 'http://testphp.vulnweb.com/artists.php' # the owner is supposed to be the URL of the page containing the element owner = opts.url.to_s # let's create a link element to audit link = Arachni::Parser::Element::Link.new( owner, { # action, to which URL to send the data :action => owner, # auditable inputs, these will be fuzzed/audited/whatever :inputs => { 'artist' => '1' } }) # get an auditor and pass it the seed URL, it'll also be used as the page URL auditor = Auditor.new( opts.url.to_s ) rdiff_opts = { # # fault injection seeds # # these must generate an exceptional condition or # general error when appended to an SQL query # :faults => [ '\'"`' ], # # boolean injection seeds # # these values should not affect the behavior of the page when evaluated # as part of the SQL query # :bools => [ ' and 1+1 = 2' ], # # rDiff precision # # Specifies the amount of rdiff iterations, # more iterations => more refined/accurate result # # default is 2 but what the hell... # :precision => 5 } # analyze and log auditor.audit_rdiff_elem( link, rdiff_opts ) # this fires the queued requests and blocks until all responses have arrived auditor.http.run # grab any logged issues/results results = Arachni::Module::Manager.results auditor.print_info "Found " + results.count.to_s + " matches." |
Again, if you want to perform your own analysis and logging:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | auditor.audit_rdiff_elem( link, rdiff_opts ) { |injection_string, element, original_response_body, bool_response, fault_response_body| # injected string # p injection_string # updated audited element element # pp element # body of the original/vanilla page # pp original_response_body # full HTTP response for the boolean injection # pp bool_response # body of the fault injection response # pp fault_response_body } |
Time-out/delay attacks
Yes, timing attacks are of course part of the repertoire but are not available on a per-element basis.
Don’t worry though, there’s an alternative way to perform them as we’ll see shortly.
Per page
The per-page approach is slightly different and requires some commitment as you have to you properly configure a page object with elements and feed that page object to the module.
The module then audits those elements in batch.
Pattern matching
Let’s see what the pattern matching example looks like when adjusted for this approach:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | require_relative 'auditor' opts = Arachni::Options.instance opts.audit_links = true opts.url = 'http://testfire.net' owner = opts.url.to_s link = Arachni::Parser::Element::Link.new( owner, { :action => owner + '/notfound.aspx', :inputs => { 'aspxerrorpath' => '' } }) # create our page and assign it the link page = Arachni::Parser::Page.new( :url => owner.to_s, :links => [link] ) # get an auditor and pass it the page auditor = Auditor.new( page ) injection_str = '<xss />' audit_opts = { :regexp => injection_str, :format => [ Arachni::Module::Auditor::Format::STRAIGHT ] } auditor.audit( injection_str, audit_opts ) auditor.http.run results = Arachni::Module::Manager.results auditor.print_info "Found " + results.count.to_s + " matches." |
I’ve removed most of the comments this time since they were identical to the first example.
I just had an idea, let’s add an audit method to the page object itself and see if it’s any easier to work with.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | require_relative 'auditor' opts = Arachni::Options.instance opts.audit_links = true opts.url = 'http://testfire.net' owner = opts.url.to_s class Arachni::Parser::Page def audit!( injection_str, opts, run = true, &block ) auditor = Auditor.new( self ) auditor.audit( injection_str, opts, &block ) auditor.http.run if run end end link = Arachni::Parser::Element::Link.new( owner, { :action => owner + '/notfound.aspx', :inputs => { 'aspxerrorpath' => '' } }) # create our page and assign it the link page = Arachni::Parser::Page.new( :url => owner.to_s, :links => [link] ) injection_str = '<xss />' audit_opts = { :regexp => injection_str, :format => [ Arachni::Module::Auditor::Format::STRAIGHT ] } page.audit!( injection_str, audit_opts ) results = Arachni::Module::Manager.results puts "Found " + results.count.to_s + " matches." |
Huh, seems kinda nice…
Differential analysis
Should be a familiar sight:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | require_relative 'auditor' opts = Arachni::Options.instance opts.audit_links = true opts.url = 'http://testphp.vulnweb.com/artists.php' owner = opts.url.to_s link = Arachni::Parser::Element::Link.new( owner, { :action => owner, :inputs => { 'artist' => '1', } }) page = Arachni::Parser::Page.new( :url => owner ) page.links << link auditor = Auditor.new( page ) rdiff_opts = { :faults => [ '\'"`' ], :bools => [ ' and 1+1 = 2' ], } auditor.audit_rdiff( rdiff_opts ) auditor.http.run results = Arachni::Module::Manager.results auditor.print_info "Found " + results.count.to_s + " matches." |
Time-out/delay attacks
And now it’s time to check out some timing attacks.
Actually, since timing attacks are wildly unreliable Arachni doesn’t simply perform a time-out check.
It performs a few time-out checks with different delay tolerances and waits for the server to normalize in between and it also does some other weird stuff to make sure that false positives are a rare occasion.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | require_relative 'auditor' opts = Arachni::Options.instance opts.audit_links = true opts.url = 'http://testphp.vulnweb.com/artists.php' owner = opts.url.to_s link = Arachni::Parser::Element::Link.new( owner, { :action => owner, :inputs => { 'artist' => '1', } }) page = Arachni::Parser::Page.new( :url => owner ) page.links << link auditor = Auditor.new( page ) timeout_opts = { :format => [ Arachni::Module::Auditor::Format::STRAIGHT ], # # set first stage timeout to 4 seconds # the rest of the stages are based on this value # :timeout => 4000, # # different platforms expect different time representations, # some expect seconds and others milliseconds. # # __TIME__ in the injection string will be replaced with: # :timeout / :timeout_divider # # # optional and defaults to 1 # :timeout_divider => 1000 } auditor.audit_timeout( [ 'sleep(__TIME__)#' ], timeout_opts ) # timing attacks are a bit different and are scheduled to be run at the end # of a full audit # # that's why simply firing up the queued HTTP requests (like with the other methods) # isn't enough Arachni::Module::Auditor.timeout_audit_run results = Arachni::Module::Manager.results auditor.print_info "Found " + results.count.to_s + " matches." |
Simple as pie.
Page delegation
Let’s investigate this idea a bit further, shall we?
Having audit functionality built-into the page objects would surely be helpful!
Time to monkey-patch the Page class I reckon so update your auditor.rb file to this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | require 'arachni/ui/cli/output' require 'arachni' # # This class is capable of providing module-like functionality to 3rd party objects. # # This is the same as a regular module but instead of including callbacks like: # * prepare # * run # * clean_up # # to perform its job, its functionality is exploited by other classes. # class Auditor < Arachni::Module::Base # # If the parameter is a Arachni::Parser::Page that's going to be the # reference page of the auditor. # # If the param is a string it'll be treated like a URL and a new page # will be created for it. # def initialize( page_or_url ) # setup a minimal page to have as a reference if !page_or_url.is_a? Arachni::Parser::Page super Arachni::Parser::Page.new( :url => page_or_url.to_s ) else super page_or_url end end def self.info; { :name => 'My auditor' } end end class Arachni::Parser::Page # # Trap any called methods that don't exist in the page class # and send them to the Auditor class if they are audit methods. # def method_missing( sym, *args, &block ) tokens = sym.to_s.split( '_' ) if tokens.first != 'audit' raise NoMethodError.new( "Undefined method '#{sym.to_s}'.", sym, args ) end @auditor ||= ::Auditor.new( self ) @auditor.method( sym ).call( *args, &block ) # check for a :run flag explicitly since we may need to queue up # a number of audits opts = {} args.flatten.each { |arg| opts.merge!( arg ) if arg.is_a?( Hash ) } return if !opts[:run] if tokens.last != 'timeout' @auditor.http.run else ::Arachni::Module::Auditor.timeout_audit_run end end end |
And from that point on you can simply audit pages like so:
Audit combo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | require_relative 'auditor' opts = Arachni::Options.instance opts.audit_links = true opts.audit_forms = true opts.url = 'http://testphp.vulnweb.com/' owner = opts.url.to_s page = Arachni::Parser::Page.new( :url => owner ) # # Vulnerable to SQL injection and will be flagged by rDiff and timeout attacks. # page.links << Arachni::Parser::Element::Link.new( owner, { :action => owner + 'artists.php', :inputs => { 'artist' => '1', } }) # # Vulnerable to XSS. # page.forms << Arachni::Parser::Element::Form.new( owner, { :action => owner + 'search.php', :method => :post, :inputs => { 'searchFor' => '' } }) # # rDiff audit # # Just queue the requests, don't run them yet. # It's better if we combine these requests with the pattern matching audit # rdiff_opts = { :faults => [ '\'"`' ], :bools => [ ' and 1+1 = 2' ] } page.audit_rdiff( rdiff_opts ) # # Pattern matching # # Will submit the queued HTTP requests. # injection_str = '<xss />' audit_opts = { :regexp => injection_str, :format => [ Arachni::Module::Auditor::Format::STRAIGHT ], :run => true } page.audit( injection_str, audit_opts ) # # Timing attack # # It's better to run it at the end of the audit on its own . # timeout_opts = { :format => [ Arachni::Module::Auditor::Format::STRAIGHT ], :timeout => 4000, :timeout_divider => 1000, :run => true } page.audit_timeout( [ 'sleep(__TIME__)#' ], timeout_opts ) results = Arachni::Module::Manager.results puts "Found " + results.count.to_s + " matches." |
Running all modules
We may have gone a little bit too deep it seems…
You may not want to perform the audit yourself…
You may want to specify a narrow scope, on a per-element basis, but use the existing modules to audit them.
How do you do that?
First of all, you can’t give Arachni’s modules individual elements…you have to give them pages.
That’s alright though since as we’ve seen today creating pages containing elements of our choosing is no big deal.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | require 'arachni/ui/cli/output' require 'arachni' opts = Arachni::Options.instance opts.audit_links = true opts.url = 'http://testfire.net' owner = opts.url.to_s page = Arachni::Parser::Page.new( :url => owner.to_s ) page.links << Arachni::Parser::Element::Link.new( owner, { :action => owner + '/notfound.aspx', :inputs => { 'aspxerrorpath' => '' } }) modules = Arachni::Module::Manager.new( opts ) # doesn't make sense to load recon modules in this case modules.load( ['audit/*'] ) modules.run( page ) Arachni::HTTP.instance.run results = Arachni::Module::Manager.results puts "Found " + results.count.to_s + " matches." |
That’s all for now, I hope you that found this post interesting — I certainly did.
Cheers
Posted in: Arachni, Open Source, Programming, Projects, Ruby, Security, Web Application
7 Comments
Comments RSS
TrackBack Identifier URI
Leave a comment








