brakeman 1.8.2 → 1.8.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES CHANGED
@@ -1,3 +1,19 @@
1
+ # 1.8.3
2
+
3
+ * Use `multi_json` gem for better harmony
4
+ * Performance improvement for call indexing
5
+ * Fix issue with processing HAML files
6
+ * Handle pre-release versions when processing `Gemfile.lock`
7
+ * Only check first argument of `redirect_to`
8
+ * Fix false positives from `Model.arel_table` accesses
9
+ * Fix false positives on redirects to models decorated with Draper gem
10
+ * Fix false positive on redirect to model association
11
+ * Fix false positive on `YAML.load`
12
+ * Fix false positive XSS on any `to_i` output
13
+ * Fix error on Rails 2 name routes with no args
14
+ * Fix error in rescan of mixins with symbols in method name
15
+ * Do not rescan non-Ruby files in config/
16
+
1
17
  # 1.8.2
2
18
 
3
19
  * Fixed rescanning problems caused by 1.8.0 changes
data/bin/brakeman CHANGED
@@ -54,7 +54,8 @@ end
54
54
 
55
55
  if options[:previous_results_json]
56
56
  vulns = Brakeman.compare options.merge(:quiet => options[:quiet])
57
- puts JSON.pretty_generate(vulns)
57
+ puts MultiJson.dump(vulns, :pretty => true)
58
+
58
59
  if options[:exit_on_warn] and (vulns[:new].count + vulns[:fixed].count > 0)
59
60
  exit Brakeman::Warnings_Found_Exit_Code
60
61
  end
data/lib/brakeman.rb CHANGED
@@ -316,19 +316,20 @@ module Brakeman
316
316
 
317
317
  # Compare JSON ouptut from a previous scan and return the diff of the two scans
318
318
  def self.compare options
319
- require 'json'
319
+ require 'multi_json'
320
320
  require 'brakeman/differ'
321
321
  raise ArgumentError.new("Comparison file doesn't exist") unless File.exists? options[:previous_results_json]
322
322
 
323
323
  begin
324
- previous_results = JSON.parse(File.read(options[:previous_results_json]), :symbolize_names =>true)[:warnings]
325
- rescue JSON::ParserError
324
+ previous_results = MultiJson.load(File.read(options[:previous_results_json]), :symbolize_keys => true)[:warnings]
325
+ rescue MultiJson::DecodeError
326
326
  self.notify "Error parsing comparison file: #{options[:previous_results_json]}"
327
327
  exit!
328
328
  end
329
329
 
330
330
  tracker = run(options)
331
- new_results = JSON.parse(tracker.report.to_json, :symbolize_names =>true)[:warnings]
331
+
332
+ new_results = MultiJson.load(tracker.report.to_json, :symbolize_keys => true)[:warnings]
332
333
 
333
334
  Brakeman::Differ.new(new_results, previous_results).diff
334
335
  end
@@ -106,7 +106,7 @@ class Brakeman::CallIndex
106
106
  def index_calls calls
107
107
  calls.each do |call|
108
108
  @methods << call[:method].to_s
109
- @targets << call[:target].to_s
109
+ @targets << call[:target].to_s if call[:target].is_a? Symbol
110
110
  @calls_by_method[call[:method]] << call
111
111
  @calls_by_target[call[:target]] << call
112
112
  end
@@ -138,7 +138,7 @@ class Brakeman::CallIndex
138
138
  elsif targets.length > 1
139
139
  calls_by_targets targets
140
140
  else
141
- calls_by_target[targets.first]
141
+ @calls_by_target[targets.first]
142
142
  end
143
143
  else
144
144
  @calls_by_target[target]
@@ -23,6 +23,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
23
23
  @current_set = nil
24
24
  @current_template = @current_module = @current_class = @current_method = nil
25
25
  @mass_assign_disabled = nil
26
+ @safe_input_attributes = Set[:to_i, :to_f, :arel_table]
26
27
  end
27
28
 
28
29
  #Add result to result list, which is used to check for duplicates
@@ -63,14 +64,16 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
63
64
 
64
65
  target = exp.target
65
66
 
