brakeman 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/brakeman CHANGED
@@ -1,10 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
- require 'optparse'
3
- require 'set'
4
-
2
+ #Adjust path in case called directly and not through gem
5
3
  $:.unshift "#{File.expand_path(File.dirname(__FILE__))}/../lib"
6
4
 
7
5
  require 'brakeman'
6
+ require 'brakeman/options'
8
7
  require 'brakeman/version'
9
8
 
10
9
  trap("INT") do
@@ -12,180 +11,40 @@ trap("INT") do
12
11
  exit!
13
12
  end
14
13
 
15
- Brakeman::Warnings_Found_Exit_Code = 3
16
-
17
- #Parse command line options
18
- options = {}
19
-
20
- OptionParser.new do |opts|
21
- opts.banner = "Usage: brakeman [options] rails/root/path"
22
-
23
- opts.on "-n", "--no-threads", "Run checks sequentially" do
24
- options[:parallel_checks] = false
25
- end
26
-
27
- opts.on "--no-progress", "Do not show progress reports" do
28
- options[:report_progress] = false
29
- end
30
-
31
- opts.on "-p", "--path PATH", "Specify path to Rails application" do |path|
32
- options[:app_path] = File.expand_path path
33
- end
34
-
35
- opts.on "-q", "--quiet", "Suppress informational messages" do
36
- options[:quiet] = true
37
- end
38
-
39
- opts.on( "-z", "--exit-on-warn", "Exit code is non-zero if warnings found") do |s|
40
- options[:exit_on_warn] = s
41
- end
42
-
43
- opts.on "-3", "--rails3", "Force Rails 3 mode" do
44
- options[:rails3] = true
45
- end
46
-
47
- opts.separator ""
48
- opts.separator "Scanning options:"
49
-
50
- opts.on "-a", "--assume-routes", "Assume all controller methods are actions" do
51
- options[:assume_all_routes] = true
52
- end
53
-
54
- opts.on "--ignore-model-output", "Consider model attributes XSS-safe" do
55
- options[:ignore_model_output] = true
56
- end
57
-
58
- opts.on "-e", "--escape-html", "Escape HTML by default" do
59
- options[:escape_html] = true
60
- end
61
-
62
- opts.on "--faster", "Faster, but less accurate scan" do
63
- options[:ignore_ifs] = true
64
- options[:skip_libs] = true
65
- end
66
-
67
- opts.on "--no-branching", "Disable flow sensitivity on conditionals" do
68
- options[:ignore_ifs] = true
69
- end
70
-
71
- opts.on "-r", "--report-direct", "Only report direct use of untrusted data" do |option|
72
- options[:check_arguments] = !option
73
- end
74
-
75
- opts.on "-s", "--safe-methods meth1,meth2,etc", Array, "Consider the specified methods safe" do |methods|
76
- options[:safe_methods] ||= Set.new
77
- options[:safe_methods].merge methods.map {|e| e.to_sym }
78
- end
79
-
80
- opts.on "--skip-libs", "Skip processing lib directory" do
81
- options[:skip_libs] = true
82
- end
83
-
84
- opts.on "-t", "--test Check1,Check2,etc", Array, "Only run the specified checks" do |checks|
85
- checks.each_with_index do |s, index|
86
- if s[0,5] != "Check"
87
- checks[index] = "Check" << s
88
- end
89
- end
90
-
91
- options[:run_checks] ||= Set.new
92
- options[:run_checks].merge checks
93
- end
94
-
95
- opts.on "-x", "--except Check1,Check2,etc", Array, "Skip the specified checks" do |skip|
96
- skip.each do |s|
97
- if s[0,5] != "Check"
98
- s = "Check" << s
99
- end
100
-
101
- options[:skip_checks] ||= Set.new
102
- options[:skip_checks] << s
103
- end
104
- end
105
-
106
- opts.separator ""
107
- opts.separator "Output options:"
108
-
109
- opts.on "-d", "--debug", "Lots of output" do
110
- options[:debug] = true
111
- end
112
-
113
- opts.on "-f",
114
- "--format TYPE",
115
- [:pdf, :text, :html, :csv, :tabs],
116
- "Specify output format. Default is text" do |type|
117
-
118
- type = "s" if type == :text
119
- options[:output_format] = ("to_" << type.to_s).to_sym
120
- end
121
-
122
- opts.on "--css-file CSSFile", "Specify CSS to use for HTML output" do |file|
123
- options[:html_style] = File.expand_path file
124
- end
125
-
126
- opts.on "-l", "--[no]-combine-locations", "Combine warning locations (Default)" do |combine|
127
- options[:combine_locations] = combine
128
- end
129
-
130
- opts.on "-m", "--routes", "Report controller information" do
131
- options[:report_routes] = true
132
- end
133
-
134
- opts.on "--message-limit LENGTH", "Limit message length in HTML report" do |limit|
135
- options[:message_limit] = limit.to_i
136
- end
137
-
138
- opts.on "-o", "--output FILE", "Specify file for output. Defaults to stdout" do |file|
139
- options[:output_file] = file
140
- end
141
-
142
- opts.on "--separate-models", "Warn on each model without attr_accessible" do
143
- options[:collapse_mass_assignment] = false
144
- end
145
-
146
- opts.on "-w",
147
- "--confidence-level LEVEL",
148
- ["1", "2", "3"],
149
- "Set minimal confidence level (1 - 3)" do |level|
150
-
151
- options[:min_confidence] = 3 - level.to_i
152
- end
153
-
154
- opts.separator ""
155
- opts.separator "Configuration files:"
156
-
157
- opts.on "-c", "--config-file FILE", "Use specified configuration file" do |file|
158
- options[:config_file] = File.expand_path(file)
159
- end
160
-
161
- opts.on "-C", "--create-config [FILE]", "Output configuration file based on options" do |file|
162
- if file
163
- options[:create_config] = file
164
- else
165
- options[:create_config] = true
166
- end
167
- end
168
-
169
- opts.separator ""
170
-
171
- opts.on "-k", "--checks", "List all available vulnerability checks" do
172
- options[:list_checks] = true
173
- end
174
-
175
- opts.on "-v", "--version", "Show Brakeman version" do
176
- puts "brakeman #{Brakeman::Version}"
177
- exit
178
- end
14
+ #Parse options
15
+ options, parser = Brakeman::Options.parse! ARGV
16
+
17
+ #Exit early for these options
18
+ if options[:list_checks]
19
+ Brakeman.list_checks
20
+ exit
21
+ elsif options[:create_config]
22
+ Brakeman.dump_config options
23
+ exit
24
+ elsif options[:show_help]
25
+ puts parser
26
+ exit
27
+ elsif options[:show_version]
28
+ puts "brakeman #{Brakeman::Version}"
29
+ exit
30
+ elsif options[:install_rake_task]
31
+ Brakeman.install_rake_task
32
+ exit
33
+ end
179
34
 
