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
@@ -1 +1 @@
1
- 1.0.2
1
+ 1.0.3
@@ -98,6 +98,26 @@ describe Arachni::Browser::Javascript do
98
98
  end
99
99
  end
100
100
 
101
+ describe '#set_element_ids' do
102
+ it 'sets custom ID attributes to elements with events but without ID' do
103
+ @browser.load( @dom_monitor_url + 'set_element_ids' )
104
+
105
+ as = @browser.watir.as
106
+
107
+ as[0].name.should == '1'
108
+ as[0].html.should_not include 'data-arachni-id'
109
+
110
+ as[1].name.should == '2'
111
+ as[1].html.should include 'data-arachni-id'
112
+
113
+ as[2].name.should == '3'
114
+ as[2].html.should_not include 'data-arachni-id'
115
+
116
+ as[3].name.should == '4'
117
+ as[3].html.should_not include 'data-arachni-id'
118
+ end
119
+ end
120
+
101
121
  describe '#dom_digest' do
102
122
  it 'returns a string digest of the current DOM tree' do
103
123
  @browser.load( @dom_monitor_url + 'digest' )
@@ -85,6 +85,47 @@ describe Arachni::Browser do
85
85
  it 'sets the HTTP request concurrency'
86
86
  end
87
87
 
88
+ describe :ignore_scope do
89
+ context true do
90
+ it 'ignores scope restrictions' do
91
+ @browser.shutdown
92
+
93
+ @browser = described_class.new( ignore_scope: true )
94
+
95
+ Arachni::Options.scope.exclude_path_patterns << /sleep/
96
+
97
+ subject.load @url + '/ajax_sleep'
98
+ subject.to_page.should be_true
99
+ end
100
+ end
101
+
102
+ context false do
103
+ it 'enforces scope restrictions' do
104
+ @browser.shutdown
105
+
106
+ @browser = described_class.new( ignore_scope: false )
107
+
108
+ Arachni::Options.scope.exclude_path_patterns << /sleep/
109
+
110
+ subject.load @url + '/ajax_sleep'
111
+ subject.to_page.should be_nil
112
+ end
113
+ end
114
+
115
+ context :default do
116
+ it 'enforces scope restrictions' do
117
+ @browser.shutdown
118
+
119
+ @browser = described_class.new( ignore_scope: false )
120
+
121
+ Arachni::Options.scope.exclude_path_patterns << /sleep/
122
+
123
+ subject.load @url + '/ajax_sleep'
124
+ subject.to_page.should be_nil
125
+ end
126
+ end
127
+ end
128
+
88
129
  describe :width do
89
130
  it 'sets the window width' do
90
131
  @browser.shutdown
@@ -203,6 +244,16 @@ describe Arachni::Browser do
203
244
  subject.wait_for_timers
204
245
  (Time.now - time).should > seconds
205
246
  end
247
+
248
+ it "caps them at #{Arachni::OptionGroups::HTTP}#request_timeout" do
249
+ subject.load( "#{@url}load_delay" )
250
+
251
+ Arachni::Options.http.request_timeout = 100
252
+
253
+ time = Time.now
254
+ subject.wait_for_timers
255
+ (Time.now - time).should < 0.2
256
+ end
206
257
  end
207
258
  end
208
259
 
@@ -21,7 +21,7 @@ describe Arachni::Element::Cookie do
21
21
  end
22
22
  subject do
