REST API

Tasos Laskos edited this page Sep 16, 2016 · 7 revisions

Server

For server configuration please consult the relevant page.

API

List scans

Scans are identified by their IDs, a list of which can be retrieved with:

Request

GET /scans

Response

{
  "02aa00c5c3ddb8c34a19e97c9a4e8db8" : {},
  "2f0982cd68caa7fc46f56b07bd695cb8" : {}
}

Information about all active scans, grouped by their id.

Currently no scan-related information is provided but that may change in the future.

Perform a new scan

Scans will run in parallel, each in its own process.

Request

POST /scans

Default options are:

{
  "url" : null,
  "http" : {
    "user_agent" : "Arachni/v2.0dev",
    "request_timeout" : 10000,
    "request_redirect_limit" : 5,
    "request_concurrency" : 20,
    "request_queue_size" : 100,
    "request_headers" : {},
    "response_max_size" : 500000,
    "cookies" : {}
  },
  "audit" : {
    "parameter_values" : true,
    "exclude_vector_patterns" : [],
    "include_vector_patterns" : [],
    "link_templates" : []
  },
  "input" : {
    "values" : {},
    "default_values" : {
      "(?i-mx:name)" : "arachni_name",
      "(?i-mx:user)" : "arachni_user",
      "(?i-mx:usr)" : "arachni_user",
      "(?i-mx:pass)" : "5543!%arachni_secret",
      "(?i-mx:txt)" : "arachni_text",
      "(?i-mx:num)" : "132",
      "(?i-mx:amount)" : "100",
      "(?i-mx:mail)" : "arachni@email.gr",
      "(?i-mx:account)" : "12",
      "(?i-mx:id)" : "1"
    },
    "without_defaults" : false,
    "force" : false
  },
  "browser_cluster" : {
    "wait_for_elements" : {},
    "pool_size" : 6,
    "job_timeout" : 25,
    "worker_time_to_live" : 100,
    "ignore_images" : false,
    "screen_width" : 1600,
    "screen_height" : 1200
  },
  "scope" : {
    "redundant_path_patterns" : {},
    "dom_depth_limit" : 5,
    "exclude_path_patterns" : [],
    "exclude_content_patterns" : [],
    "include_path_patterns" : [],
    "restrict_paths" : [],
    "extend_paths" : [],
    "url_rewrites" : {}
  },
  "session" : {},
  "checks" : [],
  "platforms" : [],
  "plugins" : {},
  "no_fingerprinting" : false,
  "authorized_by" : null
}
  • Only the url option is mandatory.
  • These options are passed to Arachni::Options#update.
  • Options expecting a type of Regexp (unavailable in JSON) can be specified as type String.
    • The string should not be enclosed in /; for example, use test.* instead of /test.*/.

Response

Currently, the response only contains the scan's ID.

{
  "id" : "1a34b6aca50bcf0065aca0a06a8d21ba"
}
Errors

A 500 error will be returned on invalid options:

