stratagem 0.1.7
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/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
|
+
|