brakeman 0.5.2 → 0.6.0

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