brakeman 1.9.5 → 2.0.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- 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]
|