brakeman 1.8.2 → 1.8.3

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