180
- opts.on_tail "-h", "--help", "Display this message" do
181
- puts opts
182
- exit
35
+ #Set application path according to the commandline arguments
36
+ unless options[:app_path]
37
+ if ARGV[-1].nil?
38
+ options[:app_path] = File.expand_path "."
39
+ else
40
+ options[:app_path] = File.expand_path ARGV[-1]
183
41
  end
184
- end.parse!(ARGV)
42
+ end
185
43
 
44
+ #Run scan and output a report
186
45
  clean = Brakeman.run options.merge(:print_report => true, :quiet => options[:quiet])
187
46
 
188
- if options[:exit_on_warn] && !clean
47
+ #Return error code if --exit-on-warn is used and warnings were found
48
+ if options[:exit_on_warn] and not clean
189
49
  exit Brakeman::Warnings_Found_Exit_Code
190
50
  end
191
-
data/lib/brakeman.rb CHANGED
@@ -4,6 +4,10 @@ require 'set'
4
4
 
5
5
  module Brakeman
6
6
 
7
+ #This exit code is used when warnings are found and the --exit-on-warn
8
+ #option is set
9
+ Warnings_Found_Exit_Code = 3
10
+
7
11
  #Run Brakeman scan. Returns Tracker object.
8
12
  #
9
13
  #Options:
@@ -14,12 +18,10 @@ module Brakeman
14
18
  # * :collapse_mass_assignment - report unprotected models in single warning (default: true)