66
- if params? target
67
- @has_user_input = Match.new(:params, exp)
68
- elsif cookies? target
69
- @has_user_input = Match.new(:cookies, exp)
70
- elsif request_env? target
71
- @has_user_input = Match.new(:request, exp)
72
- elsif sexp? target and model_name? target[1]
73
- @has_user_input = Match.new(:model, exp)
67
+ unless @safe_input_attributes.include? exp.method
68
+ if params? target
69
+ @has_user_input = Match.new(:params, exp)
70
+ elsif cookies? target
71
+ @has_user_input = Match.new(:cookies, exp)
72
+ elsif request_env? target
73
+ @has_user_input = Match.new(:request, exp)
74
+ elsif sexp? target and model_name? target[1]
75
+ @has_user_input = Match.new(:model, exp)
76
+ end
74
77
  end
75
78
 
76
79
  exp
@@ -255,19 +258,15 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
255
258
  def has_immediate_user_input? exp
256
259
  if exp.nil?
257
260
  false
258
- elsif params? exp
259
- return Match.new(:params, exp)
260
- elsif cookies? exp
261
- return Match.new(:cookies, exp)
262
- elsif call? exp
263
- if params? exp.target
261
+ elsif call? exp and not @safe_input_attributes.include? exp.method
262
+ if params? exp
264
263
  return Match.new(:params, exp)
265
- elsif cookies? exp.target
264
+ elsif cookies? exp
266
265
  return Match.new(:cookies, exp)
267
- elsif request_env? exp.target
266
+ elsif request_env? exp
268
267
  return Match.new(:request, exp)
269
268
  else
270
- false
269
+ has_immediate_user_input? exp.target
271
270
  end
272
271
  elsif sexp? exp
273
272
  case exp.node_type
@@ -318,9 +317,11 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
318
317
  target = exp.target
319
318
  method = exp.method
320
319
 
321
- if call? target and not method.to_s[-1,1] == "?"
320
+ if @safe_input_attributes.include? method
321
+ false
322
+ elsif call? target and not method.to_s[-1,1] == "?"
322
323
  has_immediate_model? target, out
323
- elsif model_name? target
324
+ elsif model_name? target
324
325
  exp
325
326
  else
326
327
  false
@@ -236,6 +236,7 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
236
236
  ((target == URI or target == CGI) and method == :escape) or
237
237
  (target == XML_HELPER and method == :escape_xml) or
238
238
  (target == FORM_BUILDER and @ignore_methods.include? method) or
239
+ (target and @safe_input_attributes.include? method) or
239
240
  (method.to_s[-1,1] == "?")
240
241
 
241
242
  #exp[0] = :ignore #should not be necessary
@@ -9,7 +9,9 @@ class Brakeman::CheckFileAccess < Brakeman::BaseCheck
9
9
 
10
10
  def run_check
11
11
  Brakeman.debug "Finding possible file access"
12
- methods = tracker.find_call :targets => [:Dir, :File, :IO, :Kernel, :"Net::FTP", :"Net::HTTP", :PStore, :Pathname, :Shell, :YAML], :methods => [:[], :chdir, :chroot, :delete, :entries, :foreach, :glob, :install, :lchmod, :lchown, :link, :load, :load_file, :makedirs, :move, :new, :open, :read, :readlines, :rename, :rmdir, :safe_unlink, :symlink, :syscopy, :sysopen, :truncate, :unlink]
12
+ methods = tracker.find_call :targets => [:Dir, :File, :IO, :Kernel, :"Net::FTP", :"Net::HTTP", :PStore, :Pathname, :Shell], :methods => [:[], :chdir, :chroot, :delete, :entries, :foreach, :glob, :install, :lchmod, :lchown, :link, :load, :load_file, :makedirs, :move, :new, :open, :read, :readlines, :rename, :rmdir, :safe_unlink, :symlink, :syscopy, :sysopen, :truncate, :unlink]
13
+
14
+ methods.concat tracker.find_call :target => :YAML, :methods => [:load_file, :parse_file]
13
15
 
14
16
  Brakeman.debug "Finding calls to load()"
15
17
  methods.concat tracker.find_call :target => false, :method => :load
@@ -53,44 +53,40 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
53
53
  #is being output directly. This is necessary because of tracker.options[:check_arguments]
54
54
  #which can be used to enable/disable reporting output of method calls which use
55
55
  #user input as arguments.
56
- def include_user_input? call
56
+ def include_user_input? call, immediate = :immediate
57
57
  Brakeman.debug "Checking if call includes user input"
58
58
 
59
- args = call.args
60
- first_arg = call.first_arg
59
+ arg = call.first_arg
61
60
 
62
61
  # if the first argument is an array, rails assumes you are building a
63
62
  # polymorphic route, which will never jump off-host
