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.
- checksums.yaml +7 -0
- data/CHANGES +872 -0
- data/FEATURES +16 -0
- data/README.md +169 -0
- data/WARNING_TYPES +95 -0
- data/bin/brakeman +89 -0
- data/lib/brakeman.rb +495 -0
- data/lib/brakeman/app_tree.rb +161 -0
- data/lib/brakeman/brakeman.rake +17 -0
- data/lib/brakeman/call_index.rb +219 -0
- data/lib/brakeman/checks.rb +191 -0
- data/lib/brakeman/checks/base_check.rb +518 -0
- data/lib/brakeman/checks/check_basic_auth.rb +88 -0
- data/lib/brakeman/checks/check_basic_auth_timing_attack.rb +33 -0
- data/lib/brakeman/checks/check_content_tag.rb +160 -0
- data/lib/brakeman/checks/check_create_with.rb +75 -0
- data/lib/brakeman/checks/check_cross_site_scripting.rb +385 -0
- data/lib/brakeman/checks/check_default_routes.rb +86 -0
- data/lib/brakeman/checks/check_deserialize.rb +57 -0
- data/lib/brakeman/checks/check_detailed_exceptions.rb +55 -0
- data/lib/brakeman/checks/check_digest_dos.rb +38 -0
- data/lib/brakeman/checks/check_dynamic_finders.rb +49 -0
- data/lib/brakeman/checks/check_escape_function.rb +21 -0
- data/lib/brakeman/checks/check_evaluation.rb +36 -0
- data/lib/brakeman/checks/check_execute.rb +167 -0
- data/lib/brakeman/checks/check_file_access.rb +63 -0
- data/lib/brakeman/checks/check_file_disclosure.rb +35 -0
- data/lib/brakeman/checks/check_filter_skipping.rb +31 -0
- data/lib/brakeman/checks/check_forgery_setting.rb +74 -0
- data/lib/brakeman/checks/check_header_dos.rb +31 -0
- data/lib/brakeman/checks/check_i18n_xss.rb +48 -0
- data/lib/brakeman/checks/check_jruby_xml.rb +38 -0
- data/lib/brakeman/checks/check_json_encoding.rb +47 -0
- data/lib/brakeman/checks/check_json_parsing.rb +107 -0
- data/lib/brakeman/checks/check_link_to.rb +132 -0
- data/lib/brakeman/checks/check_link_to_href.rb +115 -0
- data/lib/brakeman/checks/check_mail_to.rb +49 -0
- data/lib/brakeman/checks/check_mass_assignment.rb +198 -0
- data/lib/brakeman/checks/check_mime_type_dos.rb +39 -0
- data/lib/brakeman/checks/check_model_attr_accessible.rb +55 -0
- data/lib/brakeman/checks/check_model_attributes.rb +119 -0
- data/lib/brakeman/checks/check_model_serialize.rb +67 -0
- data/lib/brakeman/checks/check_nested_attributes.rb +38 -0
- data/lib/brakeman/checks/check_nested_attributes_bypass.rb +58 -0
- data/lib/brakeman/checks/check_number_to_currency.rb +74 -0
- data/lib/brakeman/checks/check_quote_table_name.rb +40 -0
- data/lib/brakeman/checks/check_redirect.rb +215 -0
- data/lib/brakeman/checks/check_regex_dos.rb +69 -0
- data/lib/brakeman/checks/check_render.rb +92 -0
- data/lib/brakeman/checks/check_render_dos.rb +37 -0
- data/lib/brakeman/checks/check_render_inline.rb +54 -0
- data/lib/brakeman/checks/check_response_splitting.rb +21 -0
- data/lib/brakeman/checks/check_route_dos.rb +42 -0
- data/lib/brakeman/checks/check_safe_buffer_manipulation.rb +31 -0
- data/lib/brakeman/checks/check_sanitize_methods.rb +79 -0
- data/lib/brakeman/checks/check_secrets.rb +40 -0
- data/lib/brakeman/checks/check_select_tag.rb +60 -0
- data/lib/brakeman/checks/check_select_vulnerability.rb +60 -0
- data/lib/brakeman/checks/check_send.rb +48 -0
- data/lib/brakeman/checks/check_send_file.rb +19 -0
- data/lib/brakeman/checks/check_session_manipulation.rb +36 -0
- data/lib/brakeman/checks/check_session_settings.rb +170 -0
- data/lib/brakeman/checks/check_simple_format.rb +59 -0
- data/lib/brakeman/checks/check_single_quotes.rb +101 -0
- data/lib/brakeman/checks/check_skip_before_filter.rb +60 -0
- data/lib/brakeman/checks/check_sql.rb +660 -0
- data/lib/brakeman/checks/check_sql_cves.rb +101 -0
- data/lib/brakeman/checks/check_ssl_verify.rb +49 -0
- data/lib/brakeman/checks/check_strip_tags.rb +89 -0
- data/lib/brakeman/checks/check_symbol_dos.rb +64 -0
- data/lib/brakeman/checks/check_symbol_dos_cve.rb +30 -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_unscoped_find.rb +41 -0
- data/lib/brakeman/checks/check_validation_regex.rb +116 -0
- data/lib/brakeman/checks/check_weak_hash.rb +151 -0
- data/lib/brakeman/checks/check_without_protection.rb +80 -0
- data/lib/brakeman/checks/check_xml_dos.rb +51 -0
- data/lib/brakeman/checks/check_yaml_parsing.rb +121 -0
- data/lib/brakeman/differ.rb +66 -0
- data/lib/brakeman/file_parser.rb +50 -0
- data/lib/brakeman/format/style.css +133 -0
- data/lib/brakeman/options.rb +301 -0
- data/lib/brakeman/parsers/rails2_erubis.rb +6 -0
- data/lib/brakeman/parsers/rails2_xss_plugin_erubis.rb +48 -0
- data/lib/brakeman/parsers/rails3_erubis.rb +74 -0
- data/lib/brakeman/parsers/template_parser.rb +89 -0
- data/lib/brakeman/processor.rb +102 -0
- data/lib/brakeman/processors/alias_processor.rb +1013 -0
- data/lib/brakeman/processors/base_processor.rb +277 -0
- data/lib/brakeman/processors/config_processor.rb +14 -0
- data/lib/brakeman/processors/controller_alias_processor.rb +273 -0
- data/lib/brakeman/processors/controller_processor.rb +326 -0
- data/lib/brakeman/processors/erb_template_processor.rb +80 -0
- data/lib/brakeman/processors/erubis_template_processor.rb +104 -0
- data/lib/brakeman/processors/gem_processor.rb +57 -0
- data/lib/brakeman/processors/haml_template_processor.rb +190 -0
- data/lib/brakeman/processors/lib/basic_processor.rb +37 -0
- data/lib/brakeman/processors/lib/find_all_calls.rb +223 -0
- data/lib/brakeman/processors/lib/find_call.rb +183 -0
- data/lib/brakeman/processors/lib/find_return_value.rb +134 -0
- data/lib/brakeman/processors/lib/processor_helper.rb +75 -0
- data/lib/brakeman/processors/lib/rails2_config_processor.rb +145 -0
- data/lib/brakeman/processors/lib/rails2_route_processor.rb +313 -0
- data/lib/brakeman/processors/lib/rails3_config_processor.rb +132 -0
- data/lib/brakeman/processors/lib/rails3_route_processor.rb +308 -0
- data/lib/brakeman/processors/lib/render_helper.rb +181 -0
- data/lib/brakeman/processors/lib/render_path.rb +107 -0
- data/lib/brakeman/processors/lib/route_helper.rb +68 -0
- data/lib/brakeman/processors/lib/safe_call_helper.rb +16 -0
- data/lib/brakeman/processors/library_processor.rb +119 -0
- data/lib/brakeman/processors/model_processor.rb +191 -0
- data/lib/brakeman/processors/output_processor.rb +171 -0
- data/lib/brakeman/processors/route_processor.rb +17 -0
- data/lib/brakeman/processors/slim_template_processor.rb +107 -0
- data/lib/brakeman/processors/template_alias_processor.rb +116 -0
- data/lib/brakeman/processors/template_processor.rb +74 -0
- data/lib/brakeman/report.rb +78 -0
- data/lib/brakeman/report/config/remediation.yml +71 -0
- data/lib/brakeman/report/ignore/config.rb +135 -0
- data/lib/brakeman/report/ignore/interactive.rb +311 -0
- data/lib/brakeman/report/renderer.rb +24 -0
- data/lib/brakeman/report/report_base.rb +286 -0
- data/lib/brakeman/report/report_codeclimate.rb +70 -0
- data/lib/brakeman/report/report_csv.rb +55 -0
- data/lib/brakeman/report/report_hash.rb +23 -0
- data/lib/brakeman/report/report_html.rb +216 -0
- data/lib/brakeman/report/report_json.rb +42 -0
- data/lib/brakeman/report/report_markdown.rb +156 -0
- data/lib/brakeman/report/report_table.rb +107 -0
- data/lib/brakeman/report/report_tabs.rb +17 -0
- data/lib/brakeman/report/templates/controller_overview.html.erb +22 -0
- data/lib/brakeman/report/templates/controller_warnings.html.erb +21 -0
- data/lib/brakeman/report/templates/error_overview.html.erb +29 -0
- data/lib/brakeman/report/templates/header.html.erb +58 -0
- data/lib/brakeman/report/templates/ignored_warnings.html.erb +25 -0
- data/lib/brakeman/report/templates/model_warnings.html.erb +21 -0
- data/lib/brakeman/report/templates/overview.html.erb +38 -0
- data/lib/brakeman/report/templates/security_warnings.html.erb +23 -0
- data/lib/brakeman/report/templates/template_overview.html.erb +21 -0
- data/lib/brakeman/report/templates/view_warnings.html.erb +34 -0
- data/lib/brakeman/report/templates/warning_overview.html.erb +17 -0
- data/lib/brakeman/rescanner.rb +483 -0
- data/lib/brakeman/scanner.rb +317 -0
- data/lib/brakeman/tracker.rb +347 -0
- data/lib/brakeman/tracker/collection.rb +93 -0
- data/lib/brakeman/tracker/config.rb +101 -0
- data/lib/brakeman/tracker/constants.rb +101 -0
- data/lib/brakeman/tracker/controller.rb +161 -0
- data/lib/brakeman/tracker/library.rb +17 -0
- data/lib/brakeman/tracker/model.rb +90 -0
- data/lib/brakeman/tracker/template.rb +33 -0
- data/lib/brakeman/util.rb +481 -0
- data/lib/brakeman/version.rb +3 -0
- data/lib/brakeman/warning.rb +255 -0
- data/lib/brakeman/warning_codes.rb +111 -0
- data/lib/ruby_parser/bm_sexp.rb +610 -0
- data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
- metadata +362 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Brakeman.load_brakeman_dependency 'erubis'
|
|
2
|
+
|
|
3
|
+
#This is from the rails_xss plugin for Rails 2
|
|
4
|
+
class Brakeman::Rails2XSSPluginErubis < ::Erubis::Eruby
|
|
5
|
+
def add_preamble(src)
|
|
6
|
+
#src << "@output_buffer = ActiveSupport::SafeBuffer.new;"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
#This is different from rails_xss - fixes some line number issues
|
|
10
|
+
def add_text(src, text)
|
|
11
|
+
if text == "\n"
|
|
12
|
+
src << "\n"
|
|
13
|
+
elsif text.include? "\n"
|
|
14
|
+
lines = text.split("\n")
|
|
15
|
+
if text.match(/\n\z/)
|
|
16
|
+
lines.each do |line|
|
|
17
|
+
src << "@output_buffer.safe_concat('" << escape_text(line) << "');\n"
|
|
18
|
+
end
|
|
19
|
+
else
|
|
20
|
+
lines[0..-2].each do |line|
|
|
21
|
+
src << "@output_buffer.safe_concat('" << escape_text(line) << "');\n"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
src << "@output_buffer.safe_concat('" << escape_text(lines.last) << "');"
|
|
25
|
+
end
|
|
26
|
+
else
|
|
27
|
+
src << "@output_buffer.safe_concat('" << escape_text(text) << "');"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
|
32
|
+
|
|
33
|
+
def add_expr_literal(src, code)
|
|
34
|
+
if code =~ BLOCK_EXPR
|
|
35
|
+
src << "@output_buffer.safe_concat((" << $1 << ").to_s);"
|
|
36
|
+
else
|
|
37
|
+
src << '@output_buffer << ((' << code << ').to_s);'
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def add_expr_escaped(src, code)
|
|
42
|
+
src << '@output_buffer << ' << escaped_expr(code) << ';'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def add_postamble(src)
|
|
46
|
+
#src << '@output_buffer.to_s'
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Brakeman.load_brakeman_dependency 'erubis'
|
|
2
|
+
|
|
3
|
+
# This is from Rails 5 version of the Erubis handler
|
|
4
|
+
# https://github.com/rails/rails/blob/ec608107801b1e505db03ba76bae4a326a5804ca/actionview/lib/action_view/template/handlers/erb.rb#L7-L73
|
|
5
|
+
class Brakeman::Rails3Erubis < ::Erubis::Eruby
|
|
6
|
+
|
|
7
|
+
def add_preamble(src)
|
|
8
|
+
@newline_pending = 0
|
|
9
|
+
src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def add_text(src, text)
|
|
13
|
+
return if text.empty?
|
|
14
|
+
|
|
15
|
+
if text == "\n"
|
|
16
|
+
@newline_pending += 1
|
|
17
|
+
else
|
|
18
|
+
src << "@output_buffer.safe_append='"
|
|
19
|
+
src << "\n" * @newline_pending if @newline_pending > 0
|
|
20
|
+
src << escape_text(text)
|
|
21
|
+
src << "'.freeze;"
|
|
22
|
+
|
|
23
|
+
@newline_pending = 0
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Erubis toggles <%= and <%== behavior when escaping is enabled.
|
|
28
|
+
# We override to always treat <%== as escaped.
|
|
29
|
+
def add_expr(src, code, indicator)
|
|
30
|
+
case indicator
|
|
31
|
+
when '=='
|
|
32
|
+
add_expr_escaped(src, code)
|
|
33
|
+
else
|
|
34
|
+
super
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
|
39
|
+
|
|
40
|
+
def add_expr_literal(src, code)
|
|
41
|
+
flush_newline_if_pending(src)
|
|
42
|
+
if code =~ BLOCK_EXPR
|
|
43
|
+
src << '@output_buffer.append= ' << code
|
|
44
|
+
else
|
|
45
|
+
src << '@output_buffer.append=(' << code << ');'
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def add_expr_escaped(src, code)
|
|
50
|
+
flush_newline_if_pending(src)
|
|
51
|
+
if code =~ BLOCK_EXPR
|
|
52
|
+
src << "@output_buffer.safe_expr_append= " << code
|
|
53
|
+
else
|
|
54
|
+
src << "@output_buffer.safe_expr_append=(" << code << ");"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def add_stmt(src, code)
|
|
59
|
+
flush_newline_if_pending(src)
|
|
60
|
+
super
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def add_postamble(src)
|
|
64
|
+
flush_newline_if_pending(src)
|
|
65
|
+
src << '@output_buffer.to_s'
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def flush_newline_if_pending(src)
|
|
69
|
+
if @newline_pending > 0
|
|
70
|
+
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
|
|
71
|
+
@newline_pending = 0
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module Brakeman
|
|
2
|
+
class TemplateParser
|
|
3
|
+
include Brakeman::Util
|
|
4
|
+
attr_reader :tracker
|
|
5
|
+
KNOWN_TEMPLATE_EXTENSIONS = /.*\.(erb|haml|rhtml|slim)$/
|
|
6
|
+
|
|
7
|
+
TemplateFile = Struct.new(:path, :ast, :name, :type)
|
|
8
|
+
|
|
9
|
+
def initialize tracker, file_parser
|
|
10
|
+
@tracker = tracker
|
|
11
|
+
@file_parser = file_parser
|
|
12
|
+
@file_parser.file_list[:templates] ||= []
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def parse_template path, text
|
|
16
|
+
type = path.match(KNOWN_TEMPLATE_EXTENSIONS)[1].to_sym
|
|
17
|
+
type = :erb if type == :rhtml
|
|
18
|
+
name = template_path_to_name path
|
|
19
|
+
Brakeman.debug "Parsing #{path}"
|
|
20
|
+
|
|
21
|
+
begin
|
|
22
|
+
src = case type
|
|
23
|
+
when :erb
|
|
24
|
+
type = :erubis if erubis?
|
|
25
|
+
parse_erb text
|
|
26
|
+
when :haml
|
|
27
|
+
parse_haml text
|
|
28
|
+
when :slim
|
|
29
|
+
parse_slim text
|
|
30
|
+
else
|
|
31
|
+
tracker.error "Unknown template type in #{path}"
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if src and ast = @file_parser.parse_ruby(src, path)
|
|
36
|
+
@file_parser.file_list[:templates] << TemplateFile.new(path, ast, name, type)
|
|
37
|
+
end
|
|
38
|
+
rescue Racc::ParseError => e
|
|
39
|
+
tracker.error e, "could not parse #{path}"
|
|
40
|
+
rescue Haml::Error => e
|
|
41
|
+
tracker.error e, ["While compiling HAML in #{path}"] << e.backtrace
|
|
42
|
+
rescue StandardError, LoadError => e
|
|
43
|
+
tracker.error e.exception(e.message + "\nWhile processing #{path}"), e.backtrace
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def parse_erb text
|
|
50
|
+
if tracker.config.escape_html?
|
|
51
|
+
if tracker.options[:rails3]
|
|
52
|
+
require 'brakeman/parsers/rails3_erubis'
|
|
53
|
+
Brakeman::Rails3Erubis.new(text).src
|
|
54
|
+
else
|
|
55
|
+
require 'brakeman/parsers/rails2_xss_plugin_erubis'
|
|
56
|
+
Brakeman::Rails2XSSPluginErubis.new(text).src
|
|
57
|
+
end
|
|
58
|
+
elsif tracker.config.erubis?
|
|
59
|
+
require 'brakeman/parsers/rails2_erubis'
|
|
60
|
+
Brakeman::ScannerErubis.new(text).src
|
|
61
|
+
else
|
|
62
|
+
require 'erb'
|
|
63
|
+
src = ERB.new(text, nil, "-").src
|
|
64
|
+
src.sub!(/^#.*\n/, '') if Brakeman::Scanner::RUBY_1_9
|
|
65
|
+
src
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def erubis?
|
|
70
|
+
tracker.config.escape_html? or
|
|
71
|
+
tracker.config.erubis?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def parse_haml text
|
|
75
|
+
Brakeman.load_brakeman_dependency 'haml'
|
|
76
|
+
Brakeman.load_brakeman_dependency 'sass'
|
|
77
|
+
|
|
78
|
+
Haml::Engine.new(text,
|
|
79
|
+
:escape_html => tracker.config.escape_html?).precompiled.gsub(/([^\\])\\n/, '\1')
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def parse_slim text
|
|
83
|
+
Brakeman.load_brakeman_dependency 'slim'
|
|
84
|
+
|
|
85
|
+
Slim::Template.new(:disable_capture => true,
|
|
86
|
+
:generator => Temple::Generators::RailsOutputBuffer) { text }.precompiled_template
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#Load all files in processors/
|
|
2
|
+
Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/processors/*.rb").each { |f| require f.match(/brakeman\/processors.*/)[0] }
|
|
3
|
+
require 'brakeman/tracker'
|
|
4
|
+
require 'set'
|
|
5
|
+
require 'pathname'
|
|
6
|
+
|
|
7
|
+
module Brakeman
|
|
8
|
+
#Makes calls to the appropriate processor.
|
|
9
|
+
#
|
|
10
|
+
#The ControllerProcessor, TemplateProcessor, and ModelProcessor will
|
|
11
|
+
#update the Tracker with information about what is parsed.
|
|
12
|
+
class Processor
|
|
13
|
+
include Util
|
|
14
|
+
|
|
15
|
+
def initialize(app_tree, options)
|
|
16
|
+
@app_tree = app_tree
|
|
17
|
+
@tracker = Tracker.new(@app_tree, self, options)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def tracked_events
|
|
21
|
+
@tracker
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
#Process configuration file source
|
|
25
|
+
def process_config src, file_name
|
|
26
|
+
ConfigProcessor.new(@tracker).process_config src, file_name
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
#Process Gemfile
|
|
30
|
+
def process_gems gem_files
|
|
31
|
+
GemProcessor.new(@tracker).process_gems gem_files
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
#Process route file source
|
|
35
|
+
def process_routes src
|
|
36
|
+
RoutesProcessor.new(@tracker).process_routes src
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
#Process controller source. +file_name+ is used for reporting
|
|
40
|
+
def process_controller src, file_name
|
|
41
|
+
if contains_class? src
|
|
42
|
+
ControllerProcessor.new(@app_tree, @tracker).process_controller src, file_name
|
|
43
|
+
else
|
|
44
|
+
LibraryProcessor.new(@tracker).process_library src, file_name
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
#Process variable aliasing in controller source and save it in the
|
|
49
|
+
#tracker.
|
|
50
|
+
def process_controller_alias name, src, only_method = nil, file = nil
|
|
51
|
+
ControllerAliasProcessor.new(@app_tree, @tracker, only_method).process_controller name, src, file
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
#Process a model source
|
|
55
|
+
def process_model src, file_name
|
|
56
|
+
result = ModelProcessor.new(@tracker).process_model src, file_name
|
|
57
|
+
AliasProcessor.new(@tracker).process_all result if result
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
#Process either an ERB or HAML template
|
|
61
|
+
def process_template name, src, type, called_from = nil, file_name = nil
|
|
62
|
+
case type
|
|
63
|
+
when :erb
|
|
64
|
+
result = ErbTemplateProcessor.new(@tracker, name, called_from, file_name).process src
|
|
65
|
+
when :haml
|
|
66
|
+
result = HamlTemplateProcessor.new(@tracker, name, called_from, file_name).process src
|
|
67
|
+
when :erubis
|
|
68
|
+
result = ErubisTemplateProcessor.new(@tracker, name, called_from, file_name).process src
|
|
69
|
+
when :slim
|
|
70
|
+
result = SlimTemplateProcessor.new(@tracker, name, called_from, file_name).process src
|
|
71
|
+
else
|
|
72
|
+
abort "Unknown template type: #{type} (#{name})"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
#Each template which is rendered is stored separately
|
|
76
|
+
#with a new name.
|
|
77
|
+
if called_from
|
|
78
|
+
name = ("#{name}.#{called_from}").to_sym
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
@tracker.templates[name].src = result
|
|
82
|
+
@tracker.templates[name].type = type
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
#Process any calls to render() within a template
|
|
86
|
+
def process_template_alias template
|
|
87
|
+
TemplateAliasProcessor.new(@tracker, template).process_safely template.src
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
#Process source for initializing files
|
|
91
|
+
def process_initializer file_name, src
|
|
92
|
+
res = BaseProcessor.new(@tracker).process_file src, file_name
|
|
93
|
+
res = AliasProcessor.new(@tracker).process_safely res, nil, file_name
|
|
94
|
+
@tracker.initializers[Pathname.new(file_name).basename.to_s] = res
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
#Process source for a library file
|
|
98
|
+
def process_lib src, file_name
|
|
99
|
+
LibraryProcessor.new(@tracker).process_library src, file_name
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,1013 @@
|
|
|
1
|
+
require 'brakeman/util'
|
|
2
|
+
require 'ruby_parser/bm_sexp_processor'
|
|
3
|
+
require 'brakeman/processors/lib/processor_helper'
|
|
4
|
+
require 'brakeman/processors/lib/safe_call_helper'
|
|
5
|
+
|
|
6
|
+
#Returns an s-expression with aliases replaced with their value.
|
|
7
|
+
#This does not preserve semantics (due to side effects, etc.), but it makes
|
|
8
|
+
#processing easier when searching for various things.
|
|
9
|
+
class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
10
|
+
include Brakeman::ProcessorHelper
|
|
11
|
+
include Brakeman::SafeCallHelper
|
|
12
|
+
include Brakeman::Util
|
|
13
|
+
|
|
14
|
+
attr_reader :result, :tracker
|
|
15
|
+
|
|
16
|
+
#Returns a new AliasProcessor with an empty environment.
|
|
17
|
+
#
|
|
18
|
+
#The recommended usage is:
|
|
19
|
+
#
|
|
20
|
+
# AliasProcessor.new.process_safely src
|
|
21
|
+
def initialize tracker = nil, file_name = nil
|
|
22
|
+
super()
|
|
23
|
+
@env = SexpProcessor::Environment.new
|
|
24
|
+
@inside_if = false
|
|
25
|
+
@ignore_ifs = nil
|
|
26
|
+
@exp_context = []
|
|
27
|
+
@current_module = nil
|
|
28
|
+
@tracker = tracker #set in subclass as necessary
|
|
29
|
+
@helper_method_cache = {}
|
|
30
|
+
@helper_method_info = Hash.new({})
|
|
31
|
+
@or_depth_limit = (tracker && tracker.options[:branch_limit]) || 5 #arbitrary default
|
|
32
|
+
@meth_env = nil
|
|
33
|
+
@file_name = file_name
|
|
34
|
+
set_env_defaults
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
#This method processes the given Sexp, but copies it first so
|
|
38
|
+
#the original argument will not be modified.
|
|
39
|
+
#
|
|
40
|
+
#_set_env_ should be an instance of SexpProcessor::Environment. If provided,
|
|
41
|
+
#it will be used as the starting environment.
|
|
42
|
+
#
|
|
43
|
+
#This method returns a new Sexp with variables replaced with their values,
|
|
44
|
+
#where possible.
|
|
45
|
+
def process_safely src, set_env = nil, file_name = nil
|
|
46
|
+
@file_name = file_name
|
|
47
|
+
@env = set_env || SexpProcessor::Environment.new
|
|
48
|
+
@result = src.deep_clone
|
|
49
|
+
process @result
|
|
50
|
+
@result
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
#Process a Sexp. If the Sexp has a value associated with it in the
|
|
54
|
+
#environment, that value will be returned.
|
|
55
|
+
def process_default exp
|
|
56
|
+
@exp_context.push exp
|
|
57
|
+
|
|
58
|
+
begin
|
|
59
|
+
exp.map! do |e|
|
|
60
|
+
if sexp? e and not e.empty?
|
|
61
|
+
process e
|
|
62
|
+
else
|
|
63
|
+
e
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
rescue => err
|
|
67
|
+
@tracker.error err if @tracker
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
result = replace(exp)
|
|
71
|
+
|
|
72
|
+
@exp_context.pop
|
|
73
|
+
|
|
74
|
+
result
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def replace exp, int = 0
|
|
78
|
+
return exp if int > 3
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if replacement = env[exp] and not duplicate? replacement
|
|
82
|
+
replace(replacement.deep_clone(exp.line), int + 1)
|
|
83
|
+
elsif tracker and replacement = tracker.constant_lookup(exp) and not duplicate? replacement
|
|
84
|
+
replace(replacement.deep_clone(exp.line), int + 1)
|
|
85
|
+
else
|
|
86
|
+
exp
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
ARRAY_CONST = s(:const, :Array)
|
|
91
|
+
HASH_CONST = s(:const, :Hash)
|
|
92
|
+
RAILS_TEST = s(:call, s(:call, s(:const, :Rails), :env), :test?)
|
|
93
|
+
|
|
94
|
+
#Process a method call.
|
|
95
|
+
def process_call exp
|
|
96
|
+
return exp if process_call_defn? exp
|
|
97
|
+
target_var = exp.target
|
|
98
|
+
target_var &&= target_var.deep_clone
|
|
99
|
+
if exp.node_type == :safe_call
|
|
100
|
+
exp.node_type = :call
|
|
101
|
+
end
|
|
102
|
+
exp = process_default exp
|
|
103
|
+
|
|
104
|
+
#In case it is replaced with something else
|
|
105
|
+
unless call? exp
|
|
106
|
+
return exp
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
target = exp.target
|
|
110
|
+
method = exp.method
|
|
111
|
+
first_arg = exp.first_arg
|
|
112
|
+
|
|
113
|
+
if method == :send or method == :try
|
|
114
|
+
collapse_send_call exp, first_arg
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
if node_type? target, :or and [:+, :-, :*, :/].include? method
|
|
118
|
+
res = process_or_simple_operation(exp)
|
|
119
|
+
return res if res
|
|
120
|
+
elsif target == ARRAY_CONST and method == :new
|
|
121
|
+
return Sexp.new(:array, *exp.args)
|
|
122
|
+
elsif target == HASH_CONST and method == :new and first_arg.nil? and !node_type?(@exp_context.last, :iter)
|
|
123
|
+
return Sexp.new(:hash)
|
|
124
|
+
elsif exp == RAILS_TEST
|
|
125
|
+
return Sexp.new(:false)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
#See if it is possible to simplify some basic cases
|
|
130
|
+
#of addition/concatenation.
|
|
131
|
+
case method
|
|
132
|
+
when :+
|
|
133
|
+
if array? target and array? first_arg
|
|
134
|
+
joined = join_arrays target, first_arg
|
|
135
|
+
joined.line(exp.line)
|
|
136
|
+
exp = joined
|
|
137
|
+
elsif string? first_arg
|
|
138
|
+
if string? target # "blah" + "blah"
|
|
139
|
+
joined = join_strings target, first_arg
|
|
140
|
+
joined.line(exp.line)
|
|
141
|
+
exp = joined
|
|
142
|
+
elsif call? target and target.method == :+ and string? target.first_arg
|
|
143
|
+
joined = join_strings target.first_arg, first_arg
|
|
144
|
+
joined.line(exp.line)
|
|
145
|
+
target.first_arg = joined
|
|
146
|
+
exp = target
|
|
147
|
+
end
|
|
148
|
+
elsif number? first_arg
|
|
149
|
+
if number? target
|
|
150
|
+
exp = Sexp.new(:lit, target.value + first_arg.value)
|
|
151
|
+
elsif call? target and target.method == :+ and number? target.first_arg
|
|
152
|
+
target.first_arg = Sexp.new(:lit, target.first_arg.value + first_arg.value)
|
|
153
|
+
exp = target
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
when :-
|
|
157
|
+
if number? target and number? first_arg
|
|
158
|
+
exp = Sexp.new(:lit, target.value - first_arg.value)
|
|
159
|
+
end
|
|
160
|
+
when :*
|
|
161
|
+
if number? target and number? first_arg
|
|
162
|
+
exp = Sexp.new(:lit, target.value * first_arg.value)
|
|
163
|
+
end
|
|
164
|
+
when :/
|
|
165
|
+
if number? target and number? first_arg
|
|
166
|
+
if first_arg.value == 0 and not target.value.is_a? Float
|
|
167
|
+
if @tracker
|
|
168
|
+
location = [@current_class, @current_method, "line #{first_arg.line}"].compact.join(' ')
|
|
169
|
+
require 'brakeman/processors/output_processor'
|
|
170
|
+
code = Brakeman::OutputProcessor.new.format(exp)
|
|
171
|
+
@tracker.error Exception.new("Potential divide by zero: #{code} (#{location})")
|
|
172
|
+
end
|
|
173
|
+
else
|
|
174
|
+
exp = Sexp.new(:lit, target.value / first_arg.value)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
when :[]
|
|
178
|
+
if array? target
|
|
179
|
+
temp_exp = process_array_access target, exp.args
|
|
180
|
+
exp = temp_exp if temp_exp
|
|
181
|
+
elsif hash? target
|
|
182
|
+
temp_exp = process_hash_access target, first_arg
|
|
183
|
+
exp = temp_exp if temp_exp
|
|
184
|
+
end
|
|
185
|
+
when :merge!, :update
|
|
186
|
+
if hash? target and hash? first_arg
|
|
187
|
+
target = process_hash_merge! target, first_arg
|
|
188
|
+
env[target_var] = target
|
|
189
|
+
return target
|
|
190
|
+
end
|
|
191
|
+
when :merge
|
|
192
|
+
if hash? target and hash? first_arg
|
|
193
|
+
return process_hash_merge(target, first_arg)
|
|
194
|
+
end
|
|
195
|
+
when :<<
|
|
196
|
+
if string? target and string? first_arg
|
|
197
|
+
target.value << first_arg.value
|
|
198
|
+
env[target_var] = target
|
|
199
|
+
return target
|
|
200
|
+
elsif string? target and string_interp? first_arg
|
|
201
|
+
exp = Sexp.new(:dstr, target.value + first_arg[1]).concat(first_arg[2..-1])
|
|
202
|
+
env[target_var] = exp
|
|
203
|
+
elsif string? first_arg and string_interp? target
|
|
204
|
+
if string? target.last
|
|
205
|
+
target.last.value << first_arg.value
|
|
206
|
+
elsif target.last.is_a? String
|
|
207
|
+
target.last << first_arg.value
|
|
208
|
+
else
|
|
209
|
+
target << first_arg
|
|
210
|
+
end
|
|
211
|
+
env[target_var] = target
|
|
212
|
+
return first_arg
|
|
213
|
+
elsif array? target
|
|
214
|
+
target << first_arg
|
|
215
|
+
env[target_var] = target
|
|
216
|
+
return target
|
|
217
|
+
else
|
|
218
|
+
target = find_push_target(target_var)
|
|
219
|
+
env[target] = exp unless target.nil? # Happens in TemplateAliasProcessor
|
|
220
|
+
end
|
|
221
|
+
when :first
|
|
222
|
+
if array? target and first_arg.nil? and sexp? target[1]
|
|
223
|
+
exp = target[1]
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
exp
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def process_iter exp
|
|
231
|
+
@exp_context.push exp
|
|
232
|
+
exp[1] = process exp.block_call
|
|
233
|
+
if array_detect_all_literals? exp[1]
|
|
234
|
+
return exp.block_call.target[1]
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
@exp_context.pop
|
|
238
|
+
|
|
239
|
+
env.scope do
|
|
240
|
+
exp.block_args.each do |e|
|
|
241
|
+
#Force block arg(s) to be local
|
|
242
|
+
if node_type? e, :lasgn
|
|
243
|
+
env.current[Sexp.new(:lvar, e.lhs)] = Sexp.new(:lvar, e.lhs)
|
|
244
|
+
elsif node_type? e, :kwarg
|
|
245
|
+
env.current[Sexp.new(:lvar, e[1])] = e[2]
|
|
246
|
+
elsif node_type? e, :masgn, :shadow
|
|
247
|
+
e[1..-1].each do |var|
|
|
248
|
+
local = Sexp.new(:lvar, var)
|
|
249
|
+
env.current[local] = local
|
|
250
|
+
end
|
|
251
|
+
elsif e.is_a? Symbol
|
|
252
|
+
local = Sexp.new(:lvar, e)
|
|
253
|
+
env.current[local] = local
|
|
254
|
+
else
|
|
255
|
+
raise "Unexpected value in block args: #{e.inspect}"
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
block = exp.block
|
|
260
|
+
|
|
261
|
+
if block? block
|
|
262
|
+
process_all! block
|
|
263
|
+
else
|
|
264
|
+
exp[3] = process block
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
exp
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
#Process a new scope.
|
|
272
|
+
def process_scope exp
|
|
273
|
+
env.scope do
|
|
274
|
+
process exp.block
|
|
275
|
+
end
|
|
276
|
+
exp
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
#Start new scope for block.
|
|
280
|
+
def process_block exp
|
|
281
|
+
env.scope do
|
|
282
|
+
process_default exp
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
#Process a method definition.
|
|
287
|
+
def process_defn exp
|
|
288
|
+
meth_env do
|
|
289
|
+
exp.body = process_all! exp.body
|
|
290
|
+
end
|
|
291
|
+
exp
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def meth_env
|
|
295
|
+
begin
|
|
296
|
+
env.scope do
|
|
297
|
+
set_env_defaults
|
|
298
|
+
@meth_env = env.current
|
|
299
|
+
yield
|
|
300
|
+
end
|
|
301
|
+
ensure
|
|
302
|
+
@meth_env = nil
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
#Process a method definition on self.
|
|
307
|
+
def process_defs exp
|
|
308
|
+
env.scope do
|
|
309
|
+
set_env_defaults
|
|
310
|
+
exp.body = process_all! exp.body
|
|
311
|
+
end
|
|
312
|
+
exp
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Handles x = y = z = 1
|
|
316
|
+
def get_rhs exp
|
|
317
|
+
if node_type? exp, :lasgn, :iasgn, :gasgn, :attrasgn, :safe_attrasgn, :cvdecl, :cdecl
|
|
318
|
+
get_rhs(exp.rhs)
|
|
319
|
+
else
|
|
320
|
+
exp
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
#Local assignment
|
|
325
|
+
# x = 1
|
|
326
|
+
def process_lasgn exp
|
|
327
|
+
self_assign = self_assign?(exp.lhs, exp.rhs)
|
|
328
|
+
exp.rhs = process exp.rhs if sexp? exp.rhs
|
|
329
|
+
return exp if exp.rhs.nil?
|
|
330
|
+
|
|
331
|
+
local = Sexp.new(:lvar, exp.lhs).line(exp.line || -2)
|
|
332
|
+
|
|
333
|
+
if self_assign
|
|
334
|
+
# Skip branching
|
|
335
|
+
env[local] = get_rhs(exp)
|
|
336
|
+
else
|
|
337
|
+
set_value local, get_rhs(exp)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
exp
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
#Instance variable assignment
|
|
344
|
+
# @x = 1
|
|
345
|
+
def process_iasgn exp
|
|
346
|
+
self_assign = self_assign?(exp.lhs, exp.rhs)
|
|
347
|
+
exp.rhs = process exp.rhs
|
|
348
|
+
ivar = Sexp.new(:ivar, exp.lhs).line(exp.line)
|
|
349
|
+
|
|
350
|
+
if self_assign
|
|
351
|
+
if env[ivar].nil? and @meth_env
|
|
352
|
+
@meth_env[ivar] = get_rhs(exp)
|
|
353
|
+
else
|
|
354
|
+
env[ivar] = get_rhs(exp)
|
|
355
|
+
end
|
|
356
|
+
else
|
|
357
|
+
set_value ivar, get_rhs(exp)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
exp
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
#Global assignment
|
|
364
|
+
# $x = 1
|
|
365
|
+
def process_gasgn exp
|
|
366
|
+
match = Sexp.new(:gvar, exp.lhs)
|
|
367
|
+
exp.rhs = process(exp.rhs)
|
|
368
|
+
value = get_rhs(exp)
|
|
369
|
+
value.line = exp.line
|
|
370
|
+
|
|
371
|
+
set_value match, value
|
|
372
|
+
|
|
373
|
+
exp
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
#Class variable assignment
|
|
377
|
+
# @@x = 1
|
|
378
|
+
def process_cvdecl exp
|
|
379
|
+
match = Sexp.new(:cvar, exp.lhs)
|
|
380
|
+
exp.rhs = process(exp.rhs)
|
|
381
|
+
value = get_rhs(exp)
|
|
382
|
+
|
|
383
|
+
set_value match, value
|
|
384
|
+
|
|
385
|
+
exp
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
#'Attribute' assignment
|
|
389
|
+
# x.y = 1
|
|
390
|
+
#or
|
|
391
|
+
# x[:y] = 1
|
|
392
|
+
def process_attrasgn exp
|
|
393
|
+
tar_variable = exp.target
|
|
394
|
+
target = exp.target = process(exp.target)
|
|
395
|
+
method = exp.method
|
|
396
|
+
index_arg = exp.first_arg
|
|
397
|
+
value_arg = exp.second_arg
|
|
398
|
+
|
|
399
|
+
if method == :[]=
|
|
400
|
+
index = exp.first_arg = process(index_arg)
|
|
401
|
+
value = exp.second_arg = process(value_arg)
|
|
402
|
+
match = Sexp.new(:call, target, :[], index)
|
|
403
|
+
|
|
404
|
+
set_value match, value
|
|
405
|
+
|
|
406
|
+
if hash? target
|
|
407
|
+
env[tar_variable] = hash_insert target.deep_clone, index, value
|
|
408
|
+
end
|
|
409
|
+
elsif method.to_s[-1,1] == "="
|
|
410
|
+
exp.first_arg = process(index_arg)
|
|
411
|
+
value = get_rhs(exp)
|
|
412
|
+
#This is what we'll replace with the value
|
|
413
|
+
match = Sexp.new(:call, target, method.to_s[0..-2].to_sym)
|
|
414
|
+
|
|
415
|
+
set_value match, value
|
|
416
|
+
else
|
|
417
|
+
raise "Unrecognized assignment: #{exp}"
|
|
418
|
+
end
|
|
419
|
+
exp
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
# Multiple/parallel assignment:
|
|
423
|
+
#
|
|
424
|
+
# x, y = z, w
|
|
425
|
+
def process_masgn exp
|
|
426
|
+
unless array? exp[1] and array? exp[2] and exp[1].length == exp[2].length
|
|
427
|
+
return process_default(exp)
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
vars = exp[1].dup
|
|
431
|
+
vals = exp[2].dup
|
|
432
|
+
|
|
433
|
+
vars.shift
|
|
434
|
+
vals.shift
|
|
435
|
+
|
|
436
|
+
# Call each assignment as if it is normal
|
|
437
|
+
vars.each_with_index do |var, i|
|
|
438
|
+
val = vals[i]
|
|
439
|
+
if val
|
|
440
|
+
assign = var.dup
|
|
441
|
+
assign.rhs = val
|
|
442
|
+
process assign
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
exp
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
#Merge values into hash when processing
|
|
450
|
+
#
|
|
451
|
+
# h.merge! :something => "value"
|
|
452
|
+
def process_hash_merge! hash, args
|
|
453
|
+
hash = hash.deep_clone
|
|
454
|
+
hash_iterate args do |key, replacement|
|
|
455
|
+
hash_insert hash, key, replacement
|
|
456
|
+
match = Sexp.new(:call, hash, :[], key)
|
|
457
|
+
env[match] = replacement
|
|
458
|
+
end
|
|
459
|
+
hash
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
#Return a new hash Sexp with the given values merged into it.
|
|
463
|
+
#
|
|
464
|
+
#+args+ should be a hash Sexp as well.
|
|
465
|
+
def process_hash_merge hash, args
|
|
466
|
+
hash = hash.deep_clone
|
|
467
|
+
hash_iterate args do |key, replacement|
|
|
468
|
+
hash_insert hash, key, replacement
|
|
469
|
+
end
|
|
470
|
+
hash
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
#Assignments like this
|
|
474
|
+
# x[:y] ||= 1
|
|
475
|
+
def process_op_asgn1 exp
|
|
476
|
+
return process_default(exp) if exp[3] != :"||"
|
|
477
|
+
|
|
478
|
+
target = exp[1] = process(exp[1])
|
|
479
|
+
index = exp[2][1] = process(exp[2][1])
|
|
480
|
+
value = exp[4] = process(exp[4])
|
|
481
|
+
match = Sexp.new(:call, target, :[], index)
|
|
482
|
+
|
|
483
|
+
unless env[match]
|
|
484
|
+
if request_value? target
|
|
485
|
+
env[match] = match.combine(value)
|
|
486
|
+
else
|
|
487
|
+
env[match] = value
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
exp
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
#Assignments like this
|
|
495
|
+
# x.y ||= 1
|
|
496
|
+
def process_op_asgn2 exp
|
|
497
|
+
return process_default(exp) if exp[3] != :"||"
|
|
498
|
+
|
|
499
|
+
target = exp[1] = process(exp[1])
|
|
500
|
+
value = exp[4] = process(exp[4])
|
|
501
|
+
method = exp[2]
|
|
502
|
+
|
|
503
|
+
match = Sexp.new(:call, target, method.to_s[0..-2].to_sym)
|
|
504
|
+
|
|
505
|
+
unless env[match]
|
|
506
|
+
env[match] = value
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
exp
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
#This is the right hand side value of a multiple assignment,
|
|
513
|
+
#like `x = y, z`
|
|
514
|
+
def process_svalue exp
|
|
515
|
+
exp.value
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
#Constant assignments like
|
|
519
|
+
# BIG_CONSTANT = 234810983
|
|
520
|
+
def process_cdecl exp
|
|
521
|
+
if sexp? exp.rhs
|
|
522
|
+
exp.rhs = process exp.rhs
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
file = case
|
|
526
|
+
when @file_name
|
|
527
|
+
@file_name
|
|
528
|
+
when @current_class.is_a?(Brakeman::Collection)
|
|
529
|
+
@current_class.file
|
|
530
|
+
when @current_module.is_a?(Brakeman::Collection)
|
|
531
|
+
@current_module.file
|
|
532
|
+
else
|
|
533
|
+
nil
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
@tracker.add_constant exp.lhs, exp.rhs, :file => file if @tracker
|
|
537
|
+
|
|
538
|
+
if exp.lhs.is_a? Symbol
|
|
539
|
+
match = Sexp.new(:const, exp.lhs)
|
|
540
|
+
else
|
|
541
|
+
match = exp.lhs
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
env[match] = get_rhs(exp)
|
|
545
|
+
|
|
546
|
+
exp
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# Check if exp is a call to Array#include? on an array literal
|
|
550
|
+
# that contains all literal values. For example:
|
|
551
|
+
#
|
|
552
|
+
# [1, 2, "a"].include? x
|
|
553
|
+
#
|
|
554
|
+
def array_include_all_literals? exp
|
|
555
|
+
call? exp and
|
|
556
|
+
exp.method == :include? and
|
|
557
|
+
node_type? exp.target, :array and
|
|
558
|
+
exp.target.length > 1 and
|
|
559
|
+
exp.target.all? { |e| e.is_a? Symbol or node_type? e, :lit, :str }
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def array_detect_all_literals? exp
|
|
563
|
+
call? exp and
|
|
564
|
+
[:detect, :find].include? exp.method and
|
|
565
|
+
node_type? exp.target, :array and
|
|
566
|
+
exp.target.length > 1 and
|
|
567
|
+
exp.first_arg.nil? and
|
|
568
|
+
exp.target.all? { |e| e.is_a? Symbol or node_type? e, :lit, :str }
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
#Sets @inside_if = true
|
|
572
|
+
def process_if exp
|
|
573
|
+
if @ignore_ifs.nil?
|
|
574
|
+
@ignore_ifs = @tracker && @tracker.options[:ignore_ifs]
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
condition = exp.condition = process exp.condition
|
|
578
|
+
|
|
579
|
+
#Check if a branch is obviously going to be taken
|
|
580
|
+
if true? condition
|
|
581
|
+
no_branch = true
|
|
582
|
+
exps = [exp.then_clause, nil]
|
|
583
|
+
elsif false? condition
|
|
584
|
+
no_branch = true
|
|
585
|
+
exps = [nil, exp.else_clause]
|
|
586
|
+
else
|
|
587
|
+
no_branch = false
|
|
588
|
+
exps = [exp.then_clause, exp.else_clause]
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
if @ignore_ifs or no_branch
|
|
592
|
+
exps.each_with_index do |branch, i|
|
|
593
|
+
exp[2 + i] = process_if_branch branch
|
|
594
|
+
end
|
|
595
|
+
else
|
|
596
|
+
was_inside = @inside_if
|
|
597
|
+
@inside_if = true
|
|
598
|
+
|
|
599
|
+
branch_scopes = []
|
|
600
|
+
exps.each_with_index do |branch, i|
|
|
601
|
+
scope do
|
|
602
|
+
@branch_env = env.current
|
|
603
|
+
branch_index = 2 + i # s(:if, condition, then_branch, else_branch)
|
|
604
|
+
if i == 0 and array_include_all_literals? condition
|
|
605
|
+
# If the condition is ["a", "b"].include? x
|
|
606
|
+
# set x to "a" inside the true branch
|
|
607
|
+
var = condition.first_arg
|
|
608
|
+
previous_value = env.current[var]
|
|
609
|
+
env.current[var] = condition.target[1]
|
|
610
|
+
exp[branch_index] = process_if_branch branch
|
|
611
|
+
env.current[var] = previous_value
|
|
612
|
+
else
|
|
613
|
+
exp[branch_index] = process_if_branch branch
|
|
614
|
+
end
|
|
615
|
+
branch_scopes << env.current
|
|
616
|
+
@branch_env = nil
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
@inside_if = was_inside
|
|
621
|
+
|
|
622
|
+
branch_scopes.each do |s|
|
|
623
|
+
merge_if_branch s
|
|
624
|
+
end
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
exp
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
def process_if_branch exp
|
|
631
|
+
if sexp? exp
|
|
632
|
+
if block? exp
|
|
633
|
+
process_default exp
|
|
634
|
+
else
|
|
635
|
+
process exp
|
|
636
|
+
end
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
def merge_if_branch branch_env
|
|
641
|
+
branch_env.each do |k, v|
|
|
642
|
+
next if v.nil?
|
|
643
|
+
|
|
644
|
+
current_val = env[k]
|
|
645
|
+
|
|
646
|
+
if current_val
|
|
647
|
+
unless same_value?(current_val, v)
|
|
648
|
+
if too_deep? current_val
|
|
649
|
+
# Give up branching, start over with latest value
|
|
650
|
+
env[k] = v
|
|
651
|
+
else
|
|
652
|
+
env[k] = current_val.combine(v, k.line)
|
|
653
|
+
end
|
|
654
|
+
end
|
|
655
|
+
else
|
|
656
|
+
env[k] = v
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
def too_deep? exp
|
|
662
|
+
@or_depth_limit >= 0 and
|
|
663
|
+
node_type? exp, :or and
|
|
664
|
+
exp.or_depth and
|
|
665
|
+
exp.or_depth >= @or_depth_limit
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
#Process single integer access to an array.
|
|
669
|
+
#
|
|
670
|
+
#Returns the value inside the array, if possible.
|
|
671
|
+
def process_array_access target, args
|
|
672
|
+
if args.length == 1 and integer? args.first
|
|
673
|
+
index = args.first.value
|
|
674
|
+
|
|
675
|
+
#Have to do this because first element is :array and we have to skip it
|
|
676
|
+
target[1..-1][index]
|
|
677
|
+
else
|
|
678
|
+
nil
|
|
679
|
+
end
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
#Process hash access by returning the value associated
|
|
683
|
+
#with the given argument.
|
|
684
|
+
def process_hash_access target, index
|
|
685
|
+
hash_access(target, index)
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
#Join two array literals into one.
|
|
689
|
+
def join_arrays array1, array2
|
|
690
|
+
result = Sexp.new(:array)
|
|
691
|
+
result.concat array1[1..-1]
|
|
692
|
+
result.concat array2[1..-1]
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
#Join two string literals into one.
|
|
696
|
+
def join_strings string1, string2
|
|
697
|
+
result = Sexp.new(:str)
|
|
698
|
+
result.value = string1.value + string2.value
|
|
699
|
+
|
|
700
|
+
if result.value.length > 50
|
|
701
|
+
string1
|
|
702
|
+
else
|
|
703
|
+
result
|
|
704
|
+
end
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
# Change x.send(:y, 1) to x.y(1)
|
|
708
|
+
def collapse_send_call exp, first_arg
|
|
709
|
+
# Handle try(&:id)
|
|
710
|
+
if node_type? first_arg, :block_pass
|
|
711
|
+
first_arg = first_arg[1]
|
|
712
|
+
end
|
|
713
|
+
|
|
714
|
+
return unless symbol? first_arg or string? first_arg
|
|
715
|
+
exp.method = first_arg.value.to_sym
|
|
716
|
+
args = exp.args
|
|
717
|
+
exp.pop # remove last arg
|
|
718
|
+
if args.length > 1
|
|
719
|
+
exp.arglist = args[1..-1]
|
|
720
|
+
end
|
|
721
|
+
end
|
|
722
|
+
|
|
723
|
+
#Returns a new SexpProcessor::Environment containing only instance variables.
|
|
724
|
+
#This is useful, for example, when processing views.
|
|
725
|
+
def only_ivars include_request_vars = false, lenv = nil
|
|
726
|
+
lenv ||= env
|
|
727
|
+
res = SexpProcessor::Environment.new
|
|
728
|
+
|
|
729
|
+
if include_request_vars
|
|
730
|
+
lenv.all.each do |k, v|
|
|
731
|
+
#TODO Why would this have nil values?
|
|
732
|
+
if (k.node_type == :ivar or request_value? k) and not v.nil?
|
|
733
|
+
res[k] = v.dup
|
|
734
|
+
end
|
|
735
|
+
end
|
|
736
|
+
else
|
|
737
|
+
lenv.all.each do |k, v|
|
|
738
|
+
#TODO Why would this have nil values?
|
|
739
|
+
if k.node_type == :ivar and not v.nil?
|
|
740
|
+
res[k] = v.dup
|
|
741
|
+
end
|
|
742
|
+
end
|
|
743
|
+
end
|
|
744
|
+
|
|
745
|
+
res
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
def only_request_vars
|
|
749
|
+
res = SexpProcessor::Environment.new
|
|
750
|
+
|
|
751
|
+
env.all.each do |k, v|
|
|
752
|
+
if request_value? k and not v.nil?
|
|
753
|
+
res[k] = v.dup
|
|
754
|
+
end
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
res
|
|
758
|
+
end
|
|
759
|
+
|
|
760
|
+
def get_call_value call
|
|
761
|
+
method_name = call.method
|
|
762
|
+
|
|
763
|
+
#Look for helper methods and see if we can get a return value
|
|
764
|
+
if found_method = find_method(method_name, @current_class)
|
|
765
|
+
helper = found_method[:method]
|
|
766
|
+
|
|
767
|
+
if sexp? helper
|
|
768
|
+
value = process_helper_method helper, call.args
|
|
769
|
+
value.line(call.line)
|
|
770
|
+
return value
|
|
771
|
+
else
|
|
772
|
+
raise "Unexpected value for method: #{found_method}"
|
|
773
|
+
end
|
|
774
|
+
else
|
|
775
|
+
call
|
|
776
|
+
end
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
def process_helper_method method_exp, args
|
|
780
|
+
method_name = method_exp.method_name
|
|
781
|
+
Brakeman.debug "Processing method #{method_name}"
|
|
782
|
+
|
|
783
|
+
info = @helper_method_info[method_name]
|
|
784
|
+
|
|
785
|
+
#If method uses instance variables, then include those and request
|
|
786
|
+
#variables (params, etc) in the method environment. Otherwise,
|
|
787
|
+
#only include request variables.
|
|
788
|
+
if info[:uses_ivars]
|
|
789
|
+
meth_env = only_ivars(:include_request_vars)
|
|
790
|
+
else
|
|
791
|
+
meth_env = only_request_vars
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
#Add arguments to method environment
|
|
795
|
+
assign_args method_exp, args, meth_env
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
#Find return values if method does not depend on environment/args
|
|
799
|
+
values = @helper_method_cache[method_name]
|
|
800
|
+
|
|
801
|
+
unless values
|
|
802
|
+
#Serialize environment for cache key
|
|
803
|
+
meth_values = meth_env.instance_variable_get(:@env).to_a
|
|
804
|
+
meth_values.sort!
|
|
805
|
+
meth_values = meth_values.to_s
|
|
806
|
+
|
|
807
|
+
digest = Digest::SHA1.new.update(meth_values << method_name.to_s).to_s.to_sym
|
|
808
|
+
|
|
809
|
+
values = @helper_method_cache[digest]
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
if values
|
|
813
|
+
#Use values from cache
|
|
814
|
+
values[:ivar_values].each do |var, val|
|
|
815
|
+
env[var] = val
|
|
816
|
+
end
|
|
817
|
+
|
|
818
|
+
values[:return_value]
|
|
819
|
+
else
|
|
820
|
+
#Find return value for method
|
|
821
|
+
frv = Brakeman::FindReturnValue.new
|
|
822
|
+
value = frv.get_return_value(method_exp.body_list, meth_env)
|
|
823
|
+
|
|
824
|
+
ivars = {}
|
|
825
|
+
|
|
826
|
+
only_ivars(false, meth_env).all.each do |var, val|
|
|
827
|
+
env[var] = val
|
|
828
|
+
ivars[var] = val
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
if not frv.uses_ivars? and args.length == 0
|
|
832
|
+
#Store return value without ivars and args if they are not used
|
|
833
|
+
@helper_method_cache[method_exp.method_name] = { :return_value => value, :ivar_values => ivars }
|
|
834
|
+
else
|
|
835
|
+
@helper_method_cache[digest] = { :return_value => value, :ivar_values => ivars }
|
|
836
|
+
end
|
|
837
|
+
|
|
838
|
+
#Store information about method, just ivar usage for now
|
|
839
|
+
@helper_method_info[method_name] = { :uses_ivars => frv.uses_ivars? }
|
|
840
|
+
|
|
841
|
+
value
|
|
842
|
+
end
|
|
843
|
+
end
|
|
844
|
+
|
|
845
|
+
def assign_args method_exp, args, meth_env = SexpProcessor::Environment.new
|
|
846
|
+
formal_args = method_exp.formal_args
|
|
847
|
+
|
|
848
|
+
formal_args.each_with_index do |arg, index|
|
|
849
|
+
next if index == 0
|
|
850
|
+
|
|
851
|
+
if arg.is_a? Symbol and sexp? args[index - 1]
|
|
852
|
+
meth_env[Sexp.new(:lvar, arg)] = args[index - 1]
|
|
853
|
+
end
|
|
854
|
+
end
|
|
855
|
+
|
|
856
|
+
meth_env
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
#Finds the inner most call target which is not the target of a call to <<
|
|
860
|
+
def find_push_target exp
|
|
861
|
+
if call? exp and exp.method == :<<
|
|
862
|
+
find_push_target exp.target
|
|
863
|
+
else
|
|
864
|
+
exp
|
|
865
|
+
end
|
|
866
|
+
end
|
|
867
|
+
|
|
868
|
+
def duplicate? exp
|
|
869
|
+
@exp_context[0..-2].reverse_each do |e|
|
|
870
|
+
return true if exp == e
|
|
871
|
+
end
|
|
872
|
+
|
|
873
|
+
false
|
|
874
|
+
end
|
|
875
|
+
|
|
876
|
+
def find_method *args
|
|
877
|
+
nil
|
|
878
|
+
end
|
|
879
|
+
|
|
880
|
+
#Return true if lhs == rhs or lhs is an or expression and
|
|
881
|
+
#rhs is one of its values
|
|
882
|
+
def same_value? lhs, rhs
|
|
883
|
+
if lhs == rhs
|
|
884
|
+
true
|
|
885
|
+
elsif node_type? lhs, :or
|
|
886
|
+
lhs.rhs == rhs or lhs.lhs == rhs
|
|
887
|
+
else
|
|
888
|
+
false
|
|
889
|
+
end
|
|
890
|
+
end
|
|
891
|
+
|
|
892
|
+
def self_assign? var, value
|
|
893
|
+
self_assign_var?(var, value) or self_assign_target?(var, value)
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
#Return true if for x += blah or @x += blah
|
|
897
|
+
def self_assign_var? var, value
|
|
898
|
+
call? value and
|
|
899
|
+
value.method == :+ and
|
|
900
|
+
node_type? value.target, :lvar, :ivar and
|
|
901
|
+
value.target.value == var
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
#Return true for x = x.blah
|
|
905
|
+
def self_assign_target? var, value
|
|
906
|
+
target = top_target(value)
|
|
907
|
+
|
|
908
|
+
if node_type? target, :lvar, :ivar
|
|
909
|
+
target = target.value
|
|
910
|
+
end
|
|
911
|
+
|
|
912
|
+
var == target
|
|
913
|
+
end
|
|
914
|
+
|
|
915
|
+
#Returns last non-nil target in a call chain
|
|
916
|
+
def top_target exp, last = nil
|
|
917
|
+
if call? exp
|
|
918
|
+
top_target exp.target, exp
|
|
919
|
+
elsif node_type? exp, :iter
|
|
920
|
+
top_target exp.block_call, last
|
|
921
|
+
else
|
|
922
|
+
exp || last
|
|
923
|
+
end
|
|
924
|
+
end
|
|
925
|
+
|
|
926
|
+
def value_from_if exp
|
|
927
|
+
if block? exp.else_clause or block? exp.then_clause
|
|
928
|
+
#If either clause is more than a single expression, just use entire
|
|
929
|
+
#if expression for now
|
|
930
|
+
exp
|
|
931
|
+
elsif exp.else_clause.nil?
|
|
932
|
+
exp.then_clause
|
|
933
|
+
elsif exp.then_clause.nil?
|
|
934
|
+
exp.else_clause
|
|
935
|
+
else
|
|
936
|
+
condition = exp.condition
|
|
937
|
+
|
|
938
|
+
if true? condition
|
|
939
|
+
exp.then_clause
|
|
940
|
+
elsif false? condition
|
|
941
|
+
exp.else_clause
|
|
942
|
+
else
|
|
943
|
+
exp.then_clause.combine(exp.else_clause, exp.line)
|
|
944
|
+
end
|
|
945
|
+
end
|
|
946
|
+
end
|
|
947
|
+
|
|
948
|
+
#Set variable to given value.
|
|
949
|
+
#Creates "branched" versions of values when appropriate.
|
|
950
|
+
#Avoids creating multiple branched versions inside same
|
|
951
|
+
#if branch.
|
|
952
|
+
def set_value var, value
|
|
953
|
+
if node_type? value, :if
|
|
954
|
+
value = value_from_if(value)
|
|
955
|
+
end
|
|
956
|
+
|
|
957
|
+
if @ignore_ifs or not @inside_if
|
|
958
|
+
if @meth_env and node_type? var, :ivar and env[var].nil?
|
|
959
|
+
@meth_env[var] = value
|
|
960
|
+
else
|
|
961
|
+
env[var] = value
|
|
962
|
+
end
|
|
963
|
+
elsif env.current[var]
|
|
964
|
+
env.current[var] = value
|
|
965
|
+
elsif @branch_env and @branch_env[var]
|
|
966
|
+
@branch_env[var] = value
|
|
967
|
+
elsif @branch_env and @meth_env and node_type? var, :ivar
|
|
968
|
+
@branch_env[var] = value
|
|
969
|
+
else
|
|
970
|
+
env.current[var] = value
|
|
971
|
+
end
|
|
972
|
+
end
|
|
973
|
+
|
|
974
|
+
#If possible, distribute operation over both sides of an or.
|
|
975
|
+
#For example,
|
|
976
|
+
#
|
|
977
|
+
# (1 or 2) * 5
|
|
978
|
+
#
|
|
979
|
+
#Becomes
|
|
980
|
+
#
|
|
981
|
+
# (5 or 10)
|
|
982
|
+
#
|
|
983
|
+
#Only works for strings and numbers right now.
|
|
984
|
+
def process_or_simple_operation exp
|
|
985
|
+
arg = exp.first_arg
|
|
986
|
+
return nil unless string? arg or number? arg
|
|
987
|
+
|
|
988
|
+
target = exp.target
|
|
989
|
+
lhs = process_or_target(target.lhs, exp.dup)
|
|
990
|
+
rhs = process_or_target(target.rhs, exp.dup)
|
|
991
|
+
|
|
992
|
+
if lhs and rhs
|
|
993
|
+
if same_value? lhs, rhs
|
|
994
|
+
lhs
|
|
995
|
+
else
|
|
996
|
+
exp.target.lhs = lhs
|
|
997
|
+
exp.target.rhs = rhs
|
|
998
|
+
exp.target
|
|
999
|
+
end
|
|
1000
|
+
else
|
|
1001
|
+
nil
|
|
1002
|
+
end
|
|
1003
|
+
end
|
|
1004
|
+
|
|
1005
|
+
def process_or_target value, copy
|
|
1006
|
+
if string? value or number? value
|
|
1007
|
+
copy.target = value
|
|
1008
|
+
process copy
|
|
1009
|
+
else
|
|
1010
|
+
false
|
|
1011
|
+
end
|
|
1012
|
+
end
|
|
1013
|
+
end
|