arachni 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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