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.
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