brakeman 0.7.2 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +4 -0
- data/bin/brakeman +3 -154
- data/lib/brakeman.rb +186 -0
- data/lib/checks/check_basic_auth.rb +47 -0
- data/lib/checks/check_cross_site_scripting.rb +7 -1
- data/lib/checks/check_mass_assignment.rb +3 -3
- data/lib/checks/check_without_protection.rb +70 -0
- data/lib/processors/controller_processor.rb +6 -2
- data/lib/processors/lib/rails3_route_processor.rb +1 -1
- data/lib/report.rb +15 -3
- data/lib/ruby_parser/ruby_lexer.rb +1320 -0
- data/lib/ruby_parser/ruby_parser.rb +6265 -0
- data/lib/scanner.rb +7 -0
- data/lib/version.rb +1 -1
- metadata +10 -5
data/README.md
CHANGED
@@ -6,6 +6,10 @@ It targets Rails versions > 2.0 with experimental support for Rails 3.x
|
|
6
6
|
|
7
7
|
There is also a [plugin available](https://github.com/presidentbeef/brakeman-jenkins-plugin) for Jenkins/Hudson.
|
8
8
|
|
9
|
+
# Homepage
|
10
|
+
|
11
|
+
http://brakemanscanner.org/
|
12
|
+
|
9
13
|
# Installation
|
10
14
|
|
11
15
|
Using RubyGems:
|
data/bin/brakeman
CHANGED
@@ -1,25 +1,17 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
require
|
2
|
+
require 'optparse'
|
3
3
|
require 'set'
|
4
|
-
require 'yaml'
|
5
4
|
|
6
5
|
$:.unshift "#{File.expand_path(File.dirname(__FILE__))}/../lib"
|
7
6
|
|
8
7
|
require 'version'
|
8
|
+
require 'brakeman'
|
9
9
|
|
10
10
|
trap("INT") do
|
11
11
|
$stderr.puts "\nInterrupted - exiting."
|
12
12
|
exit!
|
13
13
|
end
|
14
14
|
|
15
|
-
def list_checks
|
16
|
-
require 'scanner'
|
17
|
-
$stderr.puts "Available Checks:"
|
18
|
-
$stderr.puts "-" * 30
|
19
|
-
$stderr.puts Checks.checks.map { |c| c.to_s }.sort.join "\n"
|
20
|
-
exit
|
21
|
-
end
|
22
|
-
|
23
15
|
#Parse command line options
|
24
16
|
options = {}
|
25
17
|
|
@@ -148,147 +140,4 @@ OptionParser.new do |opts|
|
|
148
140
|
end
|
149
141
|
end.parse!(ARGV)
|
150
142
|
|
151
|
-
|
152
|
-
[File.expand_path(options[:config_file].to_s),
|
153
|
-
File.expand_path("./config.yaml"),
|
154
|
-
File.expand_path("~/.brakeman/config.yaml"),
|
155
|
-
File.expand_path("/etc/brakeman/config.yaml"),
|
156
|
-
"#{File.expand_path(File.dirname(__FILE__))}/../lib/config.yaml"].each do |f|
|
157
|
-
|
158
|
-
if File.exist? f and not File.directory? f
|
159
|
-
warn "[Notice] Using configuration in #{f}" unless options[:quiet]
|
160
|
-
OPTIONS = YAML.load_file f
|
161
|
-
OPTIONS.merge! options
|
162
|
-
OPTIONS.each do |k,v|
|
163
|
-
if v.is_a? Array
|
164
|
-
OPTIONS[k] = Set.new v
|
165
|
-
end
|
166
|
-
end
|
167
|
-
break
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
OPTIONS = options unless defined? OPTIONS
|
172
|
-
|
173
|
-
#List available checks and exits
|
174
|
-
list_checks if OPTIONS[:list_checks]
|
175
|
-
|
176
|
-
#Set defaults just in case
|
177
|
-
{ :skip_checks => Set.new,
|
178
|
-
:check_arguments => true,
|
179
|
-
:safe_methods => Set.new,
|
180
|
-
:min_confidence => 2,
|
181
|
-
:combine_locations => true,
|
182
|
-
:collapse_mass_assignment => true,
|
183
|
-
:ignore_redirect_to_model => true,
|
184
|
-
:ignore_model_output => false,
|
185
|
-
:message_limit => 100,
|
186
|
-
:html_style => "#{File.expand_path(File.dirname(__FILE__))}/../lib/format/style.css"
|
187
|
-
}.each do |k,v|
|
188
|
-
OPTIONS[k] = v if OPTIONS[k].nil?
|
189
|
-
end
|
190
|
-
|
191
|
-
|
192
|
-
#Set output format
|
193
|
-
if OPTIONS[:output_format]
|
194
|
-
case OPTIONS[:output_format]
|
195
|
-
when :html, :to_html
|
196
|
-
OPTIONS[:output_format] = :to_html
|
197
|
-
when :csv, :to_csv
|
198
|
-
OPTIONS[:output_format] = :to_csv
|
199
|
-
when :pdf, :to_pdf
|
200
|
-
OPTIONS[:output_format] = :to_pdf
|
201
|
-
when :tabs, :to_tabs
|
202
|
-
OPTIONS[:output_format] = :to_tabs
|
203
|
-
else
|
204
|
-
OPTIONS[:output_format] = :to_s
|
205
|
-
end
|
206
|
-
else
|
207
|
-
case OPTIONS[:output_file]
|
208
|
-
when /\.html$/i
|
209
|
-
OPTIONS[:output_format] = :to_html
|
210
|
-
when /\.csv$/i
|
211
|
-
OPTIONS[:output_format] = :to_csv
|
212
|
-
when /\.pdf$/i
|
213
|
-
OPTIONS[:output_format] = :to_pdf
|
214
|
-
when /\.tabs$/i
|
215
|
-
OPTIONS[:output_format] = :to_tabs
|
216
|
-
else
|
217
|
-
OPTIONS[:output_format] = :to_s
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
#Output configuration if requested
|
222
|
-
if OPTIONS[:create_config]
|
223
|
-
|
224
|
-
if OPTIONS[:create_config].is_a? String
|
225
|
-
file = OPTIONS[:create_config]
|
226
|
-
else
|
227
|
-
file = nil
|
228
|
-
end
|
229
|
-
|
230
|
-
OPTIONS.delete :create_config
|
231
|
-
|
232
|
-
OPTIONS.each do |k,v|
|
233
|
-
if v.is_a? Set
|
234
|
-
OPTIONS[k] = v.to_a
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
if file
|
239
|
-
File.open file, "w" do |f|
|
240
|
-
YAML.dump OPTIONS, f
|
241
|
-
end
|
242
|
-
puts "Output configuration to #{file}"
|
243
|
-
else
|
244
|
-
puts YAML.dump(OPTIONS)
|
245
|
-
end
|
246
|
-
exit
|
247
|
-
end
|
248
|
-
|
249
|
-
|
250
|
-
#Check application path
|
251
|
-
unless OPTIONS[:app_path]
|
252
|
-
if ARGV[-1].nil?
|
253
|
-
OPTIONS[:app_path] = File.expand_path "."
|
254
|
-
else
|
255
|
-
OPTIONS[:app_path] = File.expand_path ARGV[-1]
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
app_path = OPTIONS[:app_path]
|
260
|
-
|
261
|
-
abort("Please supply the path to a Rails application.") unless app_path and File.exist? app_path + "/app"
|
262
|
-
|
263
|
-
warn "[Notice] Using Ruby #{RUBY_VERSION}. Please make sure this matches the one used to run your Rails application."
|
264
|
-
|
265
|
-
if File.exist? app_path + "/script/rails"
|
266
|
-
OPTIONS[:rails3] = true
|
267
|
-
warn "[Notice] Detected Rails 3 application. Enabling experimental Rails 3 support."
|
268
|
-
end
|
269
|
-
|
270
|
-
#Load scanner
|
271
|
-
begin
|
272
|
-
require 'scanner'
|
273
|
-
rescue LoadError
|
274
|
-
abort "Cannot find lib/ directory."
|
275
|
-
end
|
276
|
-
|
277
|
-
#Start scanning
|
278
|
-
scanner = Scanner.new app_path
|
279
|
-
|
280
|
-
warn "Processing application in #{app_path}"
|
281
|
-
tracker = scanner.process
|
282
|
-
|
283
|
-
warn "Running checks..."
|
284
|
-
tracker.run_checks
|
285
|
-
|
286
|
-
warn "Generating report..."
|
287
|
-
if OPTIONS[:output_file]
|
288
|
-
File.open OPTIONS[:output_file], "w" do |f|
|
289
|
-
f.puts tracker.report.send(OPTIONS[:output_format])
|
290
|
-
end
|
291
|
-
warn "Report saved in '#{OPTIONS[:output_file]}'"
|
292
|
-
else
|
293
|
-
puts tracker.report.send(OPTIONS[:output_format])
|
294
|
-
end
|
143
|
+
Brakeman.run options
|
data/lib/brakeman.rb
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
OPTIONS = {}
|
4
|
+
|
5
|
+
module Brakeman
|
6
|
+
def self.run options
|
7
|
+
if options[:list_checks]
|
8
|
+
list_checks
|
9
|
+
exit
|
10
|
+
end
|
11
|
+
|
12
|
+
if options[:create_config]
|
13
|
+
dump_config options
|
14
|
+
exit
|
15
|
+
end
|
16
|
+
|
17
|
+
scan set_options(options)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def self.set_options options
|
23
|
+
options = load_options(options[:config_file]).merge! options
|
24
|
+
options = get_defaults.merge! options
|
25
|
+
options[:output_format] = get_output_format options
|
26
|
+
|
27
|
+
#Check application path
|
28
|
+
unless options[:app_path]
|
29
|
+
if ARGV[-1].nil?
|
30
|
+
options[:app_path] = File.expand_path "."
|
31
|
+
else
|
32
|
+
options[:app_path] = File.expand_path ARGV[-1]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
app_path = options[:app_path]
|
37
|
+
|
38
|
+
abort("Please supply the path to a Rails application.") unless app_path and File.exist? app_path + "/app"
|
39
|
+
|
40
|
+
if File.exist? app_path + "/script/rails"
|
41
|
+
options[:rails3] = true
|
42
|
+
warn "[Notice] Detected Rails 3 application. Enabling experimental Rails 3 support."
|
43
|
+
end
|
44
|
+
|
45
|
+
options
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.load_options config_file
|
49
|
+
config_file ||= ""
|
50
|
+
|
51
|
+
#Load configuation file
|
52
|
+
[File.expand_path(config_file),
|
53
|
+
File.expand_path("./config.yaml"),
|
54
|
+
File.expand_path("~/.brakeman/config.yaml"),
|
55
|
+
File.expand_path("/etc/brakeman/config.yaml"),
|
56
|
+
"#{File.expand_path(File.dirname(__FILE__))}/../lib/config.yaml"].each do |f|
|
57
|
+
|
58
|
+
if File.exist? f and not File.directory? f
|
59
|
+
warn "[Notice] Using configuration in #{f}" unless options[:quiet]
|
60
|
+
options = YAML.load_file f
|
61
|
+
options.each do |k,v|
|
62
|
+
if v.is_a? Array
|
63
|
+
options[k] = Set.new v
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
return options
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
return {}
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.get_defaults
|
75
|
+
{ :skip_checks => Set.new,
|
76
|
+
:check_arguments => true,
|
77
|
+
:safe_methods => Set.new,
|
78
|
+
:min_confidence => 2,
|
79
|
+
:combine_locations => true,
|
80
|
+
:collapse_mass_assignment => true,
|
81
|
+
:ignore_redirect_to_model => true,
|
82
|
+
:ignore_model_output => false,
|
83
|
+
:message_limit => 100,
|
84
|
+
:html_style => "#{File.expand_path(File.dirname(__FILE__))}/../lib/format/style.css"
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.get_output_format options
|
89
|
+
#Set output format
|
90
|
+
if options[:output_format]
|
91
|
+
case options[:output_format]
|
92
|
+
when :html, :to_html
|
93
|
+
:to_html
|
94
|
+
when :csv, :to_csv
|
95
|
+
:to_csv
|
96
|
+
when :pdf, :to_pdf
|
97
|
+
:to_pdf
|
98
|
+
when :tabs, :to_tabs
|
99
|
+
:to_tabs
|
100
|
+
else
|
101
|
+
:to_s
|
102
|
+
end
|
103
|
+
else
|
104
|
+
case options[:output_file]
|
105
|
+
when /\.html$/i
|
106
|
+
:to_html
|
107
|
+
when /\.csv$/i
|
108
|
+
:to_csv
|
109
|
+
when /\.pdf$/i
|
110
|
+
:to_pdf
|
111
|
+
when /\.tabs$/i
|
112
|
+
:to_tabs
|
113
|
+
else
|
114
|
+
:to_s
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.list_checks
|
120
|
+
require 'scanner'
|
121
|
+
$stderr.puts "Available Checks:"
|
122
|
+
$stderr.puts "-" * 30
|
123
|
+
$stderr.puts Checks.checks.map { |c| c.to_s }.sort.join "\n"
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.dump_config options
|
127
|
+
if options[:create_config].is_a? String
|
128
|
+
file = options[:create_config]
|
129
|
+
else
|
130
|
+
file = nil
|
131
|
+
end
|
132
|
+
|
133
|
+
options.delete :create_config
|
134
|
+
|
135
|
+
options.each do |k,v|
|
136
|
+
if v.is_a? Set
|
137
|
+
options[k] = v.to_a
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
if file
|
142
|
+
File.open file, "w" do |f|
|
143
|
+
YAML.dump options, f
|
144
|
+
end
|
145
|
+
puts "Output configuration to #{file}"
|
146
|
+
else
|
147
|
+
puts YAML.dump(options)
|
148
|
+
end
|
149
|
+
exit
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.scan options
|
153
|
+
OPTIONS.clear
|
154
|
+
OPTIONS.merge! options
|
155
|
+
|
156
|
+
#Load scanner
|
157
|
+
warn "Loading scanner..."
|
158
|
+
|
159
|
+
begin
|
160
|
+
require 'scanner'
|
161
|
+
rescue LoadError
|
162
|
+
abort "Cannot find lib/ directory."
|
163
|
+
end
|
164
|
+
|
165
|
+
#Start scanning
|
166
|
+
scanner = Scanner.new options[:app_path]
|
167
|
+
|
168
|
+
warn "[Notice] Using Ruby #{RUBY_VERSION}. Please make sure this matches the one used to run your Rails application."
|
169
|
+
|
170
|
+
warn "Processing application in #{options[:app_path]}"
|
171
|
+
tracker = scanner.process
|
172
|
+
|
173
|
+
warn "Running checks..."
|
174
|
+
tracker.run_checks
|
175
|
+
|
176
|
+
warn "Generating report..."
|
177
|
+
if OPTIONS[:output_file]
|
178
|
+
File.open OPTIONS[:output_file], "w" do |f|
|
179
|
+
f.puts tracker.report.send(OPTIONS[:output_format])
|
180
|
+
end
|
181
|
+
warn "Report saved in '#{OPTIONS[:output_file]}'"
|
182
|
+
else
|
183
|
+
puts tracker.report.send(OPTIONS[:output_format])
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'checks/base_check'
|
2
|
+
|
3
|
+
#Checks if password is stored in controller
|
4
|
+
#when using http_basic_authenticate_with
|
5
|
+
#
|
6
|
+
#Only for Rails >= 3.1
|
7
|
+
class CheckBasicAuth < BaseCheck
|
8
|
+
Checks.add self
|
9
|
+
|
10
|
+
def run_check
|
11
|
+
return if version_between? "0.0.0", "3.0.99"
|
12
|
+
|
13
|
+
controllers = tracker.controllers.select do |name, c|
|
14
|
+
c[:options][:http_basic_authenticate_with]
|
15
|
+
end
|
16
|
+
|
17
|
+
Hash[controllers].each do |name, controller|
|
18
|
+
controller[:options][:http_basic_authenticate_with].each do |call|
|
19
|
+
|
20
|
+
if pass = get_password(call) and string? pass
|
21
|
+
warn :controller => name,
|
22
|
+
:warning_type => "Basic Auth",
|
23
|
+
:message => "Basic authentication password stored in source code",
|
24
|
+
:line => call.line,
|
25
|
+
:code => call,
|
26
|
+
:confidence => 0
|
27
|
+
|
28
|
+
break
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_password call
|
35
|
+
args = call[3][1]
|
36
|
+
|
37
|
+
return false if args.nil? or not hash? args
|
38
|
+
|
39
|
+
hash_iterate(args) do |k, v|
|
40
|
+
if symbol? k and k[1] == :password
|
41
|
+
return v
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
end
|
@@ -28,7 +28,7 @@ class CheckCrossSiteScripting < BaseCheck
|
|
28
28
|
IGNORE_MODEL_METHODS = Set.new([:average, :count, :maximum, :minimum, :sum])
|
29
29
|
|
30
30
|
#Methods known to not escape their input
|
31
|
-
KNOWN_DANGEROUS = Set.new([:
|
31
|
+
KNOWN_DANGEROUS = Set.new([:truncate, :concat])
|
32
32
|
|
33
33
|
MODEL_METHODS = Set.new([:all, :find, :first, :last, :new])
|
34
34
|
|
@@ -52,6 +52,12 @@ class CheckCrossSiteScripting < BaseCheck
|
|
52
52
|
|
53
53
|
CheckLinkTo.new(checks, tracker).run_check
|
54
54
|
|
55
|
+
if version_between? "2.0.0", "3.0.5"
|
56
|
+
KNOWN_DANGEROUS << :auto_link
|
57
|
+
elsif version_between? "3.0.6", "3.0.99"
|
58
|
+
IGNORE_METHODS << :auto_link
|
59
|
+
end
|
60
|
+
|
55
61
|
tracker.each_template do |name, template|
|
56
62
|
@current_template = template
|
57
63
|
|
@@ -42,7 +42,7 @@ class CheckMassAssignment < BaseCheck
|
|
42
42
|
if check and not @results.include? call
|
43
43
|
@results << call
|
44
44
|
|
45
|
-
if include_user_input? call[3]
|
45
|
+
if include_user_input? call[3] and not hash? call[3][1]
|
46
46
|
confidence = CONFIDENCE[:high]
|
47
47
|
else
|
48
48
|
confidence = CONFIDENCE[:med]
|
@@ -55,6 +55,7 @@ class CheckMassAssignment < BaseCheck
|
|
55
55
|
:code => call,
|
56
56
|
:confidence => confidence
|
57
57
|
end
|
58
|
+
|
58
59
|
res
|
59
60
|
end
|
60
61
|
|
@@ -63,8 +64,7 @@ class CheckMassAssignment < BaseCheck
|
|
63
64
|
args = process call[3]
|
64
65
|
if args.length <= 1 #empty new()
|
65
66
|
false
|
66
|
-
elsif hash? args[1]
|
67
|
-
#Still should probably check contents of hash
|
67
|
+
elsif hash? args[1] and not include_user_input? args[1]
|
68
68
|
false
|
69
69
|
else
|
70
70
|
true
|