brakeman 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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