15
19
  # * :combine_locations - combine warning locations (default: true)
16
20
  # * :config_file - configuration file
17
- # * :create_config - output configuration file
18
21
  # * :escape_html - escape HTML by default (automatic)
19
22
  # * :exit_on_warn - return false if warnings found, true otherwise. Not recommended for library use (default: false)
20
23
  # * :html_style - path to CSS file
21
24
  # * :ignore_model_output - consider models safe (default: false)
22
- # * :list_checks - list all checks (does not run scan)
23
25
  # * :message_limit - limit length of messages
24
26
  # * :min_confidence - minimum confidence (0-2, 0 is highest)
25
27
  # * :output_file - file for output
@@ -33,18 +35,11 @@ module Brakeman
33
35
  # * :safe_methods - array of methods to consider safe
34
36
  # * :skip_libs - do not process lib/ directory (default: false)
35
37
  # * :skip_checks - checks not to run (run all if not specified)
38
+ # * :summary_only - only output summary section of report
39
+ # (does not apply to tabs format)
36
40
  #
41
+ #Alternatively, just supply a path as a string.
37
42
  def self.run options
38
- if options[:list_checks]
39
- list_checks
40
- exit
41
- end
42
-
43
- if options[:create_config]
44
- dump_config options
45
- exit
46
- end
47
-
48
43
  options = set_options options
49
44
 
50
45
  if options[:quiet]
@@ -55,22 +50,17 @@ module Brakeman
55
50
  scan options
56
51
  end
57
52
 
58
- private
59
-
60
53
  def self.set_options options
54
+ if options.is_a? String
55
+ options = { :app_path => options }
56
+ end
57
+
58
+ options[:app_path] = File.expand_path(options[:app_path])
59
+
61
60
  options = load_options(options[:config_file]).merge! options
62
61
  options = get_defaults.merge! options
63
62
  options[:output_format] = get_output_format options
64
63
 
65
- #Check application path
66
- unless options[:app_path]
67
- if ARGV[-1].nil?
68
- options[:app_path] = File.expand_path "."
69
- else
70
- options[:app_path] = File.expand_path ARGV[-1]
71
- end
72
- end
73
-
74
64
  app_path = options[:app_path]
75
65
 
76
66
  abort("Please supply the path to a Rails application.") unless app_path and File.exist? app_path + "/app"
@@ -86,7 +76,7 @@ module Brakeman
86
76
  def self.load_options config_file
87
77
  config_file ||= ""
88
78
 