23
23
  described_class.new(
24
- url: "#{url}/submit",
24
+ url: "#{url}submit",
25
25
  name: inputs.keys.first,
26
26
  value: inputs.values.first,
27
27
  expires: Time.now + 99999999999
@@ -176,6 +176,27 @@ describe Arachni::Element::Cookie do
176
176
  end
177
177
  end
178
178
 
179
+ describe '#data' do
180
+ it 'returns the cookie data' do
181
+ subject.data.should == {
182
+ name: 'mycookie',
183
+ value: 'myvalue',
184
+ url: subject.action,
185
+ expires: subject.expires_at,
186
+ version: 0,
187
+ port: nil,
188
+ discard: nil,
189
+ comment_url: nil,
190
+ max_age: nil,
191
+ comment: nil,
192
+ secure: nil,
193
+ path: '/submit',
194
+ domain: '127.0.0.2',
195
+ httponly: false
196
+ }
197
+ end
198
+ end
199
+
179
200
  describe '#dom' do
180
201
  context 'when there are no #inputs' do
181
202
  it 'returns nil' do
@@ -660,9 +660,10 @@ describe Arachni::Element::Form do
660
660
  described_class.from_document( '', '' ).should be_empty
661
661
  end
662
662
  end
663
+
663
664
  context 'when forms have actions that are out of scope' do
664
- it 'ignores them' do
665
- html = '
665
+ let(:form_html) do
666
+ <<EOHTML
666
667
  <html>
667
668
  <body>
668
669
  <form method="get" action="form_action/exclude" name="my_form">
@@ -670,20 +671,30 @@ describe Arachni::Element::Form do
670
671
  <input name="my_second_input" value="my_second_value" />
671
672
  </form>
672
673
 
673
- <form method="get" action="form_action" name="my_form">
674
- <input name="my_first_input" value="my_first_value" />
675
- <input name="my_second_input" value="my_second_value" />
676
- </form>
674
+ #{html}
677
675
  </body>
678
- </html>'
676
+ </html>
677
+ EOHTML
678
+ end
679
679
 
680
+ it 'ignores them' do
680
681
  Arachni::Options.scope.exclude_path_patterns = [/exclude/]
681
682
 
682
- forms = described_class.from_document( url, html )
683
+ forms = described_class.from_document( url, form_html )
683
684
  forms.size.should == 1
684
685
  forms.first.action.should == utilities.normalize_url( url + '/form_action' )
685
686
  end
687
+
688
+ context 'when ignore_scope is set' do
689
+ it 'includes them' do
690
+ Arachni::Options.scope.exclude_path_patterns = [/exclude/]
691
+
692
+ forms = described_class.from_document( url, form_html, true )
693
+ forms.size.should == 2
694
+ end
695
+ end
686
696
  end
697
+
687
698
  context 'when the response contains forms' do
688
699
  context 'with text inputs' do
689
700
  it 'returns an array of forms' do
@@ -1033,7 +1044,6 @@ describe Arachni::Element::Form do
1033
1044
  }
1034
1045
  end
1035
1046
  end
1036
-
1037
1047
  end
1038
1048
  end
1039
1049
 
@@ -209,6 +209,23 @@ describe Arachni::Framework do
209
209
  end
210
210
  end
211
211
 
212
+ describe "#{Arachni::OptionGroups::Scope}#extend_paths" do
213
+ it 'extends the crawl scope' do
214
+ Arachni::Framework.new do |f|
215
+ f.options.url = "#{@url}/elem_combo"
216
+ f.options.scope.extend_paths = %w(/some/stuff /more/stuff)
217
+ f.options.audit.elements :links, :forms, :cookies
218
+ f.checks.load :taint
219
+
220
+ f.run
221
+
222
+ f.report.sitemap.should include "#{@url}/some/stuff"
223
+ f.report.sitemap.should include "#{@url}/more/stuff"
224
+ f.report.sitemap.size.should > 3
225
+ end
226
+ end
227
+ end
228
+
212
229
  describe "#{Arachni::OptionGroups::Scope}#restrict_paths" do
213
230
  it 'serves as a replacement to crawling' do
214
231
  Arachni::Framework.new do |f|
@@ -1,6 +1,21 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Arachni::OptionGroups::Paths do
4
+
5
+ before :all do
6
+ @created_resources = []
7
+ end
8
+
9
+ after :each do
10
+ ENV['ARACHNI_FRAMEWORK_LOGDIR'] = nil
11
+
12
+ (@created_resources + [paths_config_file]).each do |r|
13
+ FileUtils.rm_rf r
14
+ end
15
+ end
16
+
17
+ let(:paths_config_file) { "#{Dir.tmpdir}/paths-#{Process.pid}.yml" }
18
+
4
19
  %w(root arachni components logs checks reporters plugins services
5
20
  path_extractors fingerprinters lib support mixins snapshots).each do |method|
6
21
 
@@ -15,20 +30,106 @@ describe Arachni::OptionGroups::Paths do
15
30
  end
16
31
 