64
- return false if array? first_arg
65
-
66
- if tracker.options[:ignore_redirect_to_model] and call? first_arg and
67
- (@model_find_calls.include? first_arg.method or first_arg.method.to_s.match(/^find_by_/)) and
68
- model_name? first_arg.target
63
+ return false if array? arg
69
64
 
70
- return false
65
+ if tracker.options[:ignore_redirect_to_model]
66
+ if model_instance?(arg) or decorated_model?(arg)
67
+ return false
68
+ end
71
69
  end
72
70
 
73
- args.each do |arg|
74
- if res = has_immediate_model?(arg)
75
- return Match.new(:immediate, res)
76
- elsif call? arg
77
- if request_value? arg
78
- return Match.new(:immediate, arg)
79
- elsif request_value? arg[1]
80
- return Match.new(:immediate, arg[1])
81
- elsif arg[2] == :url_for and include_user_input? arg
82
- return Match.new(:immediate, arg)
83
- #Ignore helpers like some_model_url?
84
- elsif arg[2].to_s =~ /_(url|path)$/
85
- return false
86
- end
87
- elsif request_value? arg
88
- return Match.new(:immediate, arg)
71
+ if res = has_immediate_model?(arg)
72
+ return Match.new(immediate, res)
73
+ elsif call? arg
74
+ if request_value? arg
75
+ return Match.new(immediate, arg)
76
+ elsif request_value? arg[1]
77
+ return Match.new(immediate, arg[1])
78
+ elsif arg[2] == :url_for and include_user_input? arg
79
+ return Match.new(immediate, arg)
80
+ #Ignore helpers like some_model_url?
81
+ elsif arg[2].to_s =~ /_(url|path)\z/
82
+ return false
89
83
  end
84
+ elsif request_value? arg
85
+ return Match.new(immediate, arg)
90
86
  end
91
87
 
92
- if tracker.options[:check_arguments]
93
- super
88
+ if tracker.options[:check_arguments] and call? arg
89
+ include_user_input? arg, false #I'm doubting if this is really necessary...
94
90
  else
95
91
  false
96
92
  end
@@ -125,4 +121,56 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
125
121
 
126
122
  true
127
123
  end
124
+
125
+ #Returns true if exp is (probably) a model instance
126
+ def model_instance? exp
127
+ if node_type? exp, :or
128
+ model_instance? exp.lhs or model_instance? exp.rhs
129
+ elsif call? exp
130
+ if model_name? exp.target and
131
+ (@model_find_calls.include? exp.method or exp.method.to_s.match(/^find_by_/))
132
+ true
133
+ else
134
+ association?(exp.target, exp.method)
135
+ end
136
+ end
137
+ end
138
+
139
+ #Returns true if exp is (probably) a decorated model instance
140
+ #using the Draper gem
141
+ def decorated_model? exp
142
+ if node_type? exp, :or
143
+ decorated_model? exp.lhs or decorated_model? exp.rhs
144
+ else
145
+ tracker.config[:gems] and
146
+ tracker.config[:gems][:draper] and
147
+ call? exp and
148
+ node_type?(exp.target, :const) and
149
+ exp.target.value.to_s.match(/Decorator$/) and
150
+ exp.method == :decorate
151
+ end
152
+ end
153
+
154
+ #Check if method is actually an association in a Model
155
+ def association? model_name, meth
156
+ if call? model_name
157
+ return association? model_name.target, meth
158
+ elsif model_name? model_name
159
+ model = tracker.models[class_name(model_name)]
160
+ else
161
+ return false
162
+ end
163
+
164
+ return false unless model
165
+
166
+ model[:associations].each do |name, args|
167
+ args.each do |arg|
168
+ if symbol? arg and arg.value == meth
169
+ return true
170
+ end
171
+ end
172
+ end
173
+
174
+ false
175
+ end
128
176
  end
@@ -40,7 +40,7 @@ class Brakeman::GemProcessor < Brakeman::BaseProcessor
40
40
  end
41
41
 
42
42
  def get_rails_version gem_lock
43
- if gem_lock =~ /\srails \((\d+.\d+.\d+)\)$/
43
+ if gem_lock =~ /\srails \((\d+.\d+.\d+.*)\)$/
44
44
  @tracker.config[:rails_version] = $1
45
45
  end
46
46
  end
@@ -74,7 +74,8 @@ class Brakeman::HamlTemplateProcessor < Brakeman::TemplateProcessor
74
74
  res
