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,65 @@
1
+ require 'checks/base_check'
2
+
3
+ #Check calls to +render()+ for dangerous values
4
+ class CheckRender < BaseCheck
5
+ Checks.add self
6
+
7
+ def run_check
8
+ tracker.each_method do |src, class_name, method_name|
9
+ @current_class = class_name
10
+ @current_method = method_name
11
+ process src
12
+ end
13
+
14
+ tracker.each_template do |name, template|
15
+ @current_template = template
16
+ process template[:src]
17
+ end
18
+ end
19
+
20
+ def process_render exp
21
+ case exp[1]
22
+ when :partial, :template, :action, :file
23
+ check_for_dynamic_path exp
24
+ when :inline
25
+ when :js
26
+ when :json
27
+ when :text
28
+ when :update
29
+ when :xml
30
+ end
31
+ exp
32
+ end
33
+
34
+ #Check if path to action or file is determined dynamically
35
+ def check_for_dynamic_path exp
36
+ view = exp[2]
37
+
38
+ if sexp? view and view.node_type != :str and view.node_type != :lit and not duplicate? exp
39
+
40
+ add_result exp
41
+
42
+ if include_user_input? view
43
+ confidence = CONFIDENCE[:high]
44
+ else
45
+ confidence = CONFIDENCE[:low]
46
+ end
47
+
48
+ warning = { :warning_type => "Dynamic Render Path",
49
+ :message => "Render path is dynamic",
50
+ :line => exp.line,
51
+ :code => exp,
52
+ :confidence => confidence }
53
+
54
+
55
+ if @current_template
56
+ warning[:template] = @current_template
57
+ else
58
+ warning[:class] = @current_class
59
+ warning[:method] = @current_method
60
+ end
61
+
62
+ warn warning
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,15 @@
1
+ require 'checks/check_file_access'
2
+ require 'processors/lib/processor_helper'
3
+
4
+ #Checks for user input in send_file()
5
+ class CheckSendFile < CheckFileAccess
6
+ Checks.add self
7
+
8
+ def run_check
9
+ methods = tracker.find_call nil, :send_file
10
+
11
+ methods.each do |call|
12
+ process_result call
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,36 @@
1
+ require 'checks/base_check'
2
+
3
+ class CheckSessionSettings < BaseCheck
4
+ Checks.add self
5
+
6
+ def run_check
7
+ settings = tracker.config[:rails] and
8
+ tracker.config[:rails][:action_controller] and
9
+ tracker.config[:rails][:action_controller][:session]
10
+
11
+ if settings and hash? settings
12
+ hash_iterate settings do |key, value|
13
+ if symbol? key
14
+
15
+ if key[1] == :session_http_only and
16
+ sexp? value and
17
+ value.node_type == :false
18
+
19
+ warn :warning_type => "Session Setting",
20
+ :message => "Session cookies should be set to HTTP only",
21
+ :confidence => CONFIDENCE[:high]
22
+
23
+ elsif key[1] == :secret and
24
+ string? value and
25
+ value[1].length < 30
26
+
27
+ warn :warning_type => "Session Setting",
28
+ :message => "Session secret should be at least 30 characters long",
29
+ :confidence => CONFIDENCE[:high]
30
+
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,124 @@
1
+ require 'checks/base_check'
2
+ require 'processors/lib/find_call'
3
+
4
+ #This check tests for find calls which do not use Rails' auto SQL escaping
5
+ #
6
+ #For example:
7
+ # Project.find(:all, :conditions => "name = '" + params[:name] + "'")
8
+ #
9
+ # Project.find(:all, :conditions => "name = '#{params[:name]}'")
10
+ #
11
+ # User.find_by_sql("SELECT * FROM projects WHERE name = '#{params[:name]}'")
12
+ class CheckSQL < BaseCheck
13
+ Checks.add self
14
+
15
+ def run_check
16
+ @rails_version = tracker.config[:rails_version]
17
+ calls = tracker.find_model_find tracker.models.keys
18
+ calls.concat tracker.find_call([], /^(find.*|last|first|all|count|sum|average|minumum|maximum)$/)
19
+ calls.each do |c|
20
+ process c
21
+ end
22
+ end
23
+
24
+ #Process result from FindCall.
25
+ def process_result exp
26
+ call = exp[-1]
27
+
28
+ args = process call[3]
29
+
30
+ if call[2] == :find_by_sql
31
+ failed = check_arguments args[1]
32
+ elsif call[2].to_s =~ /^find/
33
+ failed = (args.length > 2 and check_arguments args[-1])
34
+ else
35
+ failed = (args.length > 1 and check_arguments args[-1])
36
+ end
37
+
38
+ if failed and not duplicate? call, exp[1]
39
+ add_result call, exp[1]
40
+
41
+ if include_user_input? args[-1]
42
+ confidence = CONFIDENCE[:high]
43
+ else
44
+ confidence = CONFIDENCE[:med]
45
+ end
46
+
47
+ warn :result => exp,
48
+ :warning_type => "SQL Injection",
49
+ :message => "Possible SQL injection",
50
+ :confidence => confidence
51
+ end
52
+
53
+ if check_for_limit_or_offset_vulnerability args[-1]
54
+ if include_user_input? args[-1]
55
+ confidence = CONFIDENCE[:high]
56
+ else
57
+ confidence = CONFIDENCE[:low]
58
+ end
59
+
60
+ warn :result => exp,
61
+ :warning_type => "SQL Injection",
62
+ :message => "Upgrade to Rails >= 2.1.2 to escape :limit and :offset. Possible SQL injection",
63
+ :confidence => confidence
64
+ end
65
+ exp
66
+ end
67
+
68
+ private
69
+
70
+ #Check arguments for any string interpolation
71
+ def check_arguments arg
72
+ if sexp? arg
73
+ case arg.node_type
74
+ when :hash
75
+ hash_iterate(arg) do |key, value|
76
+ if check_arguments value
77
+ return true
78
+ end
79
+ end
80
+ when :array
81
+ return check_arguments(arg[1])
82
+ when :string_interp
83
+ return true
84
+ when :call
85
+ return check_call(arg)
86
+ end
87
+ end
88
+
89
+ false
90
+ end
91
+
92
+ #Check call for user input and string building
93
+ def check_call exp
94
+ target = exp[1]
95
+ method = exp[2]
96
+ args = exp[3]
97
+ if sexp? target and
98
+ (method == :+ or method == :<< or method == :concat) and
99
+ (string? target or include_user_input? exp)
100
+
101
+ true
102
+ elsif call? target
103
+ check_call target
104
+ else
105
+ false
106
+ end
107
+ end
108
+
109
+ #Prior to Rails 2.1.1, the :offset and :limit parameters were not
110
+ #escaping input properly.
111
+ #
112
+ #http://www.rorsecurity.info/2008/09/08/sql-injection-issue-in-limit-and-offset-parameter/
113
+ def check_for_limit_or_offset_vulnerability options
114
+ return false if @rails_version.nil? or @rails_version >= "2.1.1" or not hash? options
115
+
116
+ hash_iterate(options) do |key, value|
117
+ if symbol? key
118
+ return (key[1] == :limit or key[1] == :offset)
119
+ end
120
+ end
121
+
122
+ false
123
+ end
124
+ end
@@ -0,0 +1,60 @@
1
+ require 'checks/base_check'
2
+
3
+ #Reports any calls to +validates_format_of+ which do not use +\A+ and +\z+
4
+ #as anchors in the given regular expression.
5
+ #
6
+ #For example:
7
+ #
8
+ # #Allows anything after new line
9
+ # validates_format_of :user_name, :with => /^\w+$/
10
+ class CheckValidationRegex < BaseCheck
11
+ Checks.add self
12
+
13
+ WITH = Sexp.new(:lit, :with)
14
+
15
+ def run_check
16
+ tracker.models.each do |name, model|
17
+ @current_model = name
18
+ format_validations = model[:options][:validates_format_of]
19
+ if format_validations
20
+ format_validations.each do |v|
21
+ process_validator v
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ #Check validates_format_of
28
+ def process_validator validator
29
+ hash_iterate(validator[-1]) do |key, value|
30
+ if key == WITH
31
+ check_regex value, validator
32
+ end
33
+ end
34
+ end
35
+
36
+ #Issue warning if the regular expression does not use
37
+ #+\A+ and +\z+
38
+ def check_regex value, validator
39
+ return unless regexp? value
40
+
41
+ regex = value[1].inspect
42
+ if regex =~ /[^\A].*[^\z]\/(m|i|x|n|e|u|s|o)*\z/
43
+ warn :model => @current_model,
44
+ :warning_type => "Format Validation",
45
+ :message => "Insufficient validation for '#{get_name validator}' using #{value[1].inspect}. Use \\A and \\z as anchors",
46
+ :line => value.line,
47
+ :confidence => CONFIDENCE[:high]
48
+ end
49
+ end
50
+
51
+ #Get the name of the attribute being validated.
52
+ def get_name validator
53
+ name = validator[1]
54
+ if sexp? name
55
+ name[1]
56
+ else
57
+ name
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,105 @@
1
+ /* CSS style used for HTML reports */
2
+
3
+ body {
4
+ font-family: sans-serif;
5
+ color: #161616;
6
+ }
7
+
8
+ p {
9
+ font-weight: bold;
10
+ font-size: 11pt;
11
+ color: #2D0200;
12
+ }
13
+
14
+ th {
15
+ background-color: #980905;
16
+ border-bottom: 5px solid #530200;
17
+ color: white;
18
+ font-size: 11pt;
19
+ padding: 1px 8px 1px 8px;
20
+ }
21
+
22
+ td {
23
+ border-bottom: 2px solid white;
24
+ font-family: monospace;
25
+ padding: 5px 8px 1px 8px;
26
+ }
27
+
28
+ table {
29
+ background-color: #FCF4D4;
30
+ border-collapse: collapse;
31
+ }
32
+
33
+ h1 {
34
+ color: #2D0200;
35
+ font-size: 14pt;
36
+ }
37
+
38
+ h2 {
39
+ color: #2D0200;
40
+ font-size: 12pt;
41
+ }
42
+
43
+ span.high-confidence {
44
+ font-weight:bold;
45
+ color: red;
46
+ }
47
+
48
+ span.med-confidence {
49
+ }
50
+
51
+ span.weak-confidence {
52
+ color:gray;
53
+ }
54
+
55
+ div.warning_message {
56
+ cursor: pointer;
57
+ }
58
+
59
+ div.warning_message:hover {
60
+ background-color: white;
61
+ }
62
+
63
+ table.context {
64
+ margin-top: 5px;
65
+ margin-bottom: 5px;
66
+ border-left: 1px solid #90e960;
67
+ color: #212121;
68
+ }
69
+
70
+ tr.context {
71
+ background-color: white;
72
+ }
73
+
74
+ tr.first {
75
+ border-top: 1px solid #7ecc54;
76
+ padding-top: 2px;
77
+ }
78
+
79
+ tr.error {
80
+ background-color: #f4c1c1 !important
81
+ }
82
+
83
+ tr.near_error {
84
+ background-color: #f4d4d4 !important
85
+ }
86
+
87
+ tr.alt {
88
+ background-color: #e8f4d4;
89
+ }
90
+
91
+ td.context {
92
+ padding: 2px 10px 0px 6px;
93
+ border-bottom: none;
94
+ }
95
+
96
+ td.context_line {
97
+ padding: 2px 8px 0px 7px;
98
+ border-right: 1px solid #b3bda4;
99
+ border-bottom: none;
100
+ color: #6e7465;
101
+ }
102
+
103
+ pre.context {
104
+ margin-bottom: 1px;
105
+ }
@@ -0,0 +1,83 @@
1
+ #Load all files in processors/
2
+ Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/processors/*.rb").each { |f| require f.match(/processors.*/)[0] }
3
+ require 'tracker'
4
+ require 'set'
5
+
6
+ #Makes calls to the appropriate processor.
7
+ #
8
+ #The ControllerProcessor, TemplateProcessor, and ModelProcessor will
9
+ #update the Tracker with information about what is parsed.
10
+ class Processor
11
+ def initialize
12
+ @tracker = Tracker.new self
13
+ end
14
+
15
+ def tracked_events
16
+ @tracker
17
+ end
18
+
19
+ #Process configuration file source
20
+ def process_config src
21
+ ConfigProcessor.new(@tracker).process_config src
22
+ end
23
+
24
+ #Process route file source
25
+ def process_routes src
26
+ RoutesProcessor.new(@tracker).process_routes src
27
+ end
28
+
29
+ #Process controller source. +file_name+ is used for reporting
30
+ def process_controller src, file_name
31
+ ControllerProcessor.new(@tracker).process_controller src, file_name
32
+ end
33
+
34
+ #Process variable aliasing in controller source and save it in the
35
+ #tracker.
36
+ def process_controller_alias src
37
+ ControllerAliasProcessor.new(@tracker).process src
38
+ end
39
+
40
+ #Process a model source
41
+ def process_model src, file_name
42
+ result = ModelProcessor.new(@tracker).process_model src, file_name
43
+ AliasProcessor.new.process result
44
+ end
45
+
46
+ #Process either an ERB or HAML template
47
+ def process_template name, src, type, called_from = nil, file_name = nil
48
+ case type
49
+ when :erb
50
+ result = ErbTemplateProcessor.new(@tracker, name, called_from, file_name).process src
51
+ when :haml
52
+ result = HamlTemplateProcessor.new(@tracker, name, called_from, file_name).process src
53
+ else
54
+ abort "Unknown template type: #{type} (#{name})"
55
+ end
56
+
57
+ #Each template which is rendered is stored separately
58
+ #with a new name.
59
+ if called_from
60
+ name = (name.to_s + "." + called_from.to_s).to_sym
61
+ end
62
+
63
+ @tracker.templates[name][:src] = result
64
+ @tracker.templates[name][:type] = type
65
+ end
66
+
67
+ #Process any calls to render() within a template
68
+ def process_template_alias template
69
+ TemplateAliasProcessor.new(@tracker, template).process_safely template[:src]
70
+ end
71
+
72
+ #Process source for initializing files
73
+ def process_initializer name, src
74
+ res = BaseProcessor.new(@tracker).process src
75
+ res = AliasProcessor.new.process res
76
+ @tracker.initializers[name] = res
77
+ end
78
+
79
+ #Process source for a library file
80
+ def process_lib src, file_name
81
+ LibraryProcessor.new(@tracker).process_library src, file_name
82
+ end
83
+ end