brakeman 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -4,6 +4,8 @@ Brakeman is a static analysis tool which checks Ruby on Rails applications for s
4
4
 
5
5
  It targets Rails versions > 2.0 with experimental support for Rails 3.x
6
6
 
7
+ There is also a [plugin available](https://github.com/presidentbeef/brakeman-jenkins-plugin) for Jenkins/Hudson.
8
+
7
9
  # Installation
8
10
 
9
11
  Using RubyGems:
@@ -19,7 +21,7 @@ From source:
19
21
 
20
22
  brakeman [app_path]
21
23
 
22
- It is simplest to run brakeman from the root directory of the Rails application. A path may also be supplied.
24
+ It is simplest to run Brakeman from the root directory of the Rails application. A path may also be supplied.
23
25
 
24
26
  # Options
25
27
 
@@ -172,7 +172,9 @@ class BaseCheck < SexpProcessor
172
172
  #(either :params or :cookies) and the second element is the matching
173
173
  #expression
174
174
  def has_immediate_user_input? exp
175
- if params? exp
175
+ if exp.nil?
176
+ return false
177
+ elsif params? exp
176
178
  return :params, exp
177
179
  elsif cookies? exp
178
180
  return :cookies, exp
@@ -180,7 +182,7 @@ class BaseCheck < SexpProcessor
180
182
  if sexp? exp[1]
181
183
  if ALL_PARAMETERS.include? exp[1] or params? exp[1]
182
184
  return :params, exp
183
- elsif exp[1] == COOKIES
185
+ elsif exp[1] == COOKIES or cookies? exp[1]
184
186
  return :cookies, exp
185
187
  else
186
188
  false
@@ -22,8 +22,12 @@ class CheckCrossSiteScripting < BaseCheck
22
22
  :fields_for, :label, :text_area, :text_field, :hidden_field, :check_box,
23
23
  :field_field])
24
24
 
25
+ #Model methods which are known to be harmless
25
26
  IGNORE_MODEL_METHODS = Set.new([:average, :count, :maximum, :minimum, :sum])
26
27
 
28
+ #Methods known to not escape their input
29
+ KNOWN_DANGEROUS = Set.new([:auto_link, :truncate, :concat])
30
+
27
31
  MODEL_METHODS = Set.new([:all, :find, :first, :last, :new])
28
32
 
29
33
  IGNORE_LIKE = /^link_to_|(_path|_tag|_url)$/
@@ -50,47 +54,7 @@ class CheckCrossSiteScripting < BaseCheck
50
54
  @current_template = template
51
55
 
52
56
  template[:outputs].each do |out|
53
- type, match = (out[0] == :output and has_immediate_user_input?(out[1]))
54
- if type and not duplicate? out
55
- add_result out
56
- case type
57
- when :params
58
- message = "Unescaped parameter value"
59
- when :cookies
60
- message = "Unescaped cookie value"
61
- else
62
- message = "Unescaped user input value"
63
- end
64
-
65
- warn :template => @current_template,
66
- :warning_type => "Cross Site Scripting",
67
- :message => message,
68
- :line => match.line,
69
- :code => match,
70
- :confidence => CONFIDENCE[:high]
71
-
72
- elsif not OPTIONS[:ignore_model_output] and match = has_immediate_model?(out[1])
73
- method = match[2]
74
-
75
- unless duplicate? out or IGNORE_MODEL_METHODS.include? method
76
- add_result out
77
-
78
- if MODEL_METHODS.include? method or method.to_s =~ /^find_by/
79
- confidence = CONFIDENCE[:high]
80
- else
81
- confidence = CONFIDENCE[:med]
82
- end
83
-
84
- code = find_chain out, match
85
- warn :template => @current_template,
86
- :warning_type => "Cross Site Scripting",
87
- :message => "Unescaped model attribute",
88
- :line => code.line,
89
- :code => code,
90
- :confidence => confidence
91
- end
92
-
93
- else
57
+ unless check_for_immediate_xss out
94
58
  @matched = false