75
75
 
76
76
  #_hamlout.buffer <<
77
- #This seems to be used rarely, but directly appends args to output buffer
77
+ #This seems to be used rarely, but directly appends args to output buffer.
78
+ #Has something to do with values of blocks?
78
79
  elsif sexp? target and method == :<< and is_buffer_target? target
79
80
  @inside_concat = true
80
81
  out = exp.arglist[1] = process(exp.arglist[1])
@@ -112,7 +112,8 @@ class Brakeman::Rails2RoutesProcessor < Brakeman::BaseProcessor
112
112
  hash_iterate(exp) do |option, value|
113
113
  case option[1]
114
114
  when :controller, :requirements, :singular, :path_prefix, :as,
115
- :path_names, :shallow, :name_prefix, :member_path, :nested_member_path
115
+ :path_names, :shallow, :name_prefix, :member_path, :nested_member_path,
116
+ :belongs_to, :conditions, :active_scaffold
116
117
  #should be able to skip
117
118
  when :collection, :member, :new
118
119
  process_collection value
@@ -129,7 +130,7 @@ class Brakeman::Rails2RoutesProcessor < Brakeman::BaseProcessor
129
130
  when :except
130
131
  process_option_except value
131
132
  else
132
- Brakeman.notify "[Notice] Unhandled resource option: #{option}"
133
+ Brakeman.notify "[Notice] Unhandled resource option, please report: #{option}"
133
134
  end
134
135
  end
135
136
  end
@@ -178,6 +179,8 @@ class Brakeman::Rails2RoutesProcessor < Brakeman::BaseProcessor
178
179
  #Process
179
180
  # map.connect '/something', :controller => 'blah', :action => 'whatever'
180
181
  def process_connect exp
182
+ return if exp.empty?
183
+
181
184
  controller = check_for_controller_name exp
182
185
  self.current_controller = controller if controller
183
186
 
@@ -2,6 +2,9 @@ require 'brakeman/processors/base_processor'
2
2
 
3
3
  #Processes models. Puts results in tracker.models
4
4
  class Brakeman::ModelProcessor < Brakeman::BaseProcessor
5
+
6
+ ASSOCIATIONS = Set[:belongs_to, :has_one, :has_many, :has_and_belongs_to_many]
7
+
5
8
  def initialize tracker
6
9
  super
7
10
  @model = nil
@@ -38,6 +41,7 @@ class Brakeman::ModelProcessor < Brakeman::BaseProcessor
38
41
  :private => {},
39
42
  :protected => {},
40
43
  :options => {},
44
+ :associations => {},
41
45
  :file => @file_name }
42
46
  @tracker.models[@model[:name]] = @model
43
47
  res = process exp.body
@@ -83,8 +87,13 @@ class Brakeman::ModelProcessor < Brakeman::BaseProcessor
83
87
  @model[:attr_accessible].concat args
84
88
  else
85
89
  if @model
86
- @model[:options][method] ||= []
87
- @model[:options][method] << exp.arglist
90
+ if ASSOCIATIONS.include? method
91
+ @model[:associations][method] ||= []
92
+ @model[:associations][method].concat exp.args
93
+ else
94
+ @model[:options][method] ||= []
95
+ @model[:options][method] << exp.arglist
96
+ end
88
97
  end
89
98
  end
90
99
  end
@@ -96,11 +96,17 @@ class Brakeman::TemplateAliasProcessor < Brakeman::AliasProcessor
96
96
  false
97
97
  end
98
98
 
99
+ #Ignore `<<` calls on template variables which are used by the templating
100
+ #library (HAML, ERB, etc.)
99
101
  def find_push_target exp
100
102
  if sexp? exp
101
103
  if exp.node_type == :lvar and (exp.value == :_buf or exp.value == :_erbout)
102
104
  return nil
103
105
  elsif exp.node_type == :ivar and exp.value == :@output_buffer
106
+ return nil
107
+ elsif exp.node_type == :call and call? exp.target and
108
+ exp.target.method == :_hamlout and exp.method == :buffer
109
+
104
110
  return nil
105
111
  end
106
112
  end
@@ -6,6 +6,7 @@ require 'brakeman/util'
6
6
  require 'terminal-table'
7
7
  require 'highline/system_extensions'
8
8
  require "csv"
9
+ require 'multi_json'
9
10
  require 'brakeman/version'
10
11
 
11
12
  if CSV.const_defined? :Reader
