arachni 1.0.2 → 1.0.3

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.
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