95
59
  @mark = false
96
60
  process out
@@ -99,6 +63,59 @@ class CheckCrossSiteScripting < BaseCheck
99
63
  end
100
64
  end
101
65
 
66
+ def check_for_immediate_xss exp
67
+ if exp[0] == :output
68
+ out = exp[1]
69
+ elsif exp[0] == :escaped_output and raw_call? exp
70
+ out = exp[1][3][1]
71
+ end
72
+
73
+ type, match = has_immediate_user_input? out
74
+
75
+ if type and not duplicate? exp
76
+ add_result exp
77
+ case type
78
+ when :params
79
+ message = "Unescaped parameter value"
80
+ when :cookies
81
+ message = "Unescaped cookie value"
82
+ else
83
+ message = "Unescaped user input value"
84
+ end
85
+
86
+ warn :template => @current_template,
87
+ :warning_type => "Cross Site Scripting",
88
+ :message => message,
89
+ :line => match.line,
90
+ :code => match,
91
+ :confidence => CONFIDENCE[:high]
92
+
93
+ elsif not OPTIONS[:ignore_model_output] and match = has_immediate_model?(out)
94
+ method = match[2]
95
+
96
+ unless duplicate? out or IGNORE_MODEL_METHODS.include? method
97
+ add_result out
98
+
99
+ if MODEL_METHODS.include? method or method.to_s =~ /^find_by/
100
+ confidence = CONFIDENCE[:high]
101
+ else
102
+ confidence = CONFIDENCE[:med]
103
+ end
104
+
105
+ code = find_chain out, match
106
+ warn :template => @current_template,
107
+ :warning_type => "Cross Site Scripting",
108
+ :message => "Unescaped model attribute",
109
+ :line => code.line,
110
+ :code => code,
111
+ :confidence => confidence
112
+ end
113
+
114
+ else
115
+ false
116
+ end
117
+ end
118
+
102
119
  #Process an output Sexp
103
120
  def process_output exp
104
121
  process exp[1].dup
@@ -107,11 +124,12 @@ class CheckCrossSiteScripting < BaseCheck
107
124
  #Look for calls to raw()
108
125
  #Otherwise, ignore
109
126
  def process_escaped_output exp
110
- if exp[1].node_type == :call and exp[1][2] == :raw
111
- process_output exp
112
- else
113
- exp
127
+ unless check_for_immediate_xss exp
128
+ if raw_call? exp
129
+ process exp[1][3][1]
130
+ end
114
131
  end
132
+ exp
115
133
  end
116
134
 
117
135
  #Check a call for user input
@@ -137,12 +155,18 @@ class CheckCrossSiteScripting < BaseCheck
137
155
  if message and not duplicate? exp
138
156
  add_result exp
139
157
 
158
+ if exp[1].nil? and KNOWN_DANGEROUS.include? exp[2]
159
+ confidence = CONFIDENCE[:high]
160
+ else
161
+ confidence = CONFIDENCE[:low]
162
+ end
163
+
140
164
  warn :template => @current_template,
141
165
  :warning_type => "Cross Site Scripting",
142
166
  :message => message,
143
167
  :line => exp.line,
144
168
  :code => exp,
145
- :confidence => CONFIDENCE[:low]
169
+ :confidence => confidence
146
170
  end
147
171
 
148
172
  @mark = @matched = false
@@ -221,6 +245,10 @@ class CheckCrossSiteScripting < BaseCheck
221
245
  end
222
246
  exp
223
247
  end
248
+
249
+ def raw_call? exp
250
+ exp[1].node_type == :call and exp[1][2] == :raw
251
+ end
224
252
  end
225
253
 
226
254
  #This _only_ checks calls to link_to
@@ -316,7 +344,6 @@ class CheckLinkTo < CheckCrossSiteScripting
316
344
  exp
317
345
  end
318
346
 
319
-
320
347
  def actually_process_call exp
321
348
  return if @matched
322
349
 
@@ -6,7 +6,9 @@ class CheckFileAccess < BaseCheck
6
6
  Checks.add self