@@ -17,6 +18,15 @@ else
17
18
  # CSV is now FasterCSV in ruby 1.9
18
19
  end
19
20
 
21
+ #This is so OkJson will work with symbol values
22
+ if MultiJson.default_adapter == :ok_json
23
+ class Symbol
24
+ def to_json
25
+ self.to_s.inspect
26
+ end
27
+ end
28
+ end
29
+
20
30
  #Generates a report based on the Tracker and the results of
21
31
  #Tracker#run_checks. Be sure to +run_checks+ before generating
22
32
  #a report.
@@ -647,8 +657,6 @@ class Brakeman::Report
647
657
  end
648
658
 
649
659
  def to_json
650
- require 'json'
651
-
652
660
  errors = tracker.errors.map{|e| { :error => e[:error], :location => e[:backtrace][0] }}
653
661
  app_path = tracker.options[:app_path]
654
662
 
@@ -662,7 +670,7 @@ class Brakeman::Report
662
670
  :app_path => File.expand_path(tracker.options[:app_path]),
663
671
  :rails_version => rails_version,
664
672
  :security_warnings => all_warnings.length,
665
- :timestamp => Time.now,
673
+ :timestamp => Time.now.to_s,
666
674
  :checks_performed => checks.checks_run.sort,
667
675
  :number_of_controllers =>tracker.controllers.length,
668
676
  # ignore the "fake" model
@@ -672,11 +680,11 @@ class Brakeman::Report
672
680
  :brakeman_version => Brakeman::Version
673
681
  }
674
682
 
675
- JSON.pretty_generate({
683
+ MultiJson.dump({
676
684
  :scan_info => scan_info,
677
685
  :warnings => warnings,
678
686
  :errors => errors
679
- })
687
+ }, :pretty => true)
680
688
  end
681
689
 
682
690
  def all_warnings
@@ -304,7 +304,7 @@ class Brakeman::Rescanner < Brakeman::Scanner
304
304
  :initializer
305
305
  when /config\/routes\.rb/
306
306
  :routes
307
- when /\/config/
307
+ when /\/config\/.+\.rb/
308
308
  :config
309
309
  when /Gemfile/
310
310
  :gemfile
@@ -322,7 +322,7 @@ class Brakeman::Rescanner < Brakeman::Scanner
322
322
  end
323
323
  end
324
324
 
325
- method_matcher = /##{method_names.join('|')}$/
325
+ method_matcher = /##{method_names.map {|n| Regexp.escape(n.to_s)}.join('|')}$/
326
326
 
327
327
  #Rescan controllers that mixed in library
328
328
  tracker.controllers.each do |name, controller|
@@ -1,3 +1,3 @@
1
1
  module Brakeman
2
- Version = "1.8.2"
2
+ Version = "1.8.3"
3
3
  end
@@ -1,3 +1,5 @@
1
+ require 'multi_json'
2
+
1
3
  #The Warning class stores information about warnings
2
4
  class Brakeman::Warning
3
5
  attr_reader :called_from, :check, :class, :confidence, :controller,
@@ -177,8 +179,6 @@ class Brakeman::Warning
177
179
  end
178
180
 
179
181
  def to_json
180
- require 'json'
181
-
182
- JSON.dump self.to_hash
182
+ MultiJson.dump self.to_hash
183
183
  end
184
184
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brakeman
3
3
  version: !ruby/object:Gem::Version
4
- hash: 51
4
+ hash: 49
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
8
  - 8
9
- - 2
10
- version: 1.8.2
9
+ - 3
10
+ version: 1.8.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Justin Collins
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-10-17 00:00:00 Z
18
+ date: 2012-11-13 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: activesupport
@@ -167,17 +167,18 @@ dependencies:
167
167
  type: :runtime
168
168
  version_requirements: *id010
169
169
  - !ruby/object:Gem::Dependency
170
- name: json_pure
170
+ name: multi_json
171
171
  prerelease: false
172
172
  requirement: &id011 !ruby/object:Gem::Requirement
173
173
  none: false
174
174
  requirements:
175
- - - ">="
175
+ - - ~>
176
176
  - !ruby/object:Gem::Version
177
- hash: 3
177
+ hash: 9
178
178
  segments:
179
- - 0
180
- version: "0"
179
+ - 1
180
+ - 3
181
+ version: "1.3"
181
182
  type: :runtime
182
183
  version_requirements: *id011
183
184
  description: Brakeman detects security vulnerabilities in Ruby on Rails applications via static analysis.