arachni 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,27 @@
1
1
 
2
2
  # ChangeLog
3
3
 
4
- ## Version 0.2.3 _(Under development)_
4
+ ## Version 0.2.4 _(July 1, 2011)_
5
+ - HTTP
6
+ - Implemented a 10s time-out [Issue #40]
7
+ - Command Line Interface
8
+ - The interrupt handler (Ctrl+C) now presents the option to generate reports mid-scan. [Issue #41]
9
+ - Added a counter of timed-out requests in the stats.
10
+ - WebUI
11
+ - The "Replay" form's action attribute now contains the full URL, including params. [Issue #38]
12
+ - Fixed path clash that caused the "shutdown" button in the Dispatchers screen not to work. [Issue #39]
13
+ - Fixed mix-up of output messages from different instances. [Issue #36]
14
+ - Added a counter of timed-out requests in "Instance" screens.
15
+ - External
16
+ - Metasploit
17
+ - Updated SQL injection exploit module to work with SQLmap 0.9. [Issue #37]
18
+ - Reports
19
+ - HTML
20
+ - Fixed yet another error condition occuring with broken encodings. [Issue #31]
21
+ - Auditor
22
+ - Timing attacks now have a "control" to verify that the server is indeed alive i.e. requests won't time-out by default.
23
+
24
+ ## Version 0.2.3 _(May 22, 2011)_
5
25
  - WebUI
6
26
  - Added connection cache for XMLRPC server instances to remove HTTPS handshake overhead and take advantage of keep-alive support.
7
27
  - Added initial support for management of multiple Dispatchers.
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  <table>
3
3
  <tr>
4
4
  <th>Version</th>
5
- <td>0.2.3</td>
5
+ <td>0.2.4</td>
6
6
  </tr>
7
7
  <tr>
8
8
  <th>Homepage</th>
@@ -229,11 +229,12 @@ Still, this can be an invaluable asset to Fuzzer modules.
229
229
 
230
230
  ### CDE packages for Linux
231
231
 
232
- Arachni is released as [CDE packages](http://stanford.edu/~pgbovine/cde.html) for your convinience.<br/>
232
+ <del>Arachni is released as [CDE packages](http://stanford.edu/~pgbovine/cde.html) for your convinience.<br/>
233
233
  CDE packages are self contained and thus alleviate the need for Ruby and other dependencies to be installed or root access.<br/>
234
234
  You can download the latest CDE package from the [download](https://github.com/Zapotek/arachni/downloads) page and escape the dependency hell.<br/>
235
- If you decide to go the CDE route you can skip the rest, you're done.
235
+ If you decide to go the CDE route you can skip the rest, you're done.</del>
236
236
 
237
+ Due to some incompatibility this release does not have a CDE package yet.
237
238
 
238
239
  ### Gem
239
240
 
@@ -2,91 +2,90 @@ require 'msf/core'
2
2
 
3
3
  class Metasploit3 < Msf::Auxiliary
4
4
 
5
- include Msf::Exploit::Remote::HttpClient
6
-
7
- def initialize(info = {})
8
- super(update_info(info,
9
- 'Name' => 'Arachni SQLMAP SQL Injection External Module',
10
- 'Description' => %q{
11
-
12
- This module is designed to be used with the Arachni plug-in.
13
-
14
- From the original:
15
-
16
- This module launches an sqlmap session.
17
- sqlmap is an automatic SQL injection tool developed in Python.
18
- Its goal is to detect and take advantage of SQL injection
19
- vulnerabilities on web applications. Once it detects one
20
- or more SQL injections on the target host, the user can
21
- choose among a variety of options to perform an extensive
22
- back-end database management system fingerprint, retrieve
23
- DBMS session user and database, enumerate users, password
24
- hashes, privileges, databases, dump entire or user
25
- specific DBMS tables/columns, run his own SQL SELECT
26
- statement, read specific files on the file system and much
27
- more.
28
- },
29
- 'Author' => [
30
- 'Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>', # modified to work with the Arachni plug-in
31
- 'Bernardo Damele A. G. <bernardo.damele[at]gmail.com>' # original module: auxiliary/scanner/http/sqlmap.rb
32
- ],
33
- 'License' => BSD_LICENSE,
34
- 'Version' => '$Revision: 9212 $',
35
- 'References' =>
36
- [
37
- ['URL', 'http://github.com/Zapotek/arachni'],
38
- ['URL', 'http://sqlmap.sourceforge.net'],
39
- ]
40
- ))
41
-
42
- register_options(
43
- [
44
- OptString.new('METHOD', [ true, "HTTP Method", 'GET' ]),
45
- OptString.new('PATH', [ true, "The path to test for SQL injection", 'index.php' ]),
46
- OptString.new('GET', [ false, "HTTP GET query", 'id=1' ]),
47
- OptString.new('POST', [ false, "The data string to be sent through POST", '' ]),
48
- OptString.new('COOKIES', [ false, "", '' ]),
49
- OptString.new('OPTS', [ false, "The sqlmap options to use", '--users --time-test --passwords --dbs --sql-shell -v 0' ]),
50
- OptPath.new('SQLMAP_PATH', [ true, "The sqlmap >= 0.8 full path ", 'sqlmap' ]),
51
- ], self.class)
52
- end
53
-
54
- def run
55
-
56
- sqlmap = datastore['SQLMAP_PATH']
57
-
58
- if not sqlmap
59
- print_error("The sqlmap script could not be found")
60
- return
61
- end
62
-
63
- data = datastore['POST'].gsub( 'XXinjectionXX', '' )
64
- method = datastore['METHOD'].upcase
65
-
66
- sqlmap_url = (datastore['SSL'] ? "https" : "http")
67
- sqlmap_url += "://" + datastore['RHOST'] + ":" + datastore['RPORT']
68
- sqlmap_url += "/" + datastore['PATH']
69
-
70
- if method == "GET"
71
- sqlmap_url += '?' + datastore['GET'].gsub( 'XXinjectionXX', '' )
72
- end
73
-
74
- cmd = sqlmap + ' -u \'' + sqlmap_url + '\''
75
- cmd += ' --method ' + method
76
- cmd += ' ' + datastore['OPTS']
77
- cmd += ' --cookie \'' + datastore['COOKIES'].to_s + '\'' if datastore['COOKIES']
78
-
79
- if not data.empty?
80
- cmd += ' --data \'' + data + '\''
81
- end
82
-
83
- if datastore['BATCH'] == true
84
- cmd += ' --batch'
85
- end
86
-
87
- print_status("exec: #{cmd}")
88
- system( cmd )
89
- end
5
+ include Msf::Exploit::Remote::HttpClient
6
+
7
+ def initialize(info = {})
8
+ super(update_info(info,
9
+ 'Name' => 'Arachni SQLMAP SQL Injection External Module',
10
+ 'Description' => %q{
11
+
12
+ This module is designed to be used with the Arachni plug-in.
13
+
14
+ From the original:
15
+
16
+ This module launches an sqlmap session.
17
+ sqlmap is an automatic SQL injection tool developed in Python.
18
+ Its goal is to detect and take advantage of SQL injection
19
+ vulnerabilities on web applications. Once it detects one
20
+ or more SQL injections on the target host, the user can
21
+ choose among a variety of options to perform an extensive
22
+ back-end database management system fingerprint, retrieve
23
+ DBMS session user and database, enumerate users, password
24
+ hashes, privileges, databases, dump entire or user
25
+ specific DBMS tables/columns, run his own SQL SELECT
26
+ statement, read specific files on the file system and much
27
+ more.
28
+ },
29
+ 'Author' => [
30
+ 'Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>', # modified to work with the Arachni plug-in
31
+ 'Bernardo Damele A. G. <bernardo.damele[at]gmail.com>' # original module: auxiliary/scanner/http/sqlmap.rb
32
+ ],
33
+ 'License' => BSD_LICENSE,
34
+ 'Version' => '$Revision: 9212 $',
35
+ 'References' =>
36
+ [
37
+ ['URL', 'http://github.com/Zapotek/arachni'],
38
+ ['URL', 'http://sqlmap.sourceforge.net'],
39
+ ]
40
+ ))
41
+
42
+ register_options(
43
+ [
44
+ OptString.new('METHOD', [ true, "HTTP Method", 'GET' ]),
45
+ OptString.new('PATH', [ true, "The path to test for SQL injection", 'index.php' ]),
46
+ OptString.new('GET', [ false, "HTTP GET query", 'id=1' ]),
47
+ OptString.new('POST', [ false, "The data string to be sent through POST", '' ]),
48
+ OptString.new('COOKIES', [ false, "", '' ]),
49
+ OptString.new('OPTS', [ false, "The sqlmap options to use", '--users --dbs --sql-shell -v 0' ]),
50
+ OptPath.new('SQLMAP_PATH', [ true, "The sqlmap 0.9 full path ", 'sqlmap' ]),
51
+ ], self.class)
52
+ end
53
+
54
+ def run
55
+
56
+ sqlmap = datastore['SQLMAP_PATH']
57
+
58
+ if not sqlmap
59
+ print_error("The sqlmap script could not be found")
60
+ return
61
+ end
62
+
63
+ data = datastore['POST'].gsub( 'XXinjectionXX', '' )
64
+ method = datastore['METHOD'].upcase
65
+
66
+ sqlmap_url = (datastore['SSL'] ? "https" : "http")
67
+ sqlmap_url += "://" + datastore['RHOST'] + ":" + datastore['RPORT']
68
+ sqlmap_url += "/" + datastore['PATH']
69
+
70
+ if method == "GET"
71
+ sqlmap_url += '?' + datastore['GET'].gsub( 'XXinjectionXX', '' )
72
+ end
73
+
74
+ cmd = sqlmap + ' -u \'' + sqlmap_url + '\''
75
+ cmd += ' ' + datastore['OPTS']
76
+ cmd += ' --cookie \'' + datastore['COOKIES'].to_s + '\'' if datastore['COOKIES']
77
+
78
+ if not data.empty?
79
+ cmd += ' --data \'' + data + '\''
80
+ end
81
+
82
+ if datastore['BATCH'] == true
83
+ cmd += ' --batch'
84
+ end
85
+
86
+ print_status("exec: #{cmd}")
87
+ system( cmd )
88
+ end
90
89
 
91
90
  end
92
91
 
@@ -11,6 +11,6 @@
11
11
  module Arachni
12
12
 
13
13
  # the universal system version
14
- VERSION = '0.2.3'
14
+ VERSION = '0.2.4'
15
15
 
16
16
  end
@@ -57,7 +57,7 @@ module Arachni
57
57
  # @author: Tasos "Zapotek" Laskos
58
58
  # <tasos.laskos@gmail.com>
59
59
  # <zapotek@segfault.gr>
60
- # @version: 0.2.2
60
+ # @version: 0.2.3
61
61
  #
62
62
  class Framework
63
63
 
@@ -217,6 +217,7 @@ class Framework
217
217
  return {
218
218
  :requests => req_cnt,
219
219
  :responses => res_cnt,
220
+ :time_out_count => http.time_out_count,
220
221
  :time => audit_store.delta_time,
221
222
  :avg => ( res_cnt / @opts.delta_time ).to_i.to_s,
222
223
  :sitemap_size => @sitemap.size,
@@ -66,6 +66,8 @@ class HTTP
66
66
  attr_reader :request_count
67
67
  attr_reader :response_count
68
68
 
69
+ attr_reader :time_out_count
70
+
69
71
  attr_reader :curr_res_time
70
72
  attr_reader :curr_res_cnt
71
73
 
@@ -127,11 +129,12 @@ class HTTP
127
129
  :user_agent => opts.user_agent,
128
130
  :follow_location => false,
129
131
  :disable_ssl_peer_verification => true,
130
- # :timeout => 8000
132
+ :timeout => 10000
131
133
  }.merge( proxy_opts )
132
134
 
133
135
  @request_count = 0
134
136
  @response_count = 0
137
+ @time_out_count = 0
135
138
 
136
139
  # we'll use it to identify our requests
137
140
  @rand_seed = seed( )
@@ -242,6 +245,11 @@ class HTTP
242
245
  print_debug( 'Train?: ' + res.request.train?.to_s )
243
246
  print_debug( '------------' )
244
247
 
248
+ if res.timed_out?
249
+ print_error( 'Request timed-out! -- ID# ' + res.request.id.to_s )
250
+ @time_out_count += 1
251
+ end
252
+
245
253
  if( req.train? )
246
254
  # handle redirections
247
255
  if( ( redir = redirect?( res.dup ) ).is_a?( String ) )
@@ -20,7 +20,7 @@ module Module
20
20
  # @author: Tasos "Zapotek" Laskos
21
21
  # <tasos.laskos@gmail.com>
22
22
  # <zapotek@segfault.gr>
23
- # @version: 0.2.2
23
+ # @version: 0.2.3
24
24
  #
25
25
  module Auditor
26
26
 
@@ -359,17 +359,28 @@ module Auditor
359
359
  ( (opts[:timeout] + 3000) / opts[:timeout_divider] ).to_s )
360
360
 
361
361
  elem.auditor( self )
362
- elem.audit( str, opts ) {
362
+
363
+ # this is the control; audit the element with an empty seed to make sure
364
+ # that the web page is alive i.e won't time-out by default
365
+ elem.audit( '' , opts ) {
363
366
  |res, opts|
367
+ if !res.timed_out?
368
+
369
+ elem.audit( str, opts ) {
370
+ |res, opts|
364
371
 
365
- if res.timed_out?
372
+ if res.timed_out?
373
+
374
+ # all issues logged by timing attacks need manual verification.
375
+ # end of story.
376
+ opts[:verification] = true
377
+ log( opts, res)
378
+ end
379
+ }
366
380
 
367
- # all issues logged by timing attacks need manual verification.
368
- # end of story.
369
- opts[:verification] = true
370
- log( opts, res)
371
381
  end
372
382
  }
383
+
373
384
  end
374
385
 
375
386
  def audit_timeout_debug_msg( phase, delay )
@@ -26,7 +26,7 @@ module UI
26
26
  # @author: Tasos "Zapotek" Laskos
27
27
  # <tasos.laskos@gmail.com>
28
28
  # <zapotek@segfault.gr>
29
- # @version: 0.1.6
29
+ # @version: 0.1.7
30
30
  # @see Arachni::Framework
31
31
  #
32
32
  class CLI
@@ -141,6 +141,7 @@ class CLI
141
141
  print_info( "Burst response count total #{stats[:curr_res_cnt]} " )
142
142
  print_info( "Burst average response time #{stats[:average_res_time]}" )
143
143
  print_info( "Burst average #{stats[:curr_avg]} requests/second" )
144
+ print_info( "Timed-out requests #{stats[:time_out_count]}" )
144
145
  print_info( "Original max concurrency #{@opts.http_req_limit}" )
145
146
  print_info( "Throttled max concurrency #{stats[:max_concurrency]}" )
146
147
 
@@ -166,13 +167,20 @@ class CLI
166
167
  @interrupt_handler = Thread.new {
167
168
 
168
169
  Thread.new {
169
- if gets[0] == 'e'
170
- @@only_positives = false
171
- unmute!
172
- @interrupt_handler.kill
173
170
 
174
- print_info( 'Exiting...' )
175
- exit 0
171
+ case gets[0]
172
+
173
+ when 'e'
174
+ @@only_positives = false
175
+ unmute!
176
+ @interrupt_handler.kill
177
+
178
+ print_info( 'Exiting...' )
179
+ exit 0
180
+
181
+ when 'r'
182
+ unmute!
183
+ @arachni.reports.run( @arachni.audit_store( ) )
176
184
  end
177
185
 
178
186
  @@only_positives = only_positives_opt
@@ -196,7 +204,7 @@ class CLI
196
204
  exit 0
197
205
  end
198
206
 
199
- print_info( 'Continue? (hit \'enter\' to continue, \'e\' to exit)' )
207
+ print_info( 'Continue? (hit \'enter\' to continue, \'r\' to generate reports and \'e\' to exit)' )
200
208
  mute!
201
209
 
202
210
  ::IO::select( nil, nil, nil, 1 )
@@ -62,13 +62,13 @@ module Web
62
62
 
63
63
  self << @instance.service.output
64
64
 
65
- @@last_output ||= ''
65
+ @last_output ||= ''
66
66
  cnt = 0
67
67
 
68
68
  if @buffer.empty?
69
- yield @@last_output
69
+ yield @last_output
70
70
  else
71
- @@last_output = ''
71
+ @last_output = ''
72
72
  end
73
73
 
74
74
  while( ( out = @buffer.pop ) && ( ( cnt += 1 ) < @lines ) )
@@ -80,7 +80,7 @@ module Web
80
80
 
81
81
  icon = @icon_whitelist[type] || ''
82
82
  str = icon + CGI.escapeHTML( " #{out.values[0]}" ) + "<br/>"
83
- @@last_output << str
83
+ @last_output << str
84
84
  yield str
85
85
 
86
86
  end
@@ -42,14 +42,14 @@ require Arachni::Options.instance.dir['lib'] + 'ui/web/output_stream'
42
42
  # @author: Tasos "Zapotek" Laskos
43
43
  # <tasos.laskos@gmail.com>
44
44
  # <zapotek@segfault.gr>
45
- # @version: 0.1.3
45
+ # @version: 0.1.4
46
46
  #
47
47
  # @see Arachni::RPC::XML::Client::Instance
48
48
  # @see Arachni::RPC::XML::Client::Dispatcher
49
49
  #
50
50
  module Web
51
51
 
52
- VERSION = '0.1.3'
52
+ VERSION = '0.1.4'
53
53
 
54
54
  class Server < Sinatra::Base
55
55
 
@@ -658,7 +658,7 @@ class Server < Sinatra::Base
658
658
  #
659
659
  # shuts down all instances
660
660
  #
661
- post "/dispatchers/:url/shutdown" do
661
+ post "/dispatchers/:url/shutdown_all" do
662
662
  shutdown_all( params[:url] )
663
663
  redirect '/dispatchers'
664
664
  end
@@ -789,7 +789,9 @@ class Server < Sinatra::Base
789
789
  begin
790
790
  arachni = connect_to_instance( params[:url] )
791
791
  if arachni.framework.busy?
792
- { 'data' => OutputStream.new( arachni, 38 ).data }.to_json
792
+ @@output_streams ||= {}
793
+ @@output_streams[params[:url]] ||= OutputStream.new( arachni, 38 )
794
+ { 'data' => @@output_streams[params[:url]].data }.to_json
793
795
  else
794
796
  settings.log.instance_shutdown( env, params[:url] )
795
797
  save_and_shutdown( arachni )
@@ -873,7 +875,7 @@ class Server < Sinatra::Base
873
875
  arachni.service.shutdown!
874
876
  end
875
877
  rescue
876
- flash.now[:notice] = "Instance at #{params[:url]} has already been shutdown."
878
+ flash.now[:notice] = "Instance at #{params[:url]} has been shutdown."
877
879
  erb params[:splat][0].to_sym, { :layout => true }, :shutdown => true, :stats => dispatcher_stats
878
880
  end
879
881
  end
@@ -21,7 +21,7 @@
21
21
  </h2>
22
22
 
23
23
  <%if !dispatcher_stats['running_jobs'].empty? %>
24
- <form action="/dispatchers/<%=sanitize_url( d_url.dup )%>/shutdown" method="post">
24
+ <form action="/dispatchers/<%=sanitize_url( d_url.dup )%>/shutdown_all" method="post">
25
25
  <%= csrf_tag %>
26
26
  <input type="submit" value="Shutdown all" />
27
27
  </form>
@@ -48,6 +48,7 @@
48
48
  <ul>
49
49
  <li>Current max concurrency: <span id="max_concurrency">0</span> requests</li>
50
50
  <li>Average response time: <span id="average_res_time">0</span> ms</li>
51
+ <li>Timed-out requests: <span id="time_out_count">0</span></li>
51
52
  <li>Current page: <span id="current_page">0</span></li>
52
53
  </ul>
53
54
 
@@ -123,6 +124,7 @@
123
124
  document.getElementById( 'crawled' ).innerHTML = stats.sitemap_size;
124
125
  document.getElementById( 'current_page' ).innerHTML = stats.current_page;
125
126
  document.getElementById( 'average_res_time' ).innerHTML = stats.average_res_time;
127
+ document.getElementById( 'time_out_count' ).innerHTML = stats.time_out_count;
126
128
  document.getElementById( 'max_concurrency' ).innerHTML = stats.max_concurrency;
127
129
 
128
130
  percentage = (stats.auditmap_size / stats.sitemap_size) * 100
@@ -33,7 +33,7 @@
33
33
 
34
34
  <% if issue.method && (issue.elem.downcase == 'form' || issue.elem.downcase == 'link' ) &&
35
35
  ( issue.method.downcase == 'get' || issue.method.downcase == 'post' ) %>
36
- <form style="display:inline" action="<%=issue.url%>" target="_blank" method="<%=issue.method.downcase%>">
36
+ <form style="display:inline" action="<%=issue.variations[0]['url']%>" target="_blank" method="<%=issue.method.downcase%>">
37
37
  <% if issue.variations[0]['opts'][:combo]%>
38
38
  <%issue.variations[0]['opts'][:combo].each_pair do |name, value|%>
39
39
  <input type="hidden" name="<%=escape(name)%>" value="<%=escape( value )%>" />
@@ -11,6 +11,7 @@
11
11
  require 'erb'
12
12
  require 'base64'
13
13
  require 'cgi'
14
+ require 'iconv'
14
15
 
15
16
  module Arachni
16
17
 
@@ -24,7 +25,7 @@ module Reports
24
25
  # @author: Tasos "Zapotek" Laskos
25
26
  # <tasos.laskos@gmail.com>
26
27
  # <zapotek@segfault.gr>
27
- # @version: 0.2.1
28
+ # @version: 0.2.2
28
29
  #
29
30
  class HTML < Arachni::Report::Base
30
31
 
@@ -87,15 +88,15 @@ class HTML < Arachni::Report::Base
87
88
  "\"" + str.gsub( "\n", '\n' ) + "\"";
88
89
  end
89
90
 
91
+ def normalize( str )
92
+ ic = ::Iconv.new( 'UTF-8//IGNORE', 'UTF-8' )
93
+ ic.iconv( str + ' ' )[0..-2]
94
+ end
95
+
90
96
  def escapeHTML( str )
91
97
  # carefully escapes HTML and converts to UTF-8
92
98
  # while removing invalid character sequences
93
- begin
94
- return CGI.escapeHTML( str )
95
- rescue
96
- ic = Iconv.new( 'UTF-8//IGNORE', 'UTF-8' )
97
- return CGI.escapeHTML( ic.iconv( str + ' ' )[0..-2] )
98
- end
99
+ return CGI.escapeHTML( normalize( str ) )
99
100
  end
100
101
 
101
102
  def self.prep_description( str )
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: arachni
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.2.3
5
+ version: 0.2.4
6
6
  platform: ruby
7
7
  authors:
8
8
  - Tasos Laskos
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-05-22 00:00:00 +01:00
13
+ date: 2011-07-01 00:00:00 +03:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency