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