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.
Files changed (47) hide show
  1. data/FEATURES +16 -0
  2. data/README.md +112 -0
  3. data/WARNING_TYPES +69 -0
  4. data/bin/brakeman +266 -0
  5. data/lib/checks.rb +67 -0
  6. data/lib/checks/base_check.rb +338 -0
  7. data/lib/checks/check_cross_site_scripting.rb +216 -0
  8. data/lib/checks/check_default_routes.rb +29 -0
  9. data/lib/checks/check_evaluation.rb +29 -0
  10. data/lib/checks/check_execute.rb +110 -0
  11. data/lib/checks/check_file_access.rb +46 -0
  12. data/lib/checks/check_forgery_setting.rb +25 -0
  13. data/lib/checks/check_mass_assignment.rb +72 -0
  14. data/lib/checks/check_model_attributes.rb +36 -0
  15. data/lib/checks/check_redirect.rb +98 -0
  16. data/lib/checks/check_render.rb +65 -0
  17. data/lib/checks/check_send_file.rb +15 -0
  18. data/lib/checks/check_session_settings.rb +36 -0
  19. data/lib/checks/check_sql.rb +124 -0
  20. data/lib/checks/check_validation_regex.rb +60 -0
  21. data/lib/format/style.css +105 -0
  22. data/lib/processor.rb +83 -0
  23. data/lib/processors/alias_processor.rb +384 -0
  24. data/lib/processors/base_processor.rb +235 -0
  25. data/lib/processors/config_processor.rb +146 -0
  26. data/lib/processors/controller_alias_processor.rb +222 -0
  27. data/lib/processors/controller_processor.rb +175 -0
  28. data/lib/processors/erb_template_processor.rb +84 -0
  29. data/lib/processors/erubis_template_processor.rb +62 -0
  30. data/lib/processors/haml_template_processor.rb +115 -0
  31. data/lib/processors/lib/find_call.rb +176 -0
  32. data/lib/processors/lib/find_model_call.rb +39 -0
  33. data/lib/processors/lib/processor_helper.rb +36 -0
  34. data/lib/processors/lib/render_helper.rb +118 -0
  35. data/lib/processors/library_processor.rb +117 -0
  36. data/lib/processors/model_processor.rb +125 -0
  37. data/lib/processors/output_processor.rb +204 -0
  38. data/lib/processors/params_processor.rb +77 -0
  39. data/lib/processors/route_processor.rb +338 -0
  40. data/lib/processors/template_alias_processor.rb +86 -0
  41. data/lib/processors/template_processor.rb +55 -0
  42. data/lib/report.rb +628 -0
  43. data/lib/scanner.rb +232 -0
  44. data/lib/tracker.rb +144 -0
  45. data/lib/util.rb +141 -0
  46. data/lib/warning.rb +97 -0
  47. 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