brakeman 1.1.0 → 1.2.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/bin/brakeman +32 -173
- data/lib/brakeman.rb +47 -25
- data/lib/brakeman/call_index.rb +37 -1
- data/lib/brakeman/checks.rb +17 -0
- data/lib/brakeman/checks/base_check.rb +5 -1
- data/lib/brakeman/checks/check_cross_site_scripting.rb +18 -22
- data/lib/brakeman/checks/check_execute.rb +11 -24
- data/lib/brakeman/checks/check_render.rb +15 -26
- data/lib/brakeman/checks/check_sql.rb +48 -3
- data/lib/brakeman/options.rb +204 -0
- data/lib/brakeman/processor.rb +2 -2
- data/lib/brakeman/processors/controller_alias_processor.rb +9 -1
- data/lib/brakeman/processors/lib/find_all_calls.rb +36 -0
- data/lib/brakeman/processors/lib/rails3_route_processor.rb +1 -0
- data/lib/brakeman/processors/model_processor.rb +1 -1
- data/lib/brakeman/report.rb +36 -122
- data/lib/brakeman/rescanner.rb +247 -0
- data/lib/brakeman/scanner.rb +94 -76
- data/lib/brakeman/tracker.rb +103 -2
- data/lib/brakeman/util.rb +106 -0
- data/lib/brakeman/version.rb +1 -1
- data/lib/brakeman/warning.rb +26 -11
- metadata +5 -3
@@ -60,29 +60,18 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
|
|
60
60
|
#
|
61
61
|
# `rm -rf #{params[:file]}`
|
62
62
|
def check_for_backticks tracker
|
63
|
-
tracker.
|
64
|
-
|
65
|
-
@current_method = method_name
|
66
|
-
|
67
|
-
process exp
|
68
|
-
end
|
69
|
-
|
70
|
-
@current_set = nil
|
71
|
-
|
72
|
-
tracker.each_template do |name, template|
|
73
|
-
@current_template = template
|
74
|
-
|
75
|
-
process template[:src]
|
63
|
+
tracker.find_call(:target => nil, :method => :`).each do |result|
|
64
|
+
process_backticks result
|
76
65
|
end
|
77
|
-
|
78
|
-
@current_template = nil
|
79
66
|
end
|
80
67
|
|
81
68
|
#Processes backticks.
|
82
|
-
def
|
83
|
-
return
|
69
|
+
def process_backticks result
|
70
|
+
return if duplicate? result
|
71
|
+
|
72
|
+
add_result result
|
84
73
|
|
85
|
-
|
74
|
+
exp = result[:call]
|
86
75
|
|
87
76
|
if include_user_input? exp
|
88
77
|
confidence = CONFIDENCE[:high]
|
@@ -96,15 +85,13 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
|
|
96
85
|
:code => exp,
|
97
86
|
:confidence => confidence }
|
98
87
|
|
99
|
-
if
|
100
|
-
warning[:template] =
|
88
|
+
if result[:location][0] == :template
|
89
|
+
warning[:template] = result[:location][1]
|
101
90
|
else
|
102
|
-
warning[:class] =
|
103
|
-
warning[:method] =
|
91
|
+
warning[:class] = result[:location][1]
|
92
|
+
warning[:method] = result[:location][2]
|
104
93
|
end
|
105
94
|
|
106
95
|
warn warning
|
107
|
-
|
108
|
-
exp
|
109
96
|
end
|
110
97
|
end
|
@@ -5,24 +5,15 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
|
|
5
5
|
Brakeman::Checks.add self
|
6
6
|
|
7
7
|
def run_check
|
8
|
-
|
9
|
-
|
10
|
-
@current_class = class_name
|
11
|
-
@current_method = method_name
|
12
|
-
process src
|
13
|
-
end
|
14
|
-
|
15
|
-
debug_info "Checking all templates for calls to render()"
|
16
|
-
tracker.each_template do |name, template|
|
17
|
-
@current_template = template
|
18
|
-
process template[:src]
|
8
|
+
tracker.find_call(:target => nil, :method => :render).each do |result|
|
9
|
+
process_render result
|
19
10
|
end
|
20
11
|
end
|
21
12
|
|
22
|
-
def process_render
|
23
|
-
case
|
13
|
+
def process_render result
|
14
|
+
case result[:call][1]
|
24
15
|
when :partial, :template, :action, :file
|
25
|
-
check_for_dynamic_path
|
16
|
+
check_for_dynamic_path result
|
26
17
|
when :inline
|
27
18
|
when :js
|
28
19
|
when :json
|
@@ -30,16 +21,15 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
|
|
30
21
|
when :update
|
31
22
|
when :xml
|
32
23
|
end
|
33
|
-
exp
|
34
24
|
end
|
35
25
|
|
36
26
|
#Check if path to action or file is determined dynamically
|
37
|
-
def check_for_dynamic_path
|
38
|
-
view =
|
27
|
+
def check_for_dynamic_path result
|
28
|
+
view = result[:call][2]
|
39
29
|
|
40
|
-
if sexp? view and view.node_type != :str and view.node_type != :lit and not duplicate?
|
30
|
+
if sexp? view and view.node_type != :str and view.node_type != :lit and not duplicate? result
|
41
31
|
|
42
|
-
add_result
|
32
|
+
add_result result
|
43
33
|
|
44
34
|
if include_user_input? view
|
45
35
|
confidence = CONFIDENCE[:high]
|
@@ -49,16 +39,15 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
|
|
49
39
|
|
50
40
|
warning = { :warning_type => "Dynamic Render Path",
|
51
41
|
:message => "Render path is dynamic",
|
52
|
-
:line =>
|
53
|
-
:code =>
|
42
|
+
:line => result[:call].line,
|
43
|
+
:code => result[:call],
|
54
44
|
:confidence => confidence }
|
55
45
|
|
56
|
-
|
57
|
-
|
58
|
-
warning[:template] = @current_template
|
46
|
+
if result[:location][0] == :template
|
47
|
+
warning[:template] = result[:location][1]
|
59
48
|
else
|
60
|
-
warning[:class] =
|
61
|
-
warning[:method] =
|
49
|
+
warning[:class] = result[:location][1]
|
50
|
+
warning[:method] = result[:location][2]
|
62
51
|
end
|
63
52
|
|
64
53
|
warn warning
|
@@ -31,17 +31,47 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
31
31
|
debug_info "Finding possible SQL calls using constantized()"
|
32
32
|
calls.concat tracker.find_call(:method => /^(find.*|last|first|all|count|sum|average|minumum|maximum|count_by_sql)$/).select { |result| constantize_call? result }
|
33
33
|
|
34
|
+
debug_info "Finding calls to named_scope or scope"
|
35
|
+
calls.concat find_scope_calls
|
36
|
+
|
34
37
|
debug_info "Processing possible SQL calls"
|
35
38
|
calls.each do |c|
|
36
39
|
process_result c
|
37
40
|
end
|
38
41
|
end
|
39
42
|
|
43
|
+
#Find calls to named_scope() or scope() in models
|
44
|
+
def find_scope_calls
|
45
|
+
scope_calls = []
|
46
|
+
|
47
|
+
if version_between? "2.1.0", "3.0.9"
|
48
|
+
tracker.models.each do |name, model|
|
49
|
+
if model[:options][:named_scope]
|
50
|
+
model[:options][:named_scope].each do |args|
|
51
|
+
call = Sexp.new(:call, nil, :named_scope, args).line(args.line)
|
52
|
+
scope_calls << { :call => call, :location => [:class, name ] }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
elsif version_between? "3.1.0", "3.9.9"
|
57
|
+
tracker.models.each do |name, model|
|
58
|
+
if model[:options][:scope]
|
59
|
+
model[:options][:scope].each do |args|
|
60
|
+
call = Sexp.new(:call, nil, :scope, args).line(args.line)
|
61
|
+
scope_calls << { :call => call, :location => [:class, name ] }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
scope_calls
|
68
|
+
end
|
69
|
+
|
40
70
|
#Process result from Tracker#find_call.
|
41
71
|
def process_result result
|
42
72
|
call = result[:call]
|
43
73
|
|
44
|
-
args =
|
74
|
+
args = call[3]
|
45
75
|
|
46
76
|
if call[2] == :find_by_sql or call[2] == :count_by_sql
|
47
77
|
failed = check_arguments args[1]
|
@@ -94,8 +124,8 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
94
124
|
end
|
95
125
|
when :array
|
96
126
|
return check_arguments(arg[1])
|
97
|
-
when :string_interp
|
98
|
-
return true
|
127
|
+
when :string_interp, :dstr
|
128
|
+
return true if check_string_interp arg
|
99
129
|
when :call
|
100
130
|
return check_call(arg)
|
101
131
|
else
|
@@ -108,11 +138,24 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
108
138
|
false
|
109
139
|
end
|
110
140
|
|
141
|
+
def check_string_interp arg
|
142
|
+
arg.each do |exp|
|
143
|
+
#For now, don't warn on interpolation of Model.table_name
|
144
|
+
#but check for other 'safe' things in the future
|
145
|
+
if sexp? exp and (exp.node_type == :string_eval or exp.node_type == :evstr)
|
146
|
+
if call? exp[1] and (model_name?(exp[1][1]) or exp[1][1].nil?) and exp[1][2] == :table_name
|
147
|
+
return false
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
111
153
|
#Check call for user input and string building
|
112
154
|
def check_call exp
|
113
155
|
target = exp[1]
|
114
156
|
method = exp[2]
|
115
157
|
args = exp[3]
|
158
|
+
|
116
159
|
if sexp? target and
|
117
160
|
(method == :+ or method == :<< or method == :concat) and
|
118
161
|
(string? target or include_user_input? exp)
|
@@ -120,6 +163,8 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
120
163
|
true
|
121
164
|
elsif call? target
|
122
165
|
check_call target
|
166
|
+
elsif target == nil and tracker.options[:rails3] and method.to_s.match /^first|last|all|where|order|group|having$/
|
167
|
+
check_arguments args
|
123
168
|
else
|
124
169
|
false
|
125
170
|
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
#Parses command line arguments for Brakeman
|
5
|
+
module Brakeman::Options
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
#Parse argument array
|
10
|
+
def parse args
|
11
|
+
get_options args
|
12
|
+
end
|
13
|
+
|
14
|
+
#Parse arguments and remove them from the array as they are matched
|
15
|
+
def parse! args
|
16
|
+
get_options args, true
|
17
|
+
end
|
18
|
+
|
19
|
+
#Return hash of options and the parser
|
20
|
+
def get_options args, destructive = false
|
21
|
+
options = {}
|
22
|
+
|
23
|
+
parser = OptionParser.new do |opts|
|
24
|
+
opts.banner = "Usage: brakeman [options] rails/root/path"
|
25
|
+
|
26
|
+
opts.on "-n", "--no-threads", "Run checks sequentially" do
|
27
|
+
options[:parallel_checks] = false
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on "--[no-]progress", "Show progress reports" do |progress|
|
31
|
+
options[:report_progress] = progress
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on "-p", "--path PATH", "Specify path to Rails application" do |path|
|
35
|
+
options[:app_path] = File.expand_path path
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on "-q", "--[no-]quiet", "Suppress informational messages" do |quiet|
|
39
|
+
options[:quiet] = quiet
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on( "-z", "--exit-on-warn", "Exit code is non-zero if warnings found") do
|
43
|
+
options[:exit_on_warn] = true
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on "-3", "--rails3", "Force Rails 3 mode" do
|
47
|
+
options[:rails3] = true
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.separator ""
|
51
|
+
opts.separator "Scanning options:"
|
52
|
+
|
53
|
+
opts.on "-a", "--assume-routes", "Assume all controller methods are actions" do
|
54
|
+
options[:assume_all_routes] = true
|
55
|
+
end
|
56
|
+
|
57
|
+
opts.on "--ignore-model-output", "Consider model attributes XSS-safe" do
|
58
|
+
options[:ignore_model_output] = true
|
59
|
+
end
|
60
|
+
|
61
|
+
opts.on "-e", "--escape-html", "Escape HTML by default" do
|
62
|
+
options[:escape_html] = true
|
63
|
+
end
|
64
|
+
|
65
|
+
opts.on "--faster", "Faster, but less accurate scan" do
|
66
|
+
options[:ignore_ifs] = true
|
67
|
+
options[:skip_libs] = true
|
68
|
+
end
|
69
|
+
|
70
|
+
opts.on "--no-branching", "Disable flow sensitivity on conditionals" do
|
71
|
+
options[:ignore_ifs] = true
|
72
|
+
end
|
73
|
+
|
74
|
+
opts.on "-r", "--report-direct", "Only report direct use of untrusted data" do |option|
|
75
|
+
options[:check_arguments] = !option
|
76
|
+
end
|
77
|
+
|
78
|
+
opts.on "-s", "--safe-methods meth1,meth2,etc", Array, "Consider the specified methods safe" do |methods|
|
79
|
+
options[:safe_methods] ||= Set.new
|
80
|
+
options[:safe_methods].merge methods.map {|e| e.to_sym }
|
81
|
+
end
|
82
|
+
|
83
|
+
opts.on "--skip-libs", "Skip processing lib directory" do
|
84
|
+
options[:skip_libs] = true
|
85
|
+
end
|
86
|
+
|
87
|
+
opts.on "-t", "--test Check1,Check2,etc", Array, "Only run the specified checks" do |checks|
|
88
|
+
checks.each_with_index do |s, index|
|
89
|
+
if s[0,5] != "Check"
|
90
|
+
checks[index] = "Check" << s
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
options[:run_checks] ||= Set.new
|
95
|
+
options[:run_checks].merge checks
|
96
|
+
end
|
97
|
+
|
98
|
+
opts.on "-x", "--except Check1,Check2,etc", Array, "Skip the specified checks" do |skip|
|
99
|
+
skip.each do |s|
|
100
|
+
if s[0,5] != "Check"
|
101
|
+
s = "Check" << s
|
102
|
+
end
|
103
|
+
|
104
|
+
options[:skip_checks] ||= Set.new
|
105
|
+
options[:skip_checks] << s
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
opts.separator ""
|
110
|
+
opts.separator "Output options:"
|
111
|
+
|
112
|
+
opts.on "-d", "--debug", "Lots of output" do
|
113
|
+
options[:debug] = true
|
114
|
+
end
|
115
|
+
|
116
|
+
opts.on "-f",
|
117
|
+
"--format TYPE",
|
118
|
+
[:pdf, :text, :html, :csv, :tabs],
|
119
|
+
"Specify output format. Default is text" do |type|
|
120
|
+
|
121
|
+
type = "s" if type == :text
|
122
|
+
options[:output_format] = ("to_" << type.to_s).to_sym
|
123
|
+
end
|
124
|
+
|
125
|
+
opts.on "--css-file CSSFile", "Specify CSS to use for HTML output" do |file|
|
126
|
+
options[:html_style] = File.expand_path file
|
127
|
+
end
|
128
|
+
|
129
|
+
opts.on "-l", "--[no-]combine-locations", "Combine warning locations (Default)" do |combine|
|
130
|
+
options[:combine_locations] = combine
|
131
|
+
end
|
132
|
+
|
133
|
+
opts.on "-m", "--routes", "Report controller information" do
|
134
|
+
options[:report_routes] = true
|
135
|
+
end
|
136
|
+
|
137
|
+
opts.on "--message-limit LENGTH", "Limit message length in HTML report" do |limit|
|
138
|
+
options[:message_limit] = limit.to_i
|
139
|
+
end
|
140
|
+
|
141
|
+
opts.on "-o", "--output FILE", "Specify file for output. Defaults to stdout" do |file|
|
142
|
+
options[:output_file] = file
|
143
|
+
end
|
144
|
+
|
145
|
+
opts.on "--separate-models", "Warn on each model without attr_accessible" do
|
146
|
+
options[:collapse_mass_assignment] = false
|
147
|
+
end
|
148
|
+
|
149
|
+
opts.on "--summary", "Only output summary of warnings" do
|
150
|
+
options[:summary_only] = true
|
151
|
+
end
|
152
|
+
|
153
|
+
opts.on "-w",
|
154
|
+
"--confidence-level LEVEL",
|
155
|
+
["1", "2", "3"],
|
156
|
+
"Set minimal confidence level (1 - 3)" do |level|
|
157
|
+
|
158
|
+
options[:min_confidence] = 3 - level.to_i
|
159
|
+
end
|
160
|
+
|
161
|
+
opts.separator ""
|
162
|
+
opts.separator "Configuration files:"
|
163
|
+
|
164
|
+
opts.on "-c", "--config-file FILE", "Use specified configuration file" do |file|
|
165
|
+
options[:config_file] = File.expand_path(file)
|
166
|
+
end
|
167
|
+
|
168
|
+
opts.on "-C", "--create-config [FILE]", "Output configuration file based on options" do |file|
|
169
|
+
if file
|
170
|
+
options[:create_config] = file
|
171
|
+
else
|
172
|
+
options[:create_config] = true
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
opts.separator ""
|
177
|
+
|
178
|
+
opts.on "-k", "--checks", "List all available vulnerability checks" do
|
179
|
+
options[:list_checks] = true
|
180
|
+
end
|
181
|
+
|
182
|
+
opts.on "--rake", "Create rake task to run Brakeman" do
|
183
|
+
options[:install_rake_task] = true
|
184
|
+
end
|
185
|
+
|
186
|
+
opts.on "-v", "--version", "Show Brakeman version" do
|
187
|
+
options[:show_version] = true
|
188
|
+
end
|
189
|
+
|
190
|
+
opts.on_tail "-h", "--help", "Display this message" do
|
191
|
+
options[:show_help] = true
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
if destructive
|
196
|
+
parser.parse! args
|
197
|
+
else
|
198
|
+
parser.parse args
|
199
|
+
end
|
200
|
+
|
201
|
+
return options, parser
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
data/lib/brakeman/processor.rb
CHANGED
@@ -40,8 +40,8 @@ module Brakeman
|
|
40
40
|
|
41
41
|
#Process variable aliasing in controller source and save it in the
|
42
42
|
#tracker.
|
43
|
-
def process_controller_alias src
|
44
|
-
ControllerAliasProcessor.new(@tracker).process src
|
43
|
+
def process_controller_alias src, only_method = nil
|
44
|
+
ControllerAliasProcessor.new(@tracker, only_method).process src
|
45
45
|
end
|
46
46
|
|
47
47
|
#Process a model source
|
@@ -6,8 +6,12 @@ require 'brakeman/processors/lib/render_helper'
|
|
6
6
|
class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
|
7
7
|
include Brakeman::RenderHelper
|
8
8
|
|
9
|
-
|
9
|
+
#If only_method is specified, only that method will be processed,
|
10
|
+
#other methods will be skipped.
|
11
|
+
#This is for rescanning just a single action.
|
12
|
+
def initialize tracker, only_method = nil
|
10
13
|
super()
|
14
|
+
@only_method = only_method
|
11
15
|
@tracker = tracker
|
12
16
|
@rendered = false
|
13
17
|
@current_class = @current_module = @current_method = nil
|
@@ -26,6 +30,10 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
|
|
26
30
|
#Processes a method definition, which may include
|
27
31
|
#processing any rendered templates.
|
28
32
|
def process_methdef exp
|
33
|
+
#Skip if instructed to only process a specific method
|
34
|
+
#(but don't skip if this method was called from elsewhere)
|
35
|
+
return exp if @current_method.nil? and @only_method and @only_method != exp[1]
|
36
|
+
|
29
37
|
is_route = route? exp[1]
|
30
38
|
other_method = @current_method
|
31
39
|
@current_method = exp[1]
|