brakeman 1.5.3 → 1.6.0.pre1
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 +20 -6
- data/lib/brakeman.rb +21 -1
- data/lib/brakeman/checks.rb +2 -7
- data/lib/brakeman/checks/base_check.rb +31 -26
- data/lib/brakeman/checks/check_basic_auth.rb +1 -7
- data/lib/brakeman/checks/check_cross_site_scripting.rb +38 -33
- data/lib/brakeman/checks/check_evaluation.rb +2 -1
- data/lib/brakeman/checks/check_execute.rb +5 -1
- data/lib/brakeman/checks/check_file_access.rb +6 -4
- data/lib/brakeman/checks/check_link_to.rb +8 -5
- data/lib/brakeman/checks/check_link_to_href.rb +6 -5
- data/lib/brakeman/checks/check_mail_to.rb +2 -4
- data/lib/brakeman/checks/check_mass_assignment.rb +12 -6
- data/lib/brakeman/checks/check_redirect.rb +17 -14
- data/lib/brakeman/checks/check_render.rb +4 -4
- data/lib/brakeman/checks/check_send.rb +4 -2
- data/lib/brakeman/checks/check_session_settings.rb +16 -21
- data/lib/brakeman/checks/check_skip_before_filter.rb +2 -4
- data/lib/brakeman/checks/check_sql.rb +8 -7
- data/lib/brakeman/checks/check_validation_regex.rb +2 -4
- data/lib/brakeman/checks/check_without_protection.rb +8 -9
- data/lib/brakeman/differ.rb +61 -0
- data/lib/brakeman/format/style.css +4 -0
- data/lib/brakeman/options.rb +8 -0
- data/lib/brakeman/processors/alias_processor.rb +5 -7
- data/lib/brakeman/processors/controller_alias_processor.rb +10 -3
- data/lib/brakeman/processors/erb_template_processor.rb +2 -0
- data/lib/brakeman/processors/erubis_template_processor.rb +2 -0
- data/lib/brakeman/processors/gem_processor.rb +8 -0
- data/lib/brakeman/processors/haml_template_processor.rb +2 -0
- data/lib/brakeman/processors/lib/rails2_route_processor.rb +2 -6
- data/lib/brakeman/processors/lib/rails3_route_processor.rb +2 -4
- data/lib/brakeman/processors/lib/render_helper.rb +3 -1
- data/lib/brakeman/processors/library_processor.rb +4 -6
- data/lib/brakeman/processors/template_alias_processor.rb +1 -1
- data/lib/brakeman/report.rb +257 -198
- data/lib/brakeman/rescanner.rb +112 -10
- data/lib/brakeman/scanner.rb +3 -4
- data/lib/brakeman/templates/controller_overview.html.erb +18 -0
- data/lib/brakeman/templates/controller_warnings.html.erb +17 -0
- data/lib/brakeman/templates/error_overview.html.erb +14 -0
- data/lib/brakeman/templates/header.html.erb +38 -0
- data/lib/brakeman/templates/model_warnings.html.erb +17 -0
- data/lib/brakeman/templates/overview.html.erb +28 -0
- data/lib/brakeman/templates/security_warnings.html.erb +28 -0
- data/lib/brakeman/templates/template_overview.html.erb +17 -0
- data/lib/brakeman/templates/view_warnings.html.erb +17 -0
- data/lib/brakeman/templates/warning_overview.html.erb +13 -0
- data/lib/brakeman/tracker.rb +1 -1
- data/lib/brakeman/util.rb +24 -4
- data/lib/brakeman/version.rb +1 -1
- data/lib/brakeman/warning.rb +11 -3
- data/lib/ruby_parser/bm_sexp.rb +5 -11
- metadata +84 -23
@@ -40,10 +40,9 @@ class Brakeman::CheckLinkToHref < Brakeman::CheckLinkTo
|
|
40
40
|
#with something before the user input
|
41
41
|
return if node_type?(url_arg, :string_interp) && !url_arg[1].chomp.empty?
|
42
42
|
|
43
|
-
type, match = has_immediate_user_input? url_arg
|
44
43
|
|
45
|
-
if
|
46
|
-
case type
|
44
|
+
if input = has_immediate_user_input?(url_arg)
|
45
|
+
case input.type
|
47
46
|
when :params
|
48
47
|
message = "Unsafe parameter value in link_to href"
|
49
48
|
when :cookies
|
@@ -57,6 +56,7 @@ class Brakeman::CheckLinkToHref < Brakeman::CheckLinkTo
|
|
57
56
|
warn :result => result,
|
58
57
|
:warning_type => "Cross Site Scripting",
|
59
58
|
:message => message,
|
59
|
+
:user_input => input.match,
|
60
60
|
:confidence => CONFIDENCE[:high]
|
61
61
|
end
|
62
62
|
elsif has_immediate_model? url_arg
|
@@ -72,9 +72,9 @@ class Brakeman::CheckLinkToHref < Brakeman::CheckLinkTo
|
|
72
72
|
# attack.
|
73
73
|
|
74
74
|
elsif @matched
|
75
|
-
if @matched == :model and not tracker.options[:ignore_model_output]
|
75
|
+
if @matched.type == :model and not tracker.options[:ignore_model_output]
|
76
76
|
message = "Unsafe model attribute in link_to href"
|
77
|
-
elsif @matched == :params
|
77
|
+
elsif @matched.type == :params
|
78
78
|
message = "Unsafe parameter value in link_to href"
|
79
79
|
end
|
80
80
|
|
@@ -83,6 +83,7 @@ class Brakeman::CheckLinkToHref < Brakeman::CheckLinkTo
|
|
83
83
|
warn :result => result,
|
84
84
|
:warning_type => "Cross Site Scripting",
|
85
85
|
:message => message,
|
86
|
+
:user_input => @matched.match,
|
86
87
|
:confidence => CONFIDENCE[:med]
|
87
88
|
end
|
88
89
|
end
|
@@ -38,10 +38,8 @@ class Brakeman::CheckMailTo < Brakeman::BaseCheck
|
|
38
38
|
|
39
39
|
args.each do |arg|
|
40
40
|
if hash? arg
|
41
|
-
|
42
|
-
|
43
|
-
return result
|
44
|
-
end
|
41
|
+
if hash_access(arg, :javascript)
|
42
|
+
return result
|
45
43
|
end
|
46
44
|
end
|
47
45
|
end
|
@@ -21,12 +21,10 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
|
|
21
21
|
|
22
22
|
return if models.empty?
|
23
23
|
|
24
|
-
@results = Set.new
|
25
24
|
|
26
25
|
Brakeman.debug "Finding possible mass assignment calls on #{models.length} models"
|
27
26
|
calls = tracker.find_call :chained => true, :targets => models, :methods => [:new,
|
28
27
|
:attributes=,
|
29
|
-
:update_attribute,
|
30
28
|
:update_attributes,
|
31
29
|
:update_attributes!,
|
32
30
|
:create,
|
@@ -45,8 +43,8 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
|
|
45
43
|
|
46
44
|
check = check_call call
|
47
45
|
|
48
|
-
if check and not
|
49
|
-
|
46
|
+
if check and not call.original_line and not duplicate? res
|
47
|
+
add_result res
|
50
48
|
|
51
49
|
model = tracker.models[res[:chain].first]
|
52
50
|
|
@@ -54,10 +52,17 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
|
|
54
52
|
|
55
53
|
if attr_protected and tracker.options[:ignore_attr_protected]
|
56
54
|
return
|
57
|
-
elsif
|
58
|
-
|
55
|
+
elsif input = include_user_input?(call[3])
|
56
|
+
if not hash? call[3][1] and not attr_protected
|
57
|
+
confidence = CONFIDENCE[:high]
|
58
|
+
user_input = input.match
|
59
|
+
else
|
60
|
+
confidence = CONFIDENCE[:low]
|
61
|
+
user_input = input.match
|
62
|
+
end
|
59
63
|
else
|
60
64
|
confidence = CONFIDENCE[:low]
|
65
|
+
user_input = nil
|
61
66
|
end
|
62
67
|
|
63
68
|
warn :result => res,
|
@@ -65,6 +70,7 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
|
|
65
70
|
:message => "Unprotected mass assignment",
|
66
71
|
:line => call.line,
|
67
72
|
:code => call,
|
73
|
+
:user_input => user_input,
|
68
74
|
:confidence => confidence
|
69
75
|
end
|
70
76
|
|
@@ -19,12 +19,16 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def process_result result
|
22
|
+
return if duplicate? result
|
23
|
+
|
22
24
|
call = result[:call]
|
23
25
|
|
24
26
|
method = call[2]
|
25
27
|
|
26
28
|
if method == :redirect_to and not only_path?(call) and res = include_user_input?(call)
|
27
|
-
|
29
|
+
add_result result
|
30
|
+
|
31
|
+
if res.type == :immediate
|
28
32
|
confidence = CONFIDENCE[:high]
|
29
33
|
else
|
30
34
|
confidence = CONFIDENCE[:low]
|
@@ -35,6 +39,7 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
|
|
35
39
|
:message => "Possible unprotected redirect",
|
36
40
|
:line => call.line,
|
37
41
|
:code => call,
|
42
|
+
:user_input => res.match,
|
38
43
|
:confidence => confidence
|
39
44
|
end
|
40
45
|
end
|
@@ -60,16 +65,18 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
|
|
60
65
|
|
61
66
|
call[3].each do |arg|
|
62
67
|
if call? arg
|
63
|
-
if
|
64
|
-
return :immediate
|
68
|
+
if request_value? arg
|
69
|
+
return Match.new(:immediate, arg)
|
70
|
+
elsif request_value? arg[1]
|
71
|
+
return Match.new(:immediate, arg[1])
|
65
72
|
elsif arg[2] == :url_for and include_user_input? arg
|
66
|
-
return :immediate
|
73
|
+
return Match.new(:immediate, arg)
|
67
74
|
#Ignore helpers like some_model_url?
|
68
75
|
elsif arg[2].to_s =~ /_(url|path)$/
|
69
76
|
return false
|
70
77
|
end
|
71
|
-
elsif
|
72
|
-
return :immediate
|
78
|
+
elsif request_value? arg
|
79
|
+
return Match.new(:immediate, arg)
|
73
80
|
end
|
74
81
|
end
|
75
82
|
|
@@ -85,10 +92,8 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
|
|
85
92
|
def only_path? call
|
86
93
|
call[3].each do |arg|
|
87
94
|
if hash? arg
|
88
|
-
|
89
|
-
|
90
|
-
return true
|
91
|
-
end
|
95
|
+
if value = hash_access(arg, :only_path)
|
96
|
+
return true if true?(value)
|
92
97
|
end
|
93
98
|
elsif call? arg and arg[2] == :url_for
|
94
99
|
return check_url_for(arg)
|
@@ -103,10 +108,8 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
|
|
103
108
|
def check_url_for call
|
104
109
|
call[3].each do |arg|
|
105
110
|
if hash? arg
|
106
|
-
|
107
|
-
|
108
|
-
return false
|
109
|
-
end
|
111
|
+
if value = hash_access(arg, :only_path)
|
112
|
+
return false if false?(value)
|
110
113
|
end
|
111
114
|
end
|
112
115
|
end
|
@@ -32,11 +32,10 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
|
|
32
32
|
if sexp? view and not duplicate? result
|
33
33
|
add_result result
|
34
34
|
|
35
|
-
type, match = has_immediate_user_input? view
|
36
35
|
|
37
|
-
if
|
36
|
+
if input = has_immediate_user_input?(view)
|
38
37
|
confidence = CONFIDENCE[:high]
|
39
|
-
elsif
|
38
|
+
elsif input = include_user_input?(view)
|
40
39
|
if node_type? view, :string_interp, :dstr
|
41
40
|
confidence = CONFIDENCE[:med]
|
42
41
|
else
|
@@ -48,7 +47,7 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
|
|
48
47
|
|
49
48
|
message = "Render path contains "
|
50
49
|
|
51
|
-
case type
|
50
|
+
case input.type
|
52
51
|
when :params
|
53
52
|
message << "parameter value"
|
54
53
|
when :cookies
|
@@ -66,6 +65,7 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
|
|
66
65
|
warn :result => result,
|
67
66
|
:warning_type => "Dynamic Render Path",
|
68
67
|
:message => message,
|
68
|
+
:user_input => input.match,
|
69
69
|
:confidence => confidence
|
70
70
|
end
|
71
71
|
end
|
@@ -19,19 +19,21 @@ class Brakeman::CheckSend < Brakeman::BaseCheck
|
|
19
19
|
args = process result[:call][3]
|
20
20
|
target = process result[:call][1]
|
21
21
|
|
22
|
-
if has_immediate_user_input?
|
22
|
+
if input = has_immediate_user_input?(args[1])
|
23
23
|
warn :result => result,
|
24
24
|
:warning_type => "Dangerous Send",
|
25
25
|
:message => "User controlled method execution",
|
26
26
|
:code => result[:call],
|
27
|
+
:user_input => input.match,
|
27
28
|
:confidence => CONFIDENCE[:high]
|
28
29
|
end
|
29
30
|
|
30
|
-
if has_immediate_user_input?(target)
|
31
|
+
if input = has_immediate_user_input?(target)
|
31
32
|
warn :result => result,
|
32
33
|
:warning_type => "Dangerous Send",
|
33
34
|
:message => "User defined target of method invocation",
|
34
35
|
:code => result[:call],
|
36
|
+
:user_input => input.match,
|
35
37
|
:confidence => CONFIDENCE[:med]
|
36
38
|
end
|
37
39
|
end
|
@@ -52,30 +52,25 @@ class Brakeman::CheckSessionSettings < Brakeman::BaseCheck
|
|
52
52
|
|
53
53
|
def check_for_issues settings, file
|
54
54
|
if settings and hash? settings
|
55
|
-
|
56
|
-
if
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
value.
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
:confidence => CONFIDENCE[:high],
|
65
|
-
:line => key.line,
|
66
|
-
:file => file
|
55
|
+
if value = hash_access(settings, :session_http_only)
|
56
|
+
if false? value
|
57
|
+
warn :warning_type => "Session Setting",
|
58
|
+
:message => "Session cookies should be set to HTTP only",
|
59
|
+
:confidence => CONFIDENCE[:high],
|
60
|
+
:line => value.line,
|
61
|
+
:file => file
|
62
|
+
end
|
63
|
+
end
|
67
64
|
|
68
|
-
|
69
|
-
|
70
|
-
value[1].length < 30
|
65
|
+
if value = hash_access(settings, :secret)
|
66
|
+
if string? value and value[1].length < 30
|
71
67
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
68
|
+
warn :warning_type => "Session Setting",
|
69
|
+
:message => "Session secret should be at least 30 characters long",
|
70
|
+
:confidence => CONFIDENCE[:high],
|
71
|
+
:line => value.line,
|
72
|
+
:file => file
|
77
73
|
|
78
|
-
end
|
79
74
|
end
|
80
75
|
end
|
81
76
|
end
|
@@ -39,10 +39,8 @@ class Brakeman::CheckSkipBeforeFilter < Brakeman::BaseCheck
|
|
39
39
|
args = filter[3]
|
40
40
|
|
41
41
|
if symbol? args[1] and args[1][1] == :verify_authenticity_token and hash? args.last
|
42
|
-
|
43
|
-
|
44
|
-
return true
|
45
|
-
end
|
42
|
+
if hash_access(args.last, :except)
|
43
|
+
return true
|
46
44
|
end
|
47
45
|
end
|
48
46
|
|
@@ -17,9 +17,9 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
17
17
|
@rails_version = tracker.config[:rails_version]
|
18
18
|
|
19
19
|
if tracker.options[:rails3]
|
20
|
-
@sql_targets = /^(find
|
20
|
+
@sql_targets = /^(find|find_by_sql|last|first|all|count|sum|average|minumum|maximum|count_by_sql|where|order|group|having)$/
|
21
21
|
else
|
22
|
-
@sql_targets = /^(find
|
22
|
+
@sql_targets = /^(find|find_by_sql|last|first|all|count|sum|average|minumum|maximum|count_by_sql)$/
|
23
23
|
end
|
24
24
|
|
25
25
|
Brakeman.debug "Finding possible SQL calls on models"
|
@@ -122,15 +122,18 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
122
122
|
if failed and not call.original_line and not duplicate? result
|
123
123
|
add_result result
|
124
124
|
|
125
|
-
if include_user_input?
|
125
|
+
if input = include_user_input?(args[-1])
|
126
126
|
confidence = CONFIDENCE[:high]
|
127
|
+
user_input = input.match
|
127
128
|
else
|
128
129
|
confidence = CONFIDENCE[:med]
|
130
|
+
user_input = nil
|
129
131
|
end
|
130
132
|
|
131
133
|
warn :result => result,
|
132
134
|
:warning_type => "SQL Injection",
|
133
135
|
:message => "Possible SQL injection",
|
136
|
+
:user_input => user_input,
|
134
137
|
:confidence => confidence
|
135
138
|
end
|
136
139
|
|
@@ -215,10 +218,8 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
215
218
|
def check_for_limit_or_offset_vulnerability options
|
216
219
|
return false if @rails_version.nil? or @rails_version >= "2.1.1" or not hash? options
|
217
220
|
|
218
|
-
|
219
|
-
|
220
|
-
return (key[1] == :limit or key[1] == :offset)
|
221
|
-
end
|
221
|
+
if hash_access(options, :limit) or hash_access(options[:offset])
|
222
|
+
return true
|
222
223
|
end
|
223
224
|
|
224
225
|
false
|
@@ -28,10 +28,8 @@ class Brakeman::CheckValidationRegex < Brakeman::BaseCheck
|
|
28
28
|
|
29
29
|
#Check validates_format_of
|
30
30
|
def process_validator validator
|
31
|
-
|
32
|
-
|
33
|
-
check_regex value, validator
|
34
|
-
end
|
31
|
+
if value = hash_access(validator[-1], WITH)
|
32
|
+
check_regex value, validator
|
35
33
|
end
|
36
34
|
end
|
37
35
|
|
@@ -23,12 +23,9 @@ class Brakeman::CheckWithoutProtection < Brakeman::BaseCheck
|
|
23
23
|
|
24
24
|
return if models.empty?
|
25
25
|
|
26
|
-
@results = Set.new
|
27
|
-
|
28
26
|
Brakeman.debug "Finding all mass assignments"
|
29
27
|
calls = tracker.find_call :targets => models, :methods => [:new,
|
30
28
|
:attributes=,
|
31
|
-
:update_attribute,
|
32
29
|
:update_attributes,
|
33
30
|
:update_attributes!,
|
34
31
|
:create,
|
@@ -45,16 +42,18 @@ class Brakeman::CheckWithoutProtection < Brakeman::BaseCheck
|
|
45
42
|
call = res[:call]
|
46
43
|
last_arg = call[3][-1]
|
47
44
|
|
48
|
-
if hash? last_arg and not
|
45
|
+
if hash? last_arg and not call.original_line and not duplicate? res
|
49
46
|
|
50
|
-
|
51
|
-
if
|
52
|
-
|
47
|
+
if value = hash_access(last_arg, :without_protection)
|
48
|
+
if true? value
|
49
|
+
add_result res
|
53
50
|
|
54
|
-
if include_user_input?
|
51
|
+
if input = include_user_input?(call[3])
|
55
52
|
confidence = CONFIDENCE[:high]
|
53
|
+
user_input = input.match
|
56
54
|
else
|
57
55
|
confidence = CONFIDENCE[:med]
|
56
|
+
user_input = nil
|
58
57
|
end
|
59
58
|
|
60
59
|
warn :result => res,
|
@@ -62,9 +61,9 @@ class Brakeman::CheckWithoutProtection < Brakeman::BaseCheck
|
|
62
61
|
:message => "Unprotected mass assignment",
|
63
62
|
:line => call.line,
|
64
63
|
:code => call,
|
64
|
+
:user_input => user_input,
|
65
65
|
:confidence => confidence
|
66
66
|
|
67
|
-
break
|
68
67
|
end
|
69
68
|
end
|
70
69
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# extracting the diff logic to it's own class for consistency. Currently handles
|
2
|
+
# an array of Brakeman::Warnings or plain hash representations.
|
3
|
+
class Brakeman::Differ
|
4
|
+
DEFAULT_HASH = {:new => [], :fixed => []}
|
5
|
+
attr_reader :old_warnings, :new_warnings
|
6
|
+
|
7
|
+
def initialize new_warnings, old_warnings
|
8
|
+
@new_warnings = new_warnings
|
9
|
+
@old_warnings = old_warnings
|
10
|
+
end
|
11
|
+
|
12
|
+
def diff
|
13
|
+
# get the type of elements
|
14
|
+
return DEFAULT_HASH if @new_warnings.empty?
|
15
|
+
|
16
|
+
warnings = {}
|
17
|
+
warnings[:new] = @new_warnings - @old_warnings
|
18
|
+
warnings[:fixed] = @old_warnings - @new_warnings
|
19
|
+
|
20
|
+
second_pass(warnings)
|
21
|
+
end
|
22
|
+
|
23
|
+
# second pass to cleanup any vulns which have changed in line number only.
|
24
|
+
# Given a list of new warnings, delete pairs of new/fixed vulns that differ
|
25
|
+
# only by line number.
|
26
|
+
# Horrible O(n^2) performance. Keep n small :-/
|
27
|
+
def second_pass(warnings)
|
28
|
+
# keep track of the number of elements deleted because the index numbers
|
29
|
+
# won't update as the list is modified
|
30
|
+
elements_deleted_offset = 0
|
31
|
+
|
32
|
+
# dup this list since we will be deleting from it and the iterator gets confused.
|
33
|
+
# use _with_index for fast deletion as opposed to .reject!{|obj| obj == *_warning}
|
34
|
+
warnings[:new].dup.each_with_index do |new_warning, new_warning_id|
|
35
|
+
warnings[:fixed].each_with_index do |fixed_warning, fixed_warning_id|
|
36
|
+
if eql_except_line_number new_warning, fixed_warning
|
37
|
+
warnings[:new].delete_at(new_warning_id - elements_deleted_offset)
|
38
|
+
elements_deleted_offset += 1
|
39
|
+
warnings[:fixed].delete_at(fixed_warning_id)
|
40
|
+
break
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
warnings
|
46
|
+
end
|
47
|
+
|
48
|
+
def eql_except_line_number new_warning, fixed_warning
|
49
|
+
# can't do this ahead of time, as callers may be expecting a Brakeman::Warning
|
50
|
+
if new_warning.is_a? Brakeman::Warning
|
51
|
+
new_warning = new_warning.to_hash
|
52
|
+
fixed_warning = fixed_warning.to_hash
|
53
|
+
end
|
54
|
+
|
55
|
+
new_warning.keys.reject{|k,v| k == :line}.each do |attr|
|
56
|
+
return false if new_warning[attr] != fixed_warning[attr]
|
57
|
+
end
|
58
|
+
|
59
|
+
true
|
60
|
+
end
|
61
|
+
end
|