spektr 0.5.6 → 1.0.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/CODE_OF_CONDUCT.md +1 -1
- data/README.md +14 -1
- data/lib/spektr/checks/base.rb +6 -4
- data/lib/spektr/checks/command_injection.rb +1 -5
- data/lib/spektr/checks/content_tag_xss.rb +1 -1
- data/lib/spektr/checks/evaluation.rb +1 -0
- data/lib/spektr/checks/file_access.rb +1 -1
- data/lib/spektr/targets/base.rb +18 -4
- data/lib/spektr/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8a830f6b6b9be5ac380e1cbf4a755b9171b77f9835d9e3f62c5f62c411ba529e
|
|
4
|
+
data.tar.gz: 97a48a8cdaf9d5ab568dcc7d201daf1433b07c1a6043901c300147185ef99dec
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 162e450e6584062cf616d010401090d8e367041042aca79b3b1a6268948e3a99185bbe08ee4fa1928a13a3a45b9cdd5a05f165a282c4abec47f52e23c059b363
|
|
7
|
+
data.tar.gz: d61db3f6438d1ddcd64564815096beb67007539a543c90a0768423d41ede3bf571ea8acd7d167339ee4dde5074677d49719d0fb14fcebabe9fe9d22bc30b2ac9
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 1.0.1
|
|
6
|
+
|
|
7
|
+
* various nil handling fixes
|
|
8
|
+
|
|
9
|
+
## 1.0.0
|
|
10
|
+
|
|
11
|
+
* Add support for ignoring findings from specific checks with a `# spektr:ignore CHECK_NAME` source comment
|
|
12
|
+
* Add File.binread to check
|
|
13
|
+
|
|
5
14
|
## 0.5.6
|
|
6
15
|
|
|
7
16
|
* fix nil handling positives
|
data/CODE_OF_CONDUCT.md
CHANGED
data/README.md
CHANGED
|
@@ -36,8 +36,21 @@ spektr path/to/app
|
|
|
36
36
|
|
|
37
37
|
To see the available options, you can run `spektr --help`.
|
|
38
38
|
|
|
39
|
-
To ignore
|
|
39
|
+
To ignore findings from the command line, use the `--ignore` flag with a comma-separated list of fingerprints from the report.
|
|
40
40
|
|
|
41
|
+
Findings can also be ignored in source code by placing `# spektr:ignore`, followed by one or more comma-separated check names, on the line immediately above them:
|
|
42
|
+
|
|
43
|
+
Use the check names shown in the report:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
# spektr:ignore Arbitrary code execution -- input is validated before this method is called
|
|
47
|
+
eval(params[:code])
|
|
48
|
+
|
|
49
|
+
# spektr:ignore Arbitrary code execution, Dangerous send -- both uses are safe
|
|
50
|
+
eval(send(params[:method]))
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The directive only applies to the named checks on the next line; a directive without a check name has no effect. Keeping a reason after `--` is recommended.
|
|
41
54
|
|
|
42
55
|
### Railsgoat Example output
|
|
43
56
|
|
data/lib/spektr/checks/base.rb
CHANGED
|
@@ -28,6 +28,7 @@ module Spektr
|
|
|
28
28
|
def warn!(target, check, location, message, confidence = :high)
|
|
29
29
|
full_path = target.is_a?(String) ? target : target.path
|
|
30
30
|
path = full_path.gsub(@app.root, "")
|
|
31
|
+
return if target.respond_to?(:ignored_at?) && target.ignored_at?(location&.start_line, check)
|
|
31
32
|
return if dupe?(path, location, message)
|
|
32
33
|
|
|
33
34
|
@app.warnings << Warning.new(path, full_path, check, location, message, confidence)
|
|
@@ -64,7 +65,7 @@ module Spektr
|
|
|
64
65
|
end
|
|
65
66
|
when :keyword_hash_node, :hash_node
|
|
66
67
|
node.elements.each do |element|
|
|
67
|
-
return true if user_input?(element.key)
|
|
68
|
+
return true if element.respond_to?(:key) && user_input?(element.key)
|
|
68
69
|
return true if user_input?(element.value)
|
|
69
70
|
end
|
|
70
71
|
when :array_node
|
|
@@ -84,7 +85,7 @@ module Spektr
|
|
|
84
85
|
actions.each do |action|
|
|
85
86
|
next unless action.body
|
|
86
87
|
action.body.each do |exp|
|
|
87
|
-
return true if exp.name == node.name && user_input?(exp)
|
|
88
|
+
return true if exp.respond_to?(:name) && exp.name == node.name && user_input?(exp)
|
|
88
89
|
end
|
|
89
90
|
end
|
|
90
91
|
when :local_variable_read_node
|
|
@@ -136,7 +137,7 @@ module Spektr
|
|
|
136
137
|
end
|
|
137
138
|
when :keyword_hash_node, :hash_node
|
|
138
139
|
node.elements.each do |element|
|
|
139
|
-
return true if model_attribute?(element.key)
|
|
140
|
+
return true if element.respond_to?(:key) && model_attribute?(element.key)
|
|
140
141
|
return true if model_attribute?(element.value)
|
|
141
142
|
end
|
|
142
143
|
when :array_node
|
|
@@ -175,8 +176,9 @@ module Spektr
|
|
|
175
176
|
}
|
|
176
177
|
end
|
|
177
178
|
actions.each do |action|
|
|
179
|
+
next unless action.body
|
|
178
180
|
action.body.each do |exp|
|
|
179
|
-
next unless node.respond_to?(:name)
|
|
181
|
+
next unless exp.respond_to?(:name) && node.respond_to?(:name)
|
|
180
182
|
return model_attribute?(exp.value) if exp.is_a?(Prism::InstanceVariableWriteNode) && exp.name == node.name
|
|
181
183
|
end
|
|
182
184
|
end
|
|
@@ -34,11 +34,7 @@ module Spektr
|
|
|
34
34
|
# TODO: might need to exclude tempfile and ActiveStorage::Filename
|
|
35
35
|
return if calls.empty?
|
|
36
36
|
calls.each do |call|
|
|
37
|
-
if call.arguments.is_a?(Prism::ArgumentsNode)
|
|
38
|
-
argument = call.arguments.arguments.first
|
|
39
|
-
else
|
|
40
|
-
argument = call.arguments.first
|
|
41
|
-
end
|
|
37
|
+
argument = call.arguments.arguments.first if call.arguments.is_a?(Prism::ArgumentsNode)
|
|
42
38
|
next unless argument
|
|
43
39
|
if user_input?(argument) || model_attribute?(argument)
|
|
44
40
|
warn! @target, self, call.location, "Command injection in #{call.name}"
|
|
@@ -36,7 +36,7 @@ module Spektr
|
|
|
36
36
|
end
|
|
37
37
|
if argument.is_a?(Prism::KeywordHashNode)
|
|
38
38
|
argument.elements.each do |element|
|
|
39
|
-
if user_input?(element.key)
|
|
39
|
+
if element.respond_to?(:key) && user_input?(element.key)
|
|
40
40
|
warn! @target, self, call.location, "Unescaped parameter in content_tag at #{element.key.name}"
|
|
41
41
|
end
|
|
42
42
|
end
|
|
@@ -12,6 +12,7 @@ module Spektr
|
|
|
12
12
|
return unless super
|
|
13
13
|
[:eval, :instance_eval, :class_eval, :module_eval].each do |name|
|
|
14
14
|
@target.find_calls(name).each do |call|
|
|
15
|
+
next unless call.arguments
|
|
15
16
|
call.arguments.arguments.each do |argument|
|
|
16
17
|
if user_input?(argument)
|
|
17
18
|
warn! @target, self, call.location, "User input in eval"
|
|
@@ -16,7 +16,7 @@ module Spektr
|
|
|
16
16
|
def run
|
|
17
17
|
return unless super
|
|
18
18
|
targets = ["Dir", "File", "IO", "Kernel", "Net::FTP", "Net::HTTP", "PStore", "Pathname", "Shell"]
|
|
19
|
-
methods = [:[], :chdir, :chroot, :delete, :entries, :foreach, :glob, :install, :lchmod, :lchown, :link, :load, :load_file, :makedirs, :move, :new, :open, :read, :readlines, :rename, :rmdir, :safe_unlink, :symlink, :syscopy, :sysopen, :truncate, :unlink]
|
|
19
|
+
methods = [:[], :binread, :chdir, :chroot, :delete, :entries, :foreach, :glob, :install, :lchmod, :lchown, :link, :load, :load_file, :makedirs, :move, :new, :open, :read, :readlines, :rename, :rmdir, :safe_unlink, :symlink, :syscopy, :sysopen, :truncate, :unlink]
|
|
20
20
|
targets.each do |target|
|
|
21
21
|
methods.each do |method|
|
|
22
22
|
check_calls_for_user_input(@target.find_calls(method, target.to_sym))
|
data/lib/spektr/targets/base.rb
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
module Spektr
|
|
2
2
|
module Targets
|
|
3
3
|
class Base < Prism::Visitor
|
|
4
|
+
IGNORE_DIRECTIVE = /^\s*#\s*spektr:ignore\s+(.+)$/i
|
|
5
|
+
|
|
4
6
|
attr_accessor :path, :name, :options, :ast, :parent, :parent_modules, :methods, :calls, :interpolated_xstrings, :lvars
|
|
5
7
|
|
|
6
8
|
def initialize(path, content)
|
|
7
9
|
Spektr.logger.debug "loading #{path}"
|
|
10
|
+
@source_lines = content.lines
|
|
8
11
|
@ast = Prism.parse(content)
|
|
9
12
|
@path = path
|
|
10
13
|
return unless @ast
|
|
14
|
+
@name = ""
|
|
11
15
|
@parent = ""
|
|
12
16
|
@parent_modules = []
|
|
13
17
|
@methods = []
|
|
@@ -20,6 +24,16 @@ module Spektr
|
|
|
20
24
|
@name = @name.prepend("#{@parent_modules.map(&:name).join('::')}::") if @name && @parent_modules.any?
|
|
21
25
|
end
|
|
22
26
|
|
|
27
|
+
def ignored_at?(line_number, check)
|
|
28
|
+
return false unless line_number && line_number > 1
|
|
29
|
+
|
|
30
|
+
directive = @source_lines[line_number - 2]&.match(IGNORE_DIRECTIVE)
|
|
31
|
+
return false unless directive
|
|
32
|
+
|
|
33
|
+
ignored_checks = directive[1].split(/\s+--\s+/, 2).first.split(',').map(&:strip)
|
|
34
|
+
ignored_checks.include?(check.name)
|
|
35
|
+
end
|
|
36
|
+
|
|
23
37
|
def method_definitions
|
|
24
38
|
@method_definitions ||= find_methods(node: @ast)
|
|
25
39
|
end
|
|
@@ -76,12 +90,12 @@ module Spektr
|
|
|
76
90
|
@parent = node.superclass.receiver.name.to_s
|
|
77
91
|
when Prism::ConstantPathNode, Prism::ConstantReadNode
|
|
78
92
|
@parent = node.superclass.name.to_s
|
|
79
|
-
@parent.prepend("#{node.superclass.parent.name}::") if node.superclass.respond_to?(:parent)
|
|
80
|
-
if node.superclass.respond_to?(:parent) && node.superclass.parent.respond_to?(:parent)
|
|
93
|
+
@parent.prepend("#{node.superclass.parent.name}::") if node.superclass.respond_to?(:parent) && node.superclass.parent
|
|
94
|
+
if node.superclass.respond_to?(:parent) && node.superclass.parent.respond_to?(:parent) && node.superclass.parent&.parent
|
|
81
95
|
@parent.prepend("#{node.superclass.parent.parent.name}::")
|
|
82
96
|
end
|
|
83
97
|
end
|
|
84
|
-
if node.is_a?(Prism::ClassNode) && node.constant_path && node.constant_path.respond_to?(:parent)
|
|
98
|
+
if node.is_a?(Prism::ClassNode) && node.constant_path && node.constant_path.respond_to?(:parent) && node.constant_path.parent
|
|
85
99
|
@parent = node.constant_path.parent.name.to_s
|
|
86
100
|
end
|
|
87
101
|
@parent = @parent.prepend("#{@parent_modules.map(&:name).join('::')}::") if @parent_modules.any?
|
|
@@ -89,7 +103,7 @@ module Spektr
|
|
|
89
103
|
end
|
|
90
104
|
|
|
91
105
|
def visit_module_node(node)
|
|
92
|
-
@parent_modules << node.constant_path.parent.name if node.constant_path && node.constant_path.respond_to?(:parent)
|
|
106
|
+
@parent_modules << node.constant_path.parent.name if node.constant_path && node.constant_path.respond_to?(:parent) && node.constant_path.parent
|
|
93
107
|
@parent_modules << node
|
|
94
108
|
super
|
|
95
109
|
end
|
data/lib/spektr/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spektr
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Greg Molnar
|
|
@@ -334,7 +334,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
334
334
|
- !ruby/object:Gem::Version
|
|
335
335
|
version: '0'
|
|
336
336
|
requirements: []
|
|
337
|
-
rubygems_version:
|
|
337
|
+
rubygems_version: 4.0.3
|
|
338
338
|
specification_version: 4
|
|
339
339
|
summary: Rails static code analyzer for security issues
|
|
340
340
|
test_files: []
|