stratagem 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest +99 -0
- data/Rakefile +17 -0
- data/bin/stratagem +10 -0
- data/init.rb +2 -0
- data/lib/bootstrap.rb +31 -0
- data/lib/stratagem/authentication.rb +64 -0
- data/lib/stratagem/auto_mock/aquifer.rb +86 -0
- data/lib/stratagem/auto_mock/factory.rb +213 -0
- data/lib/stratagem/auto_mock/value_generator.rb +174 -0
- data/lib/stratagem/auto_mock.rb +6 -0
- data/lib/stratagem/blocker.rb +16 -0
- data/lib/stratagem/client.rb +32 -0
- data/lib/stratagem/command.rb +13 -0
- data/lib/stratagem/commands/analyze.rb +22 -0
- data/lib/stratagem/commands/base.rb +11 -0
- data/lib/stratagem/commands/devel_crawl.rb +27 -0
- data/lib/stratagem/commands/devel_mock.rb +10 -0
- data/lib/stratagem/commands.rb +7 -0
- data/lib/stratagem/crawler/authentication.rb +109 -0
- data/lib/stratagem/crawler/form.rb +101 -0
- data/lib/stratagem/crawler/html_utils.rb +92 -0
- data/lib/stratagem/crawler/session.rb +296 -0
- data/lib/stratagem/crawler/site_model.rb +138 -0
- data/lib/stratagem/crawler/trace_utils.rb +10 -0
- data/lib/stratagem/crawler.rb +9 -0
- data/lib/stratagem/extensions/class.rb +9 -0
- data/lib/stratagem/extensions/hash.rb +16 -0
- data/lib/stratagem/extensions/module.rb +11 -0
- data/lib/stratagem/extensions/object.rb +15 -0
- data/lib/stratagem/extensions/red_parse.rb +86 -0
- data/lib/stratagem/extensions/string.rb +20 -0
- data/lib/stratagem/extensions.rb +6 -0
- data/lib/stratagem/framework_extensions/controllers/action_controller.rb +10 -0
- data/lib/stratagem/framework_extensions/controllers/action_mailer.rb +12 -0
- data/lib/stratagem/framework_extensions/controllers.rb +5 -0
- data/lib/stratagem/framework_extensions/models/adapters/active_model/detect.rb +7 -0
- data/lib/stratagem/framework_extensions/models/adapters/active_model/extensions.rb +35 -0
- data/lib/stratagem/framework_extensions/models/adapters/active_model/metadata.rb +103 -0
- data/lib/stratagem/framework_extensions/models/adapters/active_model/tracing.rb +50 -0
- data/lib/stratagem/framework_extensions/models/adapters/authlogic/detect.rb +11 -0
- data/lib/stratagem/framework_extensions/models/adapters/authlogic/extensions.rb +10 -0
- data/lib/stratagem/framework_extensions/models/adapters/authlogic/metadata.rb +30 -0
- data/lib/stratagem/framework_extensions/models/adapters/authlogic/tracing.rb +4 -0
- data/lib/stratagem/framework_extensions/models/adapters/common/authentication_metadata.rb +21 -0
- data/lib/stratagem/framework_extensions/models/adapters/restful_authentication/detect.rb +13 -0
- data/lib/stratagem/framework_extensions/models/adapters/restful_authentication/extensions.rb +19 -0
- data/lib/stratagem/framework_extensions/models/adapters/restful_authentication/metadata.rb +30 -0
- data/lib/stratagem/framework_extensions/models/adapters/restful_authentication/tracing.rb +4 -0
- data/lib/stratagem/framework_extensions/models/annotations.rb +79 -0
- data/lib/stratagem/framework_extensions/models/detect.rb +7 -0
- data/lib/stratagem/framework_extensions/models/metadata.rb +85 -0
- data/lib/stratagem/framework_extensions/models/mocking.rb +23 -0
- data/lib/stratagem/framework_extensions/models/tracing.rb +71 -0
- data/lib/stratagem/framework_extensions/models.rb +21 -0
- data/lib/stratagem/framework_extensions/rails.rb +8 -0
- data/lib/stratagem/framework_extensions.rb +6 -0
- data/lib/stratagem/interface/browser.rb +37 -0
- data/lib/stratagem/interface/public/images/backgrounds/content.png +0 -0
- data/lib/stratagem/interface/public/images/backgrounds/shadow.png +0 -0
- data/lib/stratagem/interface/public/javascripts/jquery-1.4.2.min.js +154 -0
- data/lib/stratagem/interface/public/javascripts/stratagem.js +27 -0
- data/lib/stratagem/interface/public/javascripts/stratagem_debug.js +53 -0
- data/lib/stratagem/interface/public/stylesheets/960.css +1 -0
- data/lib/stratagem/interface/public/stylesheets/reset.css +10 -0
- data/lib/stratagem/interface/public/stylesheets/stratagem.css +20 -0
- data/lib/stratagem/interface/public/stylesheets/stratagem_debug.css +20 -0
- data/lib/stratagem/interface/views/debug.haml +43 -0
- data/lib/stratagem/interface/views/index.haml +35 -0
- data/lib/stratagem/labs/auto_mock.rb +7 -0
- data/lib/stratagem/labs/crawler.rb +0 -0
- data/lib/stratagem/logger.rb +46 -0
- data/lib/stratagem/model/application.rb +157 -0
- data/lib/stratagem/model/components/base.rb +55 -0
- data/lib/stratagem/model/components/controller.rb +118 -0
- data/lib/stratagem/model/components/model.rb +170 -0
- data/lib/stratagem/model/components/reference.rb +30 -0
- data/lib/stratagem/model/components/route.rb +53 -0
- data/lib/stratagem/model/components/static_file.rb +18 -0
- data/lib/stratagem/model/components/view.rb +186 -0
- data/lib/stratagem/model/parse_util.rb +61 -0
- data/lib/stratagem/model.rb +12 -0
- data/lib/stratagem/model_builder.rb +146 -0
- data/lib/stratagem/recipes/deploy.rb +30 -0
- data/lib/stratagem/scan/checks/capistrano/secure_deploy.rb +43 -0
- data/lib/stratagem/scan/checks/email_address.rb +15 -0
- data/lib/stratagem/scan/checks/error_pages.rb +25 -0
- data/lib/stratagem/scan/checks/filter_parameter_logging.rb +6 -0
- data/lib/stratagem/scan/checks/mongo_mapper/base.rb +19 -0
- data/lib/stratagem/scan/checks/mongo_mapper/foreign_keys_exposed.rb +32 -0
- data/lib/stratagem/scan/checks/routes.rb +16 -0
- data/lib/stratagem/scan/checks/ssl/secure_login_page.rb +19 -0
- data/lib/stratagem/scan/checks/ssl/secure_login_submit.rb +18 -0
- data/lib/stratagem/scan/result.rb +45 -0
- data/lib/stratagem/scan.rb +19 -0
- data/lib/stratagem/scanner.rb +32 -0
- data/lib/stratagem/site_crawler.rb +47 -0
- data/lib/stratagem/snapshot.rb +33 -0
- data/lib/stratagem.rb +77 -0
- data/lib/tasks/_old_stratagem.rake +99 -0
- data/stratagem.gemspec +56 -0
- metadata +380 -0
@@ -0,0 +1,186 @@
|
|
1
|
+
module Stratagem::Model::Component
|
2
|
+
class View < Base
|
3
|
+
include Stratagem::Model::ParseUtil
|
4
|
+
|
5
|
+
RAILS_FORM_FIELDS = ['check_box', 'file_field', 'hidden_field', 'password_field', 'radio_button', 'text_area', 'text_field']
|
6
|
+
|
7
|
+
attr_reader :extension, :render_path # as seen by the controllers
|
8
|
+
|
9
|
+
def initialize(render_path)
|
10
|
+
render_path =~ /\.(.*)/
|
11
|
+
@extension = $1
|
12
|
+
@path = render_path
|
13
|
+
@render_path = render_path.gsub(/\..*/, '')
|
14
|
+
end
|
15
|
+
|
16
|
+
def read
|
17
|
+
File.open(full_path) {|file| file.readlines().join }
|
18
|
+
end
|
19
|
+
|
20
|
+
def partial?
|
21
|
+
File.basename(render_path) =~ /^_/
|
22
|
+
end
|
23
|
+
|
24
|
+
def full_path
|
25
|
+
File.join(RAILS_ROOT, 'app', 'views', render_path+'.'+extension)
|
26
|
+
end
|
27
|
+
|
28
|
+
def directory
|
29
|
+
File.dirname(full_path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def export
|
33
|
+
begin
|
34
|
+
{
|
35
|
+
:type => :view,
|
36
|
+
:path => @path,
|
37
|
+
:render_path => @render_path,
|
38
|
+
:forms => forms.map {|form| form.export }
|
39
|
+
}
|
40
|
+
rescue
|
41
|
+
logger.fatal($!)
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def rendered_by
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
def forms
|
51
|
+
# extract ruby from the html
|
52
|
+
ruby = ruby_blocks(approximate_html).join("\n")
|
53
|
+
|
54
|
+
# dump ruby into a parse tree
|
55
|
+
begin
|
56
|
+
parse_tree = RedParse.new(ruby).parse
|
57
|
+
# find the form nodes and send to a specialized function
|
58
|
+
forms = []
|
59
|
+
walk_tree(parse_tree) do |node|
|
60
|
+
if (node.kind_of?(RedParse::CallNode) && (node.name =~ /form_/) && (node.block))
|
61
|
+
forms << walk_form(node)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
forms
|
65
|
+
rescue
|
66
|
+
puts "ERROR: Unable to parse ruby. - #{$!.message}"
|
67
|
+
puts ruby
|
68
|
+
[]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def approximate_html
|
73
|
+
html = read().gsub(RUBY_REGEX) do |match|
|
74
|
+
handle_render(match)
|
75
|
+
end
|
76
|
+
html
|
77
|
+
end
|
78
|
+
|
79
|
+
def handle_render(ruby)
|
80
|
+
# remove the surrounding ruby indicators
|
81
|
+
result = ruby
|
82
|
+
ruby = ruby.stratagem_strip_erb
|
83
|
+
if (ruby =~ /^render/)
|
84
|
+
# load into parse tree
|
85
|
+
|
86
|
+
parse_tree = RedParse.new(ruby).parse
|
87
|
+
|
88
|
+
parse_tree.walk {|parent,i,subi,node|
|
89
|
+
case node
|
90
|
+
when RedParse::CallNode #... do something with method calls
|
91
|
+
params = node.params.first # render always takes a hash
|
92
|
+
render_path = nil
|
93
|
+
passed_vars = {}
|
94
|
+
if (params.kind_of?(RedParse::StringNode))
|
95
|
+
render_path = params.first.to_s
|
96
|
+
else
|
97
|
+
render_path = params.get(:partial).first.split('/')
|
98
|
+
object = params.get(:object).name if params.get(:object)
|
99
|
+
|
100
|
+
locals = params.get(:locals)
|
101
|
+
render_path.last.gsub!(/^/, '_')
|
102
|
+
render_path = render_path.join('/')
|
103
|
+
end
|
104
|
+
|
105
|
+
full_path = nil
|
106
|
+
if (render_path =~ /\//)
|
107
|
+
full_path = File.join(RAILS_ROOT, 'app', 'views', render_path+"."+extension)
|
108
|
+
else
|
109
|
+
full_path = File.join(directory, render_path+"."+extension)
|
110
|
+
end
|
111
|
+
|
112
|
+
begin
|
113
|
+
result = File.read(full_path)
|
114
|
+
rescue
|
115
|
+
puts "ERROR: #{full_path} not found"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
}
|
119
|
+
end
|
120
|
+
result
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def walk_tree(tree)
|
126
|
+
tree.walk {|parent,i,subi,node|
|
127
|
+
yield node
|
128
|
+
case node
|
129
|
+
when RedParse::SequenceNode
|
130
|
+
node.each {|child|
|
131
|
+
walk_tree(child) { yield }
|
132
|
+
}
|
133
|
+
end
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
def walk_form(form_node)
|
138
|
+
form = Form.new(nil)
|
139
|
+
|
140
|
+
form_node.block.each {|node|
|
141
|
+
case node
|
142
|
+
when RedParse::CallNode
|
143
|
+
RAILS_FORM_FIELDS.each {|field_name|
|
144
|
+
if (node.name.include?(field_name) && node.params)
|
145
|
+
param = node.params.first
|
146
|
+
value = param.methods_include?(:val) ? param.val : param.to_s
|
147
|
+
form.add_field(value, node.name)
|
148
|
+
break
|
149
|
+
end
|
150
|
+
}
|
151
|
+
end
|
152
|
+
}
|
153
|
+
form
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class Form
|
158
|
+
attr_reader :fields
|
159
|
+
|
160
|
+
def initialize(model)
|
161
|
+
@fields = []
|
162
|
+
end
|
163
|
+
|
164
|
+
def add_field(name, type)
|
165
|
+
@fields << FormField.new(name, type)
|
166
|
+
end
|
167
|
+
|
168
|
+
def export
|
169
|
+
{:model => @model, :fields => @fields.map {|f| f.export } }
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
class FormField
|
174
|
+
attr_reader :name, :field_type
|
175
|
+
|
176
|
+
def initialize(name, field_type)
|
177
|
+
@name = name
|
178
|
+
@field_type = field_type
|
179
|
+
end
|
180
|
+
|
181
|
+
def export
|
182
|
+
{:name => @name, :field_type => @field_type }
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Stratagem::Model
|
2
|
+
module ParseUtil
|
3
|
+
RUBY_REGEX = /\<\%(.*?\%)\>/m
|
4
|
+
RUBY_OUTPUT_REGEX = /\<\%\=(.*?\%)\>/m
|
5
|
+
|
6
|
+
def self.find_classes(parse_tree)
|
7
|
+
class_names = qualified_class_name(parse_tree)
|
8
|
+
class_names.map {|name|
|
9
|
+
clazz = Kernel
|
10
|
+
begin
|
11
|
+
name.split('::').each {|part| clazz = clazz.const_get(part) }
|
12
|
+
clazz
|
13
|
+
rescue
|
14
|
+
$!
|
15
|
+
end
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
# assumes a single class in the tree
|
20
|
+
# will return the qualified class name (modules + class)
|
21
|
+
def self.qualified_class_name(parse_tree)
|
22
|
+
qualified_names = []
|
23
|
+
path = []
|
24
|
+
parse_tree.walk {|parent,i,subi,node|
|
25
|
+
path.pop while (path.include?(parent) && (path.last != parent))
|
26
|
+
path << parent unless path.last == parent
|
27
|
+
qualified_names << (path.clone << node) if node.kind_of?(RedParse::ClassNode)
|
28
|
+
true
|
29
|
+
}
|
30
|
+
|
31
|
+
qualified_names.map! {|qualified_name|
|
32
|
+
qualified_name.select {|node| node.kind_of?(RedParse::ModuleNode) || node.kind_of?(RedParse::ClassNode)}.map {|node|
|
33
|
+
begin
|
34
|
+
node.name.ident
|
35
|
+
rescue
|
36
|
+
node.name
|
37
|
+
end
|
38
|
+
}.join('::')
|
39
|
+
}
|
40
|
+
qualified_names
|
41
|
+
end
|
42
|
+
|
43
|
+
def ruby_output_blocks(view_erb)
|
44
|
+
view_erb.scan(RUBY_OUTPUT_REGEX).flatten.map {|line|
|
45
|
+
line.strip.gsub(/^\=\s*/, '').gsub(/\%$/, '').gsub(/\-$/, '')
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def ruby_blocks(view_erb)
|
50
|
+
view_erb.scan(RUBY_REGEX).flatten.map {|line|
|
51
|
+
line.strip.gsub(/^\=\s*/, '').gsub(/\%$/, '').gsub(/\-$/, '')
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def gsub_ruby_blocks(view_erb)
|
56
|
+
view_erb.gsub(RUBY_REGEX) {|ruby|
|
57
|
+
yield ruby
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Stratagem::Model
|
2
|
+
end
|
3
|
+
|
4
|
+
require 'stratagem/model/application'
|
5
|
+
require 'stratagem/model/parse_util'
|
6
|
+
require 'stratagem/model/components/base'
|
7
|
+
require 'stratagem/model/components/reference'
|
8
|
+
require 'stratagem/model/components/model'
|
9
|
+
require 'stratagem/model/components/view'
|
10
|
+
require 'stratagem/model/components/controller'
|
11
|
+
require 'stratagem/model/components/route'
|
12
|
+
require 'stratagem/model/components/static_file'
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module Stratagem
|
2
|
+
class ModelBuilder
|
3
|
+
attr_reader :parsed_models, :parsed_controllers, :aquifer
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@model = Stratagem::Model::Application.instance
|
7
|
+
@aquifer = Stratagem::AutoMock::Aquifer.init(@model)
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
load_plugins
|
12
|
+
load_public
|
13
|
+
load_template_paths
|
14
|
+
load_routes
|
15
|
+
load_models
|
16
|
+
|
17
|
+
print_errors
|
18
|
+
|
19
|
+
@aquifer.fill
|
20
|
+
@model
|
21
|
+
end
|
22
|
+
|
23
|
+
def log(msg)
|
24
|
+
Stratagem.logger.debug msg
|
25
|
+
end
|
26
|
+
|
27
|
+
def print_errors
|
28
|
+
@model.routes.invalid.each {|route|
|
29
|
+
puts "route: #{route.route.to_s} is invalid"
|
30
|
+
}
|
31
|
+
|
32
|
+
@model.controllers.missing.each {|controller, routes|
|
33
|
+
puts "controller: #{controller}, with #{routes.size} routes is missing"
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def load_plugins()
|
38
|
+
loader = Rails::Plugin::Loader.new(Rails::Initializer.new(Rails::Configuration.new))
|
39
|
+
loader.load_plugins
|
40
|
+
@model.plugins << loader.initializer.loaded_plugins
|
41
|
+
end
|
42
|
+
|
43
|
+
def load_models()
|
44
|
+
# load files into classes
|
45
|
+
log "loading models"
|
46
|
+
root = File.join(RAILS_ROOT, 'app','models')
|
47
|
+
load_files(File.join(root)).map {|model|
|
48
|
+
models = Stratagem::Model::Component::Model.load_all(File.join(root, model))
|
49
|
+
models.each do |c|
|
50
|
+
log "\t#{c.klass.name} loaded from #{model}"
|
51
|
+
|
52
|
+
references = []
|
53
|
+
@model.controllers.each do |controller|
|
54
|
+
references += controller.modifies(c)
|
55
|
+
end
|
56
|
+
log "\t\t#{references.size} references from controllers"
|
57
|
+
c.model_referenced_by = references
|
58
|
+
end
|
59
|
+
@model.models << models
|
60
|
+
}
|
61
|
+
log ""
|
62
|
+
end
|
63
|
+
|
64
|
+
def load_public
|
65
|
+
log "loading static files"
|
66
|
+
Dir[File.join(RAILS_ROOT, 'public', '**', '*.html')].each {|static|
|
67
|
+
static.gsub!(RAILS_ROOT, '').gsub!(/^\/public\//, '')
|
68
|
+
@model.static_files << Stratagem::Model::Component::StaticFile.new(static)
|
69
|
+
log "\t#{static}"
|
70
|
+
}
|
71
|
+
log ""
|
72
|
+
end
|
73
|
+
|
74
|
+
def load_template_paths
|
75
|
+
log "loading templates"
|
76
|
+
root = File.join(RAILS_ROOT, 'app','views')
|
77
|
+
load_files(root).map {|template|
|
78
|
+
@model.views << Stratagem::Model::Component::View.new(template)
|
79
|
+
log "\t#{template}"
|
80
|
+
}
|
81
|
+
log ""
|
82
|
+
end
|
83
|
+
|
84
|
+
def load_routes
|
85
|
+
log 'loading routes'
|
86
|
+
root = File.join(RAILS_ROOT, 'app','controllers')
|
87
|
+
ActionController::Routing::Routes.routes.each {|route|
|
88
|
+
route_container = Stratagem::Model::Component::Route.new(route)
|
89
|
+
@model.routes << route_container
|
90
|
+
begin
|
91
|
+
filename = File.join(root, "#{route.parameter_shell[:controller]}_controller.rb")
|
92
|
+
|
93
|
+
controllers = @model.controllers.select {|c| c.path == filename }
|
94
|
+
unless controllers.size > 0
|
95
|
+
controllers = Stratagem::Model::Component::Controller.load_all(filename)
|
96
|
+
puts "loading controllers from #{filename} -> controllers #{controllers.map {|c| c.klass.name }.inspect}"
|
97
|
+
@model.controllers << controllers
|
98
|
+
end
|
99
|
+
|
100
|
+
controller_name = route.parameter_shell[:controller].gsub('/','::').split('::').map {|part| part.camelcase }.join('::')
|
101
|
+
controller_name << 'Controller'
|
102
|
+
controller_class = controllers.find {|controller| controller.klass.name == controller_name }
|
103
|
+
controller_object = controller_class ? controller_class.klass.new : nil
|
104
|
+
controller_action = route.parameter_shell[:action].to_sym
|
105
|
+
|
106
|
+
if (controller_object) && (controller_object.methods_include?(controller_action))
|
107
|
+
controllers.each do |controller|
|
108
|
+
controller.add_routable_action(controller_action, route.conditions[:method] || :get)
|
109
|
+
end
|
110
|
+
else
|
111
|
+
# if the controller does not contain the indicated method
|
112
|
+
# then check for a template that may be rendered anyway
|
113
|
+
log "\tinvalid route #{route.to_s}"
|
114
|
+
unless @model.views.find {|v| v.render_path == "#{route.parameter_shell[:controller]}/#{controller_action}" }
|
115
|
+
# route is invalid
|
116
|
+
@model.routes.invalid << Stratagem::Model::Component::Route.new(route)
|
117
|
+
log "\tinvalid route #{route.to_s}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
rescue Errno::ENOENT
|
122
|
+
@model.routes.invalid << Stratagem::Model::Component::Route.new(route)
|
123
|
+
rescue MissingSourceFile
|
124
|
+
@model.routes.invalid << Stratagem::Model::Component::Route.new(route)
|
125
|
+
end
|
126
|
+
}
|
127
|
+
log ""
|
128
|
+
end
|
129
|
+
|
130
|
+
def load_files(base_dir,path=[],file_list=[])
|
131
|
+
dir = File.join(base_dir, *path)
|
132
|
+
files = Dir.new(dir).entries
|
133
|
+
files.each do |file|
|
134
|
+
next if file =~ /^\./
|
135
|
+
|
136
|
+
if (File.directory?(File.join(dir, file)))
|
137
|
+
load_files(base_dir, path + [file], file_list)
|
138
|
+
else
|
139
|
+
file_path = File.join(path, file)
|
140
|
+
file_list << file_path
|
141
|
+
end
|
142
|
+
end
|
143
|
+
file_list
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
Capistrano::Configuration.instance(:must_exist).load do
|
2
|
+
namespace :stratagem do
|
3
|
+
|
4
|
+
desc "Analyzes your server environments for correct configuration and gem versions."
|
5
|
+
task :default do
|
6
|
+
servers = find_servers_for_task(current_task)
|
7
|
+
servers.each do |server|
|
8
|
+
puts "listing gems on #{server.host}"
|
9
|
+
run "gem list", :hosts => server do |ch, stream, data|
|
10
|
+
if stream == :err
|
11
|
+
logger.fatal "ERROR listing gems #{data}"
|
12
|
+
else # stream == :out
|
13
|
+
gems = {}
|
14
|
+
data.split("\n").each do |line|
|
15
|
+
line =~ /(.*)\s\((.*)\)/
|
16
|
+
name = $1
|
17
|
+
versions = $2.split(', ')
|
18
|
+
gems[name] = versions
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
# variables.each do |k,v| p k; end
|
24
|
+
# run "echo 'hi'"
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
# Stratagem::Scan::Checks::SecureDeploy
|
4
|
+
module Stratagem::Scan::Checks::Capistrano
|
5
|
+
class SecureDeploy < Stratagem::Scan::Checks::Base
|
6
|
+
SECURE_PROTOCOLS = ['https']
|
7
|
+
|
8
|
+
def run
|
9
|
+
begin
|
10
|
+
gem 'capistrano'
|
11
|
+
require 'capistrano/configuration'
|
12
|
+
|
13
|
+
begin
|
14
|
+
config = Capistrano::Configuration.new
|
15
|
+
config.load "config/deploy"
|
16
|
+
|
17
|
+
vars = config.variables
|
18
|
+
repository_url = vars[:repository]
|
19
|
+
if (repository_url)
|
20
|
+
uri = URI::parse(repository_url)
|
21
|
+
unless (SECURE_PROTOCOLS.include?(uri.scheme.downcase))
|
22
|
+
result(
|
23
|
+
:concern_type => :best_practice,
|
24
|
+
:unique => 'repository_url',
|
25
|
+
:component => nil,
|
26
|
+
:payload => repository_url)
|
27
|
+
end
|
28
|
+
else
|
29
|
+
puts "Unable to locate Capistrano repository in deploy script"
|
30
|
+
end
|
31
|
+
rescue ArgumentError
|
32
|
+
puts "Capistrano deploy script could not be loaded. - #{$!.message}"
|
33
|
+
rescue LoadError
|
34
|
+
puts "Capistrano deploy script not found. - #{$!.message}"
|
35
|
+
puts $!.class.name
|
36
|
+
end
|
37
|
+
rescue Gem::LoadError
|
38
|
+
puts "ERROR: Unable to load Capistrano"
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Stratagem::Scan::Checks::EmailAddress
|
2
|
+
|
3
|
+
module Stratagem::Scan::Checks
|
4
|
+
class EmailAddress < Base
|
5
|
+
include ViewBase
|
6
|
+
|
7
|
+
Scanner = Regexp.compile(/\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}\b/)
|
8
|
+
|
9
|
+
def scan(view)
|
10
|
+
view.scan(Scanner).uniq.each do |email|
|
11
|
+
result(:concern_type => :warning, :unique => email, :payload => email)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Stratagem::Scan::Checks::ErrorPages
|
2
|
+
|
3
|
+
module Stratagem::Scan::Checks
|
4
|
+
class ErrorPages < Base
|
5
|
+
include ViewBase
|
6
|
+
|
7
|
+
Strings = {
|
8
|
+
404 => ['The page you were looking for doesn\'t exist.', 'You may have mistyped the address or the page may have moved.'],
|
9
|
+
500 => ['We\'re sorry, but something went wrong.', 'We\'ve been notified about this issue and we\'ll take a look at it shortly.']
|
10
|
+
}
|
11
|
+
|
12
|
+
def scan(view)
|
13
|
+
Strings.each {|type, set|
|
14
|
+
matched = true
|
15
|
+
set.each {|s|
|
16
|
+
unless view.include?(s)
|
17
|
+
matched = false
|
18
|
+
break
|
19
|
+
end
|
20
|
+
}
|
21
|
+
result(:concern_type => :best_practice, :unique => type, :payload => type) if (matched)
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Stratagem::Scan::Checks::MongoMapper
|
2
|
+
class Base < Stratagem::Scan::Checks::Base
|
3
|
+
alias_method :parent_result, :result
|
4
|
+
|
5
|
+
def run
|
6
|
+
if (self.class.method_defined?(:scan))
|
7
|
+
application_model.models.each {|model|
|
8
|
+
log "scanning model #{model.klass.name}"
|
9
|
+
scan(model)
|
10
|
+
}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def result(hash)
|
15
|
+
hash[:specialization] = :mongo_mapper
|
16
|
+
parent_result(hash)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Stratagem::Scan::Checks::MassAssignment
|
2
|
+
|
3
|
+
module Stratagem::Scan::Checks::MongoMapper
|
4
|
+
class ForeignKeysExposed < Base
|
5
|
+
|
6
|
+
def description
|
7
|
+
"analyzes application to find models vulnerable to mass assignment"
|
8
|
+
end
|
9
|
+
|
10
|
+
def scan(model)
|
11
|
+
return unless model.methods_include?(:stratagem)
|
12
|
+
|
13
|
+
# look up the controllers that reference it
|
14
|
+
instance = model.klass.new
|
15
|
+
assignable_keys = model.model_assignable_attributes & instance.stratagem.foreign_keys
|
16
|
+
if (assignable_keys.size > 0)
|
17
|
+
references = application_model.controllers.map {|controller| controller.modifies(model) }.flatten.compact
|
18
|
+
concern_type = references.size > 0 ? :error : :best_practice
|
19
|
+
solution_payload = assignable_keys
|
20
|
+
result(
|
21
|
+
:concern_type => concern_type,
|
22
|
+
:unique => model.klass.name,
|
23
|
+
:payload => model.klass.name,
|
24
|
+
:component => model,
|
25
|
+
:confirmed => false,
|
26
|
+
:solution_payload => solution_payload
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Stratagem::Scan::Checks::EmailAddress
|
2
|
+
|
3
|
+
module Stratagem::Scan::Checks
|
4
|
+
class Routes < Base
|
5
|
+
def run
|
6
|
+
application_model.routes.invalid.each {|route|
|
7
|
+
payload = {
|
8
|
+
:path => route.route.segments.inject("") { |str,s| str << s.to_s },
|
9
|
+
:method => route.route.conditions[:method],
|
10
|
+
:requirements => route.route.requirements
|
11
|
+
}
|
12
|
+
result :concern_type => :best_practice, :unique => payload.inspect, :payload => payload
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Stratagem::Scan::Checks::EmailAddress
|
2
|
+
|
3
|
+
module Stratagem::Scan::Checks::Ssl
|
4
|
+
class SecureLoginPage < Stratagem::Scan::Checks::Base
|
5
|
+
def run
|
6
|
+
auth = application_model.crawler.authentication
|
7
|
+
if (auth.success && !auth.login_page.response.request.ssl?)
|
8
|
+
|
9
|
+
route = application_model.routes.recognize(auth.login_page)
|
10
|
+
payload = {
|
11
|
+
:path => auth.login_page.response.request.path,
|
12
|
+
:method => auth.login_page.response.request.method,
|
13
|
+
:action => route.action
|
14
|
+
}
|
15
|
+
result :concern_type => :error, :unique => :secure_login_page, :component => route.controller, :payload => payload
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Stratagem::Scan::Checks::EmailAddress
|
2
|
+
|
3
|
+
module Stratagem::Scan::Checks::Ssl
|
4
|
+
class SecureLoginSubmit < Stratagem::Scan::Checks::Base
|
5
|
+
def run
|
6
|
+
auth = application_model.crawler.authentication
|
7
|
+
if (auth.success && !auth.ssl)
|
8
|
+
route = application_model.routes.recognize(auth.response_page)
|
9
|
+
payload = {
|
10
|
+
:path => auth.response_page.response.request.path,
|
11
|
+
:method => auth.response_page.response.request.method,
|
12
|
+
:action => route.action
|
13
|
+
}
|
14
|
+
result :concern_type => :error, :unique => :secure_login_submit, :component => route.controller, :payload => payload
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Stratagem::Scan::Result
|
2
|
+
|
3
|
+
module Stratagem::Scan
|
4
|
+
# Each security check emits 1 or more result objects based on its findings
|
5
|
+
# Payload is an arbitrary piece of data that the check produces. It must be able to be encoded to JSON
|
6
|
+
# Unique is a value that identifies the check result within the namespace of the check
|
7
|
+
class Result
|
8
|
+
attr_accessor :unique, :check, :component, :payload, :line_number, :code, :passed, :concern_type, :confirmed, :solution_payload, :specialization
|
9
|
+
|
10
|
+
# passed = true / false
|
11
|
+
def initialize(args)
|
12
|
+
args.each {|key,value| self.send("#{key}=", value) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def export
|
16
|
+
h = {
|
17
|
+
:guid => guid,
|
18
|
+
:check_name => check_name,
|
19
|
+
:specialization => specialization,
|
20
|
+
:component => component_name,
|
21
|
+
:payload => payload,
|
22
|
+
:line_number => line_number,
|
23
|
+
:code => code,
|
24
|
+
:concern_type => concern_type,
|
25
|
+
:confirmed => confirmed || false,
|
26
|
+
:solution_payload => solution_payload
|
27
|
+
}
|
28
|
+
h[:path] = component.path.gsub(RAILS_ROOT+'/', '') if component
|
29
|
+
h
|
30
|
+
end
|
31
|
+
|
32
|
+
def component_name
|
33
|
+
component ? component.name : nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def check_name
|
37
|
+
check ? check.name : nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def guid
|
41
|
+
"#{check_name.underscore}:#{(component_name || '').underscore}:#{unique.to_s.underscore}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|