arachni 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -0
  3. data/README.md +1 -9
  4. data/bin/arachni_script +1 -1
  5. data/components/checks/active/xss_dom_script_context.rb +1 -1
  6. data/components/checks/active/xss_event.rb +1 -1
  7. data/components/checks/active/xss_script_context.rb +1 -1
  8. data/components/plugins/autologin.rb +2 -2
  9. data/components/plugins/content_types.rb +4 -5
  10. data/components/plugins/cookie_collector.rb +6 -3
  11. data/components/plugins/uncommon_headers.rb +6 -2
  12. data/lib/arachni/browser.rb +26 -2
  13. data/lib/arachni/browser/element_locator.rb +9 -2
  14. data/lib/arachni/browser/javascript.rb +6 -0
  15. data/lib/arachni/browser/javascript/scripts/dom_monitor.js +39 -0
  16. data/lib/arachni/browser_cluster.rb +11 -25
  17. data/lib/arachni/element/capabilities/analyzable/differential.rb +4 -0
  18. data/lib/arachni/element/capabilities/analyzable/timeout.rb +4 -0
  19. data/lib/arachni/element/capabilities/auditable/dom.rb +1 -0
  20. data/lib/arachni/element/capabilities/mutable.rb +0 -9
  21. data/lib/arachni/element/capabilities/with_auditor/output.rb +2 -0
  22. data/lib/arachni/element/cookie.rb +9 -4
  23. data/lib/arachni/element/form.rb +6 -6
  24. data/lib/arachni/element/header.rb +1 -1
  25. data/lib/arachni/framework.rb +1 -0
  26. data/lib/arachni/http/client.rb +1 -0
  27. data/lib/arachni/option_groups.rb +3 -0
  28. data/lib/arachni/option_groups/paths.rb +63 -6
  29. data/lib/arachni/option_groups/snapshot.rb +4 -0
  30. data/lib/arachni/session.rb +73 -17
  31. data/lib/arachni/state/audit.rb +2 -0
  32. data/lib/version +1 -1
  33. data/spec/arachni/browser/javascript_spec.rb +20 -0
  34. data/spec/arachni/browser_spec.rb +51 -0
  35. data/spec/arachni/element/cookie_spec.rb +22 -1
  36. data/spec/arachni/element/form_spec.rb +19 -9
  37. data/spec/arachni/framework_spec.rb +17 -0
  38. data/spec/arachni/option_groups/paths_spec.rb +109 -8
  39. data/spec/arachni/option_groups/snapshot_spec.rb +17 -0
  40. data/spec/arachni/session_spec.rb +54 -26
  41. data/spec/components/plugins/autologin_spec.rb +59 -0
  42. data/spec/spec_helper.rb +1 -3
  43. data/spec/support/factories/element/body.rb +3 -0
  44. data/spec/support/factories/element/generic_dom.rb +6 -0
  45. data/spec/support/factories/element/path.rb +3 -0
  46. data/spec/support/factories/element/server.rb +3 -0
  47. data/spec/support/factories/page/dom/transition.rb +21 -0
  48. data/spec/support/helpers/resets.rb +1 -0
  49. data/spec/support/servers/arachni/browser/javascript/dom_monitor.rb +15 -0
  50. data/ui/cli/framework.rb +5 -0
  51. data/ui/cli/framework/option_parser.rb +1 -1
  52. data/ui/cli/option_parser.rb +3 -0
  53. data/ui/cli/output.rb +45 -19
  54. metadata +10 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f1ab8801aa396dbd2a6aa10c911a91777b046542
4
- data.tar.gz: 2251a40388dec9fc771bc8aa51ddc7680bb4f825
3
+ metadata.gz: fe4bb5082b1faba59faf683145dd95841b0299a2
4
+ data.tar.gz: 83374892882e248fb3334b7651c7f166c99c3bc2
5
5
  SHA512:
6
- metadata.gz: ccf69071cb3b2ddb1d880041979ac24d8d2f0d61662eadae5ad77544cbd226d47aa746e8505022d03b466290bece49410efc61957d0fc3fbd9359e5361d0b198
7
- data.tar.gz: 8d3f66447ef08172a43b29605b1f611131a0565a06ba70c706ee199d191043ee78c93b1eb8bef80d95e7215950e54a1ad3d650e3d6d440cc9fb5ab2babbb21f1
6
+ metadata.gz: def0427f488fd60f1aed48b0a38d2cb3e3deff96c3270305f27a5a7e2e9a00d01074716b492169ef6e1f45984ac84f89ef39e81b1d155791b73aa560c40f5e33
7
+ data.tar.gz: 0edb331de9533d6f5b9a63d3fdcb3f4a7f64697ce6f0f3a98f610a53d41629037d50c01cee7037d28afa9b2489edaa82fd97dee4311c9c4188f4336e78815600
@@ -1,5 +1,62 @@
1
1
  # ChangeLog