17
32
  describe '#logs' do
33
+ it 'returns the default location' do
34
+ subject.logs.should == "#{subject.root}logs/"
35
+ end
36
+
18
37
  context 'when the ARACHNI_FRAMEWORK_LOGDIR environment variable' do
19
- context 'has been set' do
20
- it 'returns its value' do
21
- ENV['ARACHNI_FRAMEWORK_LOGDIR'] = 'test'
22
- subject.logs.should == 'test/'
38
+ it 'returns its value' do
39
+ ENV['ARACHNI_FRAMEWORK_LOGDIR'] = 'test'
40
+ subject.logs.should == 'test/'
41
+ end
42
+ end
43
+
44
+ context "when #{described_class}.config['framework']['logs']" do
45
+ it 'returns its value' do
46
+ described_class.stub(:config) do
47
+ {
48
+ 'framework' => {
49
+ 'logs' => 'logs-stuff/'
50
+ }
51
+ }
23
52
  end
53
+
54
+ described_class.new.logs.should == 'logs-stuff/'
24
55
  end
25
- context 'has not been set' do
26
- it 'returns the default location' do
27
- ENV['ARACHNI_FRAMEWORK_LOGDIR'] = nil
28
- subject.logs.should == "#{subject.root}logs/"
56
+ end
57
+ end
58
+
59
+ describe '#snapshots' do
60
+ it 'returns the default location' do
61
+ subject.snapshots.should == "#{subject.root}snapshots/"
62
+ end
63
+
64
+ context "when #{described_class}.config['framework']['snapshots']" do
65
+ it 'returns its value' do
66
+ described_class.stub(:config) do
67
+ {
68
+ 'framework' => {
69
+ 'snapshots' => 'snapshots-stuff/'
70
+ }
71
+ }
29
72
  end
73
+
74
+ described_class.new.snapshots.should == 'snapshots-stuff/'
30
75
  end
31
76
  end
32
77
  end
33
78
 
79
+ describe '.config' do
80
+ let(:config) { described_class.config }
81
+
82
+ it 'expands ~ to $HOME' do
83
+ yaml = {
84
+ 'stuff' => {
85
+ 'blah' => "~/foo-#{Process.pid}/"
86
+ }
87
+ }.to_yaml
88
+
89
+ described_class.stub(:paths_config_file) { paths_config_file }
90
+ IO.write( described_class.paths_config_file, yaml )
91
+ described_class.clear_config_cache
92
+
93
+ @created_resources << described_class.config['stuff']['blah']
94
+
95
+ described_class.config['stuff']['blah'].should == "#{ENV['HOME']}/foo-#{Process.pid}/"
96
+ end
97
+
98
+ it 'appends / to paths' do
99
+ dir = "#{Dir.tmpdir}/foo-#{Process.pid}"
100
+ yaml = {
101
+ 'stuff' => {
102
+ 'blah' => dir
103
+ }
104
+ }.to_yaml
105
+
106
+ described_class.stub(:paths_config_file) { paths_config_file }
107
+ IO.write( described_class.paths_config_file, yaml )
108
+ described_class.clear_config_cache
109
+
110
+ @created_resources << described_class.config['stuff']['blah']
111
+
112
+ described_class.config['stuff']['blah'].should == "#{dir}/"
113
+ end
114
+
115
+ it 'creates the given directories' do
116
+ dir = "#{Dir.tmpdir}/foo/stuff-#{Process.pid}"
117
+ yaml = {
118
+ 'stuff' => {
119
+ 'blah' => dir
120
+ }
121
+ }.to_yaml
122
+
123
+ described_class.stub(:paths_config_file) { paths_config_file }
124
+ IO.write( described_class.paths_config_file, yaml )
125
+ described_class.clear_config_cache
126
+
127
+ @created_resources << dir
128
+
129
+ File.exist?( dir ).should be_false
130
+ described_class.config
131
+ File.exist?( dir ).should be_true
132
+ end
133
+ end
134
+
34
135
  end
@@ -8,4 +8,21 @@ describe Arachni::OptionGroups::Snapshot do
8
8
  it { should respond_to method }
9
9
  it { should respond_to "#{method}=" }
10
10
  end
