brakeman 1.0.rc1 → 1.0.0

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