2
2
 
3
+ ## 1.0.3 _(October 3, 2014)_
4
+
5
+ - Added overrides for system write directories in `config/write_paths.yml`.
6
+ - `OptionGroups`
7
+ - `Paths`
8
+ - Added `.config` -- Parsing `config/write_paths.yml`.
9
+ - `#logs` -- Can now be set via `.config`.
10
+ - `#snapshots` -- Can now be set via `.config`.
11
+ - `Snapshot`
12
+ - `#save_path` -- Can now be set via `Paths.config`.
13
+ - `UI::Output`
14
+ - Moved default error log under `OptionGroups::Paths.logs`.
15
+ - Optimized file descriptor handling.
16
+ - `UI::CLI`
17
+ - `OptionParser`
18
+ - Set default report location save-dir from `OptionGroups::Paths.config`.
19
+ - `Framework`
20
+ - Print the error-log location at the end of the scan if there were errors.
21
+ - `Framework`
22
+ - Use `OptionGroups::Scope#extend_paths` to seed the crawl.
23
+ - `Browser`
24
+ - `ElementLocator.supported_element_attributes_for`
25
+ - Fixed `nil`-error when dealing with unknown attributes.
26
+ - Added `:ignore_scope` option, allowing the browser to roam completely
27
+ unrestricted.
28
+ - Capped `setTimeout` waiting period to `OptionGroups::HTTP#request_timeout`.
29
+ - Fixed issue resulting in multiple cookies with the same name being sent
30
+ to the web application.
31
+ - Assigned unique custom IDs to DOM elements without ID attributes.
32
+ - `BrowserCluster`
33
+ - Spawn browsers in series instead of in parallel to make it easier on
34
+ low resource systems.
35
+ - `Session`
36
+ - Fallback to `Framework` DOM Level 1 handlers when no `Browser` is available.
37
+ - When `OptionGroups::Scope#dom_depth_limit` is 0 don't use the `Browser`.
38
+ - Configured its `Browser` with `:ignore_scope` to allow for SSO support.
39
+ - `#logged_in?` -- Follow redirections for login check HTTP request.
40
+ - `Element`
41
+ - `Cookie`
42
+ - Added `#data` -- Providing access to raw cookie data.
43
+ - `Capabilities`
44
+ - `Analyzable`
45
+ - `Differential` -- Forcibly disable `OptionGroups::Audit#cookies_extensively`.
46
+ - `Timeout` -- Forcibly disable `OptionGroups::Audit#cookies_extensively`.
47
+ - `Mutable`
48
+ - `#each_mutation` -- Removed obsolete method-switch with default inputs.
49
+ - Plugins
50
+ - `uncommon_headers`
51
+ - Added `keep-alive` and `content-disposition` in the common list.
52
+ - Ignore out-of-scope responses.
53
+ - `content_types`
54
+ - Ignore out-of-scope responses.
55
+ - `cookie_collector`
56
+ - `Set-Cookie` header is now always an `Array`.
57
+ - `autologin`
58
+ - Don't modify `OptionGroups::Session` (login-check) options if already set.
59
+
3
60
  ## 1.0.2 _(September 13, 2014)_
4
61
 
5
62
  - `UI::Output` -- Updated null output interface with placeholder debugging methods.
data/README.md CHANGED
@@ -1,17 +1,9 @@
1
- **NOTICE**:
2
-
3
- * Arachni's license has changed, please see the _LICENSE_ file before working
4
- with the project.
5
- * v1.0 is not backwards compatible with v0.4.
6
-
7
- <hr/>
8
-
9
1
  # Arachni - Web Application Security Scanner Framework
10
2
 
11
3
  <table>
12
4
  <tr>
13
5
  <th>Version</th>
14
- <td>1.0.2</td>
6
+ <td>1.0.3</td>
15
7
  </tr>
16
8
  <tr>
17
9
  <th>Homepage</th>
@@ -9,8 +9,8 @@
9
9
 
10
10
  $LOAD_PATH.unshift( File.expand_path( File.dirname( __FILE__ ) + '/../lib' ) )
11
11
 
12
- require_relative '../ui/cli/output'
13
12
  require 'arachni'
13
+ require_relative '../ui/cli/output'
14
14
 
15
15
  include Arachni
16
16
  include Utilities
@@ -68,7 +68,7 @@ Injects JS taint code and checks to see if it gets executed as proof of vulnerab
68
68
  version: '0.1',
