arachni 0.2.2.1 → 0.2.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +30 -0
- data/CONTRIBUTORS.md +1 -0
- data/README.md +28 -8
- data/Rakefile +1 -0
- data/bin/arachni_web_autostart +46 -0
- data/lib/anemone/page.rb +1 -0
- data/lib/arachni.rb +1 -1
- data/lib/framework.rb +8 -3
- data/lib/http.rb +9 -39
- data/lib/mixins/observable.rb +87 -0
- data/lib/module/auditor.rb +14 -0
- data/lib/module/base.rb +0 -14
- data/lib/nokogiri/xml/node.rb +42 -0
- data/lib/ui/cli/cli.rb +1 -1
- data/lib/ui/web/log.rb +21 -14
- data/lib/ui/web/report_manager.rb +100 -15
- data/lib/ui/web/server.rb +24 -33
- data/lib/ui/web/server/public/reports/demo.testfire.net:Sun Mar 20 02:48:10 2011.afr +104829 -0
- data/lib/ui/web/server/views/layout.erb +1 -1
- data/lib/ui/web/server/views/options.erb +10 -2
- data/lib/ui/web/server/views/plugins.erb +1 -1
- data/lib/ui/web/server/views/reports.erb +8 -4
- data/lib/ui/xmlrpc/xmlrpc.rb +1 -1
- data/metamodules/autothrottle.rb +2 -2
- data/metamodules/timeout_notice.rb +1 -1
- data/modules/audit/sqli_blind_rdiff.rb +1 -1
- data/modules/recon/common_files/filenames.txt +2 -0
- data/modules/recon/directory_listing.rb +1 -0
- data/modules/recon/interesting_responses.rb +3 -3
- data/path_extractors/generic.rb +5 -1
- data/plugins/autologin.rb +15 -4
- data/plugins/content_types.rb +2 -2
- data/plugins/cookie_collector.rb +9 -16
- data/plugins/profiler.rb +237 -0
- data/reports/html.rb +21 -6
- data/reports/html/default.erb +4 -2
- data/reports/plugin_formatters/html/autologin.rb +63 -0
- data/reports/plugin_formatters/html/profiler.rb +71 -0
- data/reports/plugin_formatters/html/profiler/template.erb +177 -0
- data/reports/plugin_formatters/stdout/autologin.rb +55 -0
- data/reports/plugin_formatters/stdout/profiler.rb +90 -0
- data/reports/plugin_formatters/xml/autologin.rb +68 -0
- data/reports/plugin_formatters/xml/profiler.rb +120 -0
- metadata +23 -68
@@ -10,9 +10,17 @@
|
|
10
10
|
<% if opt['type'] == 'path' %>
|
11
11
|
disabled="disabled" type="file" value="<%=opt['default']%>"
|
12
12
|
<% elsif opt['type'] == 'bool' %>
|
13
|
-
type="checkbox"
|
13
|
+
type="checkbox"
|
14
|
+
<% if session_options[component['plug_name']] && !(val = session_options[component['plug_name']][opt['name']]).nil? %>
|
15
|
+
|
16
|
+
<%=val ? 'checked="checked"' : '' %>
|
17
|
+
|
18
|
+
<% elsif opt['default'] %>
|
19
|
+
checked="checked"
|
20
|
+
<%end%>
|
21
|
+
|
14
22
|
<% else %>
|
15
|
-
value="<%=opt['default']%>"
|
23
|
+
value="<%=( session_options[component['plug_name']] && (val = session_options[component['plug_name']][opt['name']]) ) ? val : opt['default']%>"
|
16
24
|
<%end%>
|
17
25
|
|
18
26
|
/>
|
@@ -30,7 +30,7 @@
|
|
30
30
|
<h5>Description</h5>
|
31
31
|
<pre class="notice"> <%=prep_description( escape( plugin['description'] ) )%></pre>
|
32
32
|
|
33
|
-
<%= erb :options, { :layout => false }, :component => plugin%>
|
33
|
+
<%= erb :options, { :layout => false }, :component => plugin, :session_options => session_options%>
|
34
34
|
|
35
35
|
<p>
|
36
36
|
<strong>Version:</strong> <%=plugin['version']%><br/>
|
@@ -22,7 +22,9 @@
|
|
22
22
|
<% if !reports.empty? %>
|
23
23
|
<table>
|
24
24
|
<tr>
|
25
|
+
<th>ID</th>
|
25
26
|
<th>Host</th>
|
27
|
+
<th>Issue count</th>
|
26
28
|
<th>Audit date</th>
|
27
29
|
<th>Report formats</th>
|
28
30
|
</tr>
|
@@ -30,19 +32,21 @@
|
|
30
32
|
<tr>
|
31
33
|
|
32
34
|
|
33
|
-
<td><%=report
|
34
|
-
<td><%=report
|
35
|
+
<td><%=report.id%></td>
|
36
|
+
<td><%=report.host%></td>
|
37
|
+
<td><%=report.issue_count%></td>
|
38
|
+
<td><%=report.datestamp%></td>
|
35
39
|
|
36
40
|
<td>
|
37
41
|
<ul class="reports">
|
38
42
|
<% available.each do |avail| %>
|
39
|
-
<li><a href="/report/<%=
|
43
|
+
<li><a href="/report/<%=report.id%>.<%=avail['rep_name']%>"><%=escape(avail['name'])%></a></li>
|
40
44
|
<%end%>
|
41
45
|
</ul>
|
42
46
|
</td>
|
43
47
|
|
44
48
|
<td>
|
45
|
-
<form action="/report/<%=
|
49
|
+
<form action="/report/<%=report.id%>/delete" method="post">
|
46
50
|
<%= csrf_tag %>
|
47
51
|
<input type="submit" value="Delete" />
|
48
52
|
</form>
|
data/lib/ui/xmlrpc/xmlrpc.rb
CHANGED
@@ -642,7 +642,7 @@ class XMLRPC
|
|
642
642
|
<zapotek@segfault.gr>
|
643
643
|
(With the support of the community and the Arachni Team.)
|
644
644
|
|
645
|
-
Website: http://github.com/Zapotek/arachni
|
645
|
+
Website: http://arachni.segfault.gr - http://github.com/Zapotek/arachni
|
646
646
|
Documentation: http://github.com/Zapotek/arachni/wiki'
|
647
647
|
print_line
|
648
648
|
print_line
|
data/metamodules/autothrottle.rb
CHANGED
@@ -17,7 +17,7 @@ module MetaModules
|
|
17
17
|
# @author: Tasos "Zapotek" Laskos
|
18
18
|
# <tasos.laskos@gmail.com>
|
19
19
|
# <zapotek@segfault.gr>
|
20
|
-
# @version: 0.1
|
20
|
+
# @version: 0.1.1
|
21
21
|
#
|
22
22
|
class AutoThrottle < Base
|
23
23
|
|
@@ -40,7 +40,7 @@ class AutoThrottle < Base
|
|
40
40
|
def prepare
|
41
41
|
|
42
42
|
# run for each response as it arrives
|
43
|
-
@http.
|
43
|
+
@http.add_on_complete {
|
44
44
|
|
45
45
|
# adjust only after finished bursts
|
46
46
|
next if @http.curr_res_cnt == 0 || @http.curr_res_cnt % @http.max_concurrency != 0
|
@@ -250,7 +250,7 @@ class BlindrDiffSQLInjection < Arachni::Module::Base
|
|
250
250
|
Issue::Element::LINK
|
251
251
|
],
|
252
252
|
:author => 'Tasos "Zapotek" Laskos <tasos.laskos@gmail.com> ',
|
253
|
-
:version => '0.
|
253
|
+
:version => '0.3',
|
254
254
|
:references => {
|
255
255
|
'OWASP' => 'http://www.owasp.org/index.php/Blind_SQL_Injection',
|
256
256
|
'MITRE - CAPEC' => 'http://capec.mitre.org/data/definitions/7.html'
|
@@ -20,7 +20,7 @@ module Modules
|
|
20
20
|
# @author: Tasos "Zapotek" Laskos
|
21
21
|
# <tasos.laskos@gmail.com>
|
22
22
|
# <zapotek@segfault.gr>
|
23
|
-
# @version: 0.1
|
23
|
+
# @version: 0.1.1
|
24
24
|
#
|
25
25
|
#
|
26
26
|
class InterestingResponses < Arachni::Module::Base
|
@@ -45,7 +45,7 @@ class InterestingResponses < Arachni::Module::Base
|
|
45
45
|
print_status( "Listening..." )
|
46
46
|
|
47
47
|
# tell the HTTP interface to cal this block every-time a request completes
|
48
|
-
@http.
|
48
|
+
@http.add_on_complete {
|
49
49
|
|res|
|
50
50
|
__log_results( res ) if !IGNORE_CODES.include?( res.code ) && !res.body.empty?
|
51
51
|
}
|
@@ -62,7 +62,7 @@ class InterestingResponses < Arachni::Module::Base
|
|
62
62
|
:description => %q{Logs all non 200 (OK) server responses.},
|
63
63
|
:elements => [ ],
|
64
64
|
:author => 'Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>',
|
65
|
-
:version => '0.1',
|
65
|
+
:version => '0.1.1',
|
66
66
|
:targets => { 'Generic' => 'all' },
|
67
67
|
:issue => {
|
68
68
|
:name => %q{Interesting server response.},
|
data/path_extractors/generic.rb
CHANGED
data/plugins/autologin.rb
CHANGED
@@ -27,6 +27,10 @@ class AutoLogin < Arachni::Plugin::Base
|
|
27
27
|
|
28
28
|
attr_accessor :http
|
29
29
|
|
30
|
+
MSG_SUCCESS = 'Form submitted successfully.'
|
31
|
+
MSG_FAILURE = 'Could not find a form suiting the provided params at: '
|
32
|
+
MSG_NO_RESPONSE = 'Form submitted but no response was returned.'
|
33
|
+
|
30
34
|
#
|
31
35
|
# @param [Arachni::Framework] framework
|
32
36
|
# @param [Hash] options options passed to the plugin
|
@@ -64,8 +68,8 @@ class AutoLogin < Arachni::Plugin::Base
|
|
64
68
|
}
|
65
69
|
|
66
70
|
if !login_form
|
67
|
-
|
68
|
-
@options['url'] )
|
71
|
+
register_results( { :code => 0, :msg => MSG_FAILURE + @options['url'] } )
|
72
|
+
print_error( MSG_FAILURE + @options['url'] )
|
69
73
|
return
|
70
74
|
end
|
71
75
|
|
@@ -80,10 +84,17 @@ class AutoLogin < Arachni::Plugin::Base
|
|
80
84
|
res = login_form.submit( :async => false ).response
|
81
85
|
|
82
86
|
if !res
|
83
|
-
|
87
|
+
register_results( { :code => -1, :msg => MSG_NO_RESPONSE } )
|
88
|
+
print_error( MSG_NO_RESPONSE )
|
84
89
|
return
|
85
90
|
else
|
86
|
-
|
91
|
+
register_results( { :code => 1, :msg => MSG_SUCCESS, :cookies => @http.current_cookies.dup } )
|
92
|
+
print_ok( MSG_SUCCESS )
|
93
|
+
print_info( 'Cookies set to:' )
|
94
|
+
@http.current_cookies.each_pair {
|
95
|
+
|name, val|
|
96
|
+
print_info( ' * ' + name + ' = ' + val )
|
97
|
+
}
|
87
98
|
end
|
88
99
|
|
89
100
|
end
|
data/plugins/content_types.rb
CHANGED
@@ -17,7 +17,7 @@ module Plugins
|
|
17
17
|
# @author: Tasos "Zapotek" Laskos
|
18
18
|
# <tasos.laskos@gmail.com>
|
19
19
|
# <zapotek@segfault.gr>
|
20
|
-
# @version: 0.1
|
20
|
+
# @version: 0.1.1
|
21
21
|
#
|
22
22
|
class ContentTypes < Arachni::Plugin::Base
|
23
23
|
|
@@ -38,7 +38,7 @@ class ContentTypes < Arachni::Plugin::Base
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def run( )
|
41
|
-
@framework.http.
|
41
|
+
@framework.http.add_on_complete {
|
42
42
|
|res|
|
43
43
|
|
44
44
|
next if @logged.include?( res.request.method.to_s.upcase + res.effective_url )
|
data/plugins/cookie_collector.rb
CHANGED
@@ -12,11 +12,12 @@ module Arachni
|
|
12
12
|
module Plugins
|
13
13
|
|
14
14
|
#
|
15
|
+
# Simple cookie collector
|
15
16
|
#
|
16
17
|
# @author: Tasos "Zapotek" Laskos
|
17
18
|
# <tasos.laskos@gmail.com>
|
18
19
|
# <zapotek@segfault.gr>
|
19
|
-
# @version: 0.1
|
20
|
+
# @version: 0.1.2
|
20
21
|
#
|
21
22
|
class CookieCollector < Arachni::Plugin::Base
|
22
23
|
|
@@ -33,18 +34,21 @@ class CookieCollector < Arachni::Plugin::Base
|
|
33
34
|
end
|
34
35
|
|
35
36
|
def run( )
|
36
|
-
@framework.http.
|
37
|
-
|res|
|
38
|
-
update(
|
37
|
+
@framework.http.add_on_new_cookies {
|
38
|
+
|cookies, res|
|
39
|
+
update( cookies, res )
|
39
40
|
}
|
40
41
|
end
|
41
42
|
|
42
43
|
def update( cookies, res )
|
43
44
|
return if cookies.empty? || !update?( cookies )
|
44
45
|
|
46
|
+
res_hash = res.to_hash
|
47
|
+
res_hash.delete( 'body' )
|
48
|
+
|
45
49
|
@cookies << {
|
46
50
|
:time => Time.now,
|
47
|
-
:res =>
|
51
|
+
:res => res_hash,
|
48
52
|
:cookies => cookies
|
49
53
|
}
|
50
54
|
end
|
@@ -60,17 +64,6 @@ class CookieCollector < Arachni::Plugin::Base
|
|
60
64
|
return false
|
61
65
|
end
|
62
66
|
|
63
|
-
def extract_cookies( res )
|
64
|
-
cookies = {}
|
65
|
-
|
66
|
-
Arachni::Parser.new( @framework.opts, res ).run.cookies.each {
|
67
|
-
|cookie|
|
68
|
-
cookies.merge!( cookie.simple )
|
69
|
-
}
|
70
|
-
|
71
|
-
return cookies
|
72
|
-
end
|
73
|
-
|
74
67
|
def clean_up
|
75
68
|
while( @framework.running? )
|
76
69
|
::IO.select( nil, nil, nil, 1 )
|
data/plugins/profiler.rb
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
=begin
|
2
|
+
Arachni
|
3
|
+
Copyright (c) 2010-2011 Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
4
|
+
|
5
|
+
This is free software; you can copy and distribute and modify
|
6
|
+
this program under the term of the GPL v2.0 License
|
7
|
+
(See LICENSE file for details)
|
8
|
+
|
9
|
+
=end
|
10
|
+
|
11
|
+
module Arachni
|
12
|
+
module Plugins
|
13
|
+
|
14
|
+
#
|
15
|
+
# Profiler plugin.
|
16
|
+
#
|
17
|
+
# Examines the behavior of the web application gathering general statistics
|
18
|
+
# and performs taint analysis to determine which inputs affect the output.
|
19
|
+
#
|
20
|
+
#
|
21
|
+
# @author: Tasos "Zapotek" Laskos
|
22
|
+
# <tasos.laskos@gmail.com>
|
23
|
+
# <zapotek@segfault.gr>
|
24
|
+
# @version: 0.1
|
25
|
+
#
|
26
|
+
class Profiler < Arachni::Plugin::Base
|
27
|
+
|
28
|
+
#
|
29
|
+
# Assumes the identity of an Auditor.
|
30
|
+
#
|
31
|
+
# It will audit all inputs and log when inserted values appear in a page's body.<br/>
|
32
|
+
# It's does not perform any vulnerability assesment nor does it send attack payloads,
|
33
|
+
# just simple benign strings.
|
34
|
+
#
|
35
|
+
# Since an Auditor has formal specifications a plug-in can't directly become one
|
36
|
+
# due to it's abstract nature.
|
37
|
+
#
|
38
|
+
# Thus, we use this helper class to perform auditing duties.
|
39
|
+
#
|
40
|
+
# @author: Tasos "Zapotek" Laskos
|
41
|
+
# <tasos.laskos@gmail.com>
|
42
|
+
# <zapotek@segfault.gr>
|
43
|
+
# @version: 0.1
|
44
|
+
#
|
45
|
+
class Auditor < Arachni::Module::Base
|
46
|
+
|
47
|
+
attr_reader :http
|
48
|
+
attr_reader :page
|
49
|
+
|
50
|
+
def initialize( page )
|
51
|
+
super( page )
|
52
|
+
|
53
|
+
@id = Digest::SHA2.hexdigest( rand( 1000 ).to_s )
|
54
|
+
@opts = {
|
55
|
+
:format => [ Format::STRAIGHT ],
|
56
|
+
:elements => [
|
57
|
+
Issue::Element::FORM,
|
58
|
+
Issue::Element::LINK,
|
59
|
+
Issue::Element::COOKIE,
|
60
|
+
Issue::Element::HEADER
|
61
|
+
],
|
62
|
+
:remove_id => true
|
63
|
+
}
|
64
|
+
|
65
|
+
@@logged ||= Set.new
|
66
|
+
end
|
67
|
+
|
68
|
+
def run( &logger )
|
69
|
+
audit( @id, @opts ) {
|
70
|
+
|res, opts, elem|
|
71
|
+
|
72
|
+
landed_elems = []
|
73
|
+
if res.body.substring?( @id )
|
74
|
+
landed_elems |= find_landing_elements( res )
|
75
|
+
end
|
76
|
+
|
77
|
+
if res.headers.to_s.substring?( @id )
|
78
|
+
landed_elems |= find_landing_header_fields( res )
|
79
|
+
end
|
80
|
+
|
81
|
+
if !landed_elems.empty?
|
82
|
+
|
83
|
+
@@logged << "#{elem.action}::#{elem.altered}::#{elem.type}"
|
84
|
+
|
85
|
+
logger.call( @id, res, elem, landed_elems )
|
86
|
+
end
|
87
|
+
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
def find_landing_header_fields( res )
|
92
|
+
elems = []
|
93
|
+
|
94
|
+
parser = Arachni::Parser.new( Arachni::Options.instance, res )
|
95
|
+
parser.cookies.each {
|
96
|
+
|cookie|
|
97
|
+
elems << cookie if cookie.auditable.to_s.substring?( @id )
|
98
|
+
}
|
99
|
+
|
100
|
+
res.headers_hash.each_pair {
|
101
|
+
|k, v|
|
102
|
+
elems << Arachni::Parser::Element::Header.new( res.effective_url, { k => v } ) if v.substring?( @id )
|
103
|
+
}
|
104
|
+
|
105
|
+
return elems
|
106
|
+
end
|
107
|
+
|
108
|
+
def find_landing_elements( res )
|
109
|
+
elems = []
|
110
|
+
|
111
|
+
elems << Struct::Body.new( 'body', nil, { 'attrs' => {} } )
|
112
|
+
|
113
|
+
parser = Arachni::Parser.new( Arachni::Options.instance, res )
|
114
|
+
parser.forms.each {
|
115
|
+
|form|
|
116
|
+
elems << form if form.auditable.to_s.substring?( @id )
|
117
|
+
}
|
118
|
+
|
119
|
+
parser.links.each {
|
120
|
+
|link|
|
121
|
+
elems << link if link.auditable.to_s.substring?( @id )
|
122
|
+
}
|
123
|
+
|
124
|
+
return elems
|
125
|
+
end
|
126
|
+
|
127
|
+
def skip?( elem )
|
128
|
+
@@logged.include?( "#{elem.action}::#{elem.altered}::#{elem.type}" )
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.info
|
132
|
+
{ :name => 'Profiler' }
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
#
|
138
|
+
# @param [Arachni::Framework] framework
|
139
|
+
# @param [Hash] options options passed to the plugin
|
140
|
+
#
|
141
|
+
def initialize( framework, options )
|
142
|
+
@framework = framework
|
143
|
+
@http = framework.http
|
144
|
+
@options = options
|
145
|
+
end
|
146
|
+
|
147
|
+
def prepare
|
148
|
+
|
149
|
+
Struct.new( 'Body', :type, :method, :raw, :auditable )
|
150
|
+
|
151
|
+
@inputs = []
|
152
|
+
@times = []
|
153
|
+
end
|
154
|
+
|
155
|
+
def run
|
156
|
+
|
157
|
+
@http.add_on_complete {
|
158
|
+
|res|
|
159
|
+
@times << {
|
160
|
+
'url' => res.effective_url,
|
161
|
+
'method' => res.request.method.to_s.upcase,
|
162
|
+
'params' => res.request.params,
|
163
|
+
'time' => res.start_transfer_time
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
@framework.add_on_run_mods {
|
168
|
+
|page|
|
169
|
+
|
170
|
+
Auditor.new( page ).run {
|
171
|
+
|taint, res, elem, found_in|
|
172
|
+
log( taint, res, elem, found_in )
|
173
|
+
}
|
174
|
+
|
175
|
+
}
|
176
|
+
end
|
177
|
+
|
178
|
+
def clean_up
|
179
|
+
::IO.select( nil, nil, nil, 1 ) while( @framework.running? )
|
180
|
+
|
181
|
+
register_results( { 'inputs' => @inputs, 'times' => @times } )
|
182
|
+
end
|
183
|
+
|
184
|
+
def log( taint, res, elem, landed_elems )
|
185
|
+
|
186
|
+
res_hash = res.to_hash
|
187
|
+
res_hash['headers'] = res_hash['headers_hash']
|
188
|
+
|
189
|
+
result = {
|
190
|
+
'taint' => taint,
|
191
|
+
'element' => {
|
192
|
+
'type' => elem.type,
|
193
|
+
'auditable' => elem.auditable,
|
194
|
+
'name' => elem.raw['attrs'] ? elem.raw['attrs']['name'] : nil,
|
195
|
+
'altered' => elem.altered,
|
196
|
+
'owner' => elem.url,
|
197
|
+
'action' => elem.action,
|
198
|
+
'method' => elem.method ? elem.method.upcase : nil,
|
199
|
+
},
|
200
|
+
'response' => res_hash,
|
201
|
+
'request' => {
|
202
|
+
'url' => res.request.url,
|
203
|
+
'method' => res.request.method.to_s.upcase,
|
204
|
+
'params' => res.request.params,
|
205
|
+
'headers' => res.request.headers,
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
result['landed'] = landed_elems.map {
|
210
|
+
|elem|
|
211
|
+
{
|
212
|
+
'type' => elem.type,
|
213
|
+
'method' => elem.method ? elem.method.upcase : nil,
|
214
|
+
'name' => elem.raw['attrs'] ? elem.raw['attrs']['name'] : nil,
|
215
|
+
'auditable' => elem.auditable
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
@inputs << result
|
220
|
+
end
|
221
|
+
|
222
|
+
def self.info
|
223
|
+
{
|
224
|
+
:name => 'Profiler',
|
225
|
+
:description => %q{Examines the behavior of the web application gathering general statistics
|
226
|
+
and performs taint analysis to determine which inputs affect the output.
|
227
|
+
|
228
|
+
It's does not perform any vulnerability assesment nor does it send attack payloads.},
|
229
|
+
:author => 'Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>',
|
230
|
+
:version => '0.1'
|
231
|
+
}
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
237
|
+
end
|