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.
- data/FEATURES +16 -0
- data/README.md +112 -0
- data/WARNING_TYPES +69 -0
- data/bin/brakeman +266 -0
- data/lib/checks.rb +67 -0
- data/lib/checks/base_check.rb +338 -0
- data/lib/checks/check_cross_site_scripting.rb +216 -0
- data/lib/checks/check_default_routes.rb +29 -0
- data/lib/checks/check_evaluation.rb +29 -0
- data/lib/checks/check_execute.rb +110 -0
- data/lib/checks/check_file_access.rb +46 -0
- data/lib/checks/check_forgery_setting.rb +25 -0
- data/lib/checks/check_mass_assignment.rb +72 -0
- data/lib/checks/check_model_attributes.rb +36 -0
- data/lib/checks/check_redirect.rb +98 -0
- data/lib/checks/check_render.rb +65 -0
- data/lib/checks/check_send_file.rb +15 -0
- data/lib/checks/check_session_settings.rb +36 -0
- data/lib/checks/check_sql.rb +124 -0
- data/lib/checks/check_validation_regex.rb +60 -0
- data/lib/format/style.css +105 -0
- data/lib/processor.rb +83 -0
- data/lib/processors/alias_processor.rb +384 -0
- data/lib/processors/base_processor.rb +235 -0
- data/lib/processors/config_processor.rb +146 -0
- data/lib/processors/controller_alias_processor.rb +222 -0
- data/lib/processors/controller_processor.rb +175 -0
- data/lib/processors/erb_template_processor.rb +84 -0
- data/lib/processors/erubis_template_processor.rb +62 -0
- data/lib/processors/haml_template_processor.rb +115 -0
- data/lib/processors/lib/find_call.rb +176 -0
- data/lib/processors/lib/find_model_call.rb +39 -0
- data/lib/processors/lib/processor_helper.rb +36 -0
- data/lib/processors/lib/render_helper.rb +118 -0
- data/lib/processors/library_processor.rb +117 -0
- data/lib/processors/model_processor.rb +125 -0
- data/lib/processors/output_processor.rb +204 -0
- data/lib/processors/params_processor.rb +77 -0
- data/lib/processors/route_processor.rb +338 -0
- data/lib/processors/template_alias_processor.rb +86 -0
- data/lib/processors/template_processor.rb +55 -0
- data/lib/report.rb +628 -0
- data/lib/scanner.rb +232 -0
- data/lib/tracker.rb +144 -0
- data/lib/util.rb +141 -0
- data/lib/warning.rb +97 -0
- 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
|
+
}
|
data/lib/processor.rb
ADDED
@@ -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
|