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
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'brakeman/checks/base_check'
|
2
|
+
|
3
|
+
#Checks if password is stored in controller
|
4
|
+
#when using http_basic_authenticate_with
|
5
|
+
#
|
6
|
+
#Only for Rails >= 3.1
|
7
|
+
class Brakeman::CheckBasicAuth < Brakeman::BaseCheck
|
8
|
+
Brakeman::Checks.add self
|
9
|
+
|
10
|
+
@description = "Checks for the use of http_basic_authenticate_with"
|
11
|
+
|
12
|
+
def run_check
|
13
|
+
return if version_between? "0.0.0", "3.0.99"
|
14
|
+
|
15
|
+
check_basic_auth_filter
|
16
|
+
check_basic_auth_request
|
17
|
+
end
|
18
|
+
|
19
|
+
def check_basic_auth_filter
|
20
|
+
controllers = tracker.controllers.select do |name, c|
|
21
|
+
c[:options][:http_basic_authenticate_with]
|
22
|
+
end
|
23
|
+
|
24
|
+
Hash[controllers].each do |name, controller|
|
25
|
+
controller[:options][:http_basic_authenticate_with].each do |call|
|
26
|
+
|
27
|
+
if pass = get_password(call) and string? pass
|
28
|
+
warn :controller => name,
|
29
|
+
:warning_type => "Basic Auth",
|
30
|
+
:warning_code => :basic_auth_password,
|
31
|
+
:message => "Basic authentication password stored in source code",
|
32
|
+
:code => call,
|
33
|
+
:confidence => 0,
|
34
|
+
:file => controller[:file]
|
35
|
+
|
36
|
+
break
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Look for
|
43
|
+
# authenticate_or_request_with_http_basic do |username, password|
|
44
|
+
# username == "foo" && password == "bar"
|
45
|
+
# end
|
46
|
+
def check_basic_auth_request
|
47
|
+
tracker.find_call(:target => nil, :method => :authenticate_or_request_with_http_basic).each do |result|
|
48
|
+
if include_password_literal? result
|
49
|
+
warn :result => result,
|
50
|
+
:code => @include_password,
|
51
|
+
:warning_type => "Basic Auth",
|
52
|
+
:warning_code => :basic_auth_password,
|
53
|
+
:message => "Basic authentication password stored in source code",
|
54
|
+
:confidence => 0
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Check if the block of a result contains a comparison of password to string
|
60
|
+
def include_password_literal? result
|
61
|
+
@password_var = result[:block_args].last
|
62
|
+
@include_password = false
|
63
|
+
process result[:block]
|
64
|
+
@include_password
|
65
|
+
end
|
66
|
+
|
67
|
+
# Looks for :== calls on password var
|
68
|
+
def process_call exp
|
69
|
+
target = exp.target
|
70
|
+
|
71
|
+
if node_type?(target, :lvar) and
|
72
|
+
target.value == @password_var and
|
73
|
+
exp.method == :== and
|
74
|
+
string? exp.first_arg
|
75
|
+
|
76
|
+
@include_password = exp
|
77
|
+
end
|
78
|
+
|
79
|
+
exp
|
80
|
+
end
|
81
|
+
|
82
|
+
def get_password call
|
83
|
+
arg = call.first_arg
|
84
|
+
|
85
|
+
return false if arg.nil? or not hash? arg
|
86
|
+
|
87
|
+
hash_access(arg, :password)
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'brakeman/checks/check_cross_site_scripting'
|
2
|
+
|
3
|
+
#Checks for unescaped values in `content_tag`
|
4
|
+
#
|
5
|
+
# content_tag :tag, body
|
6
|
+
# ^-- Unescaped in Rails 2.x
|
7
|
+
#
|
8
|
+
# content_tag, :tag, body, attribute => value
|
9
|
+
# ^-- Unescaped in all versions
|
10
|
+
#
|
11
|
+
# content_tag, :tag, body, attribute => value
|
12
|
+
# ^
|
13
|
+
# |
|
14
|
+
# Escaped by default, can be explicitly escaped
|
15
|
+
# or not by passing in (true|false) as fourth argument
|
16
|
+
class Brakeman::CheckContentTag < Brakeman::CheckCrossSiteScripting
|
17
|
+
Brakeman::Checks.add self
|
18
|
+
|
19
|
+
@description = "Checks for XSS in calls to content_tag"
|
20
|
+
|
21
|
+
def run_check
|
22
|
+
@ignore_methods = Set[:button_to, :check_box, :escapeHTML, :escape_once,
|
23
|
+
:field_field, :fields_for, :h, :hidden_field,
|
24
|
+
:hidden_field, :hidden_field_tag, :image_tag, :label,
|
25
|
+
:mail_to, :radio_button, :select,
|
26
|
+
:submit_tag, :text_area, :text_field,
|
27
|
+
:text_field_tag, :url_encode, :url_for,
|
28
|
+
:will_paginate].merge tracker.options[:safe_methods]
|
29
|
+
|
30
|
+
@known_dangerous = []
|
31
|
+
methods = tracker.find_call :target => false, :method => :content_tag
|
32
|
+
|
33
|
+
@models = tracker.models.keys
|
34
|
+
@inspect_arguments = tracker.options[:check_arguments]
|
35
|
+
@mark = nil
|
36
|
+
|
37
|
+
Brakeman.debug "Checking for XSS in content_tag"
|
38
|
+
methods.each do |call|
|
39
|
+
process_result call
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def process_result result
|
44
|
+
return if duplicate? result
|
45
|
+
|
46
|
+
call = result[:call] = result[:call].dup
|
47
|
+
|
48
|
+
args = call.arglist
|
49
|
+
|
50
|
+
tag_name = args[1]
|
51
|
+
content = args[2]
|
52
|
+
attributes = args[3]
|
53
|
+
escape_attr = args[4]
|
54
|
+
|
55
|
+
@matched = false
|
56
|
+
|
57
|
+
#Silly, but still dangerous if someone uses user input in the tag type
|
58
|
+
check_argument result, tag_name
|
59
|
+
|
60
|
+
#Versions before 3.x do not escape body of tag, nor does the rails_xss gem
|
61
|
+
unless @matched or (tracker.options[:rails3] and not raw? content)
|
62
|
+
check_argument result, content
|
63
|
+
end
|
64
|
+
|
65
|
+
#Attribute keys are never escaped, so check them for user input
|
66
|
+
if not @matched and hash? attributes and not request_value? attributes
|
67
|
+
hash_iterate(attributes) do |k, v|
|
68
|
+
check_argument result, k
|
69
|
+
return if @matched
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
#By default, content_tag escapes attribute values passed in as a hash.
|
74
|
+
#But this behavior can be disabled. So only check attributes hash
|
75
|
+
#if they are explicitly not escaped.
|
76
|
+
if not @matched and attributes and false? escape_attr
|
77
|
+
if request_value? attributes or not hash? attributes
|
78
|
+
check_argument result, attributes
|
79
|
+
else #check hash values
|
80
|
+
hash_iterate(attributes) do |k, v|
|
81
|
+
check_argument result, v
|
82
|
+
return if @matched
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def check_argument result, exp
|
89
|
+
#Check contents of raw() calls directly
|
90
|
+
if call? exp and exp.method == :raw
|
91
|
+
arg = process exp.first_arg
|
92
|
+
else
|
93
|
+
arg = process exp
|
94
|
+
end
|
95
|
+
|
96
|
+
if input = has_immediate_user_input?(arg)
|
97
|
+
message = "Unescaped #{friendly_type_of input} in content_tag"
|
98
|
+
|
99
|
+
add_result result
|
100
|
+
|
101
|
+
warn :result => result,
|
102
|
+
:warning_type => "Cross Site Scripting",
|
103
|
+
:warning_code => :xss_content_tag,
|
104
|
+
:message => message,
|
105
|
+
:user_input => input.match,
|
106
|
+
:confidence => CONFIDENCE[:high],
|
107
|
+
:link_path => "content_tag"
|
108
|
+
|
109
|
+
elsif not tracker.options[:ignore_model_output] and match = has_immediate_model?(arg)
|
110
|
+
method = match[2]
|
111
|
+
|
112
|
+
unless IGNORE_MODEL_METHODS.include? method
|
113
|
+
add_result result
|
114
|
+
|
115
|
+
if MODEL_METHODS.include? method or method.to_s =~ /^find_by/
|
116
|
+
confidence = CONFIDENCE[:high]
|
117
|
+
else
|
118
|
+
confidence = CONFIDENCE[:med]
|
119
|
+
end
|
120
|
+
|
121
|
+
warn :result => result,
|
122
|
+
:warning_type => "Cross Site Scripting",
|
123
|
+
:warning_code => :xss_content_tag,
|
124
|
+
:message => "Unescaped model attribute in content_tag",
|
125
|
+
:user_input => match,
|
126
|
+
:confidence => confidence,
|
127
|
+
:link_path => "content_tag"
|
128
|
+
end
|
129
|
+
|
130
|
+
elsif @matched
|
131
|
+
return if @matched.type == :model and tracker.options[:ignore_model_output]
|
132
|
+
|
133
|
+
message = "Unescaped #{friendly_type_of @matched} in content_tag"
|
134
|
+
|
135
|
+
add_result result
|
136
|
+
|
137
|
+
warn :result => result,
|
138
|
+
:warning_type => "Cross Site Scripting",
|
139
|
+
:warning_code => :xss_content_tag,
|
140
|
+
:message => message,
|
141
|
+
:user_input => @matched.match,
|
142
|
+
:confidence => CONFIDENCE[:med],
|
143
|
+
:link_path => "content_tag"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def process_call exp
|
148
|
+
if @mark
|
149
|
+
actually_process_call exp
|
150
|
+
else
|
151
|
+
@mark = true
|
152
|
+
actually_process_call exp
|
153
|
+
@mark = false
|
154
|
+
end
|
155
|
+
|
156
|
+
exp
|
157
|
+
end
|
158
|
+
|
159
|
+
def raw? exp
|
160
|
+
call? exp and exp.method == :raw
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,334 @@
|
|
1
|
+
require 'brakeman/checks/base_check'
|
2
|
+
require 'brakeman/processors/lib/find_call'
|
3
|
+
require 'brakeman/processors/lib/processor_helper'
|
4
|
+
require 'brakeman/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 Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
|
15
|
+
Brakeman::Checks.add self
|
16
|
+
|
17
|
+
@description = "Checks for unescaped output in views"
|
18
|
+
|
19
|
+
#Model methods which are known to be harmless
|
20
|
+
IGNORE_MODEL_METHODS = Set[:average, :count, :maximum, :minimum, :sum, :id]
|
21
|
+
|
22
|
+
MODEL_METHODS = Set[:all, :find, :first, :last, :new]
|
23
|
+
|
24
|
+
IGNORE_LIKE = /^link_to_|(_path|_tag|_url)$/
|
25
|
+
|
26
|
+
HAML_HELPERS = Sexp.new(:colon2, Sexp.new(:const, :Haml), :Helpers)
|
27
|
+
|
28
|
+
XML_HELPER = Sexp.new(:colon2, Sexp.new(:const, :Erubis), :XmlHelper)
|
29
|
+
|
30
|
+
URI = Sexp.new(:const, :URI)
|
31
|
+
|
32
|
+
CGI = Sexp.new(:const, :CGI)
|
33
|
+
|
34
|
+
FORM_BUILDER = Sexp.new(:call, Sexp.new(:const, :FormBuilder), :new)
|
35
|
+
|
36
|
+
#Run check
|
37
|
+
def run_check
|
38
|
+
@ignore_methods = Set[:button_to, :check_box, :content_tag, :escapeHTML, :escape_once,
|
39
|
+
:field_field, :fields_for, :h, :hidden_field,
|
40
|
+
:hidden_field, :hidden_field_tag, :image_tag, :label,
|
41
|
+
:link_to, :mail_to, :radio_button, :select,
|
42
|
+
:submit_tag, :text_area, :text_field,
|
43
|
+
:text_field_tag, :url_encode, :url_for,
|
44
|
+
:will_paginate].merge tracker.options[:safe_methods]
|
45
|
+
|
46
|
+
@models = tracker.models.keys
|
47
|
+
@inspect_arguments = tracker.options[:check_arguments]
|
48
|
+
|
49
|
+
@known_dangerous = Set[:truncate, :concat]
|
50
|
+
|
51
|
+
if version_between? "2.0.0", "3.0.5"
|
52
|
+
@known_dangerous << :auto_link
|
53
|
+
elsif version_between? "3.0.6", "3.0.99"
|
54
|
+
@ignore_methods << :auto_link
|
55
|
+
end
|
56
|
+
|
57
|
+
if version_between? "2.0.0", "2.3.14"
|
58
|
+
@known_dangerous << :strip_tags
|
59
|
+
end
|
60
|
+
|
61
|
+
json_escape_on = false
|
62
|
+
initializers = tracker.check_initializers :ActiveSupport, :escape_html_entities_in_json=
|
63
|
+
initializers.each {|result| json_escape_on = true?(result.call.first_arg) }
|
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
|
+
|
71
|
+
if !json_escape_on or version_between? "0.0.0", "2.0.99"
|
72
|
+
@known_dangerous << :to_json
|
73
|
+
Brakeman.debug("Automatic to_json escaping not enabled, consider to_json dangerous")
|
74
|
+
else
|
75
|
+
@safe_input_attributes << :to_json
|
76
|
+
Brakeman.debug("Automatic to_json escaping is enabled.")
|
77
|
+
end
|
78
|
+
|
79
|
+
tracker.each_template do |name, template|
|
80
|
+
Brakeman.debug "Checking #{name} for XSS"
|
81
|
+
|
82
|
+
@current_template = template
|
83
|
+
|
84
|
+
template[:outputs].each do |out|
|
85
|
+
unless check_for_immediate_xss out
|
86
|
+
@matched = false
|
87
|
+
@mark = false
|
88
|
+
process out
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def check_for_immediate_xss exp
|
95
|
+
return :duplicate if duplicate? exp
|
96
|
+
|
97
|
+
if exp.node_type == :output
|
98
|
+
out = exp.value
|
99
|
+
elsif exp.node_type == :escaped_output and raw_call? exp
|
100
|
+
out = exp.value.first_arg
|
101
|
+
end
|
102
|
+
|
103
|
+
if input = has_immediate_user_input?(out)
|
104
|
+
add_result exp
|
105
|
+
|
106
|
+
message = "Unescaped #{friendly_type_of input}"
|
107
|
+
|
108
|
+
warn :template => @current_template,
|
109
|
+
:warning_type => "Cross Site Scripting",
|
110
|
+
:warning_code => :cross_site_scripting,
|
111
|
+
:message => message,
|
112
|
+
:code => input.match,
|
113
|
+
:confidence => CONFIDENCE[:high]
|
114
|
+
|
115
|
+
elsif not tracker.options[:ignore_model_output] and match = has_immediate_model?(out)
|
116
|
+
method = if call? match
|
117
|
+
match.method
|
118
|
+
else
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
|
122
|
+
unless IGNORE_MODEL_METHODS.include? method
|
123
|
+
add_result exp
|
124
|
+
|
125
|
+
if MODEL_METHODS.include? method or method.to_s =~ /^find_by/
|
126
|
+
confidence = CONFIDENCE[:high]
|
127
|
+
else
|
128
|
+
confidence = CONFIDENCE[:med]
|
129
|
+
end
|
130
|
+
|
131
|
+
message = "Unescaped model attribute"
|
132
|
+
link_path = "cross_site_scripting"
|
133
|
+
warning_code = :cross_site_scripting
|
134
|
+
|
135
|
+
if node_type?(out, :call, :attrasgn) && out.method == :to_json
|
136
|
+
message += " in JSON hash"
|
137
|
+
link_path += "_to_json"
|
138
|
+
warning_code = :xss_to_json
|
139
|
+
end
|
140
|
+
|
141
|
+
code = find_chain out, match
|
142
|
+
warn :template => @current_template,
|
143
|
+
:warning_type => "Cross Site Scripting",
|
144
|
+
:warning_code => warning_code,
|
145
|
+
:message => message,
|
146
|
+
:code => code,
|
147
|
+
:confidence => confidence,
|
148
|
+
:link_path => link_path
|
149
|
+
end
|
150
|
+
|
151
|
+
else
|
152
|
+
false
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
#Process an output Sexp
|
157
|
+
def process_output exp
|
158
|
+
process exp.value.dup
|
159
|
+
end
|
160
|
+
|
161
|
+
#Look for calls to raw()
|
162
|
+
#Otherwise, ignore
|
163
|
+
def process_escaped_output exp
|
164
|
+
unless check_for_immediate_xss exp
|
165
|
+
if raw_call? exp and not duplicate? exp
|
166
|
+
process exp.value.first_arg
|
167
|
+
end
|
168
|
+
end
|
169
|
+
exp
|
170
|
+
end
|
171
|
+
|
172
|
+
#Check a call for user input
|
173
|
+
#
|
174
|
+
#
|
175
|
+
#Since we want to report an entire call and not just part of one, use @mark
|
176
|
+
#to mark when a call is started. Any dangerous values inside will then
|
177
|
+
#report the entire call chain.
|
178
|
+
def process_call exp
|
179
|
+
if @mark
|
180
|
+
actually_process_call exp
|
181
|
+
else
|
182
|
+
@mark = true
|
183
|
+
actually_process_call exp
|
184
|
+
message = nil
|
185
|
+
|
186
|
+
if @matched
|
187
|
+
unless @matched.type and tracker.options[:ignore_model_output]
|
188
|
+
message = "Unescaped #{friendly_type_of @matched}"
|
189
|
+
end
|
190
|
+
|
191
|
+
if message and not duplicate? exp
|
192
|
+
add_result exp
|
193
|
+
|
194
|
+
link_path = "cross_site_scripting"
|
195
|
+
if @known_dangerous.include? exp.method
|
196
|
+
confidence = CONFIDENCE[:high]
|
197
|
+
if exp.method == :to_json
|
198
|
+
message += " in JSON hash"
|
199
|
+
link_path += "_to_json"
|
200
|
+
end
|
201
|
+
else
|
202
|
+
confidence = CONFIDENCE[:low]
|
203
|
+
end
|
204
|
+
|
205
|
+
warn :template => @current_template,
|
206
|
+
:warning_type => "Cross Site Scripting",
|
207
|
+
:warning_code => :xss_to_json,
|
208
|
+
:message => message,
|
209
|
+
:code => exp,
|
210
|
+
:user_input => @matched.match,
|
211
|
+
:confidence => confidence,
|
212
|
+
:link_path => link_path
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
@mark = @matched = false
|
217
|
+
end
|
218
|
+
|
219
|
+
exp
|
220
|
+
end
|
221
|
+
|
222
|
+
def actually_process_call exp
|
223
|
+
return if @matched
|
224
|
+
target = exp.target
|
225
|
+
if sexp? target
|
226
|
+
target = process target
|
227
|
+
end
|
228
|
+
|
229
|
+
method = exp.method
|
230
|
+
|
231
|
+
#Ignore safe items
|
232
|
+
if ignore_call? target, method
|
233
|
+
@matched = false
|
234
|
+
elsif sexp? target and model_name? target[1] #TODO: use method call?
|
235
|
+
@matched = Match.new(:model, exp)
|
236
|
+
elsif cookies? exp
|
237
|
+
@matched = Match.new(:cookies, exp)
|
238
|
+
elsif @inspect_arguments and params? exp
|
239
|
+
@matched = Match.new(:params, exp)
|
240
|
+
elsif @inspect_arguments
|
241
|
+
process_call_args exp
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
#Note that params have been found
|
246
|
+
def process_params exp
|
247
|
+
@matched = Match.new(:params, exp)
|
248
|
+
exp
|
249
|
+
end
|
250
|
+
|
251
|
+
#Note that cookies have been found
|
252
|
+
def process_cookies exp
|
253
|
+
@matched = Match.new(:cookies, exp)
|
254
|
+
exp
|
255
|
+
end
|
256
|
+
|
257
|
+
#Ignore calls to render
|
258
|
+
def process_render exp
|
259
|
+
exp
|
260
|
+
end
|
261
|
+
|
262
|
+
#Process as default
|
263
|
+
def process_string_interp exp
|
264
|
+
process_default exp
|
265
|
+
end
|
266
|
+
|
267
|
+
#Process as default
|
268
|
+
def process_format exp
|
269
|
+
process_default exp
|
270
|
+
end
|
271
|
+
|
272
|
+
#Ignore output HTML escaped via HAML
|
273
|
+
def process_format_escaped exp
|
274
|
+
exp
|
275
|
+
end
|
276
|
+
|
277
|
+
#Ignore condition in if Sexp
|
278
|
+
def process_if exp
|
279
|
+
process exp.then_clause if sexp? exp.then_clause
|
280
|
+
process exp.else_clause if sexp? exp.else_clause
|
281
|
+
exp
|
282
|
+
end
|
283
|
+
|
284
|
+
def raw_call? exp
|
285
|
+
exp.value.node_type == :call and exp.value.method == :raw
|
286
|
+
end
|
287
|
+
|
288
|
+
def ignore_call? target, method
|
289
|
+
ignored_method?(target, method) or
|
290
|
+
safe_input_attribute?(target, method) or
|
291
|
+
ignored_model_method?(method) or
|
292
|
+
form_builder_method?(target, method) or
|
293
|
+
haml_escaped?(target, method) or
|
294
|
+
boolean_method?(method) or
|
295
|
+
cgi_escaped?(target, method) or
|
296
|
+
xml_escaped?(target, method)
|
297
|
+
end
|
298
|
+
|
299
|
+
def ignored_model_method? method
|
300
|
+
@matched and
|
301
|
+
@matched.type == :model and
|
302
|
+
IGNORE_MODEL_METHODS.include? method
|
303
|
+
end
|
304
|
+
|
305
|
+
def ignored_method? target, method
|
306
|
+
target.nil? and
|
307
|
+
(@ignore_methods.include? method or method.to_s =~ IGNORE_LIKE)
|
308
|
+
end
|
309
|
+
|
310
|
+
def cgi_escaped? target, method
|
311
|
+
method == :escape and
|
312
|
+
(target == URI or target == CGI)
|
313
|
+
end
|
314
|
+
|
315
|
+
def haml_escaped? target, method
|
316
|
+
method == :html_escape and target == HAML_HELPERS
|
317
|
+
end
|
318
|
+
|
319
|
+
def xml_escaped? target, method
|
320
|
+
method == :escape_xml and target == XML_HELPER
|
321
|
+
end
|
322
|
+
|
323
|
+
def form_builder_method? target, method
|
324
|
+
target == FORM_BUILDER and @ignore_methods.include? method
|
325
|
+
end
|
326
|
+
|
327
|
+
def safe_input_attribute? target, method
|
328
|
+
target and @safe_input_attributes.include? method
|
329
|
+
end
|
330
|
+
|
331
|
+
def boolean_method? method
|
332
|
+
method.to_s.end_with? "?"
|
333
|
+
end
|
334
|
+
end
|