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