7
7
 
8
8
  def run_check
9
- methods = tracker.find_call [[:Dir, :File, :IO, :Kernel, :"Net::FTP", :"Net::HTTP", :PStore, :Pathname, :Shell, :YAML], []], [:[], :chdir, :chroot, :delete, :entries, :foreach, :glob, :install, :lchmod, :lchown, :link, :load, :load_file, :makedirs, :move, :new, :open, :read, :read_lines, :rename, :rmdir, :safe_unlink, :symlink, :syscopy, :sysopen, :truncate, :unlink]
9
+ methods = tracker.find_call [:Dir, :File, :IO, :Kernel, :"Net::FTP", :"Net::HTTP", :PStore, :Pathname, :Shell, :YAML], [:[], :chdir, :chroot, :delete, :entries, :foreach, :glob, :install, :lchmod, :lchown, :link, :load, :load_file, :makedirs, :move, :new, :open, :read, :read_lines, :rename, :rmdir, :safe_unlink, :symlink, :syscopy, :sysopen, :truncate, :unlink]
10
+
11
+ methods.concat tracker.find_call [], [:load]
10
12
 
11
13
  methods.concat tracker.find_call(:FileUtils, nil)
12
14
 
@@ -28,14 +28,14 @@ class CheckForgerySetting < BaseCheck
28
28
 
29
29
  warn :controller => :ApplicationController,
30
30
  :warning_type => "Cross-Site Request Forgery",
31
- :message => "CSRF protection is flawed in #{tracker.config[:rails_version]} (CVE-2011-0447). Upgrade to 2.3.11 or apply patches",
31
+ :message => "CSRF protection is flawed in unpatched versions of Rails #{tracker.config[:rails_version]} (CVE-2011-0447). Upgrade to 2.3.11 or apply patches as needed",
32
32
  :confidence => CONFIDENCE[:high]
33
33
 
34
34
  elsif version_between? "3.0.0", "3.0.3"
35
35
 
36
36
  warn :controller => :ApplicationController,
37
37
  :warning_type => "Cross-Site Request Forgery",
38
- :message => "CSRF protection is flawed in #{tracker.config[:rails_version]} (CVE-2011-0447). Upgrade to 3.0.4",
38
+ :message => "CSRF protection is flawed in unpatched versions of Rails #{tracker.config[:rails_version]} (CVE-2011-0447). Upgrade to 3.0.4 or apply patches as needed",
39
39
  :confidence => CONFIDENCE[:high]
40
40
  end
41
41
  end
@@ -61,7 +61,9 @@ class ErubisTemplateProcessor < TemplateProcessor
61
61
  block
62
62
  end
63
63
 
64
- #Look for assignments to output buffer
64
+ #Look for assignments to output buffer that look like this:
65
+ # @output_buffer.append = some_output
66
+ # @output_buffer.safe_append = some_output
65
67
  def process_attrasgn exp
66
68
  if exp[1].node_type == :ivar and exp[1][1] == :@output_buffer
67
69
  if exp[2] == :append= or exp[2] == :safe_append=
@@ -76,7 +78,7 @@ class ErubisTemplateProcessor < TemplateProcessor
76
78
  s
77
79
  end
78
80
  else
79
- ignore
81
+ super
80
82
  end
81
83
  else
82
84
  super
@@ -153,7 +153,7 @@ class OutputProcessor < Ruby2Ruby
153
153
  out
154
154
  end
155
155
 
156
- def process_escaped_output exp
156
+ def process_escaped_output exp
157
157
  out = if exp[0].node_type == :str
158
158
  ""
159
159
  else
@@ -52,4 +52,8 @@ class TemplateProcessor < BaseProcessor
52
52
  @current_template[:outputs] << exp
53
53
  exp
54
54
  end
55
+
56
+ def process_escaped_output exp
57
+ process_output exp
58
+ end
55
59
  end
@@ -274,7 +274,9 @@ class Report
274
274
  end
