brakeman 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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]