{
  "error" : "Arachni::RPC::Exceptions::RemoteException: undefined method `stuff=' for #<Arachni::Options:0x00000001fa0dd8>",
  "backtrace" : [
    "/home/zapotek/workspace/arachni/lib/arachni/options.rb:276:in `block in update'",
    "/home/zapotek/workspace/arachni/lib/arachni/options.rb:271:in `each'",
    "/home/zapotek/workspace/arachni/lib/arachni/options.rb:271:in `update'",
    "/home/zapotek/workspace/arachni/lib/arachni/rpc/server/active_options.rb:34:in `set'",
    "/home/zapotek/workspace/arachni/lib/arachni/rpc/server/instance.rb:584:in `scan'",
    "/home/zapotek/.rvm/gems/ruby-2.2.0/gems/arachni-rpc-0.2.1.2/lib/arachni/rpc/server.rb:207:in `call'",
    "/home/zapotek/.rvm/gems/ruby-2.2.0/gems/arachni-rpc-0.2.1.2/lib/arachni/rpc/server/handler.rb:57:in `receive_request'",
    "/home/zapotek/.rvm/gems/ruby-2.2.0/gems/arachni-rpc-0.2.1.2/lib/arachni/rpc/server/handler.rb:96:in `receive_object'",
    "/home/zapotek/.rvm/gems/ruby-2.2.0/gems/arachni-rpc-0.2.1.2/lib/arachni/rpc/protocol.rb:52:in `on_read'",
    "/home/zapotek/.rvm/gems/ruby-2.2.0/gems/arachni-reactor-0.1.0/lib/arachni/reactor/connection.rb:255:in `block in _read'",
    "/home/zapotek/.rvm/gems/ruby-2.2.0/gems/arachni-reactor-0.1.0/lib/arachni/reactor/connection/error.rb:26:in `call'",
    "/home/zapotek/.rvm/gems/ruby-2.2.0/gems/arachni-reactor-0.1.0/lib/arachni/reactor/connection/error.rb:26:in `translate'",
    "/home/zapotek/.rvm/gems/ruby-2.2.0/gems/arachni-reactor-0.1.0/lib/arachni/reactor/connection.rb:254:in `_read'",
    "/home/zapotek/.rvm/gems/ruby-2.2.0/gems/arachni-reactor-0.1.0/lib/arachni/reactor/connection/tls.rb:115:in `_read'",
    "/home/zapotek/.rvm/gems/ruby-2.2.0/gems/arachni-reactor-0.1.0/lib/arachni/reactor.rb:574:in `each'",
    "/home/zapotek/.rvm/gems/ruby-2.2.0/gems/arachni-reactor-0.1.0/lib/arachni/reactor.rb:574:in `block in process_connections'",
    "/home/zapotek/.rvm/gems/ruby-2.2.0/gems/arachni-reactor-0.1.0/lib/arachni/reactor.rb:574:in `each'",
    "/home/zapotek/.rvm/gems/ruby-2.2.0/gems/arachni-reactor-0.1.0/lib/arachni/reactor.rb:574:in `process_connections'",
    "/home/zapotek/.rvm/gems/ruby-2.2.0/gems/arachni-reactor-0.1.0/lib/arachni/reactor.rb:316:in `block in run'",
    "/home/zapotek/.rvm/gems/ruby-2.2.0/gems/arachni-reactor-0.1.0/lib/arachni/reactor.rb:307:in `loop'",
    "/home/zapotek/.rvm/gems/ruby-2.2.0/gems/arachni-reactor-0.1.0/lib/arachni/reactor.rb:307:in `run'",
    "/home/zapotek/workspace/arachni/lib/arachni/rpc/server/instance.rb:152:in `initialize'",
    "/home/zapotek/workspace/arachni/lib/arachni/processes/executables/instance.rb:13:in `new'",
    "/home/zapotek/workspace/arachni/lib/arachni/processes/executables/instance.rb:13:in `<top (required)>'",
    "/home/zapotek/workspace/arachni/lib/arachni/processes/executables/base.rb:9:in `load'",
    "/home/zapotek/workspace/arachni/lib/arachni/processes/executables/base.rb:9:in `<main>'"
  ]
}

Monitor scan progress

Request

GET /scans/:id

Response

{
  "status": "scanning",
  "busy": true,
  "seed": "c0c039750bef4f5688da4fba929b06ac",
  "statistics": {
    "http": {
      "request_count": 1312,
      "response_count": 1208,
      "time_out_count": 0,
      "total_responses_per_second": 145.55173283136,
      "burst_response_time_sum": 0,
      "burst_response_count": 0,
      "burst_responses_per_second": 0,
      "burst_average_response_time": 0,
      "total_average_response_time": 0.12118887582781,
      "max_concurrency": 20,
      "original_max_concurrency": 20
    },
    "browser_cluster": {
      "seconds_per_job": 1.6666666666667,
      "total_job_time": 25,
      "queued_job_count": 31,
      "completed_job_count": 15
    },
    "runtime": 9.251885252,
    "found_pages": 10,
    "audited_pages": 2,
    "current_page": "http:\/\/testhtml5.vulnweb.com\/ajax\/popular?offset=0"
  },
  "errors": [],
  "messages": [],
  "issues": [],
  "sitemap": {}
}
  • status can be:
    • ready -- Initialised and waiting for instructions.
    • preparing -- Getting ready to start (i.e. initializing plugins etc.).
    • scanning -- The instance is currently scanning the webapp.
    • pausing -- The instance is being paused.
    • paused -- The instance has been paused.
    • cleanup -- The scan has completed and the instance is cleaning up after itself (i.e. waiting for plugins to finish etc.).
    • aborted -- The scan has been aborted, you can grab the report and shutdown.
    • done -- The scan has completed, you can grab the report and shutdown.
  • busy
    • true -- The scan is still in progress.
    • false -- The scan has finished, it is safe to grab the report and shutdown.
  • errors -- Recoverable runtime errors.
  • issues -- Identified issues.
  • sitemap -- Scanned pages by URL and their HTTP status code.
    • url: code
  • messages -- Status messages.

So long as the client maintains a session with the service, only new issues, sitemap entries and errors will be returned. If no session is being maintained, each call will always return all data.

Summary

Returns the same data as "Monitor scan progress" but without issues, errors and sitemap.