11
+
12
+ describe '.save_path' do
13
+ context "when #{Arachni::OptionGroups::Paths}.config['framework']['snapshots']" do
14
+ it 'returns it' do
15
+ Arachni::OptionGroups::Paths.stub(:config) do
16
+ {
17
+ 'framework' => {
18
+ 'snapshots' => 'stuff/'
19
+ }
20
+ }
21
+ end
22
+
23
+ subject.save_path.should == 'stuff/'
24
+ end
25
+ end
26
+ end
27
+
11
28
  end
@@ -52,6 +52,22 @@ describe Arachni::Session do
52
52
  end
53
53
  end
54
54
 
55
+ describe '#has_browser?' do
56
+ context "when #{Arachni::OptionGroups::Scope}#dom_depth_limit is 0" do
57
+ it 'returns false' do
58
+ Arachni::Options.scope.dom_depth_limit = 0
59
+ subject.has_browser?.should be_false
60
+ end
61
+ end
62
+
63
+ context "when not #{Arachni::Browser}.has_executable?" do
64
+ it 'returns false' do
65
+ Arachni::Browser.stub(:has_executable?) { false }
66
+ subject.has_browser?.should be_false
67
+ end
68
+ end
69
+ end
70
+
55
71
  describe '#configuration' do
56
72
  it "returns #{Arachni::Data::Session}#configuration" do
57
73
  subject.configuration.object_id.should ==
@@ -97,40 +113,52 @@ describe Arachni::Session do
97
113
  configured.should be_logged_in
98
114
  end
99
115
 
100
- it 'returns the resulting page' do
101
- configured.login.should be_kind_of Arachni::Page
116
+ context 'when a browser is available' do
117
+ before { subject.stub(:has_browser?) { false } }
118
+
119
+ it 'uses the framework Page helpers' do
120
+ configured.should_not be_logged_in
121
+ configured.login.should be_kind_of Arachni::Page
122
+ configured.should be_logged_in
123
+ end
124
+ end
125
+
126
+ context 'when a browser is available' do
127
+ it 'can handle Javascript forms' do
128
+ subject.configure(
129
+ url: "#{@url}/javascript_login",
130
+ inputs: {
131
+ username: 'john',
132
+ password: 'doe'
133
+ }
134
+ )
102
135
 
103
- transition = configured.login.dom.transitions.first
104
- transition.event.should == :load
105
- transition.element.should == :page
106
- transition.options[:url].should == configured.configuration[:url]
136
+ @opts.session.check_url = @url
137
+ @opts.session.check_pattern = 'logged-in user'
107
138
 
108
- transition = configured.login.dom.transitions.last
109
- transition.event.should == :submit
110
- transition.element.tag_name.should == :form
139
+ subject.login
111
140
 
112
- transition.options[:inputs]['username'].should ==
113
- configured.configuration[:inputs][:username]
141
+ subject.should be_logged_in
142
+ end
114
143
 
115
- transition.options[:inputs]['password'].should ==
116
- configured.configuration[:inputs][:password]
117
- end
144
+ it 'returns the resulting browser evaluated page' do
145
+ configured.login.should be_kind_of Arachni::Page
118
146
 
119
- it 'can handle Javascript forms' do
120
- subject.configure(
121
- url: "#{@url}/javascript_login",
122
- inputs: {
123
- username: 'john',
124
- password: 'doe'
125
- }
126
- )
147
+ transition = configured.login.dom.transitions.first
148
+ transition.event.should == :load
149
+ transition.element.should == :page
150
+ transition.options[:url].should == configured.configuration[:url]
127
151
 
128
- @opts.session.check_url = @url
129
- @opts.session.check_pattern = 'logged-in user'
152
+ transition = configured.login.dom.transitions.last
153
+ transition.event.should == :submit
154
+ transition.element.tag_name.should == :form
130
155
 
131
- subject.login
156
+ transition.options[:inputs]['username'].should ==
157
+ configured.configuration[:inputs][:username]
132
158
 
133
- subject.should be_logged_in
159
+ transition.options[:inputs]['password'].should ==
160
+ configured.configuration[:inputs][:password]
161
+ end
134
162
  end
135
163
 
136
164
  context 'when no configuration has been provided' do