brakeman-lib 4.10.1 → 5.0.4
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.
- checksums.yaml +4 -4
- data/CHANGES.md +44 -0
- data/README.md +11 -2
- data/lib/brakeman.rb +17 -4
- data/lib/brakeman/app_tree.rb +36 -3
- data/lib/brakeman/checks/base_check.rb +7 -1
- data/lib/brakeman/checks/check_execute.rb +1 -0
- data/lib/brakeman/checks/check_mass_assignment.rb +4 -6
- data/lib/brakeman/checks/check_sanitize_methods.rb +2 -1
- data/lib/brakeman/checks/check_sql.rb +1 -1
- data/lib/brakeman/checks/check_unsafe_reflection_methods.rb +68 -0
- data/lib/brakeman/checks/check_verb_confusion.rb +75 -0
- data/lib/brakeman/file_parser.rb +19 -18
- data/lib/brakeman/options.rb +5 -1
- data/lib/brakeman/parsers/template_parser.rb +26 -3
- data/lib/brakeman/processors/alias_processor.rb +39 -12
- data/lib/brakeman/processors/base_processor.rb +4 -4
- data/lib/brakeman/processors/lib/file_type_detector.rb +64 -0
- data/lib/brakeman/processors/lib/rails3_config_processor.rb +16 -16
- data/lib/brakeman/processors/lib/rails4_config_processor.rb +2 -1
- data/lib/brakeman/report.rb +8 -0
- data/lib/brakeman/report/report_base.rb +0 -2
- data/lib/brakeman/report/report_csv.rb +37 -60
- data/lib/brakeman/report/report_junit.rb +2 -2
- data/lib/brakeman/report/report_sarif.rb +1 -1
- data/lib/brakeman/report/report_sonar.rb +38 -0
- data/lib/brakeman/report/report_tabs.rb +1 -1
- data/lib/brakeman/report/report_text.rb +1 -1
- data/lib/brakeman/rescanner.rb +7 -5
- data/lib/brakeman/scanner.rb +44 -18
- data/lib/brakeman/tracker.rb +6 -0
- data/lib/brakeman/tracker/config.rb +73 -0
- data/lib/brakeman/util.rb +7 -2
- data/lib/brakeman/version.rb +1 -1
- data/lib/brakeman/warning.rb +10 -2
- data/lib/brakeman/warning_codes.rb +2 -0
- metadata +8 -4
@@ -1,72 +1,49 @@
|
|
1
1
|
require 'csv'
|
2
|
-
require "brakeman/report/report_table"
|
3
2
|
|
4
|
-
class Brakeman::Report::CSV < Brakeman::Report::
|
3
|
+
class Brakeman::Report::CSV < Brakeman::Report::Base
|
5
4
|
def generate_report
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
output << "TEMPLATES\n\n"
|
25
|
-
output << table_to_csv(generate_templates) << "\n"
|
5
|
+
headers = [
|
6
|
+
"Confidence",
|
7
|
+
"Warning Type",
|
8
|
+
"File",
|
9
|
+
"Line",
|
10
|
+
"Message",
|
11
|
+
"Code",
|
12
|
+
"User Input",
|
13
|
+
"Check Name",
|
14
|
+
"Warning Code",
|
15
|
+
"Fingerprint",
|
16
|
+
"Link"
|
17
|
+
]
|
18
|
+
|
19
|
+
rows = tracker.filtered_warnings.sort_by do |w|
|
20
|
+
[w.confidence, w.warning_type, w.file, w.line, w.fingerprint]
|
21
|
+
end.map do |warning|
|
22
|
+
generate_row(headers, warning)
|
26
23
|
end
|
27
24
|
|
28
|
-
|
29
|
-
output << "ERRORS\n" << table_to_csv(res) << "\n" if res
|
30
|
-
|
31
|
-
res = generate_warnings
|
32
|
-
output << "SECURITY WARNINGS\n" << table_to_csv(res) << "\n" if res
|
25
|
+
table = CSV::Table.new(rows)
|
33
26
|
|
34
|
-
|
35
|
-
res = generate_controller_warnings
|
36
|
-
output << table_to_csv(res) << "\n" if res
|
37
|
-
|
38
|
-
output << "Model Warnings\n"
|
39
|
-
res = generate_model_warnings
|
40
|
-
output << table_to_csv(res) << "\n" if res
|
41
|
-
|
42
|
-
res = generate_template_warnings
|
43
|
-
output << "Template Warnings\n"
|
44
|
-
output << table_to_csv(res) << "\n" if res
|
45
|
-
|
46
|
-
output
|
27
|
+
table.to_csv
|
47
28
|
end
|
48
29
|
|
49
|
-
|
50
|
-
|
51
|
-
header = CSV.generate_line(["Application Path", "Report Generation Time", "Checks Performed", "Rails Version"])
|
52
|
-
header << CSV.generate_line([File.expand_path(tracker.app_path), Time.now.to_s, checks.checks_run.sort.join(", "), rails_version])
|
53
|
-
"BRAKEMAN REPORT\n\n" + header
|
30
|
+
def generate_row headers, warning
|
31
|
+
CSV::Row.new headers, warning_row(warning)
|
54
32
|
end
|
55
33
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
output
|
34
|
+
def warning_row warning
|
35
|
+
[
|
36
|
+
warning.confidence_name,
|
37
|
+
warning.warning_type,
|
38
|
+
warning_file(warning),
|
39
|
+
warning.line,
|
40
|
+
warning.message,
|
41
|
+
warning.code && warning.format_code(false),
|
42
|
+
warning.user_input && warning.format_user_input(false),
|
43
|
+
warning.check_name,
|
44
|
+
warning.warning_code,
|
45
|
+
warning.fingerprint,
|
46
|
+
warning.link,
|
47
|
+
]
|
71
48
|
end
|
72
49
|
end
|
@@ -47,7 +47,7 @@ class Brakeman::Report::JUnit < Brakeman::Report::Base
|
|
47
47
|
warning.add_attribute 'brakeman:file', warning_file(w)
|
48
48
|
warning.add_attribute 'brakeman:line', w.line
|
49
49
|
warning.add_attribute 'brakeman:fingerprint', w.fingerprint
|
50
|
-
warning.add_attribute 'brakeman:confidence',
|
50
|
+
warning.add_attribute 'brakeman:confidence', w.confidence_name
|
51
51
|
warning.add_attribute 'brakeman:code', w.format_code
|
52
52
|
warning.add_text w.to_s
|
53
53
|
}
|
@@ -88,7 +88,7 @@ class Brakeman::Report::JUnit < Brakeman::Report::Base
|
|
88
88
|
failure.add_attribute 'brakeman:fingerprint', warning.fingerprint
|
89
89
|
failure.add_attribute 'brakeman:file', warning_file(warning)
|
90
90
|
failure.add_attribute 'brakeman:line', warning.line
|
91
|
-
failure.add_attribute 'brakeman:confidence',
|
91
|
+
failure.add_attribute 'brakeman:confidence', warning.confidence_name
|
92
92
|
failure.add_attribute 'brakeman:code', warning.format_code
|
93
93
|
failure.add_text warning.to_s
|
94
94
|
}
|
@@ -27,7 +27,7 @@ class Brakeman::Report::SARIF < Brakeman::Report::Base
|
|
27
27
|
def rules
|
28
28
|
@rules ||= unique_warnings_by_warning_code.map do |warning|
|
29
29
|
rule_id = render_id warning
|
30
|
-
check_name = warning.
|
30
|
+
check_name = warning.check_name
|
31
31
|
check_description = render_message check_descriptions[check_name]
|
32
32
|
{
|
33
33
|
:id => rule_id,
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class Brakeman::Report::Sonar < Brakeman::Report::Base
|
2
|
+
def generate_report
|
3
|
+
report_object = {
|
4
|
+
issues: all_warnings.map { |warning| issue_json(warning) }
|
5
|
+
}
|
6
|
+
return JSON.pretty_generate report_object
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def issue_json(warning)
|
12
|
+
{
|
13
|
+
engineId: "Brakeman",
|
14
|
+
ruleId: warning.warning_code,
|
15
|
+
type: "VULNERABILITY",
|
16
|
+
severity: severity_level_for(warning.confidence),
|
17
|
+
primaryLocation: {
|
18
|
+
message: warning.message,
|
19
|
+
filePath: warning.file.relative,
|
20
|
+
textRange: {
|
21
|
+
"startLine": warning.line || 1,
|
22
|
+
"endLine": warning.line || 1,
|
23
|
+
}
|
24
|
+
},
|
25
|
+
effortMinutes: (4 - warning.confidence) * 15
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def severity_level_for(confidence)
|
30
|
+
if confidence == 0
|
31
|
+
"CRITICAL"
|
32
|
+
elsif confidence == 1
|
33
|
+
"MAJOR"
|
34
|
+
else
|
35
|
+
"MINOR"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -10,7 +10,7 @@ class Brakeman::Report::Tabs < Brakeman::Report::Table
|
|
10
10
|
self.send(meth).map do |w|
|
11
11
|
line = w.line || 0
|
12
12
|
w.warning_type.gsub!(/[^\w\s]/, ' ')
|
13
|
-
"#{(w.file.absolute)}\t#{line}\t#{w.warning_type}\t#{category}\t#{w.format_message}\t#{
|
13
|
+
"#{(w.file.absolute)}\t#{line}\t#{w.warning_type}\t#{category}\t#{w.format_message}\t#{w.confidence_name}"
|
14
14
|
end.join "\n"
|
15
15
|
|
16
16
|
end.join "\n"
|
@@ -160,7 +160,7 @@ class Brakeman::Report::Text < Brakeman::Report::Base
|
|
160
160
|
when :category
|
161
161
|
label('Category', w.warning_type.to_s)
|
162
162
|
when :check
|
163
|
-
label('Check', w.
|
163
|
+
label('Check', w.check_name)
|
164
164
|
when :message
|
165
165
|
label('Message', w.message)
|
166
166
|
when :code
|
data/lib/brakeman/rescanner.rb
CHANGED
@@ -132,10 +132,11 @@ class Brakeman::Rescanner < Brakeman::Scanner
|
|
132
132
|
template_name = template_path_to_name(path)
|
133
133
|
|
134
134
|
tracker.reset_template template_name
|
135
|
-
fp = Brakeman::FileParser.new(tracker)
|
135
|
+
fp = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
|
136
136
|
template_parser = Brakeman::TemplateParser.new(tracker, fp)
|
137
137
|
template_parser.parse_template path, path.read
|
138
|
-
|
138
|
+
tracker.add_errors(fp.errors)
|
139
|
+
process_template fp.file_list.first
|
139
140
|
|
140
141
|
@processor.process_template_alias tracker.templates[template_name]
|
141
142
|
|
@@ -390,9 +391,10 @@ class Brakeman::Rescanner < Brakeman::Scanner
|
|
390
391
|
|
391
392
|
def parse_ruby_files list
|
392
393
|
paths = list.select(&:exists?)
|
393
|
-
file_parser = Brakeman::FileParser.new(tracker)
|
394
|
-
file_parser.parse_files paths
|
395
|
-
file_parser.
|
394
|
+
file_parser = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
|
395
|
+
file_parser.parse_files paths
|
396
|
+
tracker.add_errors(file_parser.errors)
|
397
|
+
file_parser.file_list
|
396
398
|
end
|
397
399
|
end
|
398
400
|
|
data/lib/brakeman/scanner.rb
CHANGED
@@ -7,6 +7,7 @@ begin
|
|
7
7
|
require 'brakeman/app_tree'
|
8
8
|
require 'brakeman/file_parser'
|
9
9
|
require 'brakeman/parsers/template_parser'
|
10
|
+
require 'brakeman/processors/lib/file_type_detector'
|
10
11
|
rescue LoadError => e
|
11
12
|
$stderr.puts e.message
|
12
13
|
$stderr.puts "Please install the appropriate dependency."
|
@@ -23,7 +24,10 @@ class Brakeman::Scanner
|
|
23
24
|
@app_tree = Brakeman::AppTree.from_options(options)
|
24
25
|
|
25
26
|
if (!@app_tree.root || !@app_tree.exists?("app")) && !options[:force_scan]
|
26
|
-
|
27
|
+
message = "Please supply the path to a Rails application (looking in #{@app_tree.root}).\n" <<
|
28
|
+
" Use `--force` to run a scan anyway."
|
29
|
+
|
30
|
+
raise Brakeman::NoApplication, message
|
27
31
|
end
|
28
32
|
|
29
33
|
@processor = processor || Brakeman::Processor.new(@app_tree, options)
|
@@ -43,6 +47,8 @@ class Brakeman::Scanner
|
|
43
47
|
process_config
|
44
48
|
Brakeman.notify "Parsing files..."
|
45
49
|
parse_files
|
50
|
+
Brakeman.notify "Detecting file types..."
|
51
|
+
detect_file_types
|
46
52
|
Brakeman.notify "Processing initializers..."
|
47
53
|
process_initializers
|
48
54
|
Brakeman.notify "Processing libs..."
|
@@ -65,29 +71,47 @@ class Brakeman::Scanner
|
|
65
71
|
end
|
66
72
|
|
67
73
|
def parse_files
|
68
|
-
fp = Brakeman::FileParser.new tracker
|
69
|
-
|
70
|
-
files = {
|
71
|
-
:initializers => @app_tree.initializer_paths,
|
72
|
-
:controllers => @app_tree.controller_paths,
|
73
|
-
:models => @app_tree.model_paths
|
74
|
-
}
|
75
|
-
|
76
|
-
unless options[:skip_libs]
|
77
|
-
files[:libs] = @app_tree.lib_paths
|
78
|
-
end
|
74
|
+
fp = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
|
79
75
|
|
80
|
-
|
81
|
-
fp.parse_files paths, name
|
82
|
-
end
|
76
|
+
fp.parse_files tracker.app_tree.ruby_file_paths
|
83
77
|
|
84
78
|
template_parser = Brakeman::TemplateParser.new(tracker, fp)
|
85
79
|
|
86
|
-
fp.read_files(@app_tree.template_paths
|
80
|
+
fp.read_files(@app_tree.template_paths) do |path, contents|
|
87
81
|
template_parser.parse_template path, contents
|
88
82
|
end
|
89
83
|
|
90
|
-
|
84
|
+
# Collect errors raised during parsing
|
85
|
+
tracker.add_errors(fp.errors)
|
86
|
+
|
87
|
+
@parsed_files = fp.file_list
|
88
|
+
end
|
89
|
+
|
90
|
+
def detect_file_types
|
91
|
+
@file_list = {
|
92
|
+
controllers: [],
|
93
|
+
initializers: [],
|
94
|
+
libs: [],
|
95
|
+
models: [],
|
96
|
+
templates: [],
|
97
|
+
}
|
98
|
+
|
99
|
+
detector = Brakeman::FileTypeDetector.new
|
100
|
+
|
101
|
+
@parsed_files.each do |file|
|
102
|
+
if file.is_a? Brakeman::TemplateParser::TemplateFile
|
103
|
+
@file_list[:templates] << file
|
104
|
+
else
|
105
|
+
type = detector.detect_type(file)
|
106
|
+
unless type == :skip
|
107
|
+
if @file_list[type].nil?
|
108
|
+
raise type.to_s
|
109
|
+
else
|
110
|
+
@file_list[type] << file
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
91
115
|
end
|
92
116
|
|
93
117
|
#Process config/environment.rb and config/gems.rb
|
@@ -115,6 +139,8 @@ class Brakeman::Scanner
|
|
115
139
|
if @app_tree.exists? ".ruby-version"
|
116
140
|
tracker.config.set_ruby_version @app_tree.file_path(".ruby-version").read
|
117
141
|
end
|
142
|
+
|
143
|
+
tracker.config.load_rails_defaults
|
118
144
|
end
|
119
145
|
|
120
146
|
def process_config_file file
|
@@ -325,7 +351,7 @@ class Brakeman::Scanner
|
|
325
351
|
end
|
326
352
|
|
327
353
|
def parse_ruby_file file
|
328
|
-
fp = Brakeman::FileParser.new(
|
354
|
+
fp = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
|
329
355
|
fp.parse_ruby(file.read, file)
|
330
356
|
end
|
331
357
|
end
|
data/lib/brakeman/tracker.rb
CHANGED
@@ -149,5 +149,78 @@ module Brakeman
|
|
149
149
|
def session_settings
|
150
150
|
@rails.dig(:action_controller, :session)
|
151
151
|
end
|
152
|
+
|
153
|
+
|
154
|
+
# Set Rails config option value
|
155
|
+
# where path is an array of attributes, e.g.
|
156
|
+
#
|
157
|
+
# :action_controller, :perform_caching
|
158
|
+
#
|
159
|
+
# then this will set
|
160
|
+
#
|
161
|
+
# rails[:action_controller][:perform_caching] = value
|
162
|
+
def set_rails_config value, *path
|
163
|
+
config = self.rails
|
164
|
+
|
165
|
+
path[0..-2].each do |o|
|
166
|
+
config[o] ||= {}
|
167
|
+
|
168
|
+
option = config[o]
|
169
|
+
|
170
|
+
if not option.is_a? Hash
|
171
|
+
Brakeman.debug "[Notice] Skipping config setting: #{path.map(&:to_s).join(".")}"
|
172
|
+
return
|
173
|
+
end
|
174
|
+
|
175
|
+
config = option
|
176
|
+
end
|
177
|
+
|
178
|
+
config[path.last] = value
|
179
|
+
end
|
180
|
+
|
181
|
+
# Load defaults based on config.load_defaults value
|
182
|
+
# as documented here: https://guides.rubyonrails.org/configuring.html#results-of-config-load-defaults
|
183
|
+
def load_rails_defaults
|
184
|
+
return unless number? tracker.config.rails[:load_defaults]
|
185
|
+
|
186
|
+
version = tracker.config.rails[:load_defaults].value
|
187
|
+
true_value = Sexp.new(:true)
|
188
|
+
false_value = Sexp.new(:false)
|
189
|
+
|
190
|
+
if version >= 5.0
|
191
|
+
set_rails_config(true_value, :action_controller, :per_form_csrf_tokens)
|
192
|
+
set_rails_config(true_value, :action_controller, :forgery_protection_origin_check)
|
193
|
+
set_rails_config(true_value, :active_record, :belongs_to_required_by_default)
|
194
|
+
# Note: this may need to be changed, because ssl_options is a Hash
|
195
|
+
set_rails_config(true_value, :ssl_options, :hsts, :subdomains)
|
196
|
+
end
|
197
|
+
|
198
|
+
if version >= 5.1
|
199
|
+
set_rails_config(false_value, :assets, :unknown_asset_fallback)
|
200
|
+
set_rails_config(true_value, :action_view, :form_with_generates_remote_forms)
|
201
|
+
end
|
202
|
+
|
203
|
+
if version >= 5.2
|
204
|
+
set_rails_config(true_value, :active_record, :cache_versioning)
|
205
|
+
set_rails_config(true_value, :action_dispatch, :use_authenticated_cookie_encryption)
|
206
|
+
set_rails_config(true_value, :active_support, :use_authenticated_message_encryption)
|
207
|
+
set_rails_config(true_value, :active_support, :use_sha1_digests)
|
208
|
+
set_rails_config(true_value, :action_controller, :default_protect_from_forgery)
|
209
|
+
set_rails_config(true_value, :action_view, :form_with_generates_ids)
|
210
|
+
end
|
211
|
+
|
212
|
+
if version >= 6.0
|
213
|
+
set_rails_config(Sexp.new(:lit, :zeitwerk), :autoloader)
|
214
|
+
set_rails_config(false_value, :action_view, :default_enforce_utf8)
|
215
|
+
set_rails_config(true_value, :action_dispatch, :use_cookies_with_metadata)
|
216
|
+
set_rails_config(false_value, :action_dispatch, :return_only_media_type_on_content_type)
|
217
|
+
set_rails_config(Sexp.new(:str, 'ActionMailer::MailDeliveryJob'), :action_mailer, :delivery_job)
|
218
|
+
set_rails_config(true_value, :active_job, :return_false_on_aborted_enqueue)
|
219
|
+
set_rails_config(Sexp.new(:lit, :active_storage_analysis), :active_storage, :queues, :analysis)
|
220
|
+
set_rails_config(Sexp.new(:lit, :active_storage_purge), :active_storage, :queues, :purge)
|
221
|
+
set_rails_config(true_value, :active_storage, :replace_on_assign_to_many)
|
222
|
+
set_rails_config(true_value, :active_record, :collection_cache_versioning)
|
223
|
+
end
|
224
|
+
end
|
152
225
|
end
|
153
226
|
end
|
data/lib/brakeman/util.rb
CHANGED
@@ -368,8 +368,13 @@ module Brakeman::Util
|
|
368
368
|
#
|
369
369
|
# views/test/something.html.erb -> test/something
|
370
370
|
def template_path_to_name path
|
371
|
-
names = path.relative.split(
|
371
|
+
names = path.relative.split('/')
|
372
372
|
names.last.gsub!(/(\.(html|js)\..*|\.(rhtml|haml|erb|slim))$/, '')
|
373
|
-
|
373
|
+
|
374
|
+
if names.include? 'views'
|
375
|
+
names[(names.index('views') + 1)..-1]
|
376
|
+
else
|
377
|
+
names
|
378
|
+
end.join('/').to_sym
|
374
379
|
end
|
375
380
|
end
|