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