brakeman 1.8.3 → 1.9.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +3 -27
- data/lib/brakeman.rb +36 -38
- data/lib/brakeman/app_tree.rb +90 -0
- data/lib/brakeman/call_index.rb +5 -38
- data/lib/brakeman/checks.rb +11 -11
- data/lib/brakeman/checks/base_check.rb +53 -29
- data/lib/brakeman/checks/check_cross_site_scripting.rb +11 -9
- data/lib/brakeman/checks/check_evaluation.rb +1 -1
- data/lib/brakeman/checks/check_execute.rb +3 -3
- data/lib/brakeman/checks/check_link_to.rb +15 -13
- data/lib/brakeman/checks/check_link_to_href.rb +1 -1
- data/lib/brakeman/checks/check_mail_to.rb +1 -1
- data/lib/brakeman/checks/check_mass_assignment.rb +27 -13
- data/lib/brakeman/checks/check_redirect.rb +4 -4
- data/lib/brakeman/checks/check_select_tag.rb +1 -1
- data/lib/brakeman/checks/check_select_vulnerability.rb +1 -1
- data/lib/brakeman/checks/check_send.rb +2 -2
- data/lib/brakeman/checks/check_session_settings.rb +12 -5
- data/lib/brakeman/checks/check_single_quotes.rb +3 -3
- data/lib/brakeman/checks/check_skip_before_filter.rb +4 -3
- data/lib/brakeman/checks/check_sql.rb +30 -30
- data/lib/brakeman/checks/check_translate_bug.rb +11 -10
- data/lib/brakeman/checks/check_validation_regex.rb +36 -11
- data/lib/brakeman/checks/check_without_protection.rb +1 -1
- data/lib/brakeman/options.rb +6 -2
- data/lib/brakeman/processor.rb +6 -5
- data/lib/brakeman/processors/alias_processor.rb +153 -38
- data/lib/brakeman/processors/base_processor.rb +16 -21
- data/lib/brakeman/processors/controller_alias_processor.rb +24 -11
- data/lib/brakeman/processors/controller_processor.rb +25 -25
- data/lib/brakeman/processors/erb_template_processor.rb +6 -7
- data/lib/brakeman/processors/erubis_template_processor.rb +2 -3
- data/lib/brakeman/processors/gem_processor.rb +5 -4
- data/lib/brakeman/processors/haml_template_processor.rb +4 -6
- data/lib/brakeman/processors/lib/find_all_calls.rb +3 -3
- data/lib/brakeman/processors/lib/find_call.rb +2 -2
- data/lib/brakeman/processors/lib/find_return_value.rb +134 -0
- data/lib/brakeman/processors/lib/processor_helper.rb +24 -2
- data/lib/brakeman/processors/lib/rails2_config_processor.rb +13 -14
- data/lib/brakeman/processors/lib/rails2_route_processor.rb +9 -4
- data/lib/brakeman/processors/lib/rails3_config_processor.rb +8 -8
- data/lib/brakeman/processors/lib/rails3_route_processor.rb +23 -21
- data/lib/brakeman/processors/lib/render_helper.rb +2 -2
- data/lib/brakeman/processors/library_processor.rb +2 -2
- data/lib/brakeman/processors/model_processor.rb +16 -12
- data/lib/brakeman/processors/output_processor.rb +2 -1
- data/lib/brakeman/processors/template_alias_processor.rb +12 -8
- data/lib/brakeman/report.rb +28 -14
- data/lib/brakeman/rescanner.rb +5 -5
- data/lib/brakeman/scanner.rb +56 -94
- data/lib/brakeman/templates/header.html.erb +7 -2
- data/lib/brakeman/tracker.rb +14 -4
- data/lib/brakeman/util.rb +38 -17
- data/lib/brakeman/version.rb +1 -1
- data/lib/brakeman/warning.rb +14 -6
- data/lib/ruby_parser/bm_sexp.rb +157 -57
- data/lib/ruby_parser/bm_sexp_processor.rb +1 -2
- metadata +26 -25
- data/lib/ruby_parser/ruby18_parser.rb +0 -5544
- data/lib/ruby_parser/ruby19_parser.rb +0 -5756
- data/lib/ruby_parser/ruby_lexer.rb +0 -1349
- data/lib/ruby_parser/ruby_parser.rb +0 -5
- data/lib/ruby_parser/ruby_parser_extras.rb +0 -1057
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
![Brakeman Logo](http://brakemanscanner.org/images/logo_medium.png)
|
2
2
|
|
3
|
-
![Travis CI Status](https://secure.travis-ci.org/presidentbeef/brakeman.png) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/presidentbeef/brakeman)
|
3
|
+
[![Travis CI Status](https://secure.travis-ci.org/presidentbeef/brakeman.png)](https://travis-ci.org/presidentbeef/brakeman) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/presidentbeef/brakeman)
|
4
4
|
|
5
5
|
# Brakeman
|
6
6
|
|
@@ -145,32 +145,8 @@ Brakeman options can stored and read from YAML files. To simplify the process of
|
|
145
145
|
|
146
146
|
Options passed in on the commandline have priority over configuration files.
|
147
147
|
|
148
|
-
The default config locations are `./config.
|
148
|
+
The default config locations are `./config/brakeman.yml`, `~/.brakeman/config.yml`, and `/etc/brakeman/config.yml`
|
149
149
|
|
150
150
|
The `-c` option can be used to specify a configuration file to use.
|
151
151
|
|
152
|
-
# License
|
153
|
-
|
154
|
-
The MIT License
|
155
|
-
|
156
|
-
Copyright (c) 2012, Twitter, Inc.
|
157
|
-
|
158
|
-
Copyright (c) 2010-2012, YELLOWPAGES.COM, LLC
|
159
|
-
|
160
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
161
|
-
of this software and associated documentation files (the "Software"), to deal
|
162
|
-
in the Software without restriction, including without limitation the rights
|
163
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
164
|
-
copies of the Software, and to permit persons to whom the Software is
|
165
|
-
furnished to do so, subject to the following conditions:
|
166
|
-
|
167
|
-
The above copyright notice and this permission notice shall be included in
|
168
|
-
all copies or substantial portions of the Software.
|
169
|
-
|
170
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
171
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
172
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
173
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
174
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
175
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
176
|
-
THE SOFTWARE.
|
152
|
+
# License see MIT-LICENSE
|
data/lib/brakeman.rb
CHANGED
@@ -16,7 +16,7 @@ module Brakeman
|
|
16
16
|
#Options:
|
17
17
|
#
|
18
18
|
# * :app_path - path to root of Rails app (required)
|
19
|
-
# * :assume_all_routes - assume all methods are routes (default:
|
19
|
+
# * :assume_all_routes - assume all methods are routes (default: true)
|
20
20
|
# * :check_arguments - check arguments of methods (default: true)
|
21
21
|
# * :collapse_mass_assignment - report unprotected models in single warning (default: true)
|
22
22
|
# * :combine_locations - combine warning locations (default: true)
|
@@ -26,6 +26,7 @@ module Brakeman
|
|
26
26
|
# * :highlight_user_input - highlight user input in reported warnings (default: true)
|
27
27
|
# * :html_style - path to CSS file
|
28
28
|
# * :ignore_model_output - consider models safe (default: false)
|
29
|
+
# * :interprocedural - limited interprocedural processing of method calls (default: false)
|
29
30
|
# * :message_limit - limit length of messages
|
30
31
|
# * :min_confidence - minimum confidence (0-2, 0 is highest)
|
31
32
|
# * :output_files - files for output
|
@@ -40,7 +41,7 @@ module Brakeman
|
|
40
41
|
# * :skip_libs - do not process lib/ directory (default: false)
|
41
42
|
# * :skip_checks - checks not to run (run all if not specified)
|
42
43
|
# * :relative_path - show relative path of each file(default: false)
|
43
|
-
# * :summary_only - only output summary section of report
|
44
|
+
# * :summary_only - only output summary section of report
|
44
45
|
# (does not apply to tabs format)
|
45
46
|
#
|
46
47
|
#Alternatively, just supply a path as a string.
|
@@ -68,49 +69,48 @@ module Brakeman
|
|
68
69
|
options = get_defaults.merge! options
|
69
70
|
options[:output_formats] = get_output_formats options
|
70
71
|
|
71
|
-
app_path = options[:app_path]
|
72
|
-
|
73
|
-
abort("Please supply the path to a Rails application.") unless app_path and File.exist? app_path + "/app"
|
74
|
-
|
75
|
-
if File.exist? app_path + "/script/rails"
|
76
|
-
options[:rails3] = true
|
77
|
-
notify "[Notice] Detected Rails 3 application" unless options[:quiet]
|
78
|
-
end
|
79
|
-
|
80
72
|
options
|
81
73
|
end
|
82
74
|
|
83
|
-
|
84
|
-
|
85
|
-
|
75
|
+
DEPRECATED_CONFIG_FILES = [
|
76
|
+
File.expand_path("./config.yaml"),
|
77
|
+
File.expand_path("~/.brakeman/config.yaml"),
|
78
|
+
File.expand_path("/etc/brakeman/config.yaml"),
|
79
|
+
"#{File.expand_path(File.dirname(__FILE__))}/../lib/config.yaml"
|
80
|
+
]
|
86
81
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
"#{File.expand_path(File.dirname(__FILE__))}/../lib/config.yaml"].each do |f|
|
93
|
-
|
94
|
-
if File.exist? f and not File.directory? f
|
95
|
-
notify "[Notice] Using configuration in #{f}"
|
96
|
-
options = YAML.load_file f
|
97
|
-
options.each do |k,v|
|
98
|
-
if v.is_a? Array
|
99
|
-
options[k] = Set.new v
|
100
|
-
end
|
101
|
-
end
|
82
|
+
CONFIG_FILES = [
|
83
|
+
File.expand_path("./config/brakeman.yml"),
|
84
|
+
File.expand_path("~/.brakeman/config.yml"),
|
85
|
+
File.expand_path("/etc/brakeman/config.yml"),
|
86
|
+
]
|
102
87
|
|
103
|
-
|
104
|
-
|
105
|
-
|
88
|
+
#Load options from YAML file
|
89
|
+
def self.load_options custom_location
|
90
|
+
#Load configuration file
|
91
|
+
if config = config_file(custom_location)
|
92
|
+
notify "[Notice] Using configuration in #{config}"
|
93
|
+
options = YAML.load_file config
|
94
|
+
options.each { |k, v| options[k] = Set.new v if v.is_a? Array }
|
95
|
+
options
|
96
|
+
else
|
97
|
+
{}
|
98
|
+
end
|
99
|
+
end
|
106
100
|
|
107
|
-
|
101
|
+
def self.config_file(custom_location=nil)
|
102
|
+
DEPRECATED_CONFIG_FILES.each do |f|
|
103
|
+
notify "#{f} is deprecated, please use one of #{CONFIG_FILES.join(", ")}" if File.file?(f)
|
104
|
+
end
|
105
|
+
supported_locations = [File.expand_path(custom_location || "")] + DEPRECATED_CONFIG_FILES + CONFIG_FILES
|
106
|
+
supported_locations.detect{|f| File.file?(f) }
|
108
107
|
end
|
109
108
|
|
110
109
|
#Default set of options
|
111
110
|
def self.get_defaults
|
112
|
-
{ :
|
113
|
-
:
|
111
|
+
{ :assume_all_routes => true,
|
112
|
+
:skip_checks => Set.new,
|
113
|
+
:check_arguments => true,
|
114
114
|
:safe_methods => Set.new,
|
115
115
|
:min_confidence => 2,
|
116
116
|
:combine_locations => true,
|
@@ -123,7 +123,7 @@ module Brakeman
|
|
123
123
|
:relative_path => false,
|
124
124
|
:quiet => true,
|
125
125
|
:report_progress => true,
|
126
|
-
:html_style => "#{File.expand_path(File.dirname(__FILE__))}/brakeman/format/style.css"
|
126
|
+
:html_style => "#{File.expand_path(File.dirname(__FILE__))}/brakeman/format/style.css"
|
127
127
|
}
|
128
128
|
end
|
129
129
|
|
@@ -252,8 +252,6 @@ module Brakeman
|
|
252
252
|
#Start scanning
|
253
253
|
scanner = Scanner.new options
|
254
254
|
|
255
|
-
notify "[Notice] Using Ruby #{RUBY_VERSION}. Please make sure this matches the one used to run your Rails application."
|
256
|
-
|
257
255
|
notify "Processing application in #{options[:app_path]}"
|
258
256
|
tracker = scanner.process
|
259
257
|
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Brakeman
|
2
|
+
class AppTree
|
3
|
+
VIEW_EXTENSIONS = %w[html.erb html.haml rhtml js.erb].join(",")
|
4
|
+
|
5
|
+
attr_reader :root
|
6
|
+
|
7
|
+
def self.from_options(options)
|
8
|
+
root = options[:app_path]
|
9
|
+
|
10
|
+
# Convert files into Regexp for matching
|
11
|
+
if options[:skip_files]
|
12
|
+
list = "(?:" << options[:skip_files].map { |f| Regexp.escape f }.join("|") << ")$"
|
13
|
+
new(root, Regexp.new(list))
|
14
|
+
else
|
15
|
+
new(root)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(root, skip_files = nil)
|
20
|
+
@root = root
|
21
|
+
@skip_files = skip_files
|
22
|
+
end
|
23
|
+
|
24
|
+
def expand_path(path)
|
25
|
+
File.expand_path(path, @root)
|
26
|
+
end
|
27
|
+
|
28
|
+
def read(path)
|
29
|
+
File.read(File.join(@root, path))
|
30
|
+
end
|
31
|
+
|
32
|
+
# This variation requires full paths instead of paths based
|
33
|
+
# off the project root. I'd prefer to get all the code outside
|
34
|
+
# of AppTree using project-root based paths (e.g. app/models/user.rb)
|
35
|
+
# instead of full paths, but I suspect it's an incompatible change.
|
36
|
+
def read_path(path)
|
37
|
+
File.read(path)
|
38
|
+
end
|
39
|
+
|
40
|
+
def exists?(path)
|
41
|
+
File.exists?(File.join(@root, path))
|
42
|
+
end
|
43
|
+
|
44
|
+
# This is a pair for #read_path. Again, would like to kill these
|
45
|
+
def path_exists?(path)
|
46
|
+
File.exists?(path)
|
47
|
+
end
|
48
|
+
|
49
|
+
def initializer_paths
|
50
|
+
@initializer_paths ||= find_paths("config/initializers")
|
51
|
+
end
|
52
|
+
|
53
|
+
def controller_paths
|
54
|
+
@controller_paths ||= find_paths("app/controllers")
|
55
|
+
end
|
56
|
+
|
57
|
+
def model_paths
|
58
|
+
@model_paths ||= find_paths("app/models")
|
59
|
+
end
|
60
|
+
|
61
|
+
def template_paths
|
62
|
+
@template_paths ||= find_paths("app/views", "*.{#{VIEW_EXTENSIONS}}")
|
63
|
+
end
|
64
|
+
|
65
|
+
def layout_exists?(name)
|
66
|
+
pattern = "#{@root}/app/views/layouts/#{name}.html.{erb,haml}"
|
67
|
+
!Dir.glob(pattern).empty?
|
68
|
+
end
|
69
|
+
|
70
|
+
def lib_paths
|
71
|
+
@lib_files ||= find_paths("lib")
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def find_paths(directory, extensions = "*.rb")
|
77
|
+
pattern = @root + "/#{directory}/**/#{extensions}"
|
78
|
+
|
79
|
+
Dir.glob(pattern).sort.tap do |paths|
|
80
|
+
reject_skipped_files(paths)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def reject_skipped_files(paths)
|
85
|
+
return unless @skip_files
|
86
|
+
paths.reject! { |f| @skip_files.match f }
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
data/lib/brakeman/call_index.rb
CHANGED
@@ -7,8 +7,6 @@ class Brakeman::CallIndex
|
|
7
7
|
def initialize calls
|
8
8
|
@calls_by_method = Hash.new { |h,k| h[k] = [] }
|
9
9
|
@calls_by_target = Hash.new { |h,k| h[k] = [] }
|
10
|
-
@methods = Set.new
|
11
|
-
@targets = Set.new
|
12
10
|
|
13
11
|
index_calls calls
|
14
12
|
end
|
@@ -25,7 +23,7 @@ class Brakeman::CallIndex
|
|
25
23
|
target = options[:target] || options[:targets]
|
26
24
|
method = options[:method] || options[:methods]
|
27
25
|
nested = options[:nested]
|
28
|
-
|
26
|
+
|
29
27
|
if options[:chained]
|
30
28
|
return find_chain options
|
31
29
|
#Find by narrowest category
|
@@ -72,16 +70,12 @@ class Brakeman::CallIndex
|
|
72
70
|
calls.delete_if do |call|
|
73
71
|
from_template call, template_name
|
74
72
|
end
|
75
|
-
|
76
|
-
@methods.delete name.to_s if calls.empty?
|
77
73
|
end
|
78
74
|
|
79
75
|
@calls_by_target.each do |name, calls|
|
80
76
|
calls.delete_if do |call|
|
81
77
|
from_template call, template_name
|
82
78
|
end
|
83
|
-
|
84
|
-
@targets.delete name.to_s if calls.empty?
|
85
79
|
end
|
86
80
|
end
|
87
81
|
|
@@ -90,25 +84,22 @@ class Brakeman::CallIndex
|
|
90
84
|
calls.delete_if do |call|
|
91
85
|
call[:location][0] == :class and classes.include? call[:location][1]
|
92
86
|
end
|
93
|
-
|
94
|
-
@methods.delete name.to_s if calls.empty?
|
95
87
|
end
|
96
88
|
|
97
89
|
@calls_by_target.each do |name, calls|
|
98
90
|
calls.delete_if do |call|
|
99
91
|
call[:location][0] == :class and classes.include? call[:location][1]
|
100
92
|
end
|
101
|
-
|
102
|
-
@targets.delete name.to_s if calls.empty?
|
103
93
|
end
|
104
94
|
end
|
105
95
|
|
106
96
|
def index_calls calls
|
107
97
|
calls.each do |call|
|
108
|
-
@methods << call[:method].to_s
|
109
|
-
@targets << call[:target].to_s if call[:target].is_a? Symbol
|
110
98
|
@calls_by_method[call[:method]] << call
|
111
|
-
|
99
|
+
|
100
|
+
unless call[:target].is_a? Sexp
|
101
|
+
@calls_by_target[call[:target]] << call
|
102
|
+
end
|
112
103
|
end
|
113
104
|
end
|
114
105
|
|
@@ -128,18 +119,6 @@ class Brakeman::CallIndex
|
|
128
119
|
def calls_by_target target
|
129
120
|
if target.is_a? Array
|
130
121
|
calls_by_targets target
|
131
|
-
elsif target.is_a? Regexp
|
132
|
-
targets = @targets.select do |t|
|
133
|
-
t.match target
|
134
|
-
end
|
135
|
-
|
136
|
-
if targets.empty?
|
137
|
-
[]
|
138
|
-
elsif targets.length > 1
|
139
|
-
calls_by_targets targets
|
140
|
-
else
|
141
|
-
@calls_by_target[targets.first]
|
142
|
-
end
|
143
122
|
else
|
144
123
|
@calls_by_target[target]
|
145
124
|
end
|
@@ -158,18 +137,6 @@ class Brakeman::CallIndex
|
|
158
137
|
def calls_by_method method
|
159
138
|
if method.is_a? Array
|
160
139
|
calls_by_methods method
|
161
|
-
elsif method.is_a? Regexp
|
162
|
-
methods = @methods.select do |m|
|
163
|
-
m.match method
|
164
|
-
end
|
165
|
-
|
166
|
-
if methods.empty?
|
167
|
-
[]
|
168
|
-
elsif methods.length > 1
|
169
|
-
calls_by_methods methods
|
170
|
-
else
|
171
|
-
@calls_by_method[methods.first.to_sym]
|
172
|
-
end
|
173
140
|
else
|
174
141
|
@calls_by_method[method.to_sym]
|
175
142
|
end
|
data/lib/brakeman/checks.rb
CHANGED
@@ -75,28 +75,28 @@ class Brakeman::Checks
|
|
75
75
|
|
76
76
|
#Run all the checks on the given Tracker.
|
77
77
|
#Returns a new instance of Checks with the results.
|
78
|
-
def self.run_checks tracker
|
78
|
+
def self.run_checks(app_tree, tracker)
|
79
79
|
if tracker.options[:parallel_checks]
|
80
|
-
self.run_checks_parallel tracker
|
80
|
+
self.run_checks_parallel(app_tree, tracker)
|
81
81
|
else
|
82
|
-
self.run_checks_sequential tracker
|
82
|
+
self.run_checks_sequential(app_tree, tracker)
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
86
|
#Run checks sequentially
|
87
|
-
def self.run_checks_sequential tracker
|
87
|
+
def self.run_checks_sequential(app_tree, tracker)
|
88
88
|
check_runner = self.new :min_confidence => tracker.options[:min_confidence]
|
89
89
|
|
90
90
|
@checks.each do |c|
|
91
91
|
check_name = get_check_name c
|
92
92
|
|
93
93
|
#Run or don't run check based on options
|
94
|
-
unless tracker.options[:skip_checks].include? check_name or
|
94
|
+
unless tracker.options[:skip_checks].include? check_name or
|
95
95
|
(tracker.options[:run_checks] and not tracker.options[:run_checks].include? check_name)
|
96
96
|
|
97
97
|
Brakeman.notify " - #{check_name}"
|
98
98
|
|
99
|
-
check = c.new(tracker)
|
99
|
+
check = c.new(app_tree, tracker)
|
100
100
|
|
101
101
|
begin
|
102
102
|
check.run_check
|
@@ -118,23 +118,23 @@ class Brakeman::Checks
|
|
118
118
|
end
|
119
119
|
|
120
120
|
#Run checks in parallel threads
|
121
|
-
def self.run_checks_parallel tracker
|
121
|
+
def self.run_checks_parallel(app_tree, tracker)
|
122
122
|
threads = []
|
123
123
|
error_mutex = Mutex.new
|
124
|
-
|
124
|
+
|
125
125
|
check_runner = self.new :min_confidence => tracker.options[:min_confidence]
|
126
126
|
|
127
127
|
@checks.each do |c|
|
128
128
|
check_name = get_check_name c
|
129
129
|
|
130
130
|
#Run or don't run check based on options
|
131
|
-
unless tracker.options[:skip_checks].include? check_name or
|
131
|
+
unless tracker.options[:skip_checks].include? check_name or
|
132
132
|
(tracker.options[:run_checks] and not tracker.options[:run_checks].include? check_name)
|
133
133
|
|
134
134
|
Brakeman.notify " - #{check_name}"
|
135
135
|
|
136
136
|
threads << Thread.new do
|
137
|
-
check = c.new(tracker)
|
137
|
+
check = c.new(app_tree, tracker)
|
138
138
|
|
139
139
|
begin
|
140
140
|
check.run_check
|
@@ -175,6 +175,6 @@ class Brakeman::Checks
|
|
175
175
|
end
|
176
176
|
|
177
177
|
#Load all files in checks/ directory
|
178
|
-
Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/checks/*.rb").sort.each do |f|
|
178
|
+
Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/checks/*.rb").sort.each do |f|
|
179
179
|
require f.match(/(brakeman\/checks\/.*)\.rb$/)[0]
|
180
180
|
end
|
@@ -14,8 +14,9 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
14
14
|
Match = Struct.new(:type, :match)
|
15
15
|
|
16
16
|
#Initialize Check with Checks.
|
17
|
-
def initialize tracker
|
17
|
+
def initialize(app_tree, tracker)
|
18
18
|
super()
|
19
|
+
@app_tree = app_tree
|
19
20
|
@results = [] #only to check for duplicates
|
20
21
|
@warnings = []
|
21
22
|
@tracker = tracker
|
@@ -60,7 +61,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
60
61
|
#Process calls and check if they include user input
|
61
62
|
def process_call exp
|
62
63
|
process exp.target if sexp? exp.target
|
63
|
-
|
64
|
+
process_call_args exp
|
64
65
|
|
65
66
|
target = exp.target
|
66
67
|
|
@@ -71,7 +72,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
71
72
|
@has_user_input = Match.new(:cookies, exp)
|
72
73
|
elsif request_env? target
|
73
74
|
@has_user_input = Match.new(:request, exp)
|
74
|
-
elsif sexp? target and model_name? target[1]
|
75
|
+
elsif sexp? target and model_name? target[1] #TODO: Can this be target.target?
|
75
76
|
@has_user_input = Match.new(:model, exp)
|
76
77
|
end
|
77
78
|
end
|
@@ -103,15 +104,21 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
103
104
|
exp
|
104
105
|
end
|
105
106
|
|
107
|
+
#Does not actually process string interpolation, but notes that it occurred.
|
108
|
+
def process_string_interp exp
|
109
|
+
@string_interp = Match.new(:interp, exp)
|
110
|
+
process_default exp
|
111
|
+
end
|
112
|
+
|
106
113
|
private
|
107
114
|
|
108
|
-
#Report a warning
|
115
|
+
#Report a warning
|
109
116
|
def warn options
|
110
117
|
warning = Brakeman::Warning.new(options.merge({ :check => self.class.to_s }))
|
111
118
|
warning.file = file_for warning
|
112
119
|
|
113
|
-
@warnings << warning
|
114
|
-
end
|
120
|
+
@warnings << warning
|
121
|
+
end
|
115
122
|
|
116
123
|
#Run _exp_ through OutputProcessor to get a nice String.
|
117
124
|
def format_output exp
|
@@ -137,7 +144,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
137
144
|
|
138
145
|
# go up the chain of parent classes to see if any have attr_accessible
|
139
146
|
def parent_classes_protected? model
|
140
|
-
if model[:attr_accessible]
|
147
|
+
if model[:attr_accessible] or model[:includes].include? :"ActiveModel::ForbiddenAttributesProtection"
|
141
148
|
true
|
142
149
|
elsif parent = tracker.models[model[:parent]]
|
143
150
|
parent_classes_protected? parent
|
@@ -149,24 +156,31 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
149
156
|
#Checks if mass assignment is disabled globally in an initializer.
|
150
157
|
def mass_assign_disabled?
|
151
158
|
return @mass_assign_disabled unless @mass_assign_disabled.nil?
|
152
|
-
|
159
|
+
|
153
160
|
@mass_assign_disabled = false
|
154
161
|
|
155
|
-
if version_between?("3.1.0", "
|
162
|
+
if version_between?("3.1.0", "3.9.9") and
|
156
163
|
tracker.config[:rails] and
|
157
|
-
tracker.config[:rails][:active_record] and
|
164
|
+
tracker.config[:rails][:active_record] and
|
158
165
|
tracker.config[:rails][:active_record][:whitelist_attributes] == Sexp.new(:true)
|
159
166
|
|
167
|
+
@mass_assign_disabled = true
|
168
|
+
elsif version_between?("4.0.0", "4.9.9")
|
169
|
+
#May need to revisit dependng on what Rails 4 actually does/has
|
160
170
|
@mass_assign_disabled = true
|
161
171
|
else
|
162
172
|
matches = tracker.check_initializers(:"ActiveRecord::Base", :send)
|
163
173
|
|
164
174
|
if matches.empty?
|
175
|
+
#Check for
|
176
|
+
# class ActiveRecord::Base
|
177
|
+
# attr_accessible nil
|
178
|
+
# end
|
165
179
|
matches = tracker.check_initializers([], :attr_accessible)
|
166
180
|
|
167
181
|
matches.each do |result|
|
168
|
-
if result
|
169
|
-
arg = result
|
182
|
+
if result.module == "ActiveRecord" and result.result_class == :Base
|
183
|
+
arg = result.call.first_arg
|
170
184
|
|
171
185
|
if arg.nil? or node_type? arg, :nil
|
172
186
|
@mass_assign_disabled = true
|
@@ -175,10 +189,31 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
175
189
|
end
|
176
190
|
end
|
177
191
|
else
|
192
|
+
#Check for ActiveRecord::Base.send(:attr_accessible, nil)
|
178
193
|
matches.each do |result|
|
179
|
-
|
194
|
+
call = result.call
|
195
|
+
if call? call
|
196
|
+
if call.first_arg == Sexp.new(:lit, :attr_accessible) and call.second_arg == Sexp.new(:nil)
|
197
|
+
@mass_assign_disabled = true
|
198
|
+
break
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
#There is a chance someone is using Rails 3.x and the `strong_parameters`
|
206
|
+
#gem and still using hack above, so this is a separate check for
|
207
|
+
#including ActiveModel::ForbiddenAttributesProtection in
|
208
|
+
#ActiveRecord::Base in an initializer.
|
209
|
+
if not @mass_assign_disabled and version_between?("3.1.0", "3.9.9") and tracker.config[:gems][:strong_parameters]
|
210
|
+
matches = tracker.check_initializers([], :include)
|
211
|
+
|
212
|
+
matches.each do |result|
|
213
|
+
call = result.call
|
214
|
+
if call? call
|
215
|
+
if call.first_arg == Sexp.new(:colon2, Sexp.new(:const, :ActiveModel), :ForbiddenAttributesProtection)
|
180
216
|
@mass_assign_disabled = true
|
181
|
-
break
|
182
217
|
end
|
183
218
|
end
|
184
219
|
end
|
@@ -216,17 +251,6 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
216
251
|
false
|
217
252
|
end
|
218
253
|
|
219
|
-
#Ignores ignores
|
220
|
-
def process_ignore exp
|
221
|
-
exp
|
222
|
-
end
|
223
|
-
|
224
|
-
#Does not actually process string interpolation, but notes that it occurred.
|
225
|
-
def process_string_interp exp
|
226
|
-
@string_interp = Match.new(:interp, exp)
|
227
|
-
exp
|
228
|
-
end
|
229
|
-
|
230
254
|
#Checks if an expression contains string interpolation.
|
231
255
|
#
|
232
256
|
#Returns Match with :interp type if found.
|
@@ -364,7 +388,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
364
388
|
|
365
389
|
#Checks if +exp+ is a model name.
|
366
390
|
#
|
367
|
-
#Prior to using this method, either @tracker must be set to
|
391
|
+
#Prior to using this method, either @tracker must be set to
|
368
392
|
#the current tracker, or else @models should contain an array of the model
|
369
393
|
#names, which is available via tracker.models.keys
|
370
394
|
def model_name? exp
|
@@ -387,14 +411,14 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
387
411
|
|
388
412
|
#Finds entire method call chain where +target+ is a target in the chain
|
389
413
|
def find_chain exp, target
|
390
|
-
return unless sexp? exp
|
414
|
+
return unless sexp? exp
|
391
415
|
|
392
416
|
case exp.node_type
|
393
417
|
when :output, :format
|
394
418
|
find_chain exp.value, target
|
395
419
|
when :call
|
396
420
|
if exp == target or include_target? exp, target
|
397
|
-
return exp
|
421
|
+
return exp
|
398
422
|
end
|
399
423
|
else
|
400
424
|
exp.each do |e|
|
@@ -448,7 +472,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
448
472
|
end
|
449
473
|
|
450
474
|
def gemfile_or_environment
|
451
|
-
if
|
475
|
+
if @app_tree.exists?("Gemfile")
|
452
476
|
"Gemfile"
|
453
477
|
else
|
454
478
|
"config/environment.rb"
|