brakeman 1.0.rc1 → 1.0.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/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  Brakeman is a static analysis tool which checks Ruby on Rails applications for security vulnerabilities.
4
4
 
5
- It targets Rails versions > 2.0 with experimental support for Rails 3.x
6
-
7
- There is also a [plugin available](https://github.com/presidentbeef/brakeman-jenkins-plugin) for Jenkins/Hudson.
5
+ It targets Rails versions 2.x and 3.x.
6
+
7
+ There is also a [plugin available](http://brakemanscanner.org/docs/jenkins/) for Jenkins/Hudson.
8
8
 
9
9
  # Homepage
10
10
 
@@ -77,6 +77,10 @@ Normally Brakeman will parse `routes.rb` and attempt to infer which controller m
77
77
 
78
78
  Note that this will be enabled automatically if Brakeman runs into an error while parsing the routes.
79
79
 
80
+ By default, Brakeman will return 0 as an exit code unless something when very wrong. To return an error code when warnings were found:
81
+
82
+ brakeman -z
83
+
80
84
  # Warning information
81
85
 
82
86
  See WARNING_TYPES for more information on the warnings reported by this tool.
@@ -170,7 +170,7 @@ OptionParser.new do |opts|
170
170
  end
171
171
  end.parse!(ARGV)
172
172
 
173
- clean = Brakeman.run options
173
+ clean = Brakeman.run options.merge(:print_report => true, :quiet => options[:quiet])
174
174
 
175
175
  if options[:exit_on_warn] && !clean
176
176
  exit Brakeman::Warnings_Found_Exit_Code
@@ -16,7 +16,7 @@ module Brakeman
16
16
  # * :config_file - configuration file
17
17
  # * :create_config - output configuration file
18
18
  # * :escape_html - escape HTML by default (automatic)
19
- # * :exit_on_warn - return error exit code on warnings (default: false)
19
+ # * :exit_on_warn - return false if warnings found, true otherwise. Not recommended for library use (default: false)
20
20
  # * :html_style - path to CSS file
21
21
  # * :ignore_model_output - consider models safe (default: false)
22
22
  # * :list_checks - list all checks (does not run scan)
@@ -25,7 +25,8 @@ module Brakeman
25
25
  # * :output_file - file for output
26
26
  # * :output_format - format for output (:to_s, :to_tabs, :to_csv, :to_html)
27
27
  # * :parallel_checks - run checks in parallel (default: true)
28
- # * :quiet - suppress most messages (default: false)
28
+ # * :print_report - if no output file specified, print to stdout (default: false)
29
+ # * :quiet - suppress most messages (default: true)
29
30
  # * :rails3 - force Rails 3 mode (automatic)
30
31
  # * :report_routes - show found routes on controllers (default: false)
31
32
  # * :run_checks - array of checks to run (run all if not specified)
@@ -44,11 +45,13 @@ module Brakeman
44
45
  exit
45
46
  end
46
47
 
48
+ options = set_options options
49
+
47
50
  if options[:quiet]
48
51
  $VERBOSE = nil
49
52
  end
50
53
 
51
- scan set_options(options)
54
+ scan options
52
55
  end
53
56
 
54
57
  private
@@ -116,6 +119,7 @@ module Brakeman
116
119
  :ignore_model_output => false,
117
120
  :message_limit => 100,
118
121
  :parallel_checks => true,
122
+ :quiet => true,
119
123
  :html_style => "#{File.expand_path(File.dirname(__FILE__))}/brakeman/format/style.css"
120
124
  }
121
125
  end
@@ -205,13 +209,16 @@ module Brakeman
205
209
  warn "Running checks..."
206
210
  tracker.run_checks
207
211
 
208
- warn "Generating report..."
209
212
  if options[:output_file]
213
+ warn "Generating report..."
214
+
210
215
  File.open options[:output_file], "w" do |f|
211
216
  f.puts tracker.report.send(options[:output_format])
212
217
  end
213
218
  warn "Report saved in '#{options[:output_file]}'"
214
- else
219
+ elsif options[:print_report]
220
+ warn "Generating report..."
221
+
215
222
  puts tracker.report.send(options[:output_format])
216
223
  end
217
224
 
@@ -220,8 +227,10 @@ module Brakeman
220
227
  next if warning.confidence > options[:min_confidence]
221
228
  return false
222
229
  end
230
+
231
+ return true
223
232
  end
224
- return true
225
233
 
234
+ tracker
226
235
  end
227
236
  end
@@ -98,6 +98,10 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
98
98
  return true
99
99
  when :call
100
100
  return check_call(arg)
101
+ else
102
+ return arg.any? do |a|
103
+ check_arguments(a)
104
+ end
101
105
  end
102
106
  end
103
107
 
@@ -0,0 +1,105 @@
1
+ /* CSS style used for HTML reports */
2
+
3
+ body {
4
+ font-family: sans-serif;
5
+ color: #161616;
6
+ }
7
+
8
+ p {
9
+ font-weight: bold;
10
+ font-size: 11pt;
11
+ color: #2D0200;
12
+ }
13
+
14
+ th {
15
+ background-color: #980905;
16
+ border-bottom: 5px solid #530200;
17
+ color: white;
18
+ font-size: 11pt;
19
+ padding: 1px 8px 1px 8px;
20
+ }
21
+
22
+ td {
23
+ border-bottom: 2px solid white;
24
+ font-family: monospace;
25
+ padding: 5px 8px 1px 8px;
26
+ }
27
+
28
+ table {
29
+ background-color: #FCF4D4;
30
+ border-collapse: collapse;
31
+ }
32
+
33
+ h1 {
34
+ color: #2D0200;
35
+ font-size: 14pt;
36
+ }
37
+
38
+ h2 {
39
+ color: #2D0200;
40
+ font-size: 12pt;
41
+ }
42
+
43
+ span.high-confidence {
44
+ font-weight:bold;
45
+ color: red;
46
+ }
47
+
48
+ span.med-confidence {
49
+ }
50
+
51
+ span.weak-confidence {
52
+ color:gray;
53
+ }
54
+
55
+ div.warning_message {
56
+ cursor: pointer;
57
+ }
58
+
59
+ div.warning_message:hover {
60
+ background-color: white;
61
+ }
62
+
63
+ table.context {
64
+ margin-top: 5px;
65
+ margin-bottom: 5px;
66
+ border-left: 1px solid #90e960;
67
+ color: #212121;
68
+ }
69
+
70
+ tr.context {
71
+ background-color: white;
72
+ }
73
+
74
+ tr.first {
75
+ border-top: 1px solid #7ecc54;
76
+ padding-top: 2px;
77
+ }
78
+
79
+ tr.error {
80
+ background-color: #f4c1c1 !important
81
+ }
82
+
83
+ tr.near_error {
84
+ background-color: #f4d4d4 !important
85
+ }
86
+
87
+ tr.alt {
88
+ background-color: #e8f4d4;
89
+ }
90
+
91
+ td.context {
92
+ padding: 2px 10px 0px 6px;
93
+ border-bottom: none;
94
+ }
95
+
96
+ td.context_line {
97
+ padding: 2px 8px 0px 7px;
98
+ border-right: 1px solid #b3bda4;
99
+ border-bottom: none;
100
+ color: #6e7465;
101
+ }
102
+
103
+ pre.context {
104
+ margin-bottom: 1px;
105
+ }
@@ -182,7 +182,13 @@ class Brakeman::AliasProcessor < SexpProcessor
182
182
  def process_lasgn exp
183
183
  exp[2] = process exp[2] if sexp? exp[2]
184
184
  local = Sexp.new(:lvar, exp[1]).line(exp.line || -2)
185
- env[local] = exp[2]
185
+
186
+ if @inside_if and env[local]
187
+ env[local] = Sexp.new(:or, env[local], exp[2]).line(exp.line || -2)
188
+ else
189
+ env[local] = exp[2]
190
+ end
191
+
186
192
  exp
187
193
  end
188
194
 
@@ -191,7 +197,13 @@ class Brakeman::AliasProcessor < SexpProcessor
191
197
  def process_iasgn exp
192
198
  exp[2] = process exp[2]
193
199
  ivar = Sexp.new(:ivar, exp[1]).line(exp.line)
194
- env[ivar] = exp[2]
200
+
201
+ if @inside_if and env[ivar]
202
+ env[ivar] = Sexp.new(:or, env[ivar], exp[2]).line(exp.line)
203
+ else
204
+ env[ivar] = exp[2]
205
+ end
206
+
195
207
  exp
196
208
  end
197
209
 
@@ -200,7 +212,13 @@ class Brakeman::AliasProcessor < SexpProcessor
200
212
  def process_gasgn exp
201
213
  match = Sexp.new(:gvar, exp[1])
202
214
  value = exp[2] = process(exp[2])
203
- env[match] = value
215
+
216
+ if @inside_if and env[match]
217
+ env[match] = Sexp.new(:or, env[match], value)
218
+ else
219
+ env[match] = value
220
+ end
221
+
204
222
  exp
205
223
  end
206
224
 
@@ -209,7 +227,13 @@ class Brakeman::AliasProcessor < SexpProcessor
209
227
  def process_cvdecl exp
210
228
  match = Sexp.new(:cvar, exp[1])
211
229
  value = exp[2] = process(exp[2])
212
- env[match] = value
230
+
231
+ if @inside_if and env[match]
232
+ env[match] = Sexp.new(:or, env[match], value)
233
+ else
234
+ env[match] = value
235
+ end
236
+
213
237
  exp
214
238
  end
215
239
 
@@ -234,7 +258,12 @@ class Brakeman::AliasProcessor < SexpProcessor
234
258
  value = exp[3][1] = process(exp[3][1])
235
259
  #This is what we'll replace with the value
236
260
  match = Sexp.new(:call, target, method.to_s[0..-2].to_sym, Sexp.new(:arglist))
237
- env[match] = value
261
+
262
+ if @inside_if and env[match]
263
+ env[match] = Sexp.new(:or, env[match], value)
264
+ else
265
+ env[match] = value
266
+ end
238
267
  else
239
268
  raise "Unrecognized assignment: #{exp}"
240
269
  end
@@ -318,6 +347,30 @@ class Brakeman::AliasProcessor < SexpProcessor
318
347
  exp
319
348
  end
320
349
 
350
+ #Sets @inside_if = true
351
+ def process_if exp
352
+ was_inside = @inside_if
353
+ @inside_if = true
354
+
355
+ condition = process exp[1]
356
+
357
+ if true? condition
358
+ exps = [exp[2]]
359
+ elsif false? condition
360
+ exps = exp[3..-1]
361
+ else
362
+ exps = exp[2..-1]
363
+ end
364
+
365
+ exps.each do |e|
366
+ process e if sexp? e
367
+ end
368
+
369
+ @inside_if = was_inside
370
+
371
+ exp
372
+ end
373
+
321
374
  #Process single integer access to an array.
322
375
  #
323
376
  #Returns the value inside the array, if possible.
@@ -1,4 +1,3 @@
1
- require 'ruby_parser'
2
1
  require 'brakeman/processors/base_processor'
3
2
 
4
3
  #Processes controller. Results are put in tracker.controllers
@@ -1,18 +1,13 @@
1
1
  require 'rubygems'
2
2
  begin
3
- require 'ruby_parser'
3
+ #Load our own version of ruby_parser :'(
4
+ require 'ruby_parser/ruby_parser.rb'
5
+
4
6
  require 'haml'
5
7
  require 'sass'
6
8
  require 'erb'
7
9
  require 'erubis'
8
10
  require 'brakeman/processor'
9
-
10
- #Load our own version of ruby_parser :(
11
- original_verbosity = $VERBOSE
12
- $VERBOSE = nil
13
- require 'ruby_parser/ruby_parser.rb'
14
- $VERBOSE = original_verbosity
15
-
16
11
  rescue LoadError => e
17
12
  $stderr.puts e.message
18
13
  $stderr.puts "Please install the appropriate dependency."
@@ -40,6 +35,12 @@ class Brakeman::Scanner
40
35
  @path = options[:app_path]
41
36
  @app_path = File.join(@path, "app")
42
37
  @processor = Brakeman::Processor.new options
38
+
39
+ if RUBY_1_9
40
+ @ruby_parser = ::Ruby19Parser
41
+ else
42
+ @ruby_parser = ::Ruby18Parser
43
+ end
43
44
  end
44
45
 
45
46
  #Returns the Tracker generated from the scan
@@ -75,13 +76,13 @@ class Brakeman::Scanner
75
76
  #Stores parsed information in tracker.config
76
77
  def process_config
77
78
  if options[:rails3]
78
- @processor.process_config(RubyParser.new.parse(File.read("#@path/config/application.rb")))
79
- @processor.process_config(RubyParser.new.parse(File.read("#@path/config/environments/production.rb")))
79
+ @processor.process_config(parse_ruby(File.read("#@path/config/application.rb")))
80
+ @processor.process_config(parse_ruby(File.read("#@path/config/environments/production.rb")))
80
81
  else
81
- @processor.process_config(RubyParser.new.parse(File.read("#@path/config/environment.rb")))
82
+ @processor.process_config(parse_ruby(File.read("#@path/config/environment.rb")))
82
83
 
83
84
  if File.exists? "#@path/config/gems.rb"
84
- @processor.process_config(RubyParser.new.parse(File.read("#@path/config/gems.rb")))
85
+ @processor.process_config(parse_ruby(File.read("#@path/config/gems.rb")))
85
86
  end
86
87
 
87
88
  end
@@ -99,9 +100,9 @@ class Brakeman::Scanner
99
100
  def process_gems
100
101
  if File.exists? "#@path/Gemfile"
101
102
  if File.exists? "#@path/Gemfile.lock"
102
- @processor.process_gems(RubyParser.new.parse(File.read("#@path/Gemfile")), File.read("#@path/Gemfile.lock"))
103
+ @processor.process_gems(parse_ruby(File.read("#@path/Gemfile")), File.read("#@path/Gemfile.lock"))
103
104
  else
104
- @processor.process_gems(RubyParser.new.parse(File.read("#@path/Gemfile")))
105
+ @processor.process_gems(parse_ruby(File.read("#@path/Gemfile")))
105
106
  end
106
107
  end
107
108
  end
@@ -112,7 +113,7 @@ class Brakeman::Scanner
112
113
  def process_initializers
113
114
  Dir.glob(@path + "/config/initializers/**/*.rb").sort.each do |f|
114
115
  begin
115
- @processor.process_initializer(f, RubyParser.new.parse(File.read(f)))
116
+ @processor.process_initializer(f, parse_ruby(File.read(f)))
116
117
  rescue Racc::ParseError => e
117
118
  tracker.error e, "could not parse #{f}. There is probably a typo in the file. Test it with 'ruby_parse #{f}'"
118
119
  rescue Exception => e
@@ -132,7 +133,7 @@ class Brakeman::Scanner
132
133
 
133
134
  Dir.glob(@path + "/lib/**/*.rb").sort.each do |f|
134
135
  begin
135
- @processor.process_lib RubyParser.new.parse(File.read(f)), f
136
+ @processor.process_lib parse_ruby(File.read(f)), f
136
137
  rescue Racc::ParseError => e
137
138
  tracker.error e, "could not parse #{f}. There is probably a typo in the file. Test it with 'ruby_parse #{f}'"
138
139
  rescue Exception => e
@@ -147,7 +148,7 @@ class Brakeman::Scanner
147
148
  def process_routes
148
149
  if File.exists? "#@path/config/routes.rb"
149
150
  begin
150
- @processor.process_routes RubyParser.new.parse(File.read("#@path/config/routes.rb"))
151
+ @processor.process_routes parse_ruby(File.read("#@path/config/routes.rb"))
151
152
  rescue Exception => e
152
153
  tracker.error e.exception(e.message + "\nWhile processing routes.rb"), e.backtrace
153
154
  warn "[Notice] Error while processing routes - assuming all public controller methods are actions."
@@ -164,7 +165,7 @@ class Brakeman::Scanner
164
165
  def process_controllers
165
166
  Dir.glob(@app_path + "/controllers/**/*.rb").sort.each do |f|
166
167
  begin
167
- @processor.process_controller(RubyParser.new.parse(File.read(f)), f)
168
+ @processor.process_controller(parse_ruby(File.read(f)), f)
168
169
  rescue Racc::ParseError => e
169
170
  tracker.error e, "could not parse #{f}. There is probably a typo in the file. Test it with 'ruby_parse #{f}'"
170
171
  rescue Exception => e
@@ -210,11 +211,11 @@ class Brakeman::Scanner
210
211
  src.sub!(/^#.*\n/, '') if RUBY_1_9
211
212
  end
212
213
 
213
- parsed = RubyParser.new.parse src
214
+ parsed = parse_ruby src
214
215
  elsif type == :haml
215
216
  src = Haml::Engine.new(text,
216
217
  :escape_html => !!tracker.config[:escape_html]).precompiled
217
- parsed = RubyParser.new.parse src
218
+ parsed = parse_ruby src
218
219
  else
219
220
  tracker.error "Unkown template type in #{f}"
220
221
  end
@@ -251,7 +252,7 @@ class Brakeman::Scanner
251
252
  def process_models
252
253
  Dir.glob(@app_path + "/models/*.rb").sort.each do |f|
253
254
  begin
254
- @processor.process_model(RubyParser.new.parse(File.read(f)), f)
255
+ @processor.process_model(parse_ruby(File.read(f)), f)
255
256
  rescue Racc::ParseError => e
256
257
  tracker.error e, "could not parse #{f}"
257
258
  rescue Exception => e
@@ -263,6 +264,14 @@ class Brakeman::Scanner
263
264
  def index_call_sites
264
265
  tracker.index_call_sites
265
266
  end
267
+
268
+ def parse_ruby input
269
+ if RUBY_1_9
270
+ Ruby19Parser.new.parse input
271
+ else
272
+ Ruby18Parser.new.parse input
273
+ end
274
+ end
266
275
  end
267
276
 
268
277
  #This is from Rails 3 version of the Erubis handler