89
- #Load configuation file
79
+ #Load configuration file
90
80
  [File.expand_path(config_file),
91
81
  File.expand_path("./config.yaml"),
92
82
  File.expand_path("~/.brakeman/config.yaml"),
@@ -161,7 +151,33 @@ module Brakeman
161
151
  require 'brakeman/scanner'
162
152
  $stderr.puts "Available Checks:"
163
153
  $stderr.puts "-" * 30
164
- $stderr.puts Checks.checks.map { |c| c.to_s }.sort.join "\n"
154
+ $stderr.puts Checks.checks.map { |c| c.to_s.match(/^Brakeman::(.*)$/)[1] }.sort.join "\n"
155
+ end
156
+
157
+ def self.install_rake_task
158
+ if not File.exists? "Rakefile"
159
+ abort "No Rakefile detected"
160
+ elsif File.exists? "lib/tasks/brakeman.rake"
161
+ abort "Task already exists"
162
+ end
163
+
164
+ require 'fileutils'
165
+
166
+ if not File.exists? "lib/tasks"
167
+ warn "Creating lib/tasks"
168
+ FileUtils.mkdir_p "lib/tasks"
169
+ end
170
+
171
+ path = File.expand_path(File.dirname(__FILE__))
172
+
173
+ FileUtils.cp "#{path}/brakeman/brakeman.rake", "lib/tasks/brakeman.rake"
174
+
175
+ if File.exists? "lib/tasks/brakeman.rake"
176
+ warn "Task created in lib/tasks/brakeman.rake"
177
+ warn "Usage: rake brakeman:run[output_file]"
178
+ else
179
+ warn "Could not create task"
180
+ end
165
181
  end
166
182
 
167
183
  def self.dump_config options
@@ -239,4 +255,10 @@ module Brakeman
239
255
 
240
256
  tracker
241
257
  end
258
+
259
+ def self.rescan tracker, files
260
+ require 'brakeman/rescanner'
261
+
262
+ Rescanner.new(tracker.options, tracker.processor, files).recheck
263
+ end
242
264
  end
@@ -67,7 +67,41 @@ class Brakeman::CallIndex
67
67
  calls
68
68
  end
69
69
 
70
- private
70
+ def remove_template_indexes
71
+ @calls_by_method.each do |name, calls|
72
+ calls.delete_if do |call|
73
+ call[:location][0] == :template
74
+ end
75
+
76
+ @methods.delete name.to_s if calls.empty?
77
+ end
78
+
79
+ @calls_by_target.each do |name, calls|
80
+ calls.delete_if do |call|
81
+ call[:location][0] == :template
82
+ end
83
+
84
+ @targets.delete name.to_s if calls.empty?
85
+ end
86
+ end
87
+
88
+ def remove_indexes_by_class classes
89
+ @calls_by_method.each do |name, calls|
90
+ calls.delete_if do |call|
91
+ call[:location][0] == :class and classes.include? call[:location][1]
92
+ end
93
+
94
+ @methods.delete name.to_s if calls.empty?
95
+ end
96
+
97
+ @calls_by_target.each do |name, calls|
98
+ calls.delete_if do |call|
99
+ call[:location][0] == :class and classes.include? call[:location][1]
100
+ end
101
+
102
+ @targets.delete name.to_s if calls.empty?
103
+ end
104
+ end
71
105
 
72
106
  def index_calls calls
73
107
  calls.each do |call|
@@ -78,6 +112,8 @@ class Brakeman::CallIndex
78
112
  end
79
113
  end
80
114
 
115
+ private
116
+
81
117
  def find_chain options
82
118
  target = options[:target] || options[:targets]
83
119
  method = options[:method] || options[:methods]
@@ -46,6 +46,23 @@ class Brakeman::Checks
46
46
  end
47
47
  end
48
48
 
49
+ #Return a hash of arrays of new and fixed warnings
50
+ #
51
+ # diff = checks.diff old_checks
52
+ # diff[:fixed] # [...]
53
+ # diff[:new] # [...]
54
+ def diff other_checks
55
+ my_warnings = self.all_warnings
56
+ other_warnings = other_checks.all_warnings
57
+
58
+ diff = {}
59
+
60
+ diff[:fixed] = other_warnings - my_warnings
61
+ diff[:new] = my_warnings - other_warnings
62
+
63
+ diff
64
+ end
65
+
49
66
  #Return an array of all warnings found.
50
67
  def all_warnings
51
68
  @warnings + @template_warnings + @controller_warnings + @model_warnings
@@ -1,5 +1,6 @@
1
1
  require 'sexp_processor'
2
2
  require 'brakeman/processors/output_processor'
3
+ require 'brakeman/processors/lib/processor_helper'
3
4
  require 'brakeman/warning'
4
5
  require 'brakeman/util'
5
6
 
@@ -91,7 +92,10 @@ class Brakeman::BaseCheck < SexpProcessor
91
92
 
92
93
  #Report a warning
93
94
  def warn options
94
- @warnings << Brakeman::Warning.new(options.merge({ :check => self.class.to_s }))
95
+ warning = Brakeman::Warning.new(options.merge({ :check => self.class.to_s }))
96
+ warning.file = file_for warning
97
+
98
+ @warnings << warning
95
99
  end
96
100
 
97
101
  #Run _exp_ through OutputProcessor to get a nice String.
@@ -14,22 +14,9 @@ require 'set'
14
14
  class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
15
15
  Brakeman::Checks.add self
16
16
 
17
- #Ignore these methods and their arguments.
18
- #It is assumed they will take care of escaping their output.
19
- IGNORE_METHODS = Set.new([:button_to, :check_box, :escapeHTML, :escape_once,
20
- :field_field, :fields_for, :h, :hidden_field,
21
- :hidden_field, :hidden_field_tag, :image_tag, :label,
22
- :link_to, :mail_to, :radio_button, :select,
23
- :submit_tag, :text_area, :text_field,
24
- :text_field_tag, :url_encode, :url_for,
25
- :will_paginate] )
26
-
27
17
  #Model methods which are known to be harmless
28
18
  IGNORE_MODEL_METHODS = Set.new([:average, :count, :maximum, :minimum, :sum])
29
19
 
30
- #Methods known to not escape their input
31
- KNOWN_DANGEROUS = Set.new([:truncate, :concat])
32
-
33
20
  MODEL_METHODS = Set.new([:all, :find, :first, :last, :new])
34
21
 
35
22
  IGNORE_LIKE = /^link_to_|(_path|_tag|_url)$/
@@ -46,7 +33,14 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
46
33
 
47
34
  #Run check
48
35
  def run_check
49
- IGNORE_METHODS.merge tracker.options[:safe_methods]
36
+ @ignore_methods = Set.new([:button_to, :check_box, :escapeHTML, :escape_once,
37
+ :field_field, :fields_for, :h, :hidden_field,
38
+ :hidden_field, :hidden_field_tag, :image_tag, :label,
39
+ :link_to, :mail_to, :radio_button, :select,
40
+ :submit_tag, :text_area, :text_field,
41
+ :text_field_tag, :url_encode, :url_for,
42
+ :will_paginate] ).merge tracker.options[:safe_methods]
43
+
50
44
  @models = tracker.models.keys
51
45
  @inspect_arguments = tracker.options[:check_arguments]
52
46
 
@@ -54,10 +48,12 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
54
48
  link_to_check.run_check
55
49
  warnings.concat link_to_check.warnings unless link_to_check.warnings.empty?
56
50
 
51
+ @known_dangerous = Set.new([:truncate, :concat])
52
+
57
53
  if version_between? "2.0.0", "3.0.5"
58
- KNOWN_DANGEROUS << :auto_link
54
+ @known_dangerous << :auto_link
59
55
  elsif version_between? "3.0.6", "3.0.99"
60
- IGNORE_METHODS << :auto_link
56
+ @ignore_methods << :auto_link
61
57
  end
62
58
 
63
59
  tracker.each_template do |name, template|
@@ -170,7 +166,7 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
170
166
  if message and not duplicate? exp
171
167
  add_result exp
172
168
 
173
- if exp[1].nil? and KNOWN_DANGEROUS.include? exp[2]
169
+ if exp[1].nil? and @known_dangerous.include? exp[2]
174
170
  confidence = CONFIDENCE[:high]
175
171
  else
176
172
  confidence = CONFIDENCE[:low]
@@ -201,15 +197,15 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
201
197
  args = exp[3]
202
198
 
203
199
  #Ignore safe items
204
- if (target.nil? and (IGNORE_METHODS.include? method or method.to_s =~ IGNORE_LIKE)) or
200
+ if (target.nil? and (@ignore_methods.include? method or method.to_s =~ IGNORE_LIKE)) or
205
201
  (@matched == :model and IGNORE_MODEL_METHODS.include? method) or
206
202
  (target == HAML_HELPERS and method == :html_escape) or
207
203
  ((target == URI or target == CGI) and method == :escape) or
208
204
  (target == XML_HELPER and method == :escape_xml) or
209
- (target == FORM_BUILDER and IGNORE_METHODS.include? method) or
205
+ (target == FORM_BUILDER and @ignore_methods.include? method) or
210
206
  (method.to_s[-1,1] == "?")
211
207
 
212
- exp[0] = :ignore
208
+ #exp[0] = :ignore #should not be necessary
213
209
  @matched = false
214
210
  elsif sexp? exp[1] and model_name? exp[1][1]
215
211
  @matched = :model
@@ -269,9 +265,9 @@ end
269
265
 
270
266
  #This _only_ checks calls to link_to
271
267
  class Brakeman::CheckLinkTo < Brakeman::CheckCrossSiteScripting
272
- IGNORE_METHODS = IGNORE_METHODS - [:link_to]
273
-
274
268
  def run_check
269
+ @ignore_methods = []
270
+ @known_dangerous = []
275
271
  #Ideally, I think this should also check to see if people are setting
276
272
  #:escape => false
277
273
  methods = tracker.find_call :target => false, :method => :link_to