railroader 4.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES.md +1091 -0
  3. data/FEATURES +16 -0
  4. data/README.md +174 -0
  5. data/bin/railroader +8 -0
  6. data/lib/railroader/app_tree.rb +191 -0
  7. data/lib/railroader/call_index.rb +219 -0
  8. data/lib/railroader/checks/base_check.rb +505 -0
  9. data/lib/railroader/checks/check_basic_auth.rb +88 -0
  10. data/lib/railroader/checks/check_basic_auth_timing_attack.rb +33 -0
  11. data/lib/railroader/checks/check_content_tag.rb +200 -0
  12. data/lib/railroader/checks/check_create_with.rb +74 -0
  13. data/lib/railroader/checks/check_cross_site_scripting.rb +381 -0
  14. data/lib/railroader/checks/check_default_routes.rb +86 -0
  15. data/lib/railroader/checks/check_deserialize.rb +56 -0
  16. data/lib/railroader/checks/check_detailed_exceptions.rb +55 -0
  17. data/lib/railroader/checks/check_digest_dos.rb +38 -0
  18. data/lib/railroader/checks/check_divide_by_zero.rb +42 -0
  19. data/lib/railroader/checks/check_dynamic_finders.rb +48 -0
  20. data/lib/railroader/checks/check_escape_function.rb +21 -0
  21. data/lib/railroader/checks/check_evaluation.rb +35 -0
  22. data/lib/railroader/checks/check_execute.rb +189 -0
  23. data/lib/railroader/checks/check_file_access.rb +71 -0
  24. data/lib/railroader/checks/check_file_disclosure.rb +35 -0
  25. data/lib/railroader/checks/check_filter_skipping.rb +31 -0
  26. data/lib/railroader/checks/check_forgery_setting.rb +81 -0
  27. data/lib/railroader/checks/check_header_dos.rb +31 -0
  28. data/lib/railroader/checks/check_i18n_xss.rb +48 -0
  29. data/lib/railroader/checks/check_jruby_xml.rb +36 -0
  30. data/lib/railroader/checks/check_json_encoding.rb +47 -0
  31. data/lib/railroader/checks/check_json_parsing.rb +107 -0
  32. data/lib/railroader/checks/check_link_to.rb +132 -0
  33. data/lib/railroader/checks/check_link_to_href.rb +146 -0
  34. data/lib/railroader/checks/check_mail_to.rb +49 -0
  35. data/lib/railroader/checks/check_mass_assignment.rb +196 -0
  36. data/lib/railroader/checks/check_mime_type_dos.rb +39 -0
  37. data/lib/railroader/checks/check_model_attr_accessible.rb +55 -0
  38. data/lib/railroader/checks/check_model_attributes.rb +119 -0
  39. data/lib/railroader/checks/check_model_serialize.rb +67 -0
  40. data/lib/railroader/checks/check_nested_attributes.rb +38 -0
  41. data/lib/railroader/checks/check_nested_attributes_bypass.rb +58 -0
  42. data/lib/railroader/checks/check_number_to_currency.rb +74 -0
  43. data/lib/railroader/checks/check_permit_attributes.rb +43 -0
  44. data/lib/railroader/checks/check_quote_table_name.rb +40 -0
  45. data/lib/railroader/checks/check_redirect.rb +256 -0
  46. data/lib/railroader/checks/check_regex_dos.rb +68 -0
  47. data/lib/railroader/checks/check_render.rb +97 -0
  48. data/lib/railroader/checks/check_render_dos.rb +37 -0
  49. data/lib/railroader/checks/check_render_inline.rb +53 -0
  50. data/lib/railroader/checks/check_response_splitting.rb +21 -0
  51. data/lib/railroader/checks/check_route_dos.rb +42 -0
  52. data/lib/railroader/checks/check_safe_buffer_manipulation.rb +31 -0
  53. data/lib/railroader/checks/check_sanitize_methods.rb +112 -0
  54. data/lib/railroader/checks/check_secrets.rb +40 -0
  55. data/lib/railroader/checks/check_select_tag.rb +59 -0
  56. data/lib/railroader/checks/check_select_vulnerability.rb +60 -0
  57. data/lib/railroader/checks/check_send.rb +47 -0
  58. data/lib/railroader/checks/check_send_file.rb +19 -0
  59. data/lib/railroader/checks/check_session_manipulation.rb +35 -0
  60. data/lib/railroader/checks/check_session_settings.rb +176 -0
  61. data/lib/railroader/checks/check_simple_format.rb +58 -0
  62. data/lib/railroader/checks/check_single_quotes.rb +101 -0
  63. data/lib/railroader/checks/check_skip_before_filter.rb +60 -0
  64. data/lib/railroader/checks/check_sql.rb +700 -0
  65. data/lib/railroader/checks/check_sql_cves.rb +106 -0
  66. data/lib/railroader/checks/check_ssl_verify.rb +48 -0
  67. data/lib/railroader/checks/check_strip_tags.rb +89 -0
  68. data/lib/railroader/checks/check_symbol_dos.rb +71 -0
  69. data/lib/railroader/checks/check_symbol_dos_cve.rb +30 -0
  70. data/lib/railroader/checks/check_translate_bug.rb +45 -0
  71. data/lib/railroader/checks/check_unsafe_reflection.rb +50 -0
  72. data/lib/railroader/checks/check_unscoped_find.rb +57 -0
  73. data/lib/railroader/checks/check_validation_regex.rb +116 -0
  74. data/lib/railroader/checks/check_weak_hash.rb +148 -0
  75. data/lib/railroader/checks/check_without_protection.rb +80 -0
  76. data/lib/railroader/checks/check_xml_dos.rb +45 -0
  77. data/lib/railroader/checks/check_yaml_parsing.rb +121 -0
  78. data/lib/railroader/checks.rb +209 -0
  79. data/lib/railroader/codeclimate/engine_configuration.rb +97 -0
  80. data/lib/railroader/commandline.rb +179 -0
  81. data/lib/railroader/differ.rb +66 -0
  82. data/lib/railroader/file_parser.rb +54 -0
  83. data/lib/railroader/format/style.css +133 -0
  84. data/lib/railroader/options.rb +339 -0
  85. data/lib/railroader/parsers/rails2_erubis.rb +6 -0
  86. data/lib/railroader/parsers/rails2_xss_plugin_erubis.rb +48 -0
  87. data/lib/railroader/parsers/rails3_erubis.rb +81 -0
  88. data/lib/railroader/parsers/template_parser.rb +108 -0
  89. data/lib/railroader/processor.rb +102 -0
  90. data/lib/railroader/processors/alias_processor.rb +1229 -0
  91. data/lib/railroader/processors/base_processor.rb +295 -0
  92. data/lib/railroader/processors/config_processor.rb +14 -0
  93. data/lib/railroader/processors/controller_alias_processor.rb +278 -0
  94. data/lib/railroader/processors/controller_processor.rb +249 -0
  95. data/lib/railroader/processors/erb_template_processor.rb +77 -0
  96. data/lib/railroader/processors/erubis_template_processor.rb +92 -0
  97. data/lib/railroader/processors/gem_processor.rb +64 -0
  98. data/lib/railroader/processors/haml_template_processor.rb +191 -0
  99. data/lib/railroader/processors/lib/basic_processor.rb +37 -0
  100. data/lib/railroader/processors/lib/call_conversion_helper.rb +90 -0
  101. data/lib/railroader/processors/lib/find_all_calls.rb +224 -0
  102. data/lib/railroader/processors/lib/find_call.rb +183 -0
  103. data/lib/railroader/processors/lib/find_return_value.rb +166 -0
  104. data/lib/railroader/processors/lib/module_helper.rb +111 -0
  105. data/lib/railroader/processors/lib/processor_helper.rb +88 -0
  106. data/lib/railroader/processors/lib/rails2_config_processor.rb +145 -0
  107. data/lib/railroader/processors/lib/rails2_route_processor.rb +313 -0
  108. data/lib/railroader/processors/lib/rails3_config_processor.rb +132 -0
  109. data/lib/railroader/processors/lib/rails3_route_processor.rb +308 -0
  110. data/lib/railroader/processors/lib/render_helper.rb +181 -0
  111. data/lib/railroader/processors/lib/render_path.rb +107 -0
  112. data/lib/railroader/processors/lib/route_helper.rb +68 -0
  113. data/lib/railroader/processors/lib/safe_call_helper.rb +16 -0
  114. data/lib/railroader/processors/library_processor.rb +74 -0
  115. data/lib/railroader/processors/model_processor.rb +91 -0
  116. data/lib/railroader/processors/output_processor.rb +144 -0
  117. data/lib/railroader/processors/route_processor.rb +17 -0
  118. data/lib/railroader/processors/slim_template_processor.rb +111 -0
  119. data/lib/railroader/processors/template_alias_processor.rb +118 -0
  120. data/lib/railroader/processors/template_processor.rb +85 -0
  121. data/lib/railroader/report/config/remediation.yml +71 -0
  122. data/lib/railroader/report/ignore/config.rb +153 -0
  123. data/lib/railroader/report/ignore/interactive.rb +362 -0
  124. data/lib/railroader/report/pager.rb +112 -0
  125. data/lib/railroader/report/renderer.rb +24 -0
  126. data/lib/railroader/report/report_base.rb +292 -0
  127. data/lib/railroader/report/report_codeclimate.rb +79 -0
  128. data/lib/railroader/report/report_csv.rb +55 -0
  129. data/lib/railroader/report/report_hash.rb +23 -0
  130. data/lib/railroader/report/report_html.rb +216 -0
  131. data/lib/railroader/report/report_json.rb +45 -0
  132. data/lib/railroader/report/report_markdown.rb +107 -0
  133. data/lib/railroader/report/report_table.rb +117 -0
  134. data/lib/railroader/report/report_tabs.rb +17 -0
  135. data/lib/railroader/report/report_text.rb +198 -0
  136. data/lib/railroader/report/templates/controller_overview.html.erb +22 -0
  137. data/lib/railroader/report/templates/controller_warnings.html.erb +21 -0
  138. data/lib/railroader/report/templates/error_overview.html.erb +29 -0
  139. data/lib/railroader/report/templates/header.html.erb +58 -0
  140. data/lib/railroader/report/templates/ignored_warnings.html.erb +25 -0
  141. data/lib/railroader/report/templates/model_warnings.html.erb +21 -0
  142. data/lib/railroader/report/templates/overview.html.erb +38 -0
  143. data/lib/railroader/report/templates/security_warnings.html.erb +23 -0
  144. data/lib/railroader/report/templates/template_overview.html.erb +21 -0
  145. data/lib/railroader/report/templates/view_warnings.html.erb +34 -0
  146. data/lib/railroader/report/templates/warning_overview.html.erb +17 -0
  147. data/lib/railroader/report.rb +88 -0
  148. data/lib/railroader/rescanner.rb +483 -0
  149. data/lib/railroader/scanner.rb +321 -0
  150. data/lib/railroader/tracker/collection.rb +93 -0
  151. data/lib/railroader/tracker/config.rb +154 -0
  152. data/lib/railroader/tracker/constants.rb +171 -0
  153. data/lib/railroader/tracker/controller.rb +161 -0
  154. data/lib/railroader/tracker/library.rb +17 -0
  155. data/lib/railroader/tracker/model.rb +90 -0
  156. data/lib/railroader/tracker/template.rb +33 -0
  157. data/lib/railroader/tracker.rb +362 -0
  158. data/lib/railroader/util.rb +503 -0
  159. data/lib/railroader/version.rb +3 -0
  160. data/lib/railroader/warning.rb +294 -0
  161. data/lib/railroader/warning_codes.rb +117 -0
  162. data/lib/railroader.rb +544 -0
  163. data/lib/ruby_parser/bm_sexp.rb +626 -0
  164. data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
  165. metadata +386 -0
