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.
Files changed (44) hide show
  1. data/CHANGELOG.md +30 -0
  2. data/CONTRIBUTORS.md +1 -0
  3. data/README.md +28 -8
  4. data/Rakefile +1 -0
  5. data/bin/arachni_web_autostart +46 -0
  6. data/lib/anemone/page.rb +1 -0
  7. data/lib/arachni.rb +1 -1
  8. data/lib/framework.rb +8 -3
  9. data/lib/http.rb +9 -39
  10. data/lib/mixins/observable.rb +87 -0
  11. data/lib/module/auditor.rb +14 -0
  12. data/lib/module/base.rb +0 -14
  13. data/lib/nokogiri/xml/node.rb +42 -0
  14. data/lib/ui/cli/cli.rb +1 -1
  15. data/lib/ui/web/log.rb +21 -14
  16. data/lib/ui/web/report_manager.rb +100 -15
  17. data/lib/ui/web/server.rb +24 -33
  18. data/lib/ui/web/server/public/reports/demo.testfire.net:Sun Mar 20 02:48:10 2011.afr +104829 -0
  19. data/lib/ui/web/server/views/layout.erb +1 -1
  20. data/lib/ui/web/server/views/options.erb +10 -2
  21. data/lib/ui/web/server/views/plugins.erb +1 -1
  22. data/lib/ui/web/server/views/reports.erb +8 -4
  23. data/lib/ui/xmlrpc/xmlrpc.rb +1 -1
  24. data/metamodules/autothrottle.rb +2 -2
  25. data/metamodules/timeout_notice.rb +1 -1
  26. data/modules/audit/sqli_blind_rdiff.rb +1 -1
  27. data/modules/recon/common_files/filenames.txt +2 -0
  28. data/modules/recon/directory_listing.rb +1 -0
  29. data/modules/recon/interesting_responses.rb +3 -3
  30. data/path_extractors/generic.rb +5 -1
  31. data/plugins/autologin.rb +15 -4
  32. data/plugins/content_types.rb +2 -2
  33. data/plugins/cookie_collector.rb +9 -16
  34. data/plugins/profiler.rb +237 -0
  35. data/reports/html.rb +21 -6
  36. data/reports/html/default.erb +4 -2
  37. data/reports/plugin_formatters/html/autologin.rb +63 -0
  38. data/reports/plugin_formatters/html/profiler.rb +71 -0
  39. data/reports/plugin_formatters/html/profiler/template.erb +177 -0
  40. data/reports/plugin_formatters/stdout/autologin.rb +55 -0
  41. data/reports/plugin_formatters/stdout/profiler.rb +90 -0
  42. data/reports/plugin_formatters/xml/autologin.rb +68 -0
  43. data/reports/plugin_formatters/xml/profiler.rb +120 -0
  44. metadata +23 -68
@@ -12,7 +12,7 @@
12
12
 
13
13
  <script type="text/javascript">
14
14
  function checkAll( type ) {
15
- $( "." + type ).attr( "checked", true )
15
+ $( "." + type + ':not(:disabled)' ).attr( "checked", true )
16
16
  }
17
17
 
18
18
  function uncheckAll( type ) {
@@ -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" <% if opt['default']%> checked="checked" <%end%>
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['host']%></td>
34
- <td><%=report['date']%></td>
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/<%=CGI.escape(report['name'])%>.<%=avail['rep_name']%>"><%=escape(avail['name'])%></a></li>
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/<%=CGI.escape(report['name'])%>/delete" method="post">
49
+ <form action="/report/<%=report.id%>/delete" method="post">
46
50
  <%= csrf_tag %>
47
51
  <input type="submit" value="Delete" />
48
52
  </form>
@@ -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
@@ -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.on_complete {
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
@@ -41,7 +41,7 @@ class TimeoutNotice < Base
41
41
 
42
42
  def prepare
43
43
  # run for each response as it arrives
44
- @http.on_complete {
44
+ @http.add_on_complete {
45
45
  |res|
46
46
 
47
47
  # we don't care about non OK responses
@@ -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.2.2',
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'
@@ -15,3 +15,5 @@ install.php
15
15
  wp-admin/install.php
16
16
  wp-admin/setup-config.php
17
17
  config.php
18
+ php.ini
19
+ error_log
@@ -46,6 +46,7 @@ class DirectoryListing < Arachni::Module::Base
46
46
 
47
47
  def run( )
48
48
 
49
+ return if @page.code != 200
49
50
  path = get_path( @page.url )
50
51
 
51
52
  return if !URI( path ).path || URI( path ).path.gsub( '/', '' ).empty?
@@ -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.on_complete {
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.},
@@ -32,7 +32,11 @@ class Generic < Paths
32
32
  # @return [Array<String>] paths
33
33
  #
34
34
  def run( doc )
35
- URI.extract( doc.to_s )
35
+ begin
36
+ URI.extract( doc.to_s )
37
+ rescue
38
+ return []
39
+ end
36
40
  end
37
41
 
38
42
  end
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
- print_error( 'Could not find a form suiting the provided params at: ' +
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
- print_error( 'Form submitted but no response was returned.' )
87
+ register_results( { :code => -1, :msg => MSG_NO_RESPONSE } )
88
+ print_error( MSG_NO_RESPONSE )
84
89
  return
85
90
  else
86
- print_ok( 'Form submitted successfully.' )
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
@@ -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.on_complete {
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 )
@@ -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.on_complete {
37
- |res|
38
- update( extract_cookies( res ), res )
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 => res.to_hash,
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 )
@@ -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