brakeman-lib 3.3.1

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 (159) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +872 -0
  3. data/FEATURES +16 -0
  4. data/README.md +169 -0
  5. data/WARNING_TYPES +95 -0
  6. data/bin/brakeman +89 -0
  7. data/lib/brakeman.rb +495 -0
  8. data/lib/brakeman/app_tree.rb +161 -0
  9. data/lib/brakeman/brakeman.rake +17 -0
  10. data/lib/brakeman/call_index.rb +219 -0
  11. data/lib/brakeman/checks.rb +191 -0
  12. data/lib/brakeman/checks/base_check.rb +518 -0
  13. data/lib/brakeman/checks/check_basic_auth.rb +88 -0
  14. data/lib/brakeman/checks/check_basic_auth_timing_attack.rb +33 -0
  15. data/lib/brakeman/checks/check_content_tag.rb +160 -0
  16. data/lib/brakeman/checks/check_create_with.rb +75 -0
  17. data/lib/brakeman/checks/check_cross_site_scripting.rb +385 -0
  18. data/lib/brakeman/checks/check_default_routes.rb +86 -0
  19. data/lib/brakeman/checks/check_deserialize.rb +57 -0
  20. data/lib/brakeman/checks/check_detailed_exceptions.rb +55 -0
  21. data/lib/brakeman/checks/check_digest_dos.rb +38 -0
  22. data/lib/brakeman/checks/check_dynamic_finders.rb +49 -0
  23. data/lib/brakeman/checks/check_escape_function.rb +21 -0
  24. data/lib/brakeman/checks/check_evaluation.rb +36 -0
  25. data/lib/brakeman/checks/check_execute.rb +167 -0
  26. data/lib/brakeman/checks/check_file_access.rb +63 -0
  27. data/lib/brakeman/checks/check_file_disclosure.rb +35 -0
  28. data/lib/brakeman/checks/check_filter_skipping.rb +31 -0
  29. data/lib/brakeman/checks/check_forgery_setting.rb +74 -0
  30. data/lib/brakeman/checks/check_header_dos.rb +31 -0
  31. data/lib/brakeman/checks/check_i18n_xss.rb +48 -0
  32. data/lib/brakeman/checks/check_jruby_xml.rb +38 -0
  33. data/lib/brakeman/checks/check_json_encoding.rb +47 -0
  34. data/lib/brakeman/checks/check_json_parsing.rb +107 -0
  35. data/lib/brakeman/checks/check_link_to.rb +132 -0
  36. data/lib/brakeman/checks/check_link_to_href.rb +115 -0
  37. data/lib/brakeman/checks/check_mail_to.rb +49 -0
  38. data/lib/brakeman/checks/check_mass_assignment.rb +198 -0
  39. data/lib/brakeman/checks/check_mime_type_dos.rb +39 -0
  40. data/lib/brakeman/checks/check_model_attr_accessible.rb +55 -0
  41. data/lib/brakeman/checks/check_model_attributes.rb +119 -0
  42. data/lib/brakeman/checks/check_model_serialize.rb +67 -0
  43. data/lib/brakeman/checks/check_nested_attributes.rb +38 -0
  44. data/lib/brakeman/checks/check_nested_attributes_bypass.rb +58 -0
  45. data/lib/brakeman/checks/check_number_to_currency.rb +74 -0
  46. data/lib/brakeman/checks/check_quote_table_name.rb +40 -0
  47. data/lib/brakeman/checks/check_redirect.rb +215 -0
  48. data/lib/brakeman/checks/check_regex_dos.rb +69 -0
  49. data/lib/brakeman/checks/check_render.rb +92 -0
  50. data/lib/brakeman/checks/check_render_dos.rb +37 -0
  51. data/lib/brakeman/checks/check_render_inline.rb +54 -0
  52. data/lib/brakeman/checks/check_response_splitting.rb +21 -0
  53. data/lib/brakeman/checks/check_route_dos.rb +42 -0
  54. data/lib/brakeman/checks/check_safe_buffer_manipulation.rb +31 -0
  55. data/lib/brakeman/checks/check_sanitize_methods.rb +79 -0
  56. data/lib/brakeman/checks/check_secrets.rb +40 -0
  57. data/lib/brakeman/checks/check_select_tag.rb +60 -0
  58. data/lib/brakeman/checks/check_select_vulnerability.rb +60 -0
  59. data/lib/brakeman/checks/check_send.rb +48 -0
  60. data/lib/brakeman/checks/check_send_file.rb +19 -0
  61. data/lib/brakeman/checks/check_session_manipulation.rb +36 -0
  62. data/lib/brakeman/checks/check_session_settings.rb +170 -0
  63. data/lib/brakeman/checks/check_simple_format.rb +59 -0
  64. data/lib/brakeman/checks/check_single_quotes.rb +101 -0
  65. data/lib/brakeman/checks/check_skip_before_filter.rb +60 -0
  66. data/lib/brakeman/checks/check_sql.rb +660 -0
  67. data/lib/brakeman/checks/check_sql_cves.rb +101 -0
  68. data/lib/brakeman/checks/check_ssl_verify.rb +49 -0
  69. data/lib/brakeman/checks/check_strip_tags.rb +89 -0
  70. data/lib/brakeman/checks/check_symbol_dos.rb +64 -0
  71. data/lib/brakeman/checks/check_symbol_dos_cve.rb +30 -0
  72. data/lib/brakeman/checks/check_translate_bug.rb +45 -0
  73. data/lib/brakeman/checks/check_unsafe_reflection.rb +51 -0
  74. data/lib/brakeman/checks/check_unscoped_find.rb +41 -0
  75. data/lib/brakeman/checks/check_validation_regex.rb +116 -0
  76. data/lib/brakeman/checks/check_weak_hash.rb +151 -0
  77. data/lib/brakeman/checks/check_without_protection.rb +80 -0
  78. data/lib/brakeman/checks/check_xml_dos.rb +51 -0
  79. data/lib/brakeman/checks/check_yaml_parsing.rb +121 -0
  80. data/lib/brakeman/differ.rb +66 -0
  81. data/lib/brakeman/file_parser.rb +50 -0
  82. data/lib/brakeman/format/style.css +133 -0
  83. data/lib/brakeman/options.rb +301 -0
  84. data/lib/brakeman/parsers/rails2_erubis.rb +6 -0
  85. data/lib/brakeman/parsers/rails2_xss_plugin_erubis.rb +48 -0
  86. data/lib/brakeman/parsers/rails3_erubis.rb +74 -0
  87. data/lib/brakeman/parsers/template_parser.rb +89 -0
  88. data/lib/brakeman/processor.rb +102 -0
  89. data/lib/brakeman/processors/alias_processor.rb +1013 -0
  90. data/lib/brakeman/processors/base_processor.rb +277 -0
  91. data/lib/brakeman/processors/config_processor.rb +14 -0
  92. data/lib/brakeman/processors/controller_alias_processor.rb +273 -0
  93. data/lib/brakeman/processors/controller_processor.rb +326 -0
  94. data/lib/brakeman/processors/erb_template_processor.rb +80 -0
  95. data/lib/brakeman/processors/erubis_template_processor.rb +104 -0
  96. data/lib/brakeman/processors/gem_processor.rb +57 -0
  97. data/lib/brakeman/processors/haml_template_processor.rb +190 -0
  98. data/lib/brakeman/processors/lib/basic_processor.rb +37 -0
  99. data/lib/brakeman/processors/lib/find_all_calls.rb +223 -0
  100. data/lib/brakeman/processors/lib/find_call.rb +183 -0
  101. data/lib/brakeman/processors/lib/find_return_value.rb +134 -0
  102. data/lib/brakeman/processors/lib/processor_helper.rb +75 -0
  103. data/lib/brakeman/processors/lib/rails2_config_processor.rb +145 -0
  104. data/lib/brakeman/processors/lib/rails2_route_processor.rb +313 -0
  105. data/lib/brakeman/processors/lib/rails3_config_processor.rb +132 -0
  106. data/lib/brakeman/processors/lib/rails3_route_processor.rb +308 -0
  107. data/lib/brakeman/processors/lib/render_helper.rb +181 -0
  108. data/lib/brakeman/processors/lib/render_path.rb +107 -0
  109. data/lib/brakeman/processors/lib/route_helper.rb +68 -0
  110. data/lib/brakeman/processors/lib/safe_call_helper.rb +16 -0
  111. data/lib/brakeman/processors/library_processor.rb +119 -0
  112. data/lib/brakeman/processors/model_processor.rb +191 -0
  113. data/lib/brakeman/processors/output_processor.rb +171 -0
  114. data/lib/brakeman/processors/route_processor.rb +17 -0
  115. data/lib/brakeman/processors/slim_template_processor.rb +107 -0
  116. data/lib/brakeman/processors/template_alias_processor.rb +116 -0
  117. data/lib/brakeman/processors/template_processor.rb +74 -0
  118. data/lib/brakeman/report.rb +78 -0
  119. data/lib/brakeman/report/config/remediation.yml +71 -0
  120. data/lib/brakeman/report/ignore/config.rb +135 -0
  121. data/lib/brakeman/report/ignore/interactive.rb +311 -0
  122. data/lib/brakeman/report/renderer.rb +24 -0
  123. data/lib/brakeman/report/report_base.rb +286 -0
  124. data/lib/brakeman/report/report_codeclimate.rb +70 -0
  125. data/lib/brakeman/report/report_csv.rb +55 -0
  126. data/lib/brakeman/report/report_hash.rb +23 -0
  127. data/lib/brakeman/report/report_html.rb +216 -0
  128. data/lib/brakeman/report/report_json.rb +42 -0
  129. data/lib/brakeman/report/report_markdown.rb +156 -0
  130. data/lib/brakeman/report/report_table.rb +107 -0
  131. data/lib/brakeman/report/report_tabs.rb +17 -0
  132. data/lib/brakeman/report/templates/controller_overview.html.erb +22 -0
  133. data/lib/brakeman/report/templates/controller_warnings.html.erb +21 -0
  134. data/lib/brakeman/report/templates/error_overview.html.erb +29 -0
  135. data/lib/brakeman/report/templates/header.html.erb +58 -0
  136. data/lib/brakeman/report/templates/ignored_warnings.html.erb +25 -0
  137. data/lib/brakeman/report/templates/model_warnings.html.erb +21 -0
  138. data/lib/brakeman/report/templates/overview.html.erb +38 -0
  139. data/lib/brakeman/report/templates/security_warnings.html.erb +23 -0
  140. data/lib/brakeman/report/templates/template_overview.html.erb +21 -0
  141. data/lib/brakeman/report/templates/view_warnings.html.erb +34 -0
  142. data/lib/brakeman/report/templates/warning_overview.html.erb +17 -0
  143. data/lib/brakeman/rescanner.rb +483 -0
  144. data/lib/brakeman/scanner.rb +317 -0
  145. data/lib/brakeman/tracker.rb +347 -0
  146. data/lib/brakeman/tracker/collection.rb +93 -0
  147. data/lib/brakeman/tracker/config.rb +101 -0
  148. data/lib/brakeman/tracker/constants.rb +101 -0
  149. data/lib/brakeman/tracker/controller.rb +161 -0
  150. data/lib/brakeman/tracker/library.rb +17 -0
  151. data/lib/brakeman/tracker/model.rb +90 -0
  152. data/lib/brakeman/tracker/template.rb +33 -0
  153. data/lib/brakeman/util.rb +481 -0
  154. data/lib/brakeman/version.rb +3 -0
  155. data/lib/brakeman/warning.rb +255 -0
  156. data/lib/brakeman/warning_codes.rb +111 -0
  157. data/lib/ruby_parser/bm_sexp.rb +610 -0
  158. data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
  159. metadata +362 -0
