arachni 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +57 -0
- data/README.md +1 -9
- data/bin/arachni_script +1 -1
- data/components/checks/active/xss_dom_script_context.rb +1 -1
- data/components/checks/active/xss_event.rb +1 -1
- data/components/checks/active/xss_script_context.rb +1 -1
- data/components/plugins/autologin.rb +2 -2
- data/components/plugins/content_types.rb +4 -5
- data/components/plugins/cookie_collector.rb +6 -3
- data/components/plugins/uncommon_headers.rb +6 -2
- data/lib/arachni/browser.rb +26 -2
- data/lib/arachni/browser/element_locator.rb +9 -2
- data/lib/arachni/browser/javascript.rb +6 -0
- data/lib/arachni/browser/javascript/scripts/dom_monitor.js +39 -0
- data/lib/arachni/browser_cluster.rb +11 -25
- data/lib/arachni/element/capabilities/analyzable/differential.rb +4 -0
- data/lib/arachni/element/capabilities/analyzable/timeout.rb +4 -0
- data/lib/arachni/element/capabilities/auditable/dom.rb +1 -0
- data/lib/arachni/element/capabilities/mutable.rb +0 -9
- data/lib/arachni/element/capabilities/with_auditor/output.rb +2 -0
- data/lib/arachni/element/cookie.rb +9 -4
- data/lib/arachni/element/form.rb +6 -6
- data/lib/arachni/element/header.rb +1 -1
- data/lib/arachni/framework.rb +1 -0
- data/lib/arachni/http/client.rb +1 -0
- data/lib/arachni/option_groups.rb +3 -0
- data/lib/arachni/option_groups/paths.rb +63 -6
- data/lib/arachni/option_groups/snapshot.rb +4 -0
- data/lib/arachni/session.rb +73 -17
- data/lib/arachni/state/audit.rb +2 -0
- data/lib/version +1 -1
- data/spec/arachni/browser/javascript_spec.rb +20 -0
- data/spec/arachni/browser_spec.rb +51 -0
- data/spec/arachni/element/cookie_spec.rb +22 -1
- data/spec/arachni/element/form_spec.rb +19 -9
- data/spec/arachni/framework_spec.rb +17 -0
- data/spec/arachni/option_groups/paths_spec.rb +109 -8
- data/spec/arachni/option_groups/snapshot_spec.rb +17 -0
- data/spec/arachni/session_spec.rb +54 -26
- data/spec/components/plugins/autologin_spec.rb +59 -0
- data/spec/spec_helper.rb +1 -3
- data/spec/support/factories/element/body.rb +3 -0
- data/spec/support/factories/element/generic_dom.rb +6 -0
- data/spec/support/factories/element/path.rb +3 -0
- data/spec/support/factories/element/server.rb +3 -0
- data/spec/support/factories/page/dom/transition.rb +21 -0
- data/spec/support/helpers/resets.rb +1 -0
- data/spec/support/servers/arachni/browser/javascript/dom_monitor.rb +15 -0
- data/ui/cli/framework.rb +5 -0
- data/ui/cli/framework/option_parser.rb +1 -1
- data/ui/cli/option_parser.rb +3 -0
- data/ui/cli/output.rb +45 -19
- metadata +10 -2
data/lib/version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.
|
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}
|
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
|
-
|
665
|
-
|
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
|
-
|
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,
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
101
|
-
|
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
|
-
|
104
|
-
|
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
|
-
|
109
|
-
transition.event.should == :submit
|
110
|
-
transition.element.tag_name.should == :form
|
139
|
+
subject.login
|
111
140
|
|
112
|
-
|
113
|
-
|
141
|
+
subject.should be_logged_in
|
142
|
+
end
|
114
143
|
|
115
|
-
|
116
|
-
configured.
|
117
|
-
end
|
144
|
+
it 'returns the resulting browser evaluated page' do
|
145
|
+
configured.login.should be_kind_of Arachni::Page
|
118
146
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|
-
|
129
|
-
|
152
|
+
transition = configured.login.dom.transitions.last
|
153
|
+
transition.event.should == :submit
|
154
|
+
transition.element.tag_name.should == :form
|
130
155
|
|
131
|
-
|
156
|
+
transition.options[:inputs]['username'].should ==
|
157
|
+
configured.configuration[:inputs][:username]
|
132
158
|
|
133
|
-
|
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
|