69
69
 
70
70
  issue: {
71
- name: %q{DOM-based Cross-Site Scripting (XSS)},
71
+ name: %q{DOM-based Cross-Site Scripting (XSS) in script context},
72
72
  description: %q{
73
73
  Client-side scripts are used extensively by modern web applications.
74
74
  They perform from simple functions (such as the formatting of text) up to full
@@ -94,7 +94,7 @@ class Arachni::Checks::XssEvent < Arachni::Check::Base
94
94
  version: '0.1.5',
95
95
 
96
96
  issue: {
97
- name: %q{Cross-Site Scripting in event tag of HTML element},
97
+ name: %q{Cross-Site Scripting (XSS) in event tag of HTML element},
98
98
  description: %q{
99
99
  Client-side scripts are used extensively by modern web applications.
100
100
  They perform from simple functions (such as the formatting of text) up to full
@@ -139,7 +139,7 @@ Injects JS taint code and check to see if it gets executed as proof of vulnerabi
139
139
  version: '0.2',
140
140
 
141
141
  issue: {
142
- name: %q{Cross-Site Scripting in HTML \'script\' tag},
142
+ name: %q{Cross-Site Scripting (XSS) in script context},
143
143
  description: %q{
144
144
  Client-side scripts are used extensively by modern web applications.
145
145
  They perform from simple functions (such as the formatting of text) up to full
@@ -48,8 +48,8 @@ class Arachni::Plugins::AutoLogin < Arachni::Plugin::Base
48
48
  return
49
49
  end
50
50
 
51
- framework.options.session.check_url = response.url
52
- framework.options.session.check_pattern = @verifier
51
+ framework.options.session.check_url ||= response.url
52
+ framework.options.session.check_pattern ||= @verifier
53
53
 
54
54
  if !session.logged_in?
55
55
  register_results(
@@ -9,7 +9,6 @@
9
9
  # Logs content-types of all server responses.
10
10
  #
11
11
  # @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
12
- # @version 0.1.6
13
12
  class Arachni::Plugins::ContentTypes < Arachni::Plugin::Base
14
13
 
15
14
  is_distributable
@@ -29,7 +28,7 @@ class Arachni::Plugins::ContentTypes < Arachni::Plugin::Base
29
28
  end
30
29
 
31
30
  def run
32
- framework.http.on_complete do |response|
31
+ http.on_complete do |response|
33
32
  next if skip?( response )
34
33
 
35
34
  type = response.headers.content_type
@@ -47,8 +46,8 @@ class Arachni::Plugins::ContentTypes < Arachni::Plugin::Base
47
46
  end
48
47
 
49
48
  def skip?( response )
50
- logged?( response ) || response.headers.content_type.to_s.empty? ||
51
- !log?( response )
49
+ response.scope.out? || logged?( response ) ||
50
+ response.headers.content_type.to_s.empty? || !log?( response )
52
51
  end
53
52
 
54
53
  def log?( response )
@@ -97,7 +96,7 @@ It can help you categorize and identify publicly available file-types which in
97
96
  turn can help you identify accidentally leaked files.
98
97
  },
99
98
  author: 'Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>',
100
- version: '0.1.6',
99
+ version: '0.1.7',
101
100
  options: [
102
101
  Options::String.new( :exclude,
103
102
  description: 'Exclude content-types that match this regular expression.',
@@ -9,7 +9,7 @@
9
9
  # Simple cookie collector
10
10
  #
11
11
  # @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
12
- # @version 0.2
12
+ # @version 0.2.1
13
13
  class Arachni::Plugins::CookieCollector < Arachni::Plugin::Base
14
14
 
15
15
  is_distributable
@@ -42,9 +42,12 @@ class Arachni::Plugins::CookieCollector < Arachni::Plugin::Base
42
42
  response_hash.delete( :body )
43
43
  response_hash.delete( :headers_string )
44
44
 
45
+ r_h = response_hash.my_stringify_keys
46
+ r_h['headers']['Set-Cookie'] = [r_h['headers']['Set-Cookie']].flatten.compact
47
+
45
48
  @cookies << {
46
49
  'time' => Time.now.to_s,
47
- 'response' => response_hash.my_stringify_keys,
50
+ 'response' => r_h,
48
51
  'cookies' => cookies
49
52
  }
50
53
  end
@@ -81,7 +84,7 @@ thousands of results leading to a huge report, highly increased memory
81
84
  consumption and CPU usage.
82
85
  },
83
86
  author: 'Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>',
84
- version: '0.2',
87
+ version: '0.2.1',
85
88
  options: [
86
89
  Options::String.new( :filter,
87
90
  description: 'Pattern to use to determine which cookies to ' +
@@ -32,7 +32,9 @@ class Arachni::Plugins::UncommonHeaders < Arachni::Plugin::Base
32
32
  'proxy-authenticate',
33
33
  'set-cookie',
34
34
  'trailer',
35
- 'transfer-encoding'
35
+ 'transfer-encoding',
36
+ 'keep-alive',
37
+ 'content-disposition'
36
38
  ])
37
39
 
38
40
  def prepare
@@ -52,6 +54,8 @@ class Arachni::Plugins::UncommonHeaders < Arachni::Plugin::Base
52
54
 
53
55
  def run
54
56
  http.on_complete do |response|
57
+ next if response.scope.out?
58
+
55
59
  headers = response.headers.
56
60
  select { |name, _| !COMMON.include?( name.to_s.downcase ) }
57
61
  next if headers.empty?
@@ -85,7 +89,7 @@ class Arachni::Plugins::UncommonHeaders < Arachni::Plugin::Base
85
89
  name: 'Uncommon headers',
86
90
  description: %q{Intercepts HTTP responses and logs uncommon headers.},
87
91
  author: 'Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>',
88
- version: '0.1.2'
92
+ version: '0.1.3'
89
93
  }
90
94
  end
91
95
 
@@ -126,6 +126,8 @@ class Browser
126
126
  super()
127
127
  @options = options.dup
128
128
 
129
+ @ignore_scope = options[:ignore_scope]
130
+
129
131
  @width = options[:width] || 1600
130
132
  @height = options[:height] || 1200
131
133
 
@@ -310,6 +312,8 @@ class Browser
310
312
  wait_for_timers
311
313
 
312
314
  wait_for_pending_requests
315
+
316
+ javascript.set_element_ids
313
317
  end
314
318
 
315
319
  if @add_request_transitions
@@ -828,7 +832,12 @@ class Browser
828
832
  def wait_for_timers
829
833
  delay = load_delay
830
834
  return if !delay
831
- sleep delay / 1000.0
835
+
836
+ sleep [Options.http.request_timeout, delay].min / 1000.0
837
+ end
838
+
839
+ def skip_path?( path )
840
+ enforce_scope? && super( path )
832
841
  end
833
842
 
834
843
  def response
@@ -998,6 +1007,13 @@ class Browser
998
1007
  kill_process
999
1008
  end
1000
1009
 
1010
+ # Something went really bad, the browser couldn't be spawned even
1011
+ # after our valiant efforts.
1012
+ #
1013
+ # Bail out for now and count on the BrowserCluster to retry to boot
1014
+ # another process ass needed.
1015
+ return if !@process
1016
+
1001
1017
  begin
1002
1018
  @pid = @process.pid
1003
1019
  # Not supported on JRuby on MS Windows.
@@ -1077,6 +1093,8 @@ class Browser
1077
1093
 
1078
1094
  set_cookies = {}
1079
1095
  HTTP::Client.cookie_jar.for_url( url ).each do |cookie|
1096
+ cookie = cookie.dup
1097
+ cookie.data.delete :domain
1080
1098
  set_cookies[cookie.name] = cookie
1081
1099
  end
1082
1100
  cookies.each do |name, value|
@@ -1181,7 +1199,7 @@ class Browser
1181
1199
  transition.complete
1182
1200
  end
1183
1201
 
1184
- return if response.scope.out?
1202
+ return if enforce_scope? && response.scope.out?
1185
1203
 
1186
1204
  intercept response
1187
1205
  save_response response
@@ -1203,6 +1221,8 @@ class Browser
1203
1221
  end
1204
1222
 
1205
1223
  def ignore_request?( request )
1224
+ return if !enforce_scope?
1225
+
1206
1226
  # Only allow CSS and JS resources to be loaded from out-of-scope domains.
1207
1227
  !['css', 'js'].include?( request.parsed_url.resource_extension ) &&
1208
1228
  request.scope.out? || request.scope.redundant?
@@ -1304,6 +1324,10 @@ class Browser
1304
1324
  end
1305
1325
  end
1306
1326
 
1327
+ def enforce_scope?
1328
+ !@ignore_scope
1329
+ end
1330
+
1307
1331
  def normalize_watir_url( url )
1308
1332
  normalize_url( ::URI.encode( url, ';' ) ).gsub( '%3B', '%253B' )
1309
1333
  end
@@ -127,8 +127,15 @@ class ElementLocator
127
127
  # List of attributes supported by Watir.
128
128
  def self.supported_element_attributes_for( tag_name )
129
129
  @supported_element_attributes_for ||= {}
130
- @supported_element_attributes_for[tag_name.to_sym] ||=
131
- Set.new( Watir.tag_to_class[tag_name.to_sym].attribute_list )
130
+
131
+ tag_name = tag_name.to_sym
132
+
133
+ if (klass = Watir.tag_to_class[tag_name])
134
+ @supported_element_attributes_for[tag_name] ||=
135
+ Set.new( klass.attribute_list )
136
+ else
137
+ @supported_element_attributes_for[tag_name] ||= Set.new
138
+ end
132
139
  end
133
140
 
134
141
  end
@@ -228,6 +228,12 @@ class Javascript
228
228
  taint_tracer.flush_data_flow_sinks
229
229
  end
230
230
 
231
+ # Sets a custom ID attribute to elements with events but without a proper ID.
232
+ def set_element_ids
233
+ return '' if !supported?
234
+ dom_monitor.setElementIds
235
+ end
236
+
231
237
  # @return [String]
232
238
  # Digest of the current DOM tree (i.e. node names and their attributes
233
239
  # without text-nodes).
@@ -142,5 +142,44 @@ var _tokenDOMMonitor = _tokenDOMMonitor || {
142
142
  registerEvent: function ( element, event, handler ) {
143
143
  if( !('events' in element) ) element['events'] = [];
144
144
  element['events'].push( [event, handler] );
145
+ },
146
+
147
+ // Sets a unique enough custom ID attribute to elements that lack proper IDs.
148
+ // This gets called externally (by the Browser) once the page is settled.
149
+ setElementIds: function() {
150
+ var elements = document.getElementsByTagName("*");
151
+ var length = elements.length;
152
+
153
+ for( var i = 0; i < length; i++ ) {
154
+ var element = elements[i];
155
+
156
+ // Window and others don't have attributes.
157
+ if( typeof( element.getAttribute ) !== 'function' ||
158
+ typeof( element.setAttribute) !== 'function' ) continue;
159
+
160
+ // If the element has an ID we're cool, move on.
161
+ if( element.getAttribute('id') ) continue;
162
+
163
+ // Skip invisible elements.
164
+ if( element.offsetWidth <= 0 && element.offsetHeight <= 0 ) continue;
165
+
166
+ // We don't care about elements without events.
167
+ if( !element.events || element.events.length == 0 ) continue;
168
+
169
+ element.setAttribute( 'data-arachni-id', _tokenDOMMonitor.hashCode( element.innerHTML ) );
170
+ }
171
+ },
172
+
173
+ hashCode: function( str ) {
174
+ var hash = 0;
175
+ if( str.length == 0 ) return hash;
176
+
177
+ for( var i = 0; i < str.length; i++ ) {
178
+ var char = str.charCodeAt( i );
179
+ hash = ((hash << 5) - hash) + char;
180
+ hash = hash & hash; // Convert to 32bit integer
181
+ }
182
+
183
+ return hash;
145
184
  }
146
185
  };
@@ -384,32 +384,18 @@ class BrowserCluster
384
384
  def initialize_workers
385
385
  print_status "Initializing #{pool_size} browsers..."
386
386
 
387
- # Calculate maximum HTTP connection concurrency for each worker based on
388
- # the HTTP request concurrency setting of the framework.
389
- #
390
- # Ideally, we'd throttle the collective connections of all browsers
391
- # for optimal concurrency, but that would require all browsers sharing
392
- # the same proxy which would make things **really** dirty and complicated
393
- # so let's avoid that for as long as possible.
394
- #concurrency = [(Options.http.request_concurrency / pool_size).to_i, 1].max
395
-
396
387
  @workers = []
397
- workers = Queue.new
398
-
399
- pool_size.times do
400
- Thread.new do
401
- workers << Worker.new(
402
- javascript_token: @javascript_token,
403
- master: self,
404
- width: Options.browser_cluster.screen_width,
405
- height: Options.browser_cluster.screen_height
406
- #concurrency: concurrency
407
- )
408
- end
409
- end
410
-
411
- pool_size.times do
412
- @workers << workers.pop.tap { |b| @consumed_pids << b.pid }
388
+ pool_size.times do |i|
389
+ worker = Worker.new(
390
+ javascript_token: @javascript_token,
391
+ master: self,
392
+ width: Options.browser_cluster.screen_width,
393
+ height: Options.browser_cluster.screen_height
394
+ )
395
+ @workers << worker
396
+ @consumed_pids << worker.pid
397
+
398
+ print_status "Spawned ##{i+1} with PID #{worker.pid}."
413
399
  end
414
400
  @consumed_pids.compact!
415
401