@@ -0,0 +1,3 @@
1
+ module Brakeman
2
+ Version = "3.3.1"
3
+ end
@@ -0,0 +1,255 @@
1
+ require 'json'
2
+ require 'digest/sha2'
3
+ require 'brakeman/warning_codes'
4
+
5
+ #The Warning class stores information about warnings
6
+ class Brakeman::Warning
7
+ attr_reader :called_from, :check, :class, :confidence, :controller,
8
+ :line, :method, :model, :template, :user_input, :user_input_type,
9
+ :warning_code, :warning_set, :warning_type
10
+
11
+ attr_accessor :code, :context, :file, :message, :relative_path
12
+
13
+ TEXT_CONFIDENCE = [ "High", "Medium", "Weak" ]
14
+
15
+ OPTIONS = {:called_from => :@called_from,
16
+ :check => :@check,
17
+ :class => :@class,
18
+ :code => :@code,
19
+ :confidence => :@confidence,
20
+ :controller => :@controller,
21
+ :file => :@file,
22
+ :gem_info => :@gem_info,
23
+ :line => :@line,
24
+ :link_path => :@link_path,
25
+ :message => :@message,
26
+ :method => :@method,
27
+ :model => :@model,
28
+ :relative_path => :@relative_path,
29
+ :template => :@template,
30
+ :user_input => :@user_input,
31
+ :warning_set => :@warning_set,
32
+ :warning_type => :@warning_type
33
+ }
34
+
35
+ #+options[:result]+ can be a result from Tracker#find_call. Otherwise, it can be +nil+.
36
+ def initialize options = {}
37
+ @view_name = nil
38
+
39
+ OPTIONS.each do |key, var|
40
+ self.instance_variable_set(var, options[key])
41
+ end
42
+
43
+ result = options[:result]
44
+ if result
45
+ @code ||= result[:call]
46
+ @file ||= result[:location][:file]
47
+
48
+ if result[:location][:type] == :template #template result
49
+ @template ||= result[:location][:template]
50
+ else
51
+ @class ||= result[:location][:class]
52
+ @method ||= result[:location][:method]
53
+ end
54
+ end
55
+
56
+ if @method.to_s =~ /^fake_filter\d+/
57
+ @method = :before_filter
58
+ end
59
+
60
+ if @user_input.is_a? Brakeman::BaseCheck::Match
61
+ @user_input_type = @user_input.type
62
+ @user_input = @user_input.match
63
+ elsif @user_input == false
64
+ @user_input = nil
65
+ end
66
+
67
+ if not @line
68
+ if @user_input and @user_input.respond_to? :line
69
+ @line = @user_input.line
70
+ elsif @code and @code.respond_to? :line
71
+ @line = @code.line
72
+ end
73
+ end
74
+
75
+ if @gem_info
76
+ if @gem_info.is_a? Hash
77
+ @line ||= @gem_info[:line]
78
+ @file ||= @gem_info[:file]
79
+ else
80
+ # Fallback behavior returns just a string for the file name
81
+ @file ||= @gem_info
82
+ end
83
+ end
84
+
85
+ unless @warning_set
86
+ if self.model
87
+ @warning_set = :model
88
+ elsif self.template
89
+ @warning_set = :template
90
+ @called_from = self.template.render_path
91
+ elsif self.controller
92
+ @warning_set = :controller
93
+ else
94
+ @warning_set = :warning
95
+ end
96
+ end
97
+
98
+ if options[:warning_code]
99
+ @warning_code = Brakeman::WarningCodes.code options[:warning_code]
100
+ end
101
+
102
+ Brakeman.debug("Warning created without warning code: #{options[:warning_code]}") unless @warning_code
103
+
104
+ @format_message = nil
105
+ @row = nil
106
+ end
107
+
108
+ def hash
109
+ self.to_s.hash
110
+ end
111
+
112
+ def eql? other_warning
113
+ self.hash == other_warning.hash
114
+ end
115
+
116
+ #Returns name of a view, including where it was rendered from
117
+ def view_name(include_renderer = true)
118
+ if called_from and include_renderer
119
+ @view_name = "#{template.name} (#{called_from.last})"
120
+ else
121
+ @view_name = template.name
122
+ end
123
+ end
124
+
125
+ #Return String of the code output from the OutputProcessor and
126
+ #stripped of newlines and tabs.
127
+ def format_code strip = true
128
+ format_ruby self.code, strip
129
+ end
130
+
131
+ #Return String of the user input formatted and
132
+ #stripped of newlines and tabs.
133
+ def format_user_input strip = true
134
+ format_ruby self.user_input, strip
135
+ end
136
+
137
+ #Return formatted warning message
138
+ def format_message
139
+ return @format_message if @format_message
140
+
141
+ @format_message = self.message.dup
142
+
143
+ if self.line
144
+ @format_message << " near line #{self.line}"
145
+ end
146
+
147
+ if self.code
148
+ @format_message << ": #{format_code}"
149
+ end
150
+
151
+ @format_message
152
+ end
153
+
154
+ def link
155
+ return @link if @link
156
+
157
+ if @link_path
158
+ if @link_path.start_with? "http"
159
+ @link = @link_path
160
+ else
161
+ @link = "http://brakemanscanner.org/docs/warning_types/#{@link_path}"
162
+ end
163
+ else
164
+ warning_path = self.warning_type.to_s.downcase.gsub(/\s+/, '_') + "/"
165
+ @link = "http://brakemanscanner.org/docs/warning_types/#{warning_path}"
166
+ end
167
+
168
+ @link
169
+ end
170
+
171
+ #Generates a hash suitable for inserting into a table
172
+ def to_row type = :warning
173
+ @row = { "Confidence" => self.confidence,
174
+ "Warning Type" => self.warning_type.to_s,
175
+ "Message" => self.format_message }
176
+
177
+ case type
178
+ when :template
179
+ @row["Template"] = self.view_name.to_s
180
+ when :model
181
+ @row["Model"] = self.model.to_s
182
+ when :controller
183
+ @row["Controller"] = self.controller.to_s
184
+ when :warning
185
+ @row["Class"] = self.class.to_s
186
+ @row["Method"] = self.method.to_s
187
+ end
188
+
189
+ @row
190
+ end
191
+
192
+ def to_s
193
+ output = "(#{TEXT_CONFIDENCE[self.confidence]}) #{self.warning_type} - #{self.message}"
194
+ output << " near line #{self.line}" if self.line
195
+ output << " in #{self.file}" if self.file
196
+ output << ": #{self.format_code}" if self.code
197
+
198
+ output
199
+ end
200
+
201
+ def fingerprint
202
+ loc = self.location
203
+ location_string = loc && loc.sort_by { |k, v| k.to_s }.inspect
204
+ warning_code_string = sprintf("%03d", @warning_code)
205
+ code_string = @code.inspect
206
+
207
+ Digest::SHA2.new(256).update("#{warning_code_string}#{code_string}#{location_string}#{@relative_path}#{self.confidence}").to_s
208
+ end
209
+
210
+ def location include_renderer = true
211
+ case @warning_set
212
+ when :template
213
+ location = { :type => :template, :template => self.view_name(include_renderer) }
214
+ when :model
215
+ location = { :type => :model, :model => self.model }
216
+ when :controller
217
+ location = { :type => :controller, :controller => self.controller }
218
+ when :warning
219
+ if self.class
220
+ location = { :type => :method, :class => self.class, :method => self.method }
221
+ else
222
+ location = nil
223
+ end
224
+ end
225
+ end
226
+
227
+ def to_hash
228
+ { :warning_type => self.warning_type,
229
+ :warning_code => @warning_code,
230
+ :fingerprint => self.fingerprint,
231
+ :message => self.message,
232
+ :file => self.file,
233
+ :line => self.line,
234
+ :link => self.link,
235
+ :code => (@code && self.format_code(false)),
236
+ :render_path => self.called_from,
237
+ :location => self.location(false),
238
+ :user_input => (@user_input && self.format_user_input(false)),
239
+ :confidence => TEXT_CONFIDENCE[self.confidence]
240
+ }
241
+ end
242
+
243
+ def to_json
244
+ JSON.generate self.to_hash
245
+ end
246
+
247
+ private
248
+
249
+ def format_ruby code, strip
250
+ formatted = Brakeman::OutputProcessor.new.format(code)
251
+ formatted.gsub!(/(\t|\r|\n)+/, " ") if strip
252
+ formatted
253
+ end
254
+ end
255
+
@@ -0,0 +1,111 @@
1
+ module Brakeman::WarningCodes
2
+ Codes = {
3
+ :sql_injection => 0,
4
+ :sql_injection_limit_offset => 1,
5
+ :cross_site_scripting => 2,
6
+ :xss_link_to => 3,
7
+ :xss_link_to_href => 4,
8
+ :xss_to_json => 5,
9
+ :csrf_protection_disabled => 6,
10
+ :csrf_protection_missing => 7,
11
+ :csrf_blacklist => 8,
12
+ :basic_auth_password => 9,
13
+ :auth_blacklist => 10,
14
+ :all_default_routes => 11,
15
+ :controller_default_routes => 12,
16
+ :code_eval => 13,
17
+ :command_injection => 14,
18
+ :dynamic_render_path => 15,
19
+ :file_access => 16,
20
+ :mass_assign_call => 17,
21
+ :open_redirect => 18,
22
+ :no_attr_accessible => 19,
23
+ :attr_protected_used => 20,
24
+ :safe_buffer_vuln => 21,
25
+ :select_options_vuln => 22,
26
+ :dangerous_send => 23,
27
+ :unsafe_constantize => 24,
28
+ :unsafe_deserialize => 25,
29
+ :http_cookies => 26,
30
+ :secure_cookies => 27,
31
+ :translate_vuln => 28,
32
+ :session_secret => 29,
33
+ :validation_regex => 30,
34
+ :CVE_2010_3933 => 31,
35
+ :CVE_2011_0446 => 32,
36
+ :CVE_2011_0447 => 33,
37
+ :CVE_2011_2929 => 34,
38
+ :CVE_2011_2930 => 35,
39
+ :CVE_2011_2931 => 36,
40
+ :CVE_2011_3186 => 37,
41
+ :CVE_2012_2660 => 38,
42
+ :CVE_2012_2661 => 39,
43
+ :CVE_2012_2695 => 40,
44
+ #:CVE_2012_2931 => 41,
45
+ :CVE_2012_3424 => 42,
46
+ :CVE_2012_3463 => 43,
47
+ :CVE_2012_3464 => 44,
48
+ :CVE_2012_3465 => 45,
49
+ :CVE_2012_5664 => 46,
50
+ :CVE_2013_0155 => 47,
51
+ :CVE_2013_0156 => 48,
52
+ :CVE_2013_0269 => 49,
53
+ :CVE_2013_0277 => 50,
54
+ :CVE_2013_0276 => 51,
55
+ :CVE_2013_0333 => 52,
56
+ :xss_content_tag => 53,
57
+ :mass_assign_without_protection => 54,
58
+ :CVE_2013_1854 => 55,
59
+ :CVE_2013_1855 => 56,
60
+ :CVE_2013_1856 => 57,
61
+ :CVE_2013_1857 => 58,
62
+ :unsafe_symbol_creation => 59,
63
+ :dangerous_attr_accessible => 60,
64
+ :local_request_config => 61,
65
+ :detailed_exceptions => 62,
66
+ :CVE_2013_4491 => 63,
67
+ :CVE_2013_6414 => 64,
68
+ # Replaced by CVE_2014_0081
69
+ #:CVE_2013_6415 => 65,
70
+ #:CVE_2013_6415_call => 66,
71
+ :CVE_2013_6416 => 67,
72
+ :CVE_2013_6416_call => 68,
73
+ :CVE_2013_6417 => 69,
74
+ :mass_assign_permit! => 70,
75
+ :ssl_verification_bypass => 71,
76
+ :CVE_2014_0080 => 72,
77
+ :CVE_2014_0081 => 73,
78
+ :CVE_2014_0081_call => 74,
79
+ :CVE_2014_0082 => 75,
80
+ :regex_dos => 76,
81
+ :CVE_2014_0130 => 77,
82
+ :CVE_2014_3482 => 78,
83
+ :CVE_2014_3483 => 79,
84
+ :CVE_2014_3514 => 80,
85
+ :CVE_2014_3514_call => 81,
86
+ :unscoped_find => 82,
87
+ :CVE_2011_2932 => 83,
88
+ :cross_site_scripting_inline => 84,
89
+ :CVE_2014_7829 => 85,
90
+ :csrf_not_protected_by_raising_exception => 86,
91
+ :CVE_2015_3226 => 87,
92
+ :CVE_2015_3227 => 88,
93
+ :session_key_manipulation => 89,
94
+ :weak_hash_digest => 90,
95
+ :weak_hash_hmac => 91,
96
+ :sql_injection_dynamic_finder => 92,
97
+ :CVE_2015_7576 => 93,
98
+ :CVE_2016_0751 => 94,
99
+ :CVE_2015_7577 => 95,
100
+ :CVE_2015_7578 => 96,
101
+ :CVE_2015_7580 => 97,
102
+ :CVE_2015_7579 => 98,
103
+ :dynamic_render_path_rce => 99,
104
+ :CVE_2015_7581 => 100,
105
+ :secret_in_source => 101,
106
+ }
107
+
108
+ def self.code name
109
+ Codes[name]
110
+ end
111
+ end
@@ -0,0 +1,610 @@
1
+ #Sexp changes from ruby_parser
2
+ #and some changes for caching hash value and tracking 'original' line number
3
+ #of a Sexp.
4
+ class Sexp
5
+ attr_accessor :original_line, :or_depth
6
+ ASSIGNMENT_BOOL = [:gasgn, :iasgn, :lasgn, :cvdecl, :cvasgn, :cdecl, :or, :and, :colon2]
7
+
8
+ def method_missing name, *args
9
+ #Brakeman does not use this functionality,
10
+ #so overriding it to raise a NoMethodError.
11
+ #
12
+ #The original functionality calls find_node and optionally
13
+ #deletes the node if found.
14
+ #
15
+ #Defining a method named "return" seems like a bad idea, so we have to
16
+ #check for it here instead
17
+ if name == :return
18
+ find_node name, *args
19
+ else
20
+ raise NoMethodError.new("No method '#{name}' for Sexp", name, args)
21
+ end
22
+ end
23
+
24
+ #Create clone of Sexp and nested Sexps but not their non-Sexp contents.
25
+ #If a line number is provided, also sets line/original_line on all Sexps.
26
+ def deep_clone line = nil
27
+ s = Sexp.new
28
+
29
+ self.each do |e|
30
+ if e.is_a? Sexp
31
+ s << e.deep_clone(line)
32
+ else
33
+ s << e
34
+ end
35
+ end
36
+
37
+ if line
38
+ s.original_line = self.original_line || self.line
39
+ s.line(line)
40
+ else
41
+ s.original_line = self.original_line
42
+ s.line(self.line)
43
+ end
44
+
45
+ s
46
+ end
47
+
48
+ def paren
49
+ @paren ||= false
50
+ end
51
+
52
+ def value
53
+ raise WrongSexpError, "Sexp#value called on multi-item Sexp", caller[1..-1] if size > 2
54
+ last
55
+ end
56
+
57
+ def value= exp
58
+ raise WrongSexpError, "Sexp#value= called on multi-item Sexp", caller[1..-1] if size > 2
59
+ @my_hash_value = nil
60
+ self[1] = exp
61
+ end
62
+
63
+ def second
64
+ self[1]
65
+ end
66
+
67
+ def to_sym
68
+ self.value.to_sym
69
+ end
70
+
71
+ def node_type= type
72
+ @my_hash_value = nil
73
+ self[0] = type
74
+ end
75
+
76
+ #Join self and exp into an :or Sexp.
77
+ #Sets or_depth.
78
+ #Used for combining "branched" values in AliasProcessor.
79
+ def combine exp, line = nil
80
+ combined = Sexp.new(:or, self, exp).line(line || -2)
81
+
82
+ combined.or_depth = [self.or_depth, exp.or_depth].compact.reduce(0, :+) + 1
83
+
84
+ combined
85
+ end
86
+
87
+ alias :node_type :sexp_type
88
+ alias :values :sexp_body # TODO: retire
89
+
90
+ alias :old_push :<<
91
+ alias :old_compact :compact
92
+ alias :old_fara :find_and_replace_all
93
+ alias :old_find_node :find_node
94
+
95
+ def << arg
96
+ @my_hash_value = nil
97
+ old_push arg
98
+ end
99
+
100
+ def hash
101
+ #There still seems to be some instances in which the hash of the
102
+ #Sexp changes, but I have not found what method call is doing it.
103
+ #Of course, Sexp is subclasses from Array, so who knows what might
104
+ #be going on.
105
+ @my_hash_value ||= super
106
+ end
107
+
108
+ def compact
109
+ @my_hash_value = nil
110
+ old_compact
111
+ end
112
+
113
+ def find_and_replace_all *args
114
+ @my_hash_value = nil
115
+ old_fara(*args)
116
+ end
117
+
118
+ def find_node *args
119
+ @my_hash_value = nil
120
+ old_find_node(*args)
121
+ end
122
+
123
+ #Iterates over the Sexps in an Sexp, skipping values that are not
124
+ #an Sexp.
125
+ def each_sexp
126
+ self.each do |e|
127
+ yield e if Sexp === e
128
+ end
129
+ end
130
+
131
+ #Raise a WrongSexpError if the nodes type does not match one of the expected
132
+ #types.
133
+ def expect *types
134
+ unless types.include? self.node_type
135
+ raise WrongSexpError, "Expected #{types.join ' or '} but given #{self.inspect}", caller[1..-1]
136
+ end
137
+ end
138
+
139
+ #Returns target of a method call:
140
+ #
141
+ #s(:call, s(:call, nil, :x, s(:arglist)), :y, s(:arglist, s(:lit, 1)))
142
+ # ^-----------target-----------^
143
+ def target
144
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
145
+ self[1]
146
+ end
147
+
148
+ #Sets the target of a method call:
149
+ def target= exp
150
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
151
+ @my_hash_value = nil
152
+ self[1] = exp
153
+ end
154
+
155
+ #Returns method of a method call:
156
+ #
157
+ #s(:call, s(:call, nil, :x, s(:arglist)), :y, s(:arglist, s(:lit, 1)))
158
+ # ^- method
159
+ def method
160
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn, :super, :zsuper, :result
161
+
162
+ case self.node_type
163
+ when :call, :attrasgn, :safe_call, :safe_attrasgn
164
+ self[2]
165
+ when :super, :zsuper
166
+ :super
167
+ when :result
168
+ self.last
169
+ end
170
+ end
171
+
172
+ def method= name
173
+ expect :call, :safe_call
174
+
175
+ self[2] = name
176
+ end
177
+
178
+ #Sets the arglist in a method call.
179
+ def arglist= exp
180
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
181
+ @my_hash_value = nil
182
+ start_index = 3
183
+
184
+ if exp.is_a? Sexp and exp.node_type == :arglist
185
+ exp = exp[1..-1]
186
+ end
187
+
188
+ exp.each_with_index do |e, i|
189
+ self[start_index + i] = e
190
+ end
191
+ end
192
+
193
+ def set_args *exp
194
+ self.arglist = exp
195
+ end
196
+
197
+ #Returns arglist for method call. This differs from Sexp#args, as Sexp#args
198
+ #does not return a 'real' Sexp (it does not have a node type) but
199
+ #Sexp#arglist returns a s(:arglist, ...)
200
+ #
201
+ # s(:call, s(:call, nil, :x, s(:arglist)), :y, s(:arglist, s(:lit, 1), s(:lit, 2)))
202
+ # ^------------ arglist ------------^
203
+ def arglist
204
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn, :super, :zsuper
205
+
206
+ case self.node_type
207
+ when :call, :attrasgn, :safe_call, :safe_attrasgn
208
+ self[3..-1].unshift :arglist
209
+ when :super, :zsuper
210
+ if self[1]
211
+ self[1..-1].unshift :arglist
212
+ else
213
+ Sexp.new(:arglist)
214
+ end
215
+ end
216
+ end
217
+
218
+ #Returns arguments of a method call. This will be an 'untyped' Sexp.
219
+ #
220
+ # s(:call, s(:call, nil, :x, s(:arglist)), :y, s(:arglist, s(:lit, 1), s(:lit, 2)))
221
+ # ^--------args--------^
222
+ def args
223
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn, :super, :zsuper
224
+
225
+ case self.node_type
226
+ when :call, :attrasgn, :safe_call, :safe_attrasgn
227
+ if self[3]
228
+ self[3..-1]
229
+ else
230
+ Sexp.new
231
+ end
232
+ when :super, :zsuper
233
+ if self[1]
234
+ self[1..-1]
235
+ else
236
+ Sexp.new
237
+ end
238
+ end
239
+ end
240
+
241
+ def each_arg replace = false
242
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn, :super, :zsuper
243
+ range = nil
244
+
245
+ case self.node_type
246
+ when :call, :attrasgn, :safe_call, :safe_attrasgn
247
+ if self[3]
248
+ range = (3...self.length)
249
+ end
250
+ when :super, :zsuper
251
+ if self[1]
252
+ range = (1...self.length)
253
+ end
254
+ end
255
+
256
+ if range
257
+ range.each do |i|
258
+ res = yield self[i]
259
+ self[i] = res if replace
260
+ end
261
+ end
262
+
263
+ self
264
+ end
265
+
266
+ def each_arg! &block
267
+ @my_hash_value = nil
268
+ self.each_arg true, &block
269
+ end
270
+
271
+ #Returns first argument of a method call.
272
+ def first_arg
273
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
274
+ self[3]
275
+ end
276
+
277
+ #Sets first argument of a method call.
278
+ def first_arg= exp
279
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
280
+ @my_hash_value = nil
281
+ self[3] = exp
282
+ end
283
+
284
+ #Returns second argument of a method call.
285
+ def second_arg
286
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
287
+ self[4]
288
+ end
289
+
290
+ #Sets second argument of a method call.
291
+ def second_arg= exp
292
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
293
+ @my_hash_value = nil
294
+ self[4] = exp
295
+ end
296
+
297
+ def third_arg
298
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
299
+ self[5]
300
+ end
301
+
302
+ def third_arg= exp
303
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
304
+ @my_hash_value = nil
305
+ self[5] = exp
306
+ end
307
+
308
+ def last_arg
309
+ expect :call, :attrasgn, :safe_call, :safe_attrasgn
310
+
311
+ if self[3]
312
+ self[-1]
313
+ else
314
+ nil
315
+ end
316
+ end
317
+
318
+ #Returns condition of an if expression:
319
+ #
320
+ # s(:if,
321
+ # s(:lvar, :condition), <-- condition
322
+ # s(:lvar, :then_val),
323
+ # s(:lvar, :else_val)))
324
+ def condition
325
+ expect :if
326
+ self[1]
327
+ end
328
+
329
+ def condition= exp
330
+ expect :if
331
+ self[1] = exp
332
+ end
333
+
334
+
335
+ #Returns 'then' clause of an if expression:
336
+ #
337
+ # s(:if,
338
+ # s(:lvar, :condition),
339
+ # s(:lvar, :then_val), <-- then clause
340
+ # s(:lvar, :else_val)))
341
+ def then_clause
342
+ expect :if
343
+ self[2]
344
+ end
345
+
346
+ #Returns 'else' clause of an if expression:
347
+ #
348
+ # s(:if,
349
+ # s(:lvar, :condition),
350
+ # s(:lvar, :then_val),
351
+ # s(:lvar, :else_val)))
352
+ # ^---else caluse---^
353
+ def else_clause
354
+ expect :if
355
+ self[3]
356
+ end
357
+
358
+ #Method call associated with a block:
359
+ #
360
+ # s(:iter,
361
+ # s(:call, nil, :x, s(:arglist)), <- block_call
362
+ # s(:lasgn, :y),
363
+ # s(:block, s(:lvar, :y), s(:call, nil, :z, s(:arglist))))
364
+ def block_call
365
+ expect :iter
366
+ self[1]
367
+ end
368
+
369
+ #Returns block of a call with a block.
370
+ #Could be a single expression or a block:
371
+ #
372
+ # s(:iter,
373
+ # s(:call, nil, :x, s(:arglist)),
374
+ # s(:lasgn, :y),
375
+ # s(:block, s(:lvar, :y), s(:call, nil, :z, s(:arglist))))
376
+ # ^-------------------- block --------------------------^
377
+ def block delete = nil
378
+ unless delete.nil? #this is from RubyParser
379
+ return find_node :block, delete
380
+ end
381
+
382
+ expect :iter, :scope, :resbody
383
+
384
+ case self.node_type
385
+ when :iter
386
+ self[3]
387
+ when :scope
388
+ self[1]
389
+ when :resbody
390
+ #This is for Ruby2Ruby ONLY
391
+ find_node :block
392
+ end
393
+ end
394
+
395
+ #Returns parameters for a block
396
+ #
397
+ # s(:iter,
398
+ # s(:call, nil, :x, s(:arglist)),
399
+ # s(:lasgn, :y), <- block_args
400
+ # s(:call, nil, :p, s(:arglist, s(:lvar, :y))))
401
+ def block_args
402
+ expect :iter
403
+ if self[2] == 0 # ?! See https://github.com/presidentbeef/brakeman/issues/331
404
+ return Sexp.new(:args)
405
+ else
406
+ self[2]
407
+ end
408
+ end
409
+
410
+ def first_param
411
+ expect :args
412
+ self[1]
413
+ end
414
+
415
+ #Returns the left hand side of assignment or boolean:
416
+ #
417
+ # s(:lasgn, :x, s(:lit, 1))
418
+ # ^--lhs
419
+ def lhs
420
+ expect(*ASSIGNMENT_BOOL)
421
+ self[1]
422
+ end
423
+
424
+ #Sets the left hand side of assignment or boolean.
425
+ def lhs= exp
426
+ expect(*ASSIGNMENT_BOOL)
427
+ @my_hash_value = nil
428
+ self[1] = exp
429
+ end
430
+
431
+ #Returns right side (value) of assignment or boolean:
432
+ #
433
+ # s(:lasgn, :x, s(:lit, 1))
434
+ # ^--rhs---^
435
+ def rhs
436
+ expect :attrasgn, :safe_attrasgn, *ASSIGNMENT_BOOL
437
+
438
+ if self.node_type == :attrasgn or self.node_type == :safe_attrasgn
439
+ self[3]
440
+ else
441
+ self[2]
442
+ end
443
+ end
444
+
445
+ #Sets the right hand side of assignment or boolean.
446
+ def rhs= exp
447
+ expect :attrasgn, :safe_attrasgn, *ASSIGNMENT_BOOL
448
+ @my_hash_value = nil
449
+
450
+ if self.node_type == :attrasgn or self.node_type == :safe_attrasgn
451
+ self[3] = exp
452
+ else
453
+ self[2] = exp
454
+ end
455
+ end
456
+
457
+ #Returns name of method being defined in a method definition.
458
+ def method_name
459
+ expect :defn, :defs
460
+
461
+ case self.node_type
462
+ when :defn
463
+ self[1]
464
+ when :defs
465
+ self[2]
466
+ end
467
+ end
468
+
469
+ def formal_args
470
+ expect :defn, :defs
471
+
472
+ case self.node_type
473
+ when :defn
474
+ self[2]
475
+ when :defs
476
+ self[3]
477
+ end
478
+ end
479
+
480
+ #Sets body, which is now a complicated process because the body is no longer
481
+ #a separate Sexp, but just a list of Sexps.
482
+ def body= exp
483
+ expect :defn, :defs, :class, :module
484
+ @my_hash_value = nil
485
+
486
+ case self.node_type
487
+ when :defn, :class
488
+ index = 3
489
+ when :defs
490
+ index = 4
491
+ when :module
492
+ index = 2
493
+ end
494
+
495
+ self.slice!(index..-1) #Remove old body
496
+
497
+ #Insert new body
498
+ exp.each do |e|
499
+ self[index] = e
500
+ index += 1
501
+ end
502
+ end
503
+
504
+ #Returns body of a method definition, class, or module.
505
+ #This will be an untyped Sexp containing a list of Sexps from the body.
506
+ def body
507
+ expect :defn, :defs, :class, :module
508
+
509
+ case self.node_type
510
+ when :defn, :class
511
+ self[3..-1]
512
+ when :defs
513
+ self[4..-1]
514
+ when :module
515
+ self[2..-1]
516
+ end
517
+ end
518
+
519
+ #Like Sexp#body, except the returned Sexp is of type :rlist
520
+ #instead of untyped.
521
+ def body_list
522
+ self.body.unshift :rlist
523
+ end
524
+
525
+ def render_type
526
+ expect :render
527
+ self[1]
528
+ end
529
+
530
+ def class_name
531
+ expect :class, :module
532
+ self[1]
533
+ end
534
+
535
+ alias module_name class_name
536
+
537
+ def parent_name
538
+ expect :class
539
+ self[2]
540
+ end
541
+
542
+ #Returns the call Sexp in a result returned from FindCall
543
+ def call
544
+ expect :result
545
+
546
+ self.last
547
+ end
548
+
549
+ #Returns the module the call is inside
550
+ def module
551
+ expect :result
552
+
553
+ self[1]
554
+ end
555
+
556
+ #Return the class the call is inside
557
+ def result_class
558
+ expect :result
559
+
560
+ self[2]
561
+ end
562
+
563
+ require 'set'
564
+ def inspect seen = Set.new
565
+ if seen.include? self.object_id
566
+ 's(...)'
567
+ else
568
+ seen << self.object_id
569
+ sexp_str = self.map do |x|
570
+ if x.is_a? Sexp
571
+ x.inspect seen
572
+ else
573
+ x.inspect
574
+ end
575
+ end.join(', ')
576
+
577
+ "s(#{sexp_str})"
578
+ end
579
+ end
580
+ end
581
+
582
+ #Invalidate hash cache if the Sexp changes
583
+ [:[]=, :clear, :collect!, :compact!, :concat, :delete, :delete_at,
584
+ :delete_if, :drop, :drop_while, :fill, :flatten!, :replace, :insert,
585
+ :keep_if, :map!, :pop, :push, :reject!, :replace, :reverse!, :rotate!,
586
+ :select!, :shift, :shuffle!, :slice!, :sort!, :sort_by!, :transpose,
587
+ :uniq!, :unshift].each do |method|
588
+
589
+ Sexp.class_eval <<-RUBY
590
+ def #{method} *args
591
+ @my_hash_value = nil
592
+ super
593
+ end
594
+ RUBY
595
+ end
596
+
597
+ #Methods used by RubyParser which would normally go through method_missing but
598
+ #we don't want that to happen because it hides Brakeman errors
599
+ [:resbody, :lasgn, :iasgn, :splat].each do |method|
600
+ Sexp.class_eval <<-RUBY
601
+ def #{method} delete = false
602
+ if delete
603
+ @my_hash_value = false
604
+ end
605
+ find_node :#{method}, delete
606
+ end
607
+ RUBY
608
+ end
609
+
610
+ class WrongSexpError < RuntimeError; end