brakeman 1.1.0 → 1.2.0

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.
@@ -60,29 +60,18 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
60
60
  #
61
61
  # `rm -rf #{params[:file]}`
62
62
  def check_for_backticks tracker
63
- tracker.each_method do |exp, set_name, method_name|
64
- @current_set = set_name
65
- @current_method = method_name
66
-
67
- process exp
68
- end
69
-
70
- @current_set = nil
71
-
72
- tracker.each_template do |name, template|
73
- @current_template = template
74
-
75
- process template[:src]
63
+ tracker.find_call(:target => nil, :method => :`).each do |result|
64
+ process_backticks result
76
65
  end
77
-
78
- @current_template = nil
79
66
  end
80
67
 
81
68
  #Processes backticks.
82
- def process_dxstr exp
83
- return exp if duplicate? exp
69
+ def process_backticks result
70
+ return if duplicate? result
71
+
72
+ add_result result
84
73
 
85
- add_result exp
74
+ exp = result[:call]
86
75
 
87
76
  if include_user_input? exp
88
77
  confidence = CONFIDENCE[:high]
@@ -96,15 +85,13 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
96
85
  :code => exp,
97
86
  :confidence => confidence }
98
87
 
99
- if @current_template
100
- warning[:template] = @current_template
88
+ if result[:location][0] == :template
89
+ warning[:template] = result[:location][1]
101
90
  else
102
- warning[:class] = @current_set
103
- warning[:method] = @current_method
91
+ warning[:class] = result[:location][1]
92
+ warning[:method] = result[:location][2]
104
93
  end
105
94
 
106
95
  warn warning
107
-
108
- exp
109
96
  end
110
97
  end
@@ -5,24 +5,15 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
5
5
  Brakeman::Checks.add self
6
6
 
7
7
  def run_check
8
- debug_info "Checking all method bodies for calls to render()"
9
- tracker.each_method do |src, class_name, method_name|
10
- @current_class = class_name
11
- @current_method = method_name
12
- process src
13
- end
14
-
15
- debug_info "Checking all templates for calls to render()"
16
- tracker.each_template do |name, template|
17
- @current_template = template
18
- process template[:src]
8
+ tracker.find_call(:target => nil, :method => :render).each do |result|
9
+ process_render result
19
10
  end
20
11
  end
21
12
 
22
- def process_render exp
23
- case exp[1]
13
+ def process_render result
14
+ case result[:call][1]
24
15
  when :partial, :template, :action, :file
25
- check_for_dynamic_path exp
16
+ check_for_dynamic_path result
26
17
  when :inline
27
18
  when :js
28
19
  when :json
@@ -30,16 +21,15 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
30
21
  when :update
31
22
  when :xml
32
23
  end
33
- exp
34
24
  end
35
25
 
36
26
  #Check if path to action or file is determined dynamically
37
- def check_for_dynamic_path exp
38
- view = exp[2]
27
+ def check_for_dynamic_path result
28
+ view = result[:call][2]
39
29
 
40
- if sexp? view and view.node_type != :str and view.node_type != :lit and not duplicate? exp
30
+ if sexp? view and view.node_type != :str and view.node_type != :lit and not duplicate? result
41
31
 
42
- add_result exp
32
+ add_result result
43
33
 
44
34
  if include_user_input? view
45
35
  confidence = CONFIDENCE[:high]
@@ -49,16 +39,15 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
49
39
 
50
40
  warning = { :warning_type => "Dynamic Render Path",
51
41
  :message => "Render path is dynamic",
52
- :line => exp.line,
53
- :code => exp,
42
+ :line => result[:call].line,
43
+ :code => result[:call],
54
44
  :confidence => confidence }
55
45
 
56
-
57
- if @current_template
58
- warning[:template] = @current_template
46
+ if result[:location][0] == :template
47
+ warning[:template] = result[:location][1]
59
48
  else
60
- warning[:class] = @current_class
61
- warning[:method] = @current_method
49
+ warning[:class] = result[:location][1]
50
+ warning[:method] = result[:location][2]
62
51
  end
63
52
 
64
53
  warn warning
@@ -31,17 +31,47 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
31
31
  debug_info "Finding possible SQL calls using constantized()"
32
32
  calls.concat tracker.find_call(:method => /^(find.*|last|first|all|count|sum|average|minumum|maximum|count_by_sql)$/).select { |result| constantize_call? result }
33
33
 
34
+ debug_info "Finding calls to named_scope or scope"
35
+ calls.concat find_scope_calls
36
+
34
37
  debug_info "Processing possible SQL calls"
35
38
  calls.each do |c|
36
39
  process_result c
37
40
  end
38
41
  end
39
42
 
43
+ #Find calls to named_scope() or scope() in models
44
+ def find_scope_calls
45
+ scope_calls = []
46
+
47
+ if version_between? "2.1.0", "3.0.9"
48
+ tracker.models.each do |name, model|
49
+ if model[:options][:named_scope]
50
+ model[:options][:named_scope].each do |args|
51
+ call = Sexp.new(:call, nil, :named_scope, args).line(args.line)
52
+ scope_calls << { :call => call, :location => [:class, name ] }
53
+ end
54
+ end
55
+ end
56
+ elsif version_between? "3.1.0", "3.9.9"
57
+ tracker.models.each do |name, model|
58
+ if model[:options][:scope]
59
+ model[:options][:scope].each do |args|
60
+ call = Sexp.new(:call, nil, :scope, args).line(args.line)
61
+ scope_calls << { :call => call, :location => [:class, name ] }
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ scope_calls
68
+ end
69
+
40
70
  #Process result from Tracker#find_call.
41
71
  def process_result result
42
72
  call = result[:call]
43
73
 
44
- args = process call[3]
74
+ args = call[3]
45
75
 
46
76
  if call[2] == :find_by_sql or call[2] == :count_by_sql
47
77
  failed = check_arguments args[1]
@@ -94,8 +124,8 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
94
124
  end
95
125
  when :array
96
126
  return check_arguments(arg[1])
97
- when :string_interp
98
- return true
127
+ when :string_interp, :dstr
128
+ return true if check_string_interp arg
99
129
  when :call
100
130
  return check_call(arg)
101
131
  else
@@ -108,11 +138,24 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
108
138
  false
109
139
  end
110
140
 
141
+ def check_string_interp arg
142
+ arg.each do |exp|
143
+ #For now, don't warn on interpolation of Model.table_name
144
+ #but check for other 'safe' things in the future
145
+ if sexp? exp and (exp.node_type == :string_eval or exp.node_type == :evstr)
146
+ if call? exp[1] and (model_name?(exp[1][1]) or exp[1][1].nil?) and exp[1][2] == :table_name
147
+ return false
148
+ end
149
+ end
150
+ end
151
+ end
152
+
111
153
  #Check call for user input and string building
112
154
  def check_call exp
113
155
  target = exp[1]
114
156
  method = exp[2]
115
157
  args = exp[3]
158
+
116
159
  if sexp? target and
117
160
  (method == :+ or method == :<< or method == :concat) and
118
161
  (string? target or include_user_input? exp)
@@ -120,6 +163,8 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
120
163
  true
121
164
  elsif call? target
122
165
  check_call target
166
+ elsif target == nil and tracker.options[:rails3] and method.to_s.match /^first|last|all|where|order|group|having$/
167
+ check_arguments args
123
168
  else
124
169
  false
125
170
  end
@@ -0,0 +1,204 @@
1
+ require 'optparse'
2
+ require 'set'
3
+
4
+ #Parses command line arguments for Brakeman
5
+ module Brakeman::Options
6
+
7
+ class << self
8
+
9
+ #Parse argument array
10
+ def parse args
11
+ get_options args
12
+ end
13
+
14
+ #Parse arguments and remove them from the array as they are matched
15
+ def parse! args
16
+ get_options args, true
17
+ end
18
+
19
+ #Return hash of options and the parser
20
+ def get_options args, destructive = false
21
+ options = {}
22
+
23
+ parser = OptionParser.new do |opts|
24
+ opts.banner = "Usage: brakeman [options] rails/root/path"
25
+
26
+ opts.on "-n", "--no-threads", "Run checks sequentially" do
27
+ options[:parallel_checks] = false
28
+ end
29
+
30
+ opts.on "--[no-]progress", "Show progress reports" do |progress|
31
+ options[:report_progress] = progress
32
+ end
33
+
34
+ opts.on "-p", "--path PATH", "Specify path to Rails application" do |path|
35
+ options[:app_path] = File.expand_path path
36
+ end
37
+
38
+ opts.on "-q", "--[no-]quiet", "Suppress informational messages" do |quiet|
39
+ options[:quiet] = quiet
40
+ end
41
+
42
+ opts.on( "-z", "--exit-on-warn", "Exit code is non-zero if warnings found") do
43
+ options[:exit_on_warn] = true
44
+ end
45
+
46
+ opts.on "-3", "--rails3", "Force Rails 3 mode" do
47
+ options[:rails3] = true
48
+ end
49
+
50
+ opts.separator ""
51
+ opts.separator "Scanning options:"
52
+
53
+ opts.on "-a", "--assume-routes", "Assume all controller methods are actions" do
54
+ options[:assume_all_routes] = true
55
+ end
56
+
57
+ opts.on "--ignore-model-output", "Consider model attributes XSS-safe" do
58
+ options[:ignore_model_output] = true
59
+ end
60
+
61
+ opts.on "-e", "--escape-html", "Escape HTML by default" do
62
+ options[:escape_html] = true
63
+ end
64
+
65
+ opts.on "--faster", "Faster, but less accurate scan" do
66
+ options[:ignore_ifs] = true
67
+ options[:skip_libs] = true
68
+ end
69
+
70
+ opts.on "--no-branching", "Disable flow sensitivity on conditionals" do
71
+ options[:ignore_ifs] = true
72
+ end
73
+
74
+ opts.on "-r", "--report-direct", "Only report direct use of untrusted data" do |option|
75
+ options[:check_arguments] = !option
76
+ end
77
+
78
+ opts.on "-s", "--safe-methods meth1,meth2,etc", Array, "Consider the specified methods safe" do |methods|
79
+ options[:safe_methods] ||= Set.new
80
+ options[:safe_methods].merge methods.map {|e| e.to_sym }
81
+ end
82
+
83
+ opts.on "--skip-libs", "Skip processing lib directory" do
84
+ options[:skip_libs] = true
85
+ end
86
+
87
+ opts.on "-t", "--test Check1,Check2,etc", Array, "Only run the specified checks" do |checks|
88
+ checks.each_with_index do |s, index|
89
+ if s[0,5] != "Check"
90
+ checks[index] = "Check" << s
91
+ end
92
+ end
93
+
94
+ options[:run_checks] ||= Set.new
95
+ options[:run_checks].merge checks
96
+ end
97
+
98
+ opts.on "-x", "--except Check1,Check2,etc", Array, "Skip the specified checks" do |skip|
99
+ skip.each do |s|
100
+ if s[0,5] != "Check"
101
+ s = "Check" << s
102
+ end
103
+
104
+ options[:skip_checks] ||= Set.new
105
+ options[:skip_checks] << s
106
+ end
107
+ end
108
+
109
+ opts.separator ""
110
+ opts.separator "Output options:"
111
+
112
+ opts.on "-d", "--debug", "Lots of output" do
113
+ options[:debug] = true
114
+ end
115
+
116
+ opts.on "-f",
117
+ "--format TYPE",
118
+ [:pdf, :text, :html, :csv, :tabs],
119
+ "Specify output format. Default is text" do |type|
120
+
121
+ type = "s" if type == :text
122
+ options[:output_format] = ("to_" << type.to_s).to_sym
123
+ end
124
+
125
+ opts.on "--css-file CSSFile", "Specify CSS to use for HTML output" do |file|
126
+ options[:html_style] = File.expand_path file
127
+ end
128
+
129
+ opts.on "-l", "--[no-]combine-locations", "Combine warning locations (Default)" do |combine|
130
+ options[:combine_locations] = combine
131
+ end
132
+
133
+ opts.on "-m", "--routes", "Report controller information" do
134
+ options[:report_routes] = true
135
+ end
136
+
137
+ opts.on "--message-limit LENGTH", "Limit message length in HTML report" do |limit|
138
+ options[:message_limit] = limit.to_i
139
+ end
140
+
141
+ opts.on "-o", "--output FILE", "Specify file for output. Defaults to stdout" do |file|
142
+ options[:output_file] = file
143
+ end
144
+
145
+ opts.on "--separate-models", "Warn on each model without attr_accessible" do
146
+ options[:collapse_mass_assignment] = false
147
+ end
148
+
149
+ opts.on "--summary", "Only output summary of warnings" do
150
+ options[:summary_only] = true
151
+ end
152
+
153
+ opts.on "-w",
154
+ "--confidence-level LEVEL",
155
+ ["1", "2", "3"],
156
+ "Set minimal confidence level (1 - 3)" do |level|
157
+
158
+ options[:min_confidence] = 3 - level.to_i
159
+ end
160
+
161
+ opts.separator ""
162
+ opts.separator "Configuration files:"
163
+
164
+ opts.on "-c", "--config-file FILE", "Use specified configuration file" do |file|
165
+ options[:config_file] = File.expand_path(file)
166
+ end
167
+
168
+ opts.on "-C", "--create-config [FILE]", "Output configuration file based on options" do |file|
169
+ if file
170
+ options[:create_config] = file
171
+ else
172
+ options[:create_config] = true
173
+ end
174
+ end
175
+
176
+ opts.separator ""
177
+
178
+ opts.on "-k", "--checks", "List all available vulnerability checks" do
179
+ options[:list_checks] = true
180
+ end
181
+
182
+ opts.on "--rake", "Create rake task to run Brakeman" do
183
+ options[:install_rake_task] = true
184
+ end
185
+
186
+ opts.on "-v", "--version", "Show Brakeman version" do
187
+ options[:show_version] = true
188
+ end
189
+
190
+ opts.on_tail "-h", "--help", "Display this message" do
191
+ options[:show_help] = true
192
+ end
193
+ end
194
+
195
+ if destructive
196
+ parser.parse! args
197
+ else
198
+ parser.parse args
199
+ end
200
+
201
+ return options, parser
202
+ end
203
+ end
204
+ end
@@ -40,8 +40,8 @@ module Brakeman
40
40
 
41
41
  #Process variable aliasing in controller source and save it in the
42
42
  #tracker.
43
- def process_controller_alias src
44
- ControllerAliasProcessor.new(@tracker).process src
43
+ def process_controller_alias src, only_method = nil
44
+ ControllerAliasProcessor.new(@tracker, only_method).process src
45
45
  end
46
46
 
47
47
  #Process a model source
@@ -6,8 +6,12 @@ require 'brakeman/processors/lib/render_helper'
6
6
  class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
7
7
  include Brakeman::RenderHelper
8
8
 
9
- def initialize tracker
9
+ #If only_method is specified, only that method will be processed,
10
+ #other methods will be skipped.
11
+ #This is for rescanning just a single action.
12
+ def initialize tracker, only_method = nil
10
13
  super()
14
+ @only_method = only_method
11
15
  @tracker = tracker
12
16
  @rendered = false
13
17
  @current_class = @current_module = @current_method = nil
@@ -26,6 +30,10 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
26
30
  #Processes a method definition, which may include
27
31
  #processing any rendered templates.
28
32
  def process_methdef exp
33
+ #Skip if instructed to only process a specific method
34
+ #(but don't skip if this method was called from elsewhere)
35
+ return exp if @current_method.nil? and @only_method and @only_method != exp[1]
36
+
29
37
  is_route = route? exp[1]
30
38
  other_method = @current_method
31
39
  @current_method = exp[1]