brakeman 0.0.2
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/FEATURES +16 -0
- data/README.md +112 -0
- data/WARNING_TYPES +69 -0
- data/bin/brakeman +266 -0
- data/lib/checks.rb +67 -0
- data/lib/checks/base_check.rb +338 -0
- data/lib/checks/check_cross_site_scripting.rb +216 -0
- data/lib/checks/check_default_routes.rb +29 -0
- data/lib/checks/check_evaluation.rb +29 -0
- data/lib/checks/check_execute.rb +110 -0
- data/lib/checks/check_file_access.rb +46 -0
- data/lib/checks/check_forgery_setting.rb +25 -0
- data/lib/checks/check_mass_assignment.rb +72 -0
- data/lib/checks/check_model_attributes.rb +36 -0
- data/lib/checks/check_redirect.rb +98 -0
- data/lib/checks/check_render.rb +65 -0
- data/lib/checks/check_send_file.rb +15 -0
- data/lib/checks/check_session_settings.rb +36 -0
- data/lib/checks/check_sql.rb +124 -0
- data/lib/checks/check_validation_regex.rb +60 -0
- data/lib/format/style.css +105 -0
- data/lib/processor.rb +83 -0
- data/lib/processors/alias_processor.rb +384 -0
- data/lib/processors/base_processor.rb +235 -0
- data/lib/processors/config_processor.rb +146 -0
- data/lib/processors/controller_alias_processor.rb +222 -0
- data/lib/processors/controller_processor.rb +175 -0
- data/lib/processors/erb_template_processor.rb +84 -0
- data/lib/processors/erubis_template_processor.rb +62 -0
- data/lib/processors/haml_template_processor.rb +115 -0
- data/lib/processors/lib/find_call.rb +176 -0
- data/lib/processors/lib/find_model_call.rb +39 -0
- data/lib/processors/lib/processor_helper.rb +36 -0
- data/lib/processors/lib/render_helper.rb +118 -0
- data/lib/processors/library_processor.rb +117 -0
- data/lib/processors/model_processor.rb +125 -0
- data/lib/processors/output_processor.rb +204 -0
- data/lib/processors/params_processor.rb +77 -0
- data/lib/processors/route_processor.rb +338 -0
- data/lib/processors/template_alias_processor.rb +86 -0
- data/lib/processors/template_processor.rb +55 -0
- data/lib/report.rb +628 -0
- data/lib/scanner.rb +232 -0
- data/lib/tracker.rb +144 -0
- data/lib/util.rb +141 -0
- data/lib/warning.rb +97 -0
- metadata +191 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'checks/base_check'
|
2
|
+
|
3
|
+
#Checks if default routes are allowed in routes.rb
|
4
|
+
class CheckDefaultRoutes < BaseCheck
|
5
|
+
Checks.add self
|
6
|
+
|
7
|
+
#Checks for :allow_all_actions globally and for individual routes
|
8
|
+
#if it is not enabled globally.
|
9
|
+
def run_check
|
10
|
+
if tracker.routes[:allow_all_actions]
|
11
|
+
#Default routes are enabled globally
|
12
|
+
warn :warning_type => "Default Routes",
|
13
|
+
:message => "All public methods in controllers are available as actions in routes.rb",
|
14
|
+
:line => tracker.routes[:allow_all_actions].line,
|
15
|
+
:confidence => CONFIDENCE[:high],
|
16
|
+
:file => "#{OPTIONS[:app_path]}/config/routes.rb"
|
17
|
+
else #Report each controller separately
|
18
|
+
tracker.routes.each do |name, actions|
|
19
|
+
if actions == :allow_all_actions
|
20
|
+
warn :controller => name,
|
21
|
+
:warning_type => "Default Routes",
|
22
|
+
:message => "Any public method in #{name} can be used as an action.",
|
23
|
+
:confidence => CONFIDENCE[:med],
|
24
|
+
:file => "#{OPTIONS[:app_path]}/config/routes.rb"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'checks/base_check'
|
2
|
+
|
3
|
+
#This check looks for calls to +eval+, +instance_eval+, etc. which include
|
4
|
+
#user input.
|
5
|
+
class CheckEvaluation < BaseCheck
|
6
|
+
Checks.add self
|
7
|
+
|
8
|
+
#Process calls
|
9
|
+
def run_check
|
10
|
+
calls = tracker.find_call nil, [:eval, :instance_eval, :class_eval, :module_eval]
|
11
|
+
|
12
|
+
@templates = tracker.templates
|
13
|
+
|
14
|
+
calls.each do |call|
|
15
|
+
process_result call
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
#Warns if result includes user input
|
20
|
+
def process_result result
|
21
|
+
if include_user_input? result[-1]
|
22
|
+
warn :result => result,
|
23
|
+
:warning_type => "Dangerous Eval",
|
24
|
+
:message => "User input in eval",
|
25
|
+
:code => result[-1],
|
26
|
+
:confidence => CONFIDENCE[:high]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'checks/base_check'
|
2
|
+
require 'processors/lib/find_call'
|
3
|
+
|
4
|
+
#Checks for string interpolation and parameters in calls to
|
5
|
+
#Kernel#system, Kernel#exec, Kernel#syscall, and inside backticks.
|
6
|
+
#
|
7
|
+
#Examples of command injection vulnerabilities:
|
8
|
+
#
|
9
|
+
# system("rf -rf #{params[:file]}")
|
10
|
+
# exec(params[:command])
|
11
|
+
# `unlink #{params[:something}`
|
12
|
+
class CheckExecute < BaseCheck
|
13
|
+
Checks.add self
|
14
|
+
|
15
|
+
#Check models, controllers, and views for command injection.
|
16
|
+
def run_check
|
17
|
+
check_for_backticks tracker
|
18
|
+
|
19
|
+
calls = tracker.find_call [:IO, :Open3, :Kernel, []], [:exec, :popen, :popen3, :syscall, :system]
|
20
|
+
|
21
|
+
calls.each do |result|
|
22
|
+
process result
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
#Processes results from FindCall.
|
27
|
+
def process_result exp
|
28
|
+
call = exp[-1]
|
29
|
+
|
30
|
+
args = process call[3]
|
31
|
+
|
32
|
+
case call[2]
|
33
|
+
when :system, :exec
|
34
|
+
failure = include_user_input?(args[1]) || include_interp?(args[1])
|
35
|
+
else
|
36
|
+
failure = include_user_input?(args) || include_interp?(args)
|
37
|
+
end
|
38
|
+
|
39
|
+
if failure and not duplicate? call, exp[1]
|
40
|
+
add_result call, exp[1]
|
41
|
+
|
42
|
+
if @string_interp
|
43
|
+
confidence = CONFIDENCE[:med]
|
44
|
+
else
|
45
|
+
confidence = CONFIDENCE[:high]
|
46
|
+
end
|
47
|
+
|
48
|
+
warn :result => exp,
|
49
|
+
:warning_type => "Command Injection",
|
50
|
+
:message => "Possible command injection",
|
51
|
+
:line => call.line,
|
52
|
+
:code => call,
|
53
|
+
:confidence => confidence
|
54
|
+
end
|
55
|
+
|
56
|
+
exp
|
57
|
+
end
|
58
|
+
|
59
|
+
#Looks for calls using backticks such as
|
60
|
+
#
|
61
|
+
# `rm -rf #{params[:file]}`
|
62
|
+
def check_for_backticks tracker
|
63
|
+
tracker.each_method do |exp, set_name, method_name|
|
64
|
+
@current_set = set_name
|
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]
|
76
|
+
end
|
77
|
+
|
78
|
+
@current_template = nil
|
79
|
+
end
|
80
|
+
|
81
|
+
#Processes backticks.
|
82
|
+
def process_dxstr exp
|
83
|
+
return exp if duplicate? exp
|
84
|
+
|
85
|
+
add_result exp
|
86
|
+
|
87
|
+
if include_user_input? exp
|
88
|
+
confidence = CONFIDENCE[:high]
|
89
|
+
else
|
90
|
+
confidence = CONFIDENCE[:med]
|
91
|
+
end
|
92
|
+
|
93
|
+
warning = { :warning_type => "Command Injection",
|
94
|
+
:message => "Possible command injection",
|
95
|
+
:line => exp.line,
|
96
|
+
:code => exp,
|
97
|
+
:confidence => confidence }
|
98
|
+
|
99
|
+
if @current_template
|
100
|
+
warning[:template] = @current_template
|
101
|
+
else
|
102
|
+
warning[:class] = @current_set
|
103
|
+
warning[:method] = @current_method
|
104
|
+
end
|
105
|
+
|
106
|
+
warn warning
|
107
|
+
|
108
|
+
exp
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'checks/base_check'
|
2
|
+
require 'processors/lib/processor_helper'
|
3
|
+
|
4
|
+
#Checks for user input in methods which open or manipulate files
|
5
|
+
class CheckFileAccess < BaseCheck
|
6
|
+
Checks.add self
|
7
|
+
|
8
|
+
def run_check
|
9
|
+
methods = tracker.find_call [[:Dir, :File, :IO, :Kernel, :"Net::FTP", :"Net::HTTP", :PStore, :Pathname, :Shell, :YAML], []], [:[], :chdir, :chroot, :delete, :entries, :foreach, :glob, :install, :lchmod, :lchown, :link, :load, :load_file, :makedirs, :move, :new, :open, :read, :read_lines, :rename, :rmdir, :safe_unlink, :symlink, :syscopy, :sysopen, :truncate, :unlink]
|
10
|
+
|
11
|
+
methods.concat tracker.find_call(:FileUtils, nil)
|
12
|
+
|
13
|
+
methods.each do |call|
|
14
|
+
process_result call
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def process_result result
|
19
|
+
call = result[-1]
|
20
|
+
|
21
|
+
file_name = call[3][1]
|
22
|
+
|
23
|
+
if check = include_user_input?(file_name)
|
24
|
+
unless duplicate? call, result[1]
|
25
|
+
add_result call, result[1]
|
26
|
+
|
27
|
+
if check == :params
|
28
|
+
message = "Parameter"
|
29
|
+
elsif check == :cookies
|
30
|
+
message = "Cookie"
|
31
|
+
else
|
32
|
+
message = "User input"
|
33
|
+
end
|
34
|
+
|
35
|
+
message << " value used in file name"
|
36
|
+
|
37
|
+
warn :result => result,
|
38
|
+
:warning_type => "File Access",
|
39
|
+
:message => message,
|
40
|
+
:confidence => CONFIDENCE[:high],
|
41
|
+
:line => call.line,
|
42
|
+
:code => call
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'checks/base_check'
|
2
|
+
|
3
|
+
#Checks that +protect_from_forgery+ is set in the ApplicationController
|
4
|
+
class CheckForgerySetting < BaseCheck
|
5
|
+
Checks.add self
|
6
|
+
|
7
|
+
def run_check
|
8
|
+
app_controller = tracker.controllers[:ApplicationController]
|
9
|
+
if tracker.config[:rails][:action_controller] and
|
10
|
+
tracker.config[:rails][:action_controller][:allow_forgery_protection] == Sexp.new(:false)
|
11
|
+
|
12
|
+
warn :controller => :ApplicationController,
|
13
|
+
:warning_type => "Cross Site Request Forgery",
|
14
|
+
:message => "Forgery protection is disabled",
|
15
|
+
:confidence => CONFIDENCE[:high]
|
16
|
+
|
17
|
+
elsif app_controller and not app_controller[:options][:protect_from_forgery]
|
18
|
+
|
19
|
+
warn :controller => :ApplicationController,
|
20
|
+
:warning_type => "Cross-Site Request Forgery",
|
21
|
+
:message => "'protect_from_forgery' should be called in ApplicationController",
|
22
|
+
:confidence => CONFIDENCE[:high]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'checks/base_check'
|
2
|
+
|
3
|
+
#Checks for mass assignments to models.
|
4
|
+
#
|
5
|
+
#See http://guides.rubyonrails.org/security.html#mass-assignment for details
|
6
|
+
class CheckMassAssignment < BaseCheck
|
7
|
+
Checks.add self
|
8
|
+
|
9
|
+
def run_check
|
10
|
+
return if mass_assign_disabled? tracker
|
11
|
+
|
12
|
+
models = []
|
13
|
+
tracker.models.each do |name, m|
|
14
|
+
if parent?(tracker, m, :"ActiveRecord::Base") and m[:attr_accessible].nil?
|
15
|
+
models << name
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
return if models.empty?
|
20
|
+
|
21
|
+
@results = Set.new
|
22
|
+
|
23
|
+
calls = tracker.find_call models, [:new,
|
24
|
+
:attributes=,
|
25
|
+
:update_attribute,
|
26
|
+
:update_attributes,
|
27
|
+
:update_attributes!]
|
28
|
+
|
29
|
+
calls.each do |result|
|
30
|
+
process result
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
#All results should be Model.new(...) or Model.attributes=() calls
|
35
|
+
def process_result res
|
36
|
+
call = res[-1]
|
37
|
+
|
38
|
+
check = check_call call
|
39
|
+
|
40
|
+
if check and not @results.include? call
|
41
|
+
@results << call
|
42
|
+
|
43
|
+
if include_user_input? call[3]
|
44
|
+
confidence = CONFIDENCE[:high]
|
45
|
+
else
|
46
|
+
confidence = CONFIDENCE[:med]
|
47
|
+
end
|
48
|
+
|
49
|
+
warn :result => res,
|
50
|
+
:warning_type => "Mass Assignment",
|
51
|
+
:message => "Unprotected mass assignment",
|
52
|
+
:line => call.line,
|
53
|
+
:code => call,
|
54
|
+
:confidence => confidence
|
55
|
+
end
|
56
|
+
res
|
57
|
+
end
|
58
|
+
|
59
|
+
#Want to ignore calls to Model.new that have no arguments
|
60
|
+
def check_call call
|
61
|
+
args = process call[3]
|
62
|
+
if args.length <= 1 #empty new()
|
63
|
+
false
|
64
|
+
elsif hash? args[1]
|
65
|
+
#Still should probably check contents of hash
|
66
|
+
false
|
67
|
+
else
|
68
|
+
true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'checks/base_check'
|
2
|
+
|
3
|
+
#Check if mass assignment is used with models
|
4
|
+
#which inherit from ActiveRecord::Base.
|
5
|
+
#
|
6
|
+
#If OPTIONS[:collapse_mass_assignment] is +true+ (default), all models which do
|
7
|
+
#not use attr_accessible will be reported in a single warning
|
8
|
+
class CheckModelAttributes < BaseCheck
|
9
|
+
Checks.add self
|
10
|
+
|
11
|
+
def run_check
|
12
|
+
return if mass_assign_disabled? tracker
|
13
|
+
|
14
|
+
names = []
|
15
|
+
|
16
|
+
tracker.models.each do |name, model|
|
17
|
+
if model[:attr_accessible].nil? and parent? tracker, model, :"ActiveRecord::Base"
|
18
|
+
if OPTIONS[:collapse_mass_assignment]
|
19
|
+
names << name.to_s
|
20
|
+
else
|
21
|
+
warn :model => name,
|
22
|
+
:warning_type => "Attribute Restriction",
|
23
|
+
:message => "Mass assignment is not restricted using attr_accessible",
|
24
|
+
:confidence => CONFIDENCE[:high]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
if OPTIONS[:collapse_mass_assignment] and not names.empty?
|
30
|
+
warn :model => names.sort.join(", "),
|
31
|
+
:warning_type => "Attribute Restriction",
|
32
|
+
:message => "Mass assignment is not restricted using attr_accessible",
|
33
|
+
:confidence => CONFIDENCE[:high]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'checks/base_check'
|
2
|
+
require 'processors/lib/find_call'
|
3
|
+
|
4
|
+
#Reports any calls to +redirect_to+ which include parameters in the arguments.
|
5
|
+
#
|
6
|
+
#For example:
|
7
|
+
#
|
8
|
+
# redirect_to params.merge(:action => :elsewhere)
|
9
|
+
class CheckRedirect < BaseCheck
|
10
|
+
Checks.add self
|
11
|
+
|
12
|
+
def run_check
|
13
|
+
@tracker.find_call(nil, :redirect_to).each do |c|
|
14
|
+
process c
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def process_result exp
|
19
|
+
call = exp[-1]
|
20
|
+
|
21
|
+
method = call[2]
|
22
|
+
|
23
|
+
if method == :redirect_to and not only_path?(call) and res = include_user_input?(call)
|
24
|
+
if res == :immediate
|
25
|
+
confidence = CONFIDENCE[:high]
|
26
|
+
else
|
27
|
+
confidence = CONFIDENCE[:low]
|
28
|
+
end
|
29
|
+
|
30
|
+
warn :result => exp,
|
31
|
+
:warning_type => "Redirect",
|
32
|
+
:message => "Possible unprotected redirect",
|
33
|
+
:line => call.line,
|
34
|
+
:code => call,
|
35
|
+
:confidence => confidence
|
36
|
+
end
|
37
|
+
|
38
|
+
exp
|
39
|
+
end
|
40
|
+
|
41
|
+
#Custom check for user input. First looks to see if the user input
|
42
|
+
#is being output directly. This is necessary because of OPTIONS[:check_arguments]
|
43
|
+
#which can be used to enable/disable reporting output of method calls which use
|
44
|
+
#user input as arguments.
|
45
|
+
def include_user_input? call
|
46
|
+
|
47
|
+
if OPTIONS[:ignore_redirect_to_model] and call? call[3][1] and
|
48
|
+
call[3][1][2] == :new and call[3][1][1]
|
49
|
+
|
50
|
+
begin
|
51
|
+
target = class_name call[3][1][1]
|
52
|
+
if @tracker.models.include? target
|
53
|
+
return false
|
54
|
+
end
|
55
|
+
rescue
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
call[3].each do |arg|
|
60
|
+
if call? arg
|
61
|
+
if ALL_PARAMETERS.include? arg or arg[2] == COOKIES
|
62
|
+
return :immediate
|
63
|
+
elsif arg[2] == :url_for and include_user_input? arg
|
64
|
+
return :immediate
|
65
|
+
#Ignore helpers like some_model_url?
|
66
|
+
elsif arg[2].to_s =~ /_(url|path)$/
|
67
|
+
return false
|
68
|
+
end
|
69
|
+
elsif params? arg or cookies? arg
|
70
|
+
return :immediate
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
if OPTIONS[:check_arguments]
|
75
|
+
super
|
76
|
+
else
|
77
|
+
false
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
#Checks +redirect_to+ arguments for +only_path => true+ which essentially
|
82
|
+
#nullifies the danger posed by redirecting with user input
|
83
|
+
def only_path? call
|
84
|
+
call[3].each do |arg|
|
85
|
+
if hash? arg
|
86
|
+
hash_iterate(arg) do |k,v|
|
87
|
+
if symbol? k and k[1] == :only_path and v.is_a? Sexp and v[0] == :true
|
88
|
+
return true
|
89
|
+
end
|
90
|
+
end
|
91
|
+
elsif call? arg and arg[2] == :url_for
|
92
|
+
return only_path?(arg)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
false
|
97
|
+
end
|
98
|
+
end
|