275
275
 
276
276
  res = generate_errors
277
- out << "<h2>Errors</h2>" << res.to_html if res
277
+ if res
278
+ out << "<div onClick=\"toggle('errors_table');\"> <h2>Exceptions raised during the analysis (click to see them)</h2 ></div> <div id='errors_table' style='display:none'>" << res.to_html<< '</div>'
279
+ end
278
280
 
279
281
  res = generate_warnings
280
282
  out << "<h2>Security Warnings</h2>" << res.to_html if res
@@ -28,7 +28,7 @@ class Scanner
28
28
  #Pass in path to the root of the Rails application
29
29
  def initialize path
30
30
  @path = path
31
- @app_path = path + "/app/"
31
+ @app_path = File.join(path, "app")
32
32
  @processor = Processor.new
33
33
  end
34
34
 
@@ -82,7 +82,7 @@ class Scanner
82
82
  begin
83
83
  @processor.process_initializer(f, RubyParser.new.parse(File.read(f)))
84
84
  rescue Racc::ParseError => e
85
- tracker.error e, "could not parse #{f}"
85
+ tracker.error e, "could not parse #{f}. There is probably a typo in the file. Test it with 'ruby_parse #{f}'"
86
86
  rescue Exception => e
87
87
  tracker.error e.exception(e.message + "\nWhile processing #{f}"), e.backtrace
88
88
  end
@@ -97,7 +97,7 @@ class Scanner
97
97
  begin
98
98
  @processor.process_lib RubyParser.new.parse(File.read(f)), f
99
99
  rescue Racc::ParseError => e
100
- tracker.error e, "could not parse #{f}"
100
+ tracker.error e, "could not parse #{f}. There is probably a typo in the file. Test it with 'ruby_parse #{f}'"
101
101
  rescue Exception => e
102
102
  tracker.error e.exception(e.message + "\nWhile processing #{f}"), e.backtrace
103
103
  end
@@ -110,6 +110,8 @@ class Scanner
110
110
  def process_routes
111
111
  if File.exists? "#@path/config/routes.rb"
112
112
  @processor.process_routes RubyParser.new.parse(File.read("#@path/config/routes.rb"))
113
+ else
114
+ warn "[Notice] No route information found"
113
115
  end
114
116
  end
115
117
 
@@ -121,7 +123,7 @@ class Scanner
121
123
  begin
122
124
  @processor.process_controller(RubyParser.new.parse(File.read(f)), f)
123
125
  rescue Racc::ParseError => e
124
- tracker.error e, "could not parse #{f}"
126
+ tracker.error e, "could not parse #{f}. There is probably a typo in the file. Test it with 'ruby_parse #{f}'"
125
127
  rescue Exception => e
126
128
  tracker.error e.exception(e.message + "\nWhile processing #{f}"), e.backtrace
127
129
  end
@@ -224,9 +226,11 @@ class RailsXSSErubis < ::Erubis::Eruby
224
226
  end
225
227
 
226
228
  def add_text(src, text)
227
- if text.include? "\n"
229
+ if text == "\n"
230
+ src << "\n"
231
+ elsif text.include? "\n"
228
232
  lines = text.split("\n")
229
- if text.match /\n\z/
233
+ if text.match(/\n\z/)
230
234
  lines.each do |line|
231
235
  src << "@output_buffer << ('" << escape_text(line) << "'.html_safe!);\n"
232
236
  end
@@ -38,6 +38,7 @@ class Tracker
38
38
  @errors = []
39
39
  @libs = {}
40
40
  @checks = nil
41
+ @processed = nil
41
42
  @template_cache = Set.new
42
43
  end
43
44
 
@@ -1 +1 @@
1
- Version = "0.5.2"
1
+ Version = "0.6.0"
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 5
8
- - 2
9
- version: 0.5.2
7
+ - 6
8
+ - 0
9
+ version: 0.6.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Justin Collins
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-06-29 00:00:00 -07:00
17
+ date: 2011-07-20 00:00:00 -07:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency