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 +7 -3
- data/bin/brakeman +1 -1
- data/lib/brakeman.rb +15 -6
- data/lib/brakeman/checks/check_sql.rb +4 -0
- data/lib/brakeman/format/style.css +105 -0
- data/lib/brakeman/processors/alias_processor.rb +58 -5
- data/lib/brakeman/processors/controller_processor.rb +0 -1
- data/lib/brakeman/scanner.rb +30 -21
- data/lib/brakeman/tracker.rb +1 -1
- data/lib/brakeman/util.rb +13 -0
- data/lib/brakeman/version.rb +1 -1
- data/lib/ruby_parser/ruby18_parser.rb +5544 -0
- data/lib/ruby_parser/ruby19_parser.rb +5756 -0
- data/lib/ruby_parser/ruby_lexer.rb +48 -19
- data/lib/ruby_parser/ruby_parser.rb +4 -5568
- data/lib/ruby_parser/ruby_parser_extras.rb +1075 -0
- metadata +36 -59
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
|
6
|
-
|
7
|
-
There is also a [plugin available](
|
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.
|
data/bin/brakeman
CHANGED
@@ -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
|
data/lib/brakeman.rb
CHANGED
@@ -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
|
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
|
-
# * :
|
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
|
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
|
-
|
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
|
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
data/lib/brakeman/scanner.rb
CHANGED
@@ -1,18 +1,13 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
begin
|
3
|
-
|
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(
|
79
|
-
@processor.process_config(
|
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(
|
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(
|
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(
|
103
|
+
@processor.process_gems(parse_ruby(File.read("#@path/Gemfile")), File.read("#@path/Gemfile.lock"))
|
103
104
|
else
|
104
|
-
@processor.process_gems(
|
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,
|
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
|
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
|
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(
|
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 =
|
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 =
|
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(
|
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
|