brakeman-min 0.5.2 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +529 -0
- data/README.md +74 -28
- data/bin/brakeman +60 -266
- data/lib/brakeman.rb +422 -0
- data/lib/brakeman/app_tree.rb +101 -0
- data/lib/brakeman/brakeman.rake +10 -0
- data/lib/brakeman/call_index.rb +215 -0
- data/lib/brakeman/checks.rb +180 -0
- data/lib/brakeman/checks/base_check.rb +538 -0
- data/lib/brakeman/checks/check_basic_auth.rb +89 -0
- data/lib/brakeman/checks/check_content_tag.rb +162 -0
- data/lib/brakeman/checks/check_cross_site_scripting.rb +334 -0
- data/lib/{checks → brakeman/checks}/check_default_routes.rb +13 -6
- data/lib/brakeman/checks/check_deserialize.rb +57 -0
- data/lib/brakeman/checks/check_digest_dos.rb +38 -0
- data/lib/brakeman/checks/check_escape_function.rb +21 -0
- data/lib/brakeman/checks/check_evaluation.rb +33 -0
- data/lib/brakeman/checks/check_execute.rb +98 -0
- data/lib/brakeman/checks/check_file_access.rb +62 -0
- data/lib/brakeman/checks/check_filter_skipping.rb +31 -0
- data/lib/brakeman/checks/check_forgery_setting.rb +54 -0
- data/lib/brakeman/checks/check_jruby_xml.rb +38 -0
- data/lib/brakeman/checks/check_json_parsing.rb +102 -0
- data/lib/brakeman/checks/check_link_to.rb +132 -0
- data/lib/brakeman/checks/check_link_to_href.rb +92 -0
- data/lib/{checks → brakeman/checks}/check_mail_to.rb +14 -13
- data/lib/brakeman/checks/check_mass_assignment.rb +143 -0
- data/lib/brakeman/checks/check_model_attr_accessible.rb +48 -0
- data/lib/brakeman/checks/check_model_attributes.rb +118 -0
- data/lib/brakeman/checks/check_model_serialize.rb +66 -0
- data/lib/{checks → brakeman/checks}/check_nested_attributes.rb +10 -6
- data/lib/brakeman/checks/check_quote_table_name.rb +40 -0
- data/lib/brakeman/checks/check_redirect.rb +177 -0
- data/lib/brakeman/checks/check_render.rb +62 -0
- data/lib/brakeman/checks/check_response_splitting.rb +21 -0
- data/lib/brakeman/checks/check_safe_buffer_manipulation.rb +31 -0
- data/lib/brakeman/checks/check_sanitize_methods.rb +54 -0
- data/lib/brakeman/checks/check_select_tag.rb +60 -0
- data/lib/brakeman/checks/check_select_vulnerability.rb +58 -0
- data/lib/brakeman/checks/check_send.rb +35 -0
- data/lib/brakeman/checks/check_send_file.rb +19 -0
- data/lib/brakeman/checks/check_session_settings.rb +145 -0
- data/lib/brakeman/checks/check_single_quotes.rb +101 -0
- data/lib/brakeman/checks/check_skip_before_filter.rb +62 -0
- data/lib/brakeman/checks/check_sql.rb +577 -0
- data/lib/brakeman/checks/check_strip_tags.rb +64 -0
- data/lib/brakeman/checks/check_symbol_dos.rb +67 -0
- data/lib/brakeman/checks/check_translate_bug.rb +45 -0
- data/lib/brakeman/checks/check_unsafe_reflection.rb +51 -0
- data/lib/brakeman/checks/check_validation_regex.rb +88 -0
- data/lib/brakeman/checks/check_without_protection.rb +64 -0
- data/lib/brakeman/checks/check_yaml_parsing.rb +121 -0
- data/lib/brakeman/differ.rb +66 -0
- data/lib/{format → brakeman/format}/style.css +28 -0
- data/lib/brakeman/options.rb +256 -0
- data/lib/brakeman/parsers/rails2_erubis.rb +6 -0
- data/lib/brakeman/parsers/rails2_xss_plugin_erubis.rb +48 -0
- data/lib/{scanner_erubis.rb → brakeman/parsers/rails3_erubis.rb} +8 -21
- data/lib/brakeman/processor.rb +102 -0
- data/lib/brakeman/processors/alias_processor.rb +780 -0
- data/lib/{processors → brakeman/processors}/base_processor.rb +90 -74
- data/lib/brakeman/processors/config_processor.rb +14 -0
- data/lib/brakeman/processors/controller_alias_processor.rb +334 -0
- data/lib/brakeman/processors/controller_processor.rb +265 -0
- data/lib/{processors → brakeman/processors}/erb_template_processor.rb +21 -19
- data/lib/brakeman/processors/erubis_template_processor.rb +96 -0
- data/lib/brakeman/processors/gem_processor.rb +59 -0
- data/lib/{processors → brakeman/processors}/haml_template_processor.rb +26 -21
- data/lib/brakeman/processors/lib/find_all_calls.rb +185 -0
- data/lib/{processors → brakeman/processors}/lib/find_call.rb +23 -28
- data/lib/brakeman/processors/lib/find_return_value.rb +134 -0
- data/lib/brakeman/processors/lib/processor_helper.rb +82 -0
- data/lib/{processors/config_processor.rb → brakeman/processors/lib/rails2_config_processor.rb} +32 -35
- data/lib/{processors → brakeman/processors}/lib/rails2_route_processor.rb +60 -52
- data/lib/brakeman/processors/lib/rails3_config_processor.rb +129 -0
- data/lib/brakeman/processors/lib/rails3_route_processor.rb +282 -0
- data/lib/{processors → brakeman/processors}/lib/render_helper.rb +54 -20
- data/lib/brakeman/processors/lib/route_helper.rb +62 -0
- data/lib/{processors → brakeman/processors}/library_processor.rb +24 -17
- data/lib/{processors → brakeman/processors}/model_processor.rb +46 -22
- data/lib/{processors → brakeman/processors}/output_processor.rb +34 -40
- data/lib/brakeman/processors/route_processor.rb +17 -0
- data/lib/brakeman/processors/slim_template_processor.rb +113 -0
- data/lib/brakeman/processors/template_alias_processor.rb +120 -0
- data/lib/{processors → brakeman/processors}/template_processor.rb +10 -7
- data/lib/brakeman/report.rb +68 -0
- data/lib/brakeman/report/ignore/config.rb +130 -0
- data/lib/brakeman/report/ignore/interactive.rb +311 -0
- data/lib/brakeman/report/initializers/faster_csv.rb +7 -0
- data/lib/brakeman/report/initializers/multi_json.rb +29 -0
- data/lib/brakeman/report/renderer.rb +24 -0
- data/lib/brakeman/report/report_base.rb +279 -0
- data/lib/brakeman/report/report_csv.rb +56 -0
- data/lib/brakeman/report/report_hash.rb +22 -0
- data/lib/brakeman/report/report_html.rb +203 -0
- data/lib/brakeman/report/report_json.rb +46 -0
- data/lib/brakeman/report/report_table.rb +109 -0
- data/lib/brakeman/report/report_tabs.rb +17 -0
- data/lib/brakeman/report/templates/controller_overview.html.erb +18 -0
- data/lib/brakeman/report/templates/controller_warnings.html.erb +17 -0
- data/lib/brakeman/report/templates/error_overview.html.erb +25 -0
- data/lib/brakeman/report/templates/header.html.erb +44 -0
- data/lib/brakeman/report/templates/ignored_warnings.html.erb +21 -0
- data/lib/brakeman/report/templates/model_warnings.html.erb +17 -0
- data/lib/brakeman/report/templates/overview.html.erb +34 -0
- data/lib/brakeman/report/templates/security_warnings.html.erb +19 -0
- data/lib/brakeman/report/templates/template_overview.html.erb +17 -0
- data/lib/brakeman/report/templates/view_warnings.html.erb +30 -0
- data/lib/brakeman/report/templates/warning_overview.html.erb +13 -0
- data/lib/brakeman/rescanner.rb +446 -0
- data/lib/brakeman/scanner.rb +362 -0
- data/lib/brakeman/tracker.rb +296 -0
- data/lib/brakeman/util.rb +413 -0
- data/lib/brakeman/version.rb +3 -0
- data/lib/brakeman/warning.rb +217 -0
- data/lib/brakeman/warning_codes.rb +68 -0
- data/lib/ruby_parser/bm_sexp.rb +562 -0
- data/lib/ruby_parser/bm_sexp_processor.rb +230 -0
- metadata +152 -66
- data/lib/checks.rb +0 -71
- data/lib/checks/base_check.rb +0 -357
- data/lib/checks/check_cross_site_scripting.rb +0 -336
- data/lib/checks/check_evaluation.rb +0 -27
- data/lib/checks/check_execute.rb +0 -110
- data/lib/checks/check_file_access.rb +0 -46
- data/lib/checks/check_forgery_setting.rb +0 -42
- data/lib/checks/check_mass_assignment.rb +0 -74
- data/lib/checks/check_model_attributes.rb +0 -36
- data/lib/checks/check_redirect.rb +0 -98
- data/lib/checks/check_render.rb +0 -65
- data/lib/checks/check_send_file.rb +0 -15
- data/lib/checks/check_session_settings.rb +0 -79
- data/lib/checks/check_sql.rb +0 -146
- data/lib/checks/check_validation_regex.rb +0 -60
- data/lib/processor.rb +0 -86
- data/lib/processors/alias_processor.rb +0 -384
- data/lib/processors/controller_alias_processor.rb +0 -237
- data/lib/processors/controller_processor.rb +0 -202
- data/lib/processors/erubis_template_processor.rb +0 -85
- data/lib/processors/lib/find_model_call.rb +0 -39
- data/lib/processors/lib/processor_helper.rb +0 -36
- data/lib/processors/lib/rails3_route_processor.rb +0 -184
- data/lib/processors/lib/route_helper.rb +0 -34
- data/lib/processors/params_processor.rb +0 -77
- data/lib/processors/route_processor.rb +0 -11
- data/lib/processors/template_alias_processor.rb +0 -86
- data/lib/report.rb +0 -680
- data/lib/scanner.rb +0 -227
- data/lib/tracker.rb +0 -144
- data/lib/util.rb +0 -141
- data/lib/version.rb +0 -1
- data/lib/warning.rb +0 -99
data/lib/checks.rb
DELETED
@@ -1,71 +0,0 @@
|
|
1
|
-
#Collects up results from running different checks.
|
2
|
-
#
|
3
|
-
#Checks can be added with +Check.add(check_class)+
|
4
|
-
#
|
5
|
-
#All .rb files in checks/ will be loaded.
|
6
|
-
class Checks
|
7
|
-
@checks = []
|
8
|
-
|
9
|
-
attr_reader :warnings, :controller_warnings, :model_warnings, :template_warnings, :checks_run
|
10
|
-
|
11
|
-
#Add a check. This will call +_klass_.new+ when running tests
|
12
|
-
def self.add klass
|
13
|
-
@checks << klass
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.checks
|
17
|
-
@checks
|
18
|
-
end
|
19
|
-
|
20
|
-
#No need to use this directly.
|
21
|
-
def initialize
|
22
|
-
@warnings = []
|
23
|
-
@template_warnings = []
|
24
|
-
@model_warnings = []
|
25
|
-
@controller_warnings = []
|
26
|
-
@checks_run = []
|
27
|
-
end
|
28
|
-
|
29
|
-
#Add Warning to list of warnings to report.
|
30
|
-
#Warnings are split into four different arrays
|
31
|
-
#for template, controller, model, and generic warnings.
|
32
|
-
def add_warning warning
|
33
|
-
case warning.warning_set
|
34
|
-
when :template
|
35
|
-
@template_warnings << warning
|
36
|
-
when :warning
|
37
|
-
@warnings << warning
|
38
|
-
when :controller
|
39
|
-
@controller_warnings << warning
|
40
|
-
when :model
|
41
|
-
@model_warnings << warning
|
42
|
-
else
|
43
|
-
raise "Unknown warning: #{warning.warning_set}"
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
#Run all the checks on the given Tracker.
|
48
|
-
#Returns a new instance of Checks with the results.
|
49
|
-
def self.run_checks tracker
|
50
|
-
checks = self.new
|
51
|
-
@checks.each do |c|
|
52
|
-
#Run or don't run check based on options
|
53
|
-
unless OPTIONS[:skip_checks].include? c.to_s or
|
54
|
-
(OPTIONS[:run_checks] and not OPTIONS[:run_checks].include? c.to_s)
|
55
|
-
|
56
|
-
warn " - #{c}"
|
57
|
-
c.new(checks, tracker).run_check
|
58
|
-
|
59
|
-
#Maintain list of which checks were run
|
60
|
-
#mainly for reporting purposes
|
61
|
-
checks.checks_run << c.to_s[5..-1]
|
62
|
-
end
|
63
|
-
end
|
64
|
-
checks
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
#Load all files in checks/ directory
|
69
|
-
Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/checks/*.rb").sort.each do |f|
|
70
|
-
require f.match(/(checks\/.*)\.rb$/)[0]
|
71
|
-
end
|
data/lib/checks/base_check.rb
DELETED
@@ -1,357 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'sexp_processor'
|
3
|
-
require 'processors/output_processor'
|
4
|
-
require 'warning'
|
5
|
-
require 'util'
|
6
|
-
|
7
|
-
#Basis of vulnerability checks.
|
8
|
-
class BaseCheck < SexpProcessor
|
9
|
-
include ProcessorHelper
|
10
|
-
include Util
|
11
|
-
attr_reader :checks, :tracker
|
12
|
-
|
13
|
-
CONFIDENCE = { :high => 0, :med => 1, :low => 2 }
|
14
|
-
|
15
|
-
#Initialize Check with Checks.
|
16
|
-
def initialize checks, tracker
|
17
|
-
super()
|
18
|
-
@results = [] #only to check for duplicates
|
19
|
-
@checks = checks
|
20
|
-
@tracker = tracker
|
21
|
-
@string_interp = false
|
22
|
-
@current_template = @current_module = @current_class = @current_method = nil
|
23
|
-
self.strict = false
|
24
|
-
self.auto_shift_type = false
|
25
|
-
self.require_empty = false
|
26
|
-
self.default_method = :process_default
|
27
|
-
self.warn_on_default = false
|
28
|
-
end
|
29
|
-
|
30
|
-
#Add result to result list, which is used to check for duplicates
|
31
|
-
def add_result result, location = nil
|
32
|
-
location ||= (@current_template && @current_template[:name]) || @current_class || @current_module || @current_set || result[1]
|
33
|
-
location = location[:name] if location.is_a? Hash
|
34
|
-
location = location.to_sym
|
35
|
-
|
36
|
-
@results << [result.line, location, result]
|
37
|
-
end
|
38
|
-
|
39
|
-
#Default Sexp processing. Iterates over each value in the Sexp
|
40
|
-
#and processes them if they are also Sexps.
|
41
|
-
def process_default exp
|
42
|
-
type = exp.shift
|
43
|
-
exp.each_with_index do |e, i|
|
44
|
-
if sexp? e
|
45
|
-
process e
|
46
|
-
else
|
47
|
-
e
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
exp.unshift type
|
52
|
-
end
|
53
|
-
|
54
|
-
#Process calls and check if they include user input
|
55
|
-
def process_call exp
|
56
|
-
process exp[1] if sexp? exp[1]
|
57
|
-
process exp[3]
|
58
|
-
|
59
|
-
if ALL_PARAMETERS.include? exp[1] or ALL_PARAMETERS.include? exp or params? exp[1]
|
60
|
-
@has_user_input = :params
|
61
|
-
elsif exp[1] == COOKIES or exp == COOKIES or cookies? exp[1]
|
62
|
-
@has_user_input = :cookies
|
63
|
-
elsif sexp? exp[1] and model_name? exp[1][1]
|
64
|
-
@has_user_input = :model
|
65
|
-
end
|
66
|
-
|
67
|
-
exp
|
68
|
-
end
|
69
|
-
|
70
|
-
#Note that params are included in current expression
|
71
|
-
def process_params exp
|
72
|
-
@has_user_input = :params
|
73
|
-
exp
|
74
|
-
end
|
75
|
-
|
76
|
-
#Note that cookies are included in current expression
|
77
|
-
def process_cookies exp
|
78
|
-
@has_user_input = :cookies
|
79
|
-
exp
|
80
|
-
end
|
81
|
-
|
82
|
-
private
|
83
|
-
|
84
|
-
#Report a warning
|
85
|
-
def warn options
|
86
|
-
@checks.add_warning Warning.new(options.merge({ :check => self.class.to_s }))
|
87
|
-
end
|
88
|
-
|
89
|
-
#Run _exp_ through OutputProcessor to get a nice String.
|
90
|
-
def format_output exp
|
91
|
-
OutputProcessor.new.format(exp).gsub(/\r|\n/, "")
|
92
|
-
end
|
93
|
-
|
94
|
-
#Checks if the model inherits from parent,
|
95
|
-
def parent? tracker, model, parent
|
96
|
-
if model == nil
|
97
|
-
false
|
98
|
-
elsif model[:parent] == parent
|
99
|
-
true
|
100
|
-
elsif model[:parent]
|
101
|
-
parent? tracker, tracker.models[model[:parent]], parent
|
102
|
-
else
|
103
|
-
false
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
#Checks if mass assignment is disabled globally in an initializer.
|
108
|
-
def mass_assign_disabled? tracker
|
109
|
-
matches = tracker.check_initializers(:"ActiveRecord::Base", :send)
|
110
|
-
if matches.empty?
|
111
|
-
false
|
112
|
-
else
|
113
|
-
matches.each do |result|
|
114
|
-
if result[3][3] == Sexp.new(:arg_list, Sexp.new(:lit, :attr_accessible), Sexp.new(:nil))
|
115
|
-
return true
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
#This is to avoid reporting duplicates. Checks if the result has been
|
122
|
-
#reported already from the same line number.
|
123
|
-
def duplicate? result, location = nil
|
124
|
-
line = result.line
|
125
|
-
location ||= (@current_template && @current_template[:name]) || @current_class || @current_module || @current_set || result[1]
|
126
|
-
|
127
|
-
location = location[:name] if location.is_a? Hash
|
128
|
-
location = location.to_sym
|
129
|
-
@results.each do |r|
|
130
|
-
if r[0] == line and r[1] == location
|
131
|
-
if OPTIONS[:combine_locations]
|
132
|
-
return true
|
133
|
-
elsif r[2] == result
|
134
|
-
return true
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
false
|
140
|
-
end
|
141
|
-
|
142
|
-
#Ignores ignores
|
143
|
-
def process_ignore exp
|
144
|
-
exp
|
145
|
-
end
|
146
|
-
|
147
|
-
#Does not actually process string interpolation, but notes that it occurred.
|
148
|
-
def process_string_interp exp
|
149
|
-
@string_interp = true
|
150
|
-
exp
|
151
|
-
end
|
152
|
-
|
153
|
-
#Checks if an expression contains string interpolation.
|
154
|
-
def include_interp? exp
|
155
|
-
@string_interp = false
|
156
|
-
process exp
|
157
|
-
@string_interp
|
158
|
-
end
|
159
|
-
|
160
|
-
#Checks if _exp_ includes parameters or cookies, but this only works
|
161
|
-
#with the base process_default.
|
162
|
-
def include_user_input? exp
|
163
|
-
@has_user_input = false
|
164
|
-
process exp
|
165
|
-
@has_user_input
|
166
|
-
end
|
167
|
-
|
168
|
-
#This is used to check for user input being used directly.
|
169
|
-
#
|
170
|
-
#Returns false if none is found, otherwise it returns an array
|
171
|
-
#where the first element is the type of user input
|
172
|
-
#(either :params or :cookies) and the second element is the matching
|
173
|
-
#expression
|
174
|
-
def has_immediate_user_input? exp
|
175
|
-
if params? exp
|
176
|
-
return :params, exp
|
177
|
-
elsif cookies? exp
|
178
|
-
return :cookies, exp
|
179
|
-
elsif call? exp
|
180
|
-
if sexp? exp[1]
|
181
|
-
if ALL_PARAMETERS.include? exp[1] or params? exp[1]
|
182
|
-
return :params, exp
|
183
|
-
elsif exp[1] == COOKIES
|
184
|
-
return :cookies, exp
|
185
|
-
else
|
186
|
-
false
|
187
|
-
end
|
188
|
-
else
|
189
|
-
false
|
190
|
-
end
|
191
|
-
elsif sexp? exp
|
192
|
-
case exp.node_type
|
193
|
-
when :string_interp
|
194
|
-
exp.each do |e|
|
195
|
-
if sexp? e
|
196
|
-
type, match = has_immediate_user_input?(e)
|
197
|
-
if type
|
198
|
-
return type, match
|
199
|
-
end
|
200
|
-
end
|
201
|
-
end
|
202
|
-
false
|
203
|
-
when :string_eval
|
204
|
-
if sexp? exp[1]
|
205
|
-
if exp[1].node_type == :rlist
|
206
|
-
exp[1].each do |e|
|
207
|
-
if sexp? e
|
208
|
-
type, match = has_immediate_user_input?(e)
|
209
|
-
if type
|
210
|
-
return type, match
|
211
|
-
end
|
212
|
-
end
|
213
|
-
end
|
214
|
-
false
|
215
|
-
else
|
216
|
-
has_immediate_user_input? exp[1]
|
217
|
-
end
|
218
|
-
end
|
219
|
-
when :format
|
220
|
-
has_immediate_user_input? exp[1]
|
221
|
-
when :if
|
222
|
-
(sexp? exp[2] and has_immediate_user_input? exp[2]) or
|
223
|
-
(sexp? exp[3] and has_immediate_user_input? exp[3])
|
224
|
-
else
|
225
|
-
false
|
226
|
-
end
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
#Checks for a model attribute at the top level of the
|
231
|
-
#expression.
|
232
|
-
def has_immediate_model? exp, out = nil
|
233
|
-
out = exp if out.nil?
|
234
|
-
|
235
|
-
if sexp? exp and exp.node_type == :output
|
236
|
-
exp = exp[1]
|
237
|
-
end
|
238
|
-
|
239
|
-
if call? exp
|
240
|
-
target = exp[1]
|
241
|
-
method = exp[2]
|
242
|
-
|
243
|
-
if call? target and not method.to_s[-1,1] == "?"
|
244
|
-
has_immediate_model? target, out
|
245
|
-
elsif model_name? target
|
246
|
-
exp
|
247
|
-
else
|
248
|
-
false
|
249
|
-
end
|
250
|
-
elsif sexp? exp
|
251
|
-
case exp.node_type
|
252
|
-
when :string_interp
|
253
|
-
exp.each do |e|
|
254
|
-
if sexp? e and match = has_immediate_model?(e, out)
|
255
|
-
return match
|
256
|
-
end
|
257
|
-
end
|
258
|
-
false
|
259
|
-
when :string_eval
|
260
|
-
if sexp? exp[1]
|
261
|
-
if exp[1].node_type == :rlist
|
262
|
-
exp[1].each do |e|
|
263
|
-
if sexp? e and match = has_immediate_model?(e, out)
|
264
|
-
return match
|
265
|
-
end
|
266
|
-
end
|
267
|
-
false
|
268
|
-
else
|
269
|
-
has_immediate_model? exp[1], out
|
270
|
-
end
|
271
|
-
end
|
272
|
-
when :format
|
273
|
-
has_immediate_model? exp[1], out
|
274
|
-
when :if
|
275
|
-
((sexp? exp[2] and has_immediate_model? exp[2], out) or
|
276
|
-
(sexp? exp[3] and has_immediate_model? exp[3], out))
|
277
|
-
else
|
278
|
-
false
|
279
|
-
end
|
280
|
-
end
|
281
|
-
end
|
282
|
-
|
283
|
-
#Checks if +exp+ is a model name.
|
284
|
-
#
|
285
|
-
#Prior to using this method, either @tracker must be set to
|
286
|
-
#the current tracker, or else @models should contain an array of the model
|
287
|
-
#names, which is available via tracker.models.keys
|
288
|
-
def model_name? exp
|
289
|
-
@models ||= @tracker.models.keys
|
290
|
-
|
291
|
-
if exp.is_a? Symbol
|
292
|
-
@models.include? exp
|
293
|
-
elsif sexp? exp
|
294
|
-
klass = nil
|
295
|
-
begin
|
296
|
-
klass = class_name exp
|
297
|
-
rescue StandardError
|
298
|
-
end
|
299
|
-
|
300
|
-
klass and @models.include? klass
|
301
|
-
else
|
302
|
-
false
|
303
|
-
end
|
304
|
-
end
|
305
|
-
|
306
|
-
#Finds entire method call chain where +target+ is a target in the chain
|
307
|
-
def find_chain exp, target
|
308
|
-
return unless sexp? exp
|
309
|
-
|
310
|
-
case exp.node_type
|
311
|
-
when :output, :format
|
312
|
-
find_chain exp[1], target
|
313
|
-
when :call
|
314
|
-
if exp == target or include_target? exp, target
|
315
|
-
return exp
|
316
|
-
end
|
317
|
-
else
|
318
|
-
exp.each do |e|
|
319
|
-
if sexp? e
|
320
|
-
res = find_chain e, target
|
321
|
-
return res if res
|
322
|
-
end
|
323
|
-
end
|
324
|
-
nil
|
325
|
-
end
|
326
|
-
end
|
327
|
-
|
328
|
-
#Returns true if +target+ is in +exp+
|
329
|
-
def include_target? exp, target
|
330
|
-
return false unless call? exp
|
331
|
-
|
332
|
-
exp.each do |e|
|
333
|
-
return true if e == target or include_target? e, target
|
334
|
-
end
|
335
|
-
|
336
|
-
false
|
337
|
-
end
|
338
|
-
|
339
|
-
#Returns true if low_version <= RAILS_VERSION <= high_version
|
340
|
-
#
|
341
|
-
#If the Rails version is unknown, returns false.
|
342
|
-
def version_between? low_version, high_version
|
343
|
-
return false unless tracker.config[:rails_version]
|
344
|
-
|
345
|
-
version = tracker.config[:rails_version].split(".").map! { |n| n.to_i }
|
346
|
-
low_version = low_version.split(".").map! { |n| n.to_i }
|
347
|
-
high_version = high_version.split(".").map! { |n| n.to_i }
|
348
|
-
|
349
|
-
version.each_with_index do |n, i|
|
350
|
-
if n < low_version[i] or n > high_version[i]
|
351
|
-
return false
|
352
|
-
end
|
353
|
-
end
|
354
|
-
|
355
|
-
return true
|
356
|
-
end
|
357
|
-
end
|
@@ -1,336 +0,0 @@
|
|
1
|
-
require 'checks/base_check'
|
2
|
-
require 'processors/lib/find_call'
|
3
|
-
require 'processors/lib/processor_helper'
|
4
|
-
require 'util'
|
5
|
-
require 'set'
|
6
|
-
|
7
|
-
#This check looks for unescaped output in templates which contains
|
8
|
-
#parameters or model attributes.
|
9
|
-
#
|
10
|
-
#For example:
|
11
|
-
#
|
12
|
-
# <%= User.find(:id).name %>
|
13
|
-
# <%= params[:id] %>
|
14
|
-
class CheckCrossSiteScripting < BaseCheck
|
15
|
-
Checks.add self
|
16
|
-
|
17
|
-
#Ignore these methods and their arguments.
|
18
|
-
#It is assumed they will take care of escaping their output.
|
19
|
-
IGNORE_METHODS = Set.new([:h, :escapeHTML, :link_to, :text_field_tag, :hidden_field_tag,
|
20
|
-
:image_tag, :select, :submit_tag, :hidden_field, :url_encode,
|
21
|
-
:radio_button, :will_paginate, :button_to, :url_for, :mail_to,
|
22
|
-
:fields_for, :label, :text_area, :text_field, :hidden_field, :check_box,
|
23
|
-
:field_field])
|
24
|
-
|
25
|
-
IGNORE_MODEL_METHODS = Set.new([:average, :count, :maximum, :minimum, :sum])
|
26
|
-
|
27
|
-
MODEL_METHODS = Set.new([:all, :find, :first, :last, :new])
|
28
|
-
|
29
|
-
IGNORE_LIKE = /^link_to_|(_path|_tag|_url)$/
|
30
|
-
|
31
|
-
HAML_HELPERS = Sexp.new(:colon2, Sexp.new(:const, :Haml), :Helpers)
|
32
|
-
|
33
|
-
XML_HELPER = Sexp.new(:colon2, Sexp.new(:const, :Erubis), :XmlHelper)
|
34
|
-
|
35
|
-
URI = Sexp.new(:const, :URI)
|
36
|
-
|
37
|
-
CGI = Sexp.new(:const, :CGI)
|
38
|
-
|
39
|
-
FORM_BUILDER = Sexp.new(:call, Sexp.new(:const, :FormBuilder), :new, Sexp.new(:arglist))
|
40
|
-
|
41
|
-
#Run check
|
42
|
-
def run_check
|
43
|
-
IGNORE_METHODS.merge OPTIONS[:safe_methods]
|
44
|
-
@models = tracker.models.keys
|
45
|
-
@inspect_arguments = OPTIONS[:check_arguments]
|
46
|
-
|
47
|
-
CheckLinkTo.new(checks, tracker).run_check
|
48
|
-
|
49
|
-
tracker.each_template do |name, template|
|
50
|
-
@current_template = template
|
51
|
-
|
52
|
-
template[:outputs].each do |out|
|
53
|
-
type, match = (out[0] == :output and has_immediate_user_input?(out[1]))
|
54
|
-
if type and not duplicate? out
|
55
|
-
add_result out
|
56
|
-
case type
|
57
|
-
when :params
|
58
|
-
message = "Unescaped parameter value"
|
59
|
-
when :cookies
|
60
|
-
message = "Unescaped cookie value"
|
61
|
-
else
|
62
|
-
message = "Unescaped user input value"
|
63
|
-
end
|
64
|
-
|
65
|
-
warn :template => @current_template,
|
66
|
-
:warning_type => "Cross Site Scripting",
|
67
|
-
:message => message,
|
68
|
-
:line => match.line,
|
69
|
-
:code => match,
|
70
|
-
:confidence => CONFIDENCE[:high]
|
71
|
-
|
72
|
-
elsif not OPTIONS[:ignore_model_output] and match = has_immediate_model?(out[1])
|
73
|
-
method = match[2]
|
74
|
-
|
75
|
-
unless duplicate? out or IGNORE_MODEL_METHODS.include? method
|
76
|
-
add_result out
|
77
|
-
|
78
|
-
if MODEL_METHODS.include? method or method.to_s =~ /^find_by/
|
79
|
-
confidence = CONFIDENCE[:high]
|
80
|
-
else
|
81
|
-
confidence = CONFIDENCE[:med]
|
82
|
-
end
|
83
|
-
|
84
|
-
code = find_chain out, match
|
85
|
-
warn :template => @current_template,
|
86
|
-
:warning_type => "Cross Site Scripting",
|
87
|
-
:message => "Unescaped model attribute",
|
88
|
-
:line => code.line,
|
89
|
-
:code => code,
|
90
|
-
:confidence => confidence
|
91
|
-
end
|
92
|
-
|
93
|
-
else
|
94
|
-
@matched = false
|
95
|
-
@mark = false
|
96
|
-
process out
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
#Process an output Sexp
|
103
|
-
def process_output exp
|
104
|
-
process exp[1].dup
|
105
|
-
end
|
106
|
-
|
107
|
-
#Look for calls to raw()
|
108
|
-
#Otherwise, ignore
|
109
|
-
def process_escaped_output exp
|
110
|
-
if exp[1].node_type == :call and exp[1][2] == :raw
|
111
|
-
process_output exp
|
112
|
-
else
|
113
|
-
exp
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
#Check a call for user input
|
118
|
-
#
|
119
|
-
#
|
120
|
-
#Since we want to report an entire call and not just part of one, use @mark
|
121
|
-
#to mark when a call is started. Any dangerous values inside will then
|
122
|
-
#report the entire call chain.
|
123
|
-
def process_call exp
|
124
|
-
if @mark
|
125
|
-
actually_process_call exp
|
126
|
-
else
|
127
|
-
@mark = true
|
128
|
-
actually_process_call exp
|
129
|
-
message = nil
|
130
|
-
|
131
|
-
if @matched == :model and not OPTIONS[:ignore_model_output]
|
132
|
-
message = "Unescaped model attribute"
|
133
|
-
elsif @matched == :params
|
134
|
-
message = "Unescaped parameter value"
|
135
|
-
end
|
136
|
-
|
137
|
-
if message and not duplicate? exp
|
138
|
-
add_result exp
|
139
|
-
|
140
|
-
warn :template => @current_template,
|
141
|
-
:warning_type => "Cross Site Scripting",
|
142
|
-
:message => message,
|
143
|
-
:line => exp.line,
|
144
|
-
:code => exp,
|
145
|
-
:confidence => CONFIDENCE[:low]
|
146
|
-
end
|
147
|
-
|
148
|
-
@mark = @matched = false
|
149
|
-
end
|
150
|
-
|
151
|
-
exp
|
152
|
-
end
|
153
|
-
|
154
|
-
def actually_process_call exp
|
155
|
-
return if @matched
|
156
|
-
target = exp[1]
|
157
|
-
if sexp? target
|
158
|
-
target = process target
|
159
|
-
end
|
160
|
-
|
161
|
-
method = exp[2]
|
162
|
-
args = exp[3]
|
163
|
-
|
164
|
-
#Ignore safe items
|
165
|
-
if (target.nil? and (IGNORE_METHODS.include? method or method.to_s =~ IGNORE_LIKE)) or
|
166
|
-
(@matched == :model and IGNORE_MODEL_METHODS.include? method) or
|
167
|
-
(target == HAML_HELPERS and method == :html_escape) or
|
168
|
-
((target == URI or target == CGI) and method == :escape) or
|
169
|
-
(target == XML_HELPER and method == :escape_xml) or
|
170
|
-
(target == FORM_BUILDER and IGNORE_METHODS.include? method) or
|
171
|
-
(method.to_s[-1,1] == "?")
|
172
|
-
|
173
|
-
exp[0] = :ignore
|
174
|
-
@matched = false
|
175
|
-
elsif sexp? exp[1] and model_name? exp[1][1]
|
176
|
-
|
177
|
-
@matched = :model
|
178
|
-
elsif @inspect_arguments and (ALL_PARAMETERS.include?(exp) or params? exp)
|
179
|
-
@matched = :params
|
180
|
-
elsif @inspect_arguments
|
181
|
-
process args
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
#Note that params have been found
|
186
|
-
def process_params exp
|
187
|
-
@matched = :params
|
188
|
-
exp
|
189
|
-
end
|
190
|
-
|
191
|
-
#Note that cookies have been found
|
192
|
-
def process_cookies exp
|
193
|
-
@matched = :cookies
|
194
|
-
exp
|
195
|
-
end
|
196
|
-
|
197
|
-
#Ignore calls to render
|
198
|
-
def process_render exp
|
199
|
-
exp
|
200
|
-
end
|
201
|
-
|
202
|
-
#Process as default
|
203
|
-
def process_string_interp exp
|
204
|
-
process_default exp
|
205
|
-
end
|
206
|
-
|
207
|
-
#Process as default
|
208
|
-
def process_format exp
|
209
|
-
process_default exp
|
210
|
-
end
|
211
|
-
|
212
|
-
#Ignore output HTML escaped via HAML
|
213
|
-
def process_format_escaped exp
|
214
|
-
exp
|
215
|
-
end
|
216
|
-
|
217
|
-
#Ignore condition in if Sexp
|
218
|
-
def process_if exp
|
219
|
-
exp[2..-1].each do |e|
|
220
|
-
process e if sexp? e
|
221
|
-
end
|
222
|
-
exp
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
#This _only_ checks calls to link_to
|
227
|
-
class CheckLinkTo < CheckCrossSiteScripting
|
228
|
-
IGNORE_METHODS = IGNORE_METHODS - [:link_to]
|
229
|
-
|
230
|
-
def run_check
|
231
|
-
#Ideally, I think this should also check to see if people are setting
|
232
|
-
#:escape => false
|
233
|
-
methods = tracker.find_call [], :link_to
|
234
|
-
|
235
|
-
@models = tracker.models.keys
|
236
|
-
@inspect_arguments = OPTIONS[:check_arguments]
|
237
|
-
|
238
|
-
methods.each do |call|
|
239
|
-
process_result call
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
|
-
def process_result result
|
244
|
-
#Have to make a copy of this, otherwise it will be changed to
|
245
|
-
#an ignored method call by the code above.
|
246
|
-
call = result[-1] = result[-1].dup
|
247
|
-
|
248
|
-
@matched = false
|
249
|
-
|
250
|
-
return if call[3][1].nil?
|
251
|
-
|
252
|
-
#Only check first argument for +link_to+, as the second
|
253
|
-
#will *usually* be a record or escaped.
|
254
|
-
first_arg = process call[3][1]
|
255
|
-
|
256
|
-
type, match = has_immediate_user_input? first_arg
|
257
|
-
|
258
|
-
if type
|
259
|
-
case type
|
260
|
-
when :params
|
261
|
-
message = "Unescaped parameter value in link_to"
|
262
|
-
when :cookies
|
263
|
-
message = "Unescaped cookie value in link_to"
|
264
|
-
else
|
265
|
-
message = "Unescaped user input value in link_to"
|
266
|
-
end
|
267
|
-
|
268
|
-
unless duplicate? result
|
269
|
-
add_result result
|
270
|
-
|
271
|
-
warn :result => result,
|
272
|
-
:warning_type => "Cross Site Scripting",
|
273
|
-
:message => message,
|
274
|
-
:confidence => CONFIDENCE[:high]
|
275
|
-
end
|
276
|
-
|
277
|
-
elsif not OPTIONS[:ignore_model_output] and match = has_immediate_model?(first_arg)
|
278
|
-
method = match[2]
|
279
|
-
|
280
|
-
unless duplicate? result or IGNORE_MODEL_METHODS.include? method
|
281
|
-
add_result result
|
282
|
-
|
283
|
-
if MODEL_METHODS.include? method or method.to_s =~ /^find_by/
|
284
|
-
confidence = CONFIDENCE[:high]
|
285
|
-
else
|
286
|
-
confidence = CONFIDENCE[:med]
|
287
|
-
end
|
288
|
-
|
289
|
-
warn :result => result,
|
290
|
-
:warning_type => "Cross Site Scripting",
|
291
|
-
:message => "Unescaped model attribute in link_to",
|
292
|
-
:confidence => confidence
|
293
|
-
end
|
294
|
-
|
295
|
-
elsif @matched
|
296
|
-
if @matched == :model and not OPTIONS[:ignore_model_output]
|
297
|
-
message = "Unescaped model attribute in link_to"
|
298
|
-
elsif @matched == :params
|
299
|
-
message = "Unescaped parameter value in link_to"
|
300
|
-
end
|
301
|
-
|
302
|
-
if message and not duplicate? result
|
303
|
-
add_result result
|
304
|
-
|
305
|
-
warn :result => result,
|
306
|
-
:warning_type => "Cross Site Scripting",
|
307
|
-
:message => message,
|
308
|
-
:confidence => CONFIDENCE[:med]
|
309
|
-
end
|
310
|
-
end
|
311
|
-
end
|
312
|
-
|
313
|
-
def process_call exp
|
314
|
-
@mark = true
|
315
|
-
actually_process_call exp
|
316
|
-
exp
|
317
|
-
end
|
318
|
-
|
319
|
-
|
320
|
-
def actually_process_call exp
|
321
|
-
return if @matched
|
322
|
-
|
323
|
-
target = exp[1]
|
324
|
-
if sexp? target
|
325
|
-
target = process target.dup
|
326
|
-
end
|
327
|
-
|
328
|
-
#Bare records create links to the model resource,
|
329
|
-
#not a string that could have injection
|
330
|
-
if model_name? target and context == [:call, :arglist]
|
331
|
-
return exp
|
332
|
-
end
|
333
|
-
|
334
|
-
super
|
335
|
-
end
|
336
|
-
end
|