@@ -0,0 +1,200 @@
1
+ require 'railroader/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 Railroader::CheckContentTag < Railroader::CheckCrossSiteScripting
17
+ Railroader::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, :u, :url_for,
28
+ :will_paginate].merge tracker.options[:safe_methods]
29
+
30
+ @known_dangerous = []
31
+ @content_tags = 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
+ Railroader.debug "Checking for XSS in content_tag"
38
+ @content_tags.each do |call|
39
+ process_result call
40
+ end
41
+
42
+ check_cve_2016_6316
43
+ end
44
+
45
+ def process_result result
46
+ return if duplicate? result
47
+
48
+ call = result[:call] = result[:call].dup
49
+
50
+ args = call.arglist
51
+
52
+ tag_name = args[1]
53
+ content = args[2]
54
+ attributes = args[3]
55
+ escape_attr = args[4]
56
+
57
+ @matched = false
58
+
59
+ #Silly, but still dangerous if someone uses user input in the tag type
60
+ check_argument result, tag_name
61
+
62
+ #Versions before 3.x do not escape body of tag, nor does the rails_xss gem
63
+ unless @matched or (tracker.options[:rails3] and not raw? content)
64
+ check_argument result, content
65
+ end
66
+
67
+ #Attribute keys are never escaped, so check them for user input
68
+ if not @matched and hash? attributes and not request_value? attributes
69
+ hash_iterate(attributes) do |k, _v|
70
+ check_argument result, k
71
+ return if @matched
72
+ end
73
+ end
74
+
75
+ #By default, content_tag escapes attribute values passed in as a hash.
76
+ #But this behavior can be disabled. So only check attributes hash
77
+ #if they are explicitly not escaped.
78
+ if not @matched and attributes and (false? escape_attr or cve_2016_6316?)
79
+ if request_value? attributes or not hash? attributes
80
+ check_argument result, attributes
81
+ else #check hash values
82
+ hash_iterate(attributes) do |_k, v|
83
+ check_argument result, v
84
+ return if @matched
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ def check_argument result, exp
91
+ #Check contents of raw() calls directly
92
+ if raw? exp
93
+ arg = process exp.first_arg
94
+ else
95
+ arg = process exp
96
+ end
97
+
98
+ if input = has_immediate_user_input?(arg)
99
+ message = "Unescaped #{friendly_type_of input} in content_tag"
100
+
101
+ add_result result
102
+
103
+ warn :result => result,
104
+ :warning_type => "Cross-Site Scripting",
105
+ :warning_code => :xss_content_tag,
106
+ :message => message,
107
+ :user_input => input,
108
+ :confidence => :high,
109
+ :link_path => "content_tag"
110
+
111
+ elsif not tracker.options[:ignore_model_output] and match = has_immediate_model?(arg)
112
+ unless IGNORE_MODEL_METHODS.include? match.method
113
+ add_result result
114
+
115
+ if likely_model_attribute? match
116
+ confidence = :high
117
+ else
118
+ confidence = :medium
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,
142
+ :confidence => :medium,
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 check_cve_2016_6316
160
+ if cve_2016_6316?
161
+ confidence = if @content_tags.any?
162
+ :high
163
+ else
164
+ :medium
165
+ end
166
+
167
+ fix_version = case
168
+ when version_between?("3.0.0", "3.2.22.3")
169
+ "3.2.22.4"
170
+ when version_between?("4.0.0", "4.2.7.0")
171
+ "4.2.7.1"
172
+ when version_between?("5.0.0", "5.0.0")
173
+ "5.0.0.1"
174
+ when (version.nil? and tracker.options[:rails3])
175
+ "3.2.22.4"
176
+ when (version.nil? and tracker.options[:rails4])
177
+ "4.2.7.2"
178
+ else
179
+ return
180
+ end
181
+
182
+ warn :warning_type => "Cross-Site Scripting",
183
+ :warning_code => :CVE_2016_6316,
184
+ :message => "Rails #{rails_version} content_tag does not escape double quotes in attribute values (CVE-2016-6316). Upgrade to #{fix_version}",
185
+ :confidence => confidence,
186
+ :gem_info => gemfile_or_environment,
187
+ :link_path => "https://groups.google.com/d/msg/ruby-security-ann/8B2iV2tPRSE/JkjCJkSoCgAJ"
188
+ end
189
+ end
190
+
191
+ def raw? exp
192
+ call? exp and exp.method == :raw
193
+ end
194
+
195
+ def cve_2016_6316?
196
+ version_between? "3.0.0", "3.2.22.3" or
197
+ version_between? "4.0.0", "4.2.7.0" or
198
+ version_between? "5.0.0", "5.0.0.0"
199
+ end
200
+ end
@@ -0,0 +1,74 @@
1
+ require 'railroader/checks/base_check'
2
+
3
+ class Railroader::CheckCreateWith < Railroader::BaseCheck
4
+ Railroader::Checks.add self
5
+
6
+ @description = "Checks for strong params bypass in CVE-2014-3514"
7
+
8
+ def run_check
9
+ @warned = false
10
+
11
+ if version_between? "4.0.0", "4.0.8"
12
+ suggested_version = "4.0.9"
13
+ elsif version_between? "4.1.0", "4.1.4"
14
+ suggested_version = "4.1.5"
15
+ else
16
+ return
17
+ end
18
+
19
+ @message = "create_with is vulnerable to strong params bypass. Upgrade to Rails #{suggested_version} or patch"
20
+
21
+ tracker.find_call(:method => :create_with, :nested => true).each do |result|
22
+ process_result result
23
+ end
24
+
25
+ generic_warning unless @warned
26
+ end
27
+
28
+ def process_result result
29
+ return unless original? result
30
+ arg = result[:call].first_arg
31
+
32
+ confidence = danger_level arg
33
+
34
+ if confidence
35
+ @warned = true
36
+
37
+ warn :warning_type => "Mass Assignment",
38
+ :warning_code => :CVE_2014_3514_call,
39
+ :result => result,
40
+ :message => @message,
41
+ :confidence => confidence,
42
+ :link_path => "https://groups.google.com/d/msg/rubyonrails-security/M4chq5Sb540/CC1Fh0Y_NWwJ"
43
+ end
44
+ end
45
+
46
+ #For a given create_with call, set confidence level.
47
+ #Ignore calls that use permit()
48
+ def danger_level exp
49
+ return unless sexp? exp
50
+
51
+ if call? exp and exp.method == :permit
52
+ nil
53
+ elsif request_value? exp
54
+ :high
55
+ elsif hash? exp
56
+ nil
57
+ elsif has_immediate_user_input?(exp)
58
+ :high
59
+ elsif include_user_input? exp
60
+ :medium
61
+ else
62
+ :weak
63
+ end
64
+ end
65
+
66
+ def generic_warning
67
+ warn :warning_type => "Mass Assignment",
68
+ :warning_code => :CVE_2014_3514,
69
+ :message => @message,
70
+ :gem_info => gemfile_or_environment,
71
+ :confidence => :medium,
72
+ :link_path => "https://groups.google.com/d/msg/rubyonrails-security/M4chq5Sb540/CC1Fh0Y_NWwJ"
73
+ end
74
+ end
@@ -0,0 +1,381 @@
1
+ require 'railroader/checks/base_check'
2
+ require 'railroader/processors/lib/find_call'
3
+ require 'railroader/processors/lib/processor_helper'
4
+ require 'railroader/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 Railroader::CheckCrossSiteScripting < Railroader::BaseCheck
15
+ Railroader::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
+ setup
39
+
40
+ tracker.each_template do |name, template|
41
+ Railroader.debug "Checking #{name} for XSS"
42
+
43
+ @current_template = template
44
+
45
+ template.each_output do |out|
46
+ unless check_for_immediate_xss out
47
+ @matched = false
48
+ @mark = false
49
+ process out
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ def check_for_immediate_xss exp
56
+ return :duplicate if duplicate? exp
57
+
58
+ if exp.node_type == :output
59
+ out = exp.value
60
+ elsif exp.node_type == :escaped_output
61
+ if raw_call? exp
62
+ out = exp.value.first_arg
63
+ elsif html_safe_call? exp
64
+ out = exp.value.target
65
+ end
66
+ end
67
+
68
+ return if call? out and ignore_call? out.target, out.method
69
+
70
+ if input = has_immediate_user_input?(out)
71
+ add_result exp
72
+
73
+ message = "Unescaped #{friendly_type_of input}"
74
+
75
+ warn :template => @current_template,
76
+ :warning_type => "Cross-Site Scripting",
77
+ :warning_code => :cross_site_scripting,
78
+ :message => message,
79
+ :code => input.match,
80
+ :confidence => :high
81
+
82
+ elsif not tracker.options[:ignore_model_output] and match = has_immediate_model?(out)
83
+ method = if call? match
84
+ match.method
85
+ else
86
+ nil
87
+ end
88
+
89
+ unless IGNORE_MODEL_METHODS.include? method
90
+ add_result exp
91
+
92
+ if likely_model_attribute? match
93
+ confidence = :high
94
+ else
95
+ confidence = :medium
96
+ end
97
+
98
+ message = "Unescaped model attribute"
99
+ link_path = "cross_site_scripting"
100
+ warning_code = :cross_site_scripting
101
+
102
+ if node_type?(out, :call, :safe_call, :attrasgn, :safe_attrasgn) && out.method == :to_json
103
+ message += " in JSON hash"
104
+ link_path += "_to_json"
105
+ warning_code = :xss_to_json
106
+ end
107
+
108
+ warn :template => @current_template,
109
+ :warning_type => "Cross-Site Scripting",
110
+ :warning_code => warning_code,
111
+ :message => message,
112
+ :code => match,
113
+ :confidence => confidence,
114
+ :link_path => link_path
115
+ end
116
+
117
+ else
118
+ false
119
+ end
120
+ end
121
+
122
+ #Call already involves a model, but might not be acting on a record
123
+ def likely_model_attribute? exp
124
+ return false unless call? exp
125
+
126
+ method = exp.method
127
+
128
+ if MODEL_METHODS.include? method or method.to_s.start_with? "find_by_"
129
+ true
130
+ else
131
+ likely_model_attribute? exp.target
132
+ end
133
+ end
134
+
135
+ #Process an output Sexp
136
+ def process_output exp
137
+ process exp.value.dup
138
+ end
139
+
140
+ #Look for calls to raw()
141
+ #Otherwise, ignore
142
+ def process_escaped_output exp
143
+ unless check_for_immediate_xss exp
144
+ if not duplicate? exp
145
+ if raw_call? exp
146
+ process exp.value.first_arg
147
+ elsif html_safe_call? exp
148
+ process exp.value.target
149
+ end
150
+ end
151
+ end
152
+ exp
153
+ end
154
+
155
+ #Check a call for user input
156
+ #
157
+ #
158
+ #Since we want to report an entire call and not just part of one, use @mark
159
+ #to mark when a call is started. Any dangerous values inside will then
160
+ #report the entire call chain.
161
+ def process_call exp
162
+ if @mark
163
+ actually_process_call exp
164
+ else
165
+ @mark = true
166
+ actually_process_call exp
167
+ message = nil
168
+
169
+ if @matched
170
+ unless @matched.type and tracker.options[:ignore_model_output]
171
+ message = "Unescaped #{friendly_type_of @matched}"
172
+ end
173
+
174
+ if message and not duplicate? exp
175
+ add_result exp
176
+
177
+ link_path = "cross_site_scripting"
178
+ warning_code = :cross_site_scripting
179
+
180
+ if @known_dangerous.include? exp.method
181
+ confidence = :high
182
+ if exp.method == :to_json
183
+ message += " in JSON hash"
184
+ link_path += "_to_json"
185
+ warning_code = :xss_to_json
186
+ end
187
+ else
188
+ confidence = :weak
189
+ end
190
+
191
+ warn :template => @current_template,
192
+ :warning_type => "Cross-Site Scripting",
193
+ :warning_code => warning_code,
194
+ :message => message,
195
+ :code => exp,
196
+ :user_input => @matched,
197
+ :confidence => confidence,
198
+ :link_path => link_path
199
+ end
200
+ end
201
+
202
+ @mark = @matched = false
203
+ end
204
+
205
+ exp
206
+ end
207
+
208
+ def actually_process_call exp
209
+ return if @matched
210
+ target = exp.target
211
+ if sexp? target
212
+ target = process target
213
+ end
214
+
215
+ method = exp.method
216
+
217
+ #Ignore safe items
218
+ if ignore_call? target, method
219
+ @matched = false
220
+ elsif sexp? target and model_name? target[1] #TODO: use method call?
221
+ @matched = Match.new(:model, exp)
222
+ elsif cookies? exp
223
+ @matched = Match.new(:cookies, exp)
224
+ elsif @inspect_arguments and params? exp
225
+ @matched = Match.new(:params, exp)
226
+ elsif @inspect_arguments
227
+ process_call_args exp
228
+ end
229
+ end
230
+
231
+ #Note that params have been found
232
+ def process_params exp
233
+ @matched = Match.new(:params, exp)
234
+ exp
235
+ end
236
+
237
+ #Note that cookies have been found
238
+ def process_cookies exp
239
+ @matched = Match.new(:cookies, exp)
240
+ exp
241
+ end
242
+
243
+ #Ignore calls to render
244
+ def process_render exp
245
+ exp
246
+ end
247
+
248
+ #Process as default
249
+ def process_dstr exp
250
+ process_default exp
251
+ end
252
+
253
+ #Process as default
254
+ def process_format exp
255
+ process_default exp
256
+ end
257
+
258
+ #Ignore output HTML escaped via HAML
259
+ def process_format_escaped exp
260
+ exp
261
+ end
262
+
263
+ #Ignore condition in if Sexp
264
+ def process_if exp
265
+ process exp.then_clause if sexp? exp.then_clause
266
+ process exp.else_clause if sexp? exp.else_clause
267
+ exp
268
+ end
269
+
270
+ def process_case exp
271
+ #Ignore user input in case value
272
+ #TODO: also ignore when values
273
+
274
+ current = 2
275
+ while current < exp.length
276
+ process exp[current] if exp[current]
277
+ current += 1
278
+ end
279
+
280
+ exp
281
+ end
282
+
283
+ def setup
284
+ @ignore_methods = Set[:==, :!=, :button_to, :check_box, :content_tag, :escapeHTML, :escape_once,
285
+ :field_field, :fields_for, :h, :hidden_field,
286
+ :hidden_field, :hidden_field_tag, :image_tag, :label,
287
+ :link_to, :mail_to, :radio_button, :select,
288
+ :submit_tag, :text_area, :text_field,
289
+ :text_field_tag, :url_encode, :u, :url_for,
290
+ :will_paginate].merge tracker.options[:safe_methods]
291
+
292
+ @models = tracker.models.keys
293
+ @inspect_arguments = tracker.options[:check_arguments]
294
+
295
+ @known_dangerous = Set[:truncate, :concat]
296
+
297
+ if version_between? "2.0.0", "3.0.5"
298
+ @known_dangerous << :auto_link
299
+ elsif version_between? "3.0.6", "3.0.99"
300
+ @ignore_methods << :auto_link
301
+ end
302
+
303
+ if version_between? "2.0.0", "2.3.14" or tracker.config.gem_version(:'rails-html-sanitizer') == '1.0.2'
304
+ @known_dangerous << :strip_tags
305
+ end
306
+
307
+ if tracker.config.has_gem? :'rails-html-sanitizer' and
308
+ version_between? "1.0.0", "1.0.2", tracker.config.gem_version(:'rails-html-sanitizer')
309
+
310
+ @known_dangerous << :sanitize
311
+ end
312
+
313
+ json_escape_on = false
314
+ initializers = tracker.check_initializers :ActiveSupport, :escape_html_entities_in_json=
315
+ initializers.each {|result| json_escape_on = true?(result.call.first_arg) }
316
+
317
+ if tracker.config.escape_html_entities_in_json?
318
+ json_escape_on = true
319
+ elsif version_between? "4.0.0", "9.9.9"
320
+ json_escape_on = true
321
+ end
322
+
323
+ if !json_escape_on or version_between? "0.0.0", "2.0.99"
324
+ @known_dangerous << :to_json
325
+ Railroader.debug("Automatic to_json escaping not enabled, consider to_json dangerous")
326
+ else
327
+ @safe_input_attributes << :to_json
328
+ Railroader.debug("Automatic to_json escaping is enabled.")
329
+ end
330
+ end
331
+
332
+ def raw_call? exp
333
+ exp.value.node_type == :call and exp.value.method == :raw
334
+ end
335
+
336
+ def html_safe_call? exp
337
+ call? exp.value and exp.value.method == :html_safe
338
+ end
339
+
340
+ def ignore_call? target, method
341
+ ignored_method?(target, method) or
342
+ safe_input_attribute?(target, method) or
343
+ ignored_model_method?(target, method) or
344
+ form_builder_method?(target, method) or
345
+ haml_escaped?(target, method) or
346
+ boolean_method?(method) or
347
+ cgi_escaped?(target, method) or
348
+ xml_escaped?(target, method)
349
+ end
350
+
351
+ def ignored_model_method? target, method
352
+ ((@matched and @matched.type == :model) or
353
+ model_name? target) and
354
+ IGNORE_MODEL_METHODS.include? method
355
+ end
356
+
357
+ def ignored_method? target, method
358
+ @ignore_methods.include? method or method.to_s =~ IGNORE_LIKE
359
+ end
360
+
361
+ def cgi_escaped? target, method
362
+ method == :escape and
363
+ (target == URI or target == CGI)
364
+ end
365
+
366
+ def haml_escaped? target, method
367
+ method == :html_escape and target == HAML_HELPERS
368
+ end
369
+
370
+ def xml_escaped? target, method
371
+ method == :escape_xml and target == XML_HELPER
372
+ end
373
+
374
+ def form_builder_method? target, method
375
+ target == FORM_BUILDER and @ignore_methods.include? method
376
+ end
377
+
378
+ def safe_input_attribute? target, method
379
+ target and always_safe_method? method
380
+ end
381
+ end