brakeman 0.7.2 → 0.8.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 +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
|