brakeman 1.9.5 → 2.0.0.pre2
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 +27 -0
- data/README.md +5 -2
- data/bin/brakeman +20 -15
- data/lib/brakeman.rb +106 -80
- data/lib/brakeman/app_tree.rb +22 -11
- data/lib/brakeman/call_index.rb +4 -4
- data/lib/brakeman/checks/base_check.rb +33 -5
- data/lib/brakeman/checks/check_basic_auth.rb +2 -1
- data/lib/brakeman/checks/check_content_tag.rb +8 -29
- data/lib/brakeman/checks/check_cross_site_scripting.rb +10 -19
- data/lib/brakeman/checks/check_deserialize.rb +57 -0
- data/lib/brakeman/checks/check_execute.rb +3 -11
- data/lib/brakeman/checks/check_file_access.rb +1 -14
- data/lib/brakeman/checks/check_forgery_setting.rb +5 -4
- data/lib/brakeman/checks/check_link_to.rb +4 -15
- data/lib/brakeman/checks/check_link_to_href.rb +1 -8
- data/lib/brakeman/checks/check_mass_assignment.rb +6 -2
- data/lib/brakeman/checks/check_model_attributes.rb +1 -0
- data/lib/brakeman/checks/check_model_serialize.rb +2 -1
- data/lib/brakeman/checks/check_render.rb +2 -15
- data/lib/brakeman/checks/check_select_tag.rb +1 -1
- data/lib/brakeman/checks/check_select_vulnerability.rb +2 -2
- data/lib/brakeman/checks/check_send.rb +3 -0
- data/lib/brakeman/checks/check_session_settings.rb +2 -3
- data/lib/brakeman/checks/check_skip_before_filter.rb +5 -2
- data/lib/brakeman/checks/check_sql.rb +59 -50
- data/lib/brakeman/checks/check_symbol_dos.rb +5 -14
- data/lib/brakeman/checks/check_unsafe_reflection.rb +2 -15
- data/lib/brakeman/options.rb +14 -9
- data/lib/brakeman/processors/controller_alias_processor.rb +4 -10
- data/lib/brakeman/processors/controller_processor.rb +53 -14
- data/lib/brakeman/processors/lib/find_all_calls.rb +40 -40
- data/lib/brakeman/processors/lib/processor_helper.rb +5 -1
- data/lib/brakeman/processors/lib/rails3_config_processor.rb +8 -0
- data/lib/brakeman/processors/output_processor.rb +23 -1
- data/lib/brakeman/report.rb +28 -14
- data/lib/brakeman/scanner.rb +5 -3
- data/lib/brakeman/tracker.rb +7 -7
- data/lib/brakeman/util.rb +5 -3
- data/lib/brakeman/version.rb +1 -1
- data/lib/brakeman/warning.rb +12 -9
- data/lib/ruby_parser/bm_sexp.rb +5 -1
- data/lib/tasks/brakeman.rake +10 -0
- metadata +11 -9
- data/lib/brakeman/checks/check_yaml_load.rb +0 -55
data/lib/brakeman/call_index.rb
CHANGED
@@ -82,13 +82,13 @@ class Brakeman::CallIndex
|
|
82
82
|
def remove_indexes_by_class classes
|
83
83
|
@calls_by_method.each do |name, calls|
|
84
84
|
calls.delete_if do |call|
|
85
|
-
call[:location][
|
85
|
+
call[:location][:type] == :class and classes.include? call[:location][:class]
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
89
|
@calls_by_target.each do |name, calls|
|
90
90
|
calls.delete_if do |call|
|
91
|
-
call[:location][
|
91
|
+
call[:location][:type] == :class and classes.include? call[:location][:class]
|
92
92
|
end
|
93
93
|
end
|
94
94
|
end
|
@@ -206,8 +206,8 @@ class Brakeman::CallIndex
|
|
206
206
|
end
|
207
207
|
|
208
208
|
def from_template call, template_name
|
209
|
-
return false unless call[:location][
|
209
|
+
return false unless call[:location][:type] == :template
|
210
210
|
return true if template_name.nil?
|
211
|
-
call[:location][
|
211
|
+
call[:location][:template] == template_name
|
212
212
|
end
|
213
213
|
end
|
@@ -12,6 +12,14 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
12
12
|
CONFIDENCE = { :high => 0, :med => 1, :low => 2 }
|
13
13
|
|
14
14
|
Match = Struct.new(:type, :match)
|
15
|
+
|
16
|
+
class << self
|
17
|
+
attr_accessor :name
|
18
|
+
|
19
|
+
def inherited(subclass)
|
20
|
+
subclass.name = subclass.to_s.match(/^Brakeman::(.*)$/)[1]
|
21
|
+
end
|
22
|
+
end
|
15
23
|
|
16
24
|
#Initialize Check with Checks.
|
17
25
|
def initialize(app_tree, tracker)
|
@@ -26,12 +34,12 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
26
34
|
@active_record_models = nil
|
27
35
|
@mass_assign_disabled = nil
|
28
36
|
@has_user_input = nil
|
29
|
-
@safe_input_attributes = Set[:to_i, :to_f, :arel_table]
|
37
|
+
@safe_input_attributes = Set[:to_i, :to_f, :arel_table, :id]
|
30
38
|
end
|
31
39
|
|
32
40
|
#Add result to result list, which is used to check for duplicates
|
33
41
|
def add_result result, location = nil
|
34
|
-
location ||= (@current_template && @current_template[:name]) || @current_class || @current_module || @current_set || result[:location][
|
42
|
+
location ||= (@current_template && @current_template[:name]) || @current_class || @current_module || @current_set || result[:location][:class] || result[:location][:template]
|
35
43
|
location = location[:name] if location.is_a? Hash
|
36
44
|
location = location.to_sym
|
37
45
|
|
@@ -116,9 +124,11 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
116
124
|
|
117
125
|
#Report a warning
|
118
126
|
def warn options
|
119
|
-
extra_opts = { :check => self.class.to_s
|
127
|
+
extra_opts = { :check => self.class.to_s }
|
128
|
+
|
120
129
|
warning = Brakeman::Warning.new(options.merge(extra_opts))
|
121
130
|
warning.file = file_for warning
|
131
|
+
warning.relative_path = relative_path(warning.file)
|
122
132
|
|
123
133
|
@warnings << warning
|
124
134
|
end
|
@@ -163,7 +173,6 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
163
173
|
@mass_assign_disabled = false
|
164
174
|
|
165
175
|
if version_between?("3.1.0", "3.9.9") and
|
166
|
-
tracker.config[:rails] and
|
167
176
|
tracker.config[:rails][:active_record] and
|
168
177
|
tracker.config[:rails][:active_record][:whitelist_attributes] == Sexp.new(:true)
|
169
178
|
|
@@ -244,7 +253,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
244
253
|
raise ArgumentError
|
245
254
|
end
|
246
255
|
|
247
|
-
location ||= (@current_template && @current_template[:name]) || @current_class || @current_module || @current_set || result[:location][
|
256
|
+
location ||= (@current_template && @current_template[:name]) || @current_class || @current_module || @current_set || result[:location][:class] || result[:location][:template]
|
248
257
|
|
249
258
|
location = location[:name] if location.is_a? Hash
|
250
259
|
location = location.to_sym
|
@@ -507,4 +516,23 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
507
516
|
|
508
517
|
@active_record_models
|
509
518
|
end
|
519
|
+
|
520
|
+
def friendly_type_of input_type
|
521
|
+
if input_type.is_a? Match
|
522
|
+
input_type = input_type.type
|
523
|
+
end
|
524
|
+
|
525
|
+
case input_type
|
526
|
+
when :params
|
527
|
+
"parameter value"
|
528
|
+
when :cookies
|
529
|
+
"cookie value"
|
530
|
+
when :request
|
531
|
+
"request value"
|
532
|
+
when :model
|
533
|
+
"model attribute"
|
534
|
+
else
|
535
|
+
"user input"
|
536
|
+
end
|
537
|
+
end
|
510
538
|
end
|
@@ -25,7 +25,8 @@ class Brakeman::CheckBasicAuth < Brakeman::BaseCheck
|
|
25
25
|
:warning_code => :basic_auth_password,
|
26
26
|
:message => "Basic authentication password stored in source code",
|
27
27
|
:code => call,
|
28
|
-
:confidence => 0
|
28
|
+
:confidence => 0,
|
29
|
+
:file => controller[:file]
|
29
30
|
|
30
31
|
break
|
31
32
|
end
|
@@ -45,7 +45,7 @@ class Brakeman::CheckContentTag < Brakeman::CheckCrossSiteScripting
|
|
45
45
|
|
46
46
|
call = result[:call] = result[:call].dup
|
47
47
|
|
48
|
-
args = call.arglist
|
48
|
+
args = call.arglist
|
49
49
|
|
50
50
|
tag_name = args[1]
|
51
51
|
content = args[2]
|
@@ -94,19 +94,12 @@ class Brakeman::CheckContentTag < Brakeman::CheckCrossSiteScripting
|
|
94
94
|
end
|
95
95
|
|
96
96
|
if input = has_immediate_user_input?(arg)
|
97
|
-
|
98
|
-
when :params
|
99
|
-
message = "Unescaped parameter value in content_tag"
|
100
|
-
when :cookies
|
101
|
-
message = "Unescaped cookie value in content_tag"
|
102
|
-
else
|
103
|
-
message = "Unescaped user input value in content_tag"
|
104
|
-
end
|
97
|
+
message = "Unescaped #{friendly_type_of input} in content_tag"
|
105
98
|
|
106
99
|
add_result result
|
107
100
|
|
108
101
|
warn :result => result,
|
109
|
-
:warning_type => "Cross Site Scripting",
|
102
|
+
:warning_type => "Cross Site Scripting",
|
110
103
|
:warning_code => :xss_content_tag,
|
111
104
|
:message => message,
|
112
105
|
:user_input => input.match,
|
@@ -126,7 +119,7 @@ class Brakeman::CheckContentTag < Brakeman::CheckCrossSiteScripting
|
|
126
119
|
end
|
127
120
|
|
128
121
|
warn :result => result,
|
129
|
-
:warning_type => "Cross Site Scripting",
|
122
|
+
:warning_type => "Cross Site Scripting",
|
130
123
|
:warning_code => :xss_content_tag,
|
131
124
|
:message => "Unescaped model attribute in content_tag",
|
132
125
|
:user_input => match,
|
@@ -135,28 +128,14 @@ class Brakeman::CheckContentTag < Brakeman::CheckCrossSiteScripting
|
|
135
128
|
end
|
136
129
|
|
137
130
|
elsif @matched
|
138
|
-
|
139
|
-
|
140
|
-
case @matched.type
|
141
|
-
when :model
|
142
|
-
return if tracker.options[:ignore_model_output]
|
143
|
-
message << "model attribute"
|
144
|
-
when :params
|
145
|
-
message << "parameter"
|
146
|
-
when :cookies
|
147
|
-
message << "cookie"
|
148
|
-
when :session
|
149
|
-
message << "session"
|
150
|
-
else
|
151
|
-
message << "user input"
|
152
|
-
end
|
131
|
+
return if @matched.type == :model and tracker.options[:ignore_model_output]
|
153
132
|
|
154
|
-
message
|
133
|
+
message = "Unescaped #{friendly_type_of @matched} in content_tag"
|
155
134
|
|
156
135
|
add_result result
|
157
136
|
|
158
|
-
warn :result => result,
|
159
|
-
:warning_type => "Cross Site Scripting",
|
137
|
+
warn :result => result,
|
138
|
+
:warning_type => "Cross Site Scripting",
|
160
139
|
:warning_code => :xss_content_tag,
|
161
140
|
:message => message,
|
162
141
|
:user_input => @matched.match,
|
@@ -62,10 +62,17 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
|
|
62
62
|
initializers = tracker.check_initializers :ActiveSupport, :escape_html_entities_in_json=
|
63
63
|
initializers.each {|result| json_escape_on = true?(result.call.first_arg) }
|
64
64
|
|
65
|
+
if tracker.config[:rails][:active_support] and
|
66
|
+
true? tracker.config[:rails][:active_support][:escape_html_entities_in_json]
|
67
|
+
|
68
|
+
json_escape_on = true
|
69
|
+
end
|
70
|
+
|
65
71
|
if !json_escape_on or version_between? "0.0.0", "2.0.99"
|
66
72
|
@known_dangerous << :to_json
|
67
73
|
Brakeman.debug("Automatic to_json escaping not enabled, consider to_json dangerous")
|
68
74
|
else
|
75
|
+
@safe_input_attributes << :to_json
|
69
76
|
Brakeman.debug("Automatic to_json escaping is enabled.")
|
70
77
|
end
|
71
78
|
|
@@ -97,16 +104,7 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
|
|
97
104
|
if input = has_immediate_user_input?(out)
|
98
105
|
add_result exp
|
99
106
|
|
100
|
-
|
101
|
-
when :params
|
102
|
-
message = "Unescaped parameter value"
|
103
|
-
when :cookies
|
104
|
-
message = "Unescaped cookie value"
|
105
|
-
when :request
|
106
|
-
message = "Unescaped request value"
|
107
|
-
else
|
108
|
-
message = "Unescaped user input value"
|
109
|
-
end
|
107
|
+
message = "Unescaped #{friendly_type_of input}"
|
110
108
|
|
111
109
|
warn :template => @current_template,
|
112
110
|
:warning_type => "Cross Site Scripting",
|
@@ -187,15 +185,8 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
|
|
187
185
|
message = nil
|
188
186
|
|
189
187
|
if @matched
|
190
|
-
|
191
|
-
|
192
|
-
unless tracker.options[:ignore_model_output]
|
193
|
-
message = "Unescaped model attribute"
|
194
|
-
end
|
195
|
-
when :params
|
196
|
-
message = "Unescaped parameter value"
|
197
|
-
when :cookies
|
198
|
-
message = "Unescaped cookie value"
|
188
|
+
unless @matched.type and tracker.options[:ignore_model_output]
|
189
|
+
message = "Unescaped #{friendly_type_of @matched}"
|
199
190
|
end
|
200
191
|
|
201
192
|
if message and not duplicate? exp
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'brakeman/checks/base_check'
|
2
|
+
|
3
|
+
class Brakeman::CheckDeserialize < Brakeman::BaseCheck
|
4
|
+
Brakeman::Checks.add self
|
5
|
+
|
6
|
+
@description = "Checks for unsafe deserialization of objects"
|
7
|
+
|
8
|
+
def run_check
|
9
|
+
check_yaml
|
10
|
+
check_csv
|
11
|
+
check_marshal
|
12
|
+
end
|
13
|
+
|
14
|
+
def check_yaml
|
15
|
+
check_methods :YAML, :load, :load_documents, :load_stream, :parse_documents, :parse_stream
|
16
|
+
end
|
17
|
+
|
18
|
+
def check_csv
|
19
|
+
check_methods :CSV, :load
|
20
|
+
end
|
21
|
+
|
22
|
+
def check_marshal
|
23
|
+
check_methods :Marshal, :load, :restore
|
24
|
+
end
|
25
|
+
|
26
|
+
def check_methods target, *methods
|
27
|
+
tracker.find_call(:target => target, :methods => methods ).each do |result|
|
28
|
+
check_deserialize result, target
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def check_deserialize result, target, arg = nil
|
33
|
+
return if duplicate? result
|
34
|
+
add_result result
|
35
|
+
|
36
|
+
arg ||= result[:call].first_arg
|
37
|
+
method = result[:call].method
|
38
|
+
|
39
|
+
if input = has_immediate_user_input?(arg)
|
40
|
+
confidence = CONFIDENCE[:high]
|
41
|
+
elsif input = include_user_input?(arg)
|
42
|
+
confidence = CONFIDENCE[:med]
|
43
|
+
end
|
44
|
+
|
45
|
+
if confidence
|
46
|
+
message = "#{target}.#{method} called with #{friendly_type_of input}"
|
47
|
+
|
48
|
+
warn :result => result,
|
49
|
+
:warning_type => "Remote Code Execution",
|
50
|
+
:warning_code => :unsafe_deserialize,
|
51
|
+
:message => message,
|
52
|
+
:user_input => input.match,
|
53
|
+
:confidence => confidence,
|
54
|
+
:link_path => "unsafe_deserialization"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -84,20 +84,12 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
|
|
84
84
|
user_input = nil
|
85
85
|
end
|
86
86
|
|
87
|
-
|
87
|
+
warn :result => result,
|
88
|
+
:warning_type => "Command Injection",
|
88
89
|
:warning_code => :command_injection,
|
89
90
|
:message => "Possible command injection",
|
90
91
|
:code => exp,
|
91
92
|
:user_input => user_input,
|
92
|
-
:confidence => confidence
|
93
|
-
|
94
|
-
if result[:location][0] == :template
|
95
|
-
warning[:template] = result[:location][1]
|
96
|
-
else
|
97
|
-
warning[:class] = result[:location][1]
|
98
|
-
warning[:method] = result[:location][2]
|
99
|
-
end
|
100
|
-
|
101
|
-
warn warning
|
93
|
+
:confidence => confidence
|
102
94
|
end
|
103
95
|
end
|
@@ -48,20 +48,7 @@ class Brakeman::CheckFileAccess < Brakeman::BaseCheck
|
|
48
48
|
end
|
49
49
|
|
50
50
|
if match
|
51
|
-
|
52
|
-
when :params
|
53
|
-
message = "Parameter"
|
54
|
-
when :cookies
|
55
|
-
message = "Cookie"
|
56
|
-
when :request
|
57
|
-
message = "Request"
|
58
|
-
when :model
|
59
|
-
message = "Model attribute"
|
60
|
-
else
|
61
|
-
message = "User input"
|
62
|
-
end
|
63
|
-
|
64
|
-
message << " value used in file name"
|
51
|
+
message = "#{friendly_type_of(match).capitalize} used in file name"
|
65
52
|
|
66
53
|
warn :result => result,
|
67
54
|
:warning_type => "File Access",
|
@@ -11,15 +11,15 @@ class Brakeman::CheckForgerySetting < Brakeman::BaseCheck
|
|
11
11
|
|
12
12
|
def run_check
|
13
13
|
app_controller = tracker.controllers[:ApplicationController]
|
14
|
-
if tracker.config[:rails] and
|
15
|
-
tracker.config[:rails][:action_controller] and
|
14
|
+
if tracker.config[:rails][:action_controller] and
|
16
15
|
tracker.config[:rails][:action_controller][:allow_forgery_protection] == Sexp.new(:false)
|
17
16
|
|
18
17
|
warn :controller => :ApplicationController,
|
19
18
|
:warning_type => "Cross-Site Request Forgery",
|
20
19
|
:warning_code => :csrf_protection_disabled,
|
21
20
|
:message => "Forgery protection is disabled",
|
22
|
-
:confidence => CONFIDENCE[:high]
|
21
|
+
:confidence => CONFIDENCE[:high],
|
22
|
+
:file => app_controller[:file]
|
23
23
|
|
24
24
|
elsif app_controller and not app_controller[:options][:protect_from_forgery]
|
25
25
|
|
@@ -27,7 +27,8 @@ class Brakeman::CheckForgerySetting < Brakeman::BaseCheck
|
|
27
27
|
:warning_type => "Cross-Site Request Forgery",
|
28
28
|
:warning_code => :csrf_protection_missing,
|
29
29
|
:message => "'protect_from_forgery' should be called in ApplicationController",
|
30
|
-
:confidence => CONFIDENCE[:high]
|
30
|
+
:confidence => CONFIDENCE[:high],
|
31
|
+
:file => app_controller[:file]
|
31
32
|
|
32
33
|
elsif version_between? "2.1.0", "2.3.10"
|
33
34
|
|
@@ -68,14 +68,7 @@ class Brakeman::CheckLinkTo < Brakeman::CheckCrossSiteScripting
|
|
68
68
|
input = has_immediate_user_input?(argument)
|
69
69
|
return false unless input
|
70
70
|
|
71
|
-
|
72
|
-
when :params
|
73
|
-
message = "Unescaped parameter value in link_to"
|
74
|
-
when :cookies
|
75
|
-
message = "Unescaped cookie value in link_to"
|
76
|
-
else
|
77
|
-
message = "Unescaped user input value in link_to"
|
78
|
-
end
|
71
|
+
message = "Unescaped #{friendly_type_of input} in link_to"
|
79
72
|
|
80
73
|
warn_xss(result, message, input.match, CONFIDENCE[:high])
|
81
74
|
end
|
@@ -96,15 +89,11 @@ class Brakeman::CheckLinkTo < Brakeman::CheckCrossSiteScripting
|
|
96
89
|
# Check if we should warn about the matched result
|
97
90
|
def check_matched(result, matched = nil)
|
98
91
|
return false unless matched
|
99
|
-
|
92
|
+
return false if matched.type == :model and tracker.options[:ignore_model_output]
|
100
93
|
|
101
|
-
|
102
|
-
message = "Unescaped model attribute in link_to"
|
103
|
-
elsif matched.type == :params
|
104
|
-
message = "Unescaped parameter value in link_to"
|
105
|
-
end
|
94
|
+
message = "Unescaped #{friendly_type_of matched} in link_to"
|
106
95
|
|
107
|
-
|
96
|
+
warn_xss(result, message, @matched.match, CONFIDENCE[:med])
|
108
97
|
end
|
109
98
|
|
110
99
|
# Create a warn for this xss
|
@@ -42,14 +42,7 @@ class Brakeman::CheckLinkToHref < Brakeman::CheckLinkTo
|
|
42
42
|
|
43
43
|
|
44
44
|
if input = has_immediate_user_input?(url_arg)
|
45
|
-
|
46
|
-
when :params
|
47
|
-
message = "Unsafe parameter value in link_to href"
|
48
|
-
when :cookies
|
49
|
-
message = "Unsafe cookie value in link_to href"
|
50
|
-
else
|
51
|
-
message = "Unsafe user input value in link_to href"
|
52
|
-
end
|
45
|
+
message = "Unsafe #{friendly_type_of input} in link_to href"
|
53
46
|
|
54
47
|
unless duplicate? result
|
55
48
|
add_result result
|
@@ -63,8 +63,12 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
|
|
63
63
|
|
64
64
|
if call? first_arg and (first_arg.method == :slice or first_arg.method == :only)
|
65
65
|
return
|
66
|
-
elsif not node_type? first_arg, :hash
|
67
|
-
|
66
|
+
elsif not node_type? first_arg, :hash
|
67
|
+
if attr_protected
|
68
|
+
confidence = CONFIDENCE[:med]
|
69
|
+
else
|
70
|
+
confidence = CONFIDENCE[:high]
|
71
|
+
end
|
68
72
|
user_input = input.match
|
69
73
|
else
|
70
74
|
confidence = CONFIDENCE[:low]
|