Request
GET /scans/:id/summary
Response
{
  "status": "scanning",
  "busy": true,
  "seed": "c0c039750bef4f5688da4fba929b06ac",
  "statistics": {
    "http": {
      "request_count": 1312,
      "response_count": 1208,
      "time_out_count": 0,
      "total_responses_per_second": 145.55173283136,
      "burst_response_time_sum": 0,
      "burst_response_count": 0,
      "burst_responses_per_second": 0,
      "burst_average_response_time": 0,
      "total_average_response_time": 0.12118887582781,
      "max_concurrency": 20,
      "original_max_concurrency": 20
    },
    "browser_cluster": {
      "seconds_per_job": 1.6666666666667,
      "total_job_time": 25,
      "queued_job_count": 31,
      "completed_job_count": 15
    },
    "runtime": 9.251885252,
    "found_pages": 10,
    "audited_pages": 2,
    "current_page": "http:\/\/testhtml5.vulnweb.com\/ajax\/popular?offset=0"
  },
  "messages": []
}

Pause a scan

This is a soft pause, it will not kill the scanner process but merely make it wait for a resume signal in order to continue the scan.

Request

PUT /scans/:id/pause

Resume a scan

Request

PUT /scans/:id/resume

Retrieve a scan report

Request

GET /scans/:id/report
GET /scans/:id/report.json
GET /scans/:id/report.xml
GET /scans/:id/report.yaml
GET /scans/:id/report.html.zip

When the extension is missing, it will default to json.

Response

Abort or shutdown a scan

Request

DELETE /scans/:id

This call needs to take place after each scan is done in order to prevent zombie processes.

Once that call is made, the scan process will be killed and removed from the service, if you wish to retrieve the report you will need to do so prior to performing this call.

Example client

#!/usr/bin/env ruby

require 'ap'
require 'typhoeus'
require 'json'

# Base URL of the REST service.
URL        = 'http://localhost:7331'

# Cookie-Jar for the session.
#
# Allows for optimizations such as scan progress calls only returning issues not
# previously seen by previous calls, instead of all issues every single time.
COOKIE_JAR = '/tmp/arachni_rest_client.cookiejar'


#
# HTTP helpers
###############

def request( path, options = {} )
    if (data = options.delete(:data))
        options[:body] = JSON.dump( data )
    end

    response = Typhoeus::Request.new(
        "#{URL}/#{path}",
        options.merge(
            # Maintain a session.
            cookiefile: COOKIE_JAR,
            cookiejar:  COOKIE_JAR,

            # Enable compression.
            accept_encoding: 'gzip, deflate',
        )
    ).run

    fail response.return_message if response.code == 0

    JSON.load( response.body )
end

def get( path )
    request( path )
end

def post( path, data = nil )
    request( path, data: data, method: :post )
end

def delete( path )
    request( path, method: :delete )
end


#
# Usage example
###############

# Start a new scan with the given options.
#
# The `id` included in the response data will allow us to manage it.
id = post( '/scans',
    url:    'http://testhtml5.vulnweb.com',

    # Only scan a few pages.
    scope: {
        page_limit: 10
    },

    audit: {
        elements: ['link', 'form', 'cookie']
    },

    # Load all checks.
    checks: ['*']
)['id']

# Poll until the scan is finished.
loop do
    print '.'

    # Get the scan's progress.
    #
    # Status messages, runtime statistics, new issues, new errors etc.
    progress = get( "/scans/#{id}" )

    # Status messages (initializing something, pausing in a bit, etc.).
    if progress['messages'].any?
        puts
        puts 'Messages:'
        puts progress['messages'].join( "\n" )
    end

    # Because we're maintaining a session with the REST server, only issues
    # which have not been previously seen will be returned by each call.
    if progress['issues'].any?
        puts
        puts 'Issues:'
        progress['issues'].each do |issue|
            summary = "#{issue['name']} in '#{issue['vector']['type']}'"

            # Passive issues don't have this.
            if issue['affected_input_name']
                summary << " input '#{issue['affected_input_name'].inspect}'"
                summary << " using #{issue['affected_input_value'].inspect}"
            end

            summary << " at: #{issue['page']['dom']['url']}"

            puts summary
        end
    end

    # Same thing as before, only new sitemap entries will be returned for each call.
    if progress['sitemap'].any?
        puts
        puts 'Scanned pages:'
        progress['sitemap'].each do |url, code|
            puts "[#{code}] #{url}"
        end
    end


    # Same thing as before, only new errors will be returned for each call.
    if progress['errors'].any?
        puts
        puts 'Errors:'
        puts progress['errors'].join( "\n" )
    end

    # When `busy` is set to `false`, it means that the scan has completed.
    break if !progress['busy']
    sleep 1
end

puts

# Get the full scan report.
ap get( "/scans/#{id}/report" )

# Get a list of all living scan processes, of course, it will include ours.
puts 'Scan list:'
ap get( '/scans' )

# Since we're done with this one, kill it.
#
# Don't forget this, we don't want any zombie processes laying around.
print 'Shutting down scan...'
delete "/scans/#{id}"
puts '... done!'

# Lo and behold, our scan is no longer listed.
puts 'Scan list:'
ap get( '/scans' )