scanny 0.1.0

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.
Files changed (138) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +11 -0
  3. data/LICENSE +23 -0
  4. data/README.md +185 -0
  5. data/Rakefile +5 -0
  6. data/bin/scanny +61 -0
  7. data/lib/scanny.rb +12 -0
  8. data/lib/scanny/checks/access_control_check.rb +52 -0
  9. data/lib/scanny/checks/backticks_check.rb +18 -0
  10. data/lib/scanny/checks/before_filters_check.rb +35 -0
  11. data/lib/scanny/checks/check.rb +33 -0
  12. data/lib/scanny/checks/csrf_check.rb +19 -0
  13. data/lib/scanny/checks/denial_of_service_check.rb +42 -0
  14. data/lib/scanny/checks/file_open_check.rb +46 -0
  15. data/lib/scanny/checks/frameworks_check.rb +24 -0
  16. data/lib/scanny/checks/helpers.rb +28 -0
  17. data/lib/scanny/checks/http_basic_auth_check.rb +39 -0
  18. data/lib/scanny/checks/http_header/header_injection_check.rb +38 -0
  19. data/lib/scanny/checks/http_redirect_check.rb +37 -0
  20. data/lib/scanny/checks/http_request_check.rb +74 -0
  21. data/lib/scanny/checks/http_usage_check.rb +31 -0
  22. data/lib/scanny/checks/information_leak_check.rb +55 -0
  23. data/lib/scanny/checks/input_filtering_check.rb +39 -0
  24. data/lib/scanny/checks/insecure_config/set_rails_env_check.rb +24 -0
  25. data/lib/scanny/checks/insecure_config/set_secret_check.rb +25 -0
  26. data/lib/scanny/checks/insecure_config/set_session_key_check.rb +23 -0
  27. data/lib/scanny/checks/insecure_method/eval_method_check.rb +26 -0
  28. data/lib/scanny/checks/insecure_method/marshal_check.rb +33 -0
  29. data/lib/scanny/checks/insecure_method/system_method_check.rb +46 -0
  30. data/lib/scanny/checks/mass_assignment_check.rb +48 -0
  31. data/lib/scanny/checks/random_numbers_check.rb +54 -0
  32. data/lib/scanny/checks/redirect_with_params_check.rb +48 -0
  33. data/lib/scanny/checks/regexp_check.rb +23 -0
  34. data/lib/scanny/checks/reset_session_check.rb +24 -0
  35. data/lib/scanny/checks/session/access_to_session_check.rb +49 -0
  36. data/lib/scanny/checks/session/session_secure_check.rb +47 -0
  37. data/lib/scanny/checks/shell_expanding_methods_check.rb +54 -0
  38. data/lib/scanny/checks/skip_before_filters_check.rb +41 -0
  39. data/lib/scanny/checks/sql_injection/find_method_check.rb +81 -0
  40. data/lib/scanny/checks/sql_injection/find_method_with_dynamic_string_check.rb +43 -0
  41. data/lib/scanny/checks/sql_injection/find_method_with_params_check.rb +80 -0
  42. data/lib/scanny/checks/sql_injection/sanitize_sql_check.rb +25 -0
  43. data/lib/scanny/checks/sql_injection/sql_check.rb +14 -0
  44. data/lib/scanny/checks/sql_injection/string_interpolation_with_params_check.rb +39 -0
  45. data/lib/scanny/checks/ssl/verify_check.rb +53 -0
  46. data/lib/scanny/checks/ssl/verify_peer_check.rb +37 -0
  47. data/lib/scanny/checks/system_tools/gpg_usage_check.rb +51 -0
  48. data/lib/scanny/checks/system_tools/sudo_check.rb +24 -0
  49. data/lib/scanny/checks/system_tools/tar_check.rb +24 -0
  50. data/lib/scanny/checks/system_tools/tar_commands_check.rb +27 -0
  51. data/lib/scanny/checks/system_tools/unzip_check.rb +30 -0
  52. data/lib/scanny/checks/temp_file_open_check.rb +57 -0
  53. data/lib/scanny/checks/user_find_check.rb +40 -0
  54. data/lib/scanny/checks/validates_check.rb +32 -0
  55. data/lib/scanny/checks/verify_check.rb +44 -0
  56. data/lib/scanny/checks/xss/xss_flash_check.rb +70 -0
  57. data/lib/scanny/checks/xss/xss_logger_check.rb +78 -0
  58. data/lib/scanny/checks/xss/xss_mark_check.rb +48 -0
  59. data/lib/scanny/checks/xss/xss_send_check.rb +70 -0
  60. data/lib/scanny/cli.rb +47 -0
  61. data/lib/scanny/issue.rb +28 -0
  62. data/lib/scanny/rake_task.rb +56 -0
  63. data/lib/scanny/reporters.rb +3 -0
  64. data/lib/scanny/reporters/reporter.rb +22 -0
  65. data/lib/scanny/reporters/simple_reporter.rb +19 -0
  66. data/lib/scanny/reporters/xml_reporter.rb +64 -0
  67. data/lib/scanny/ruby_version_check.rb +15 -0
  68. data/lib/scanny/runner.rb +90 -0
  69. data/scanny.gemspec +22 -0
  70. data/spec/scanny/check_spec.rb +22 -0
  71. data/spec/scanny/checks/access_control_check_spec.rb +43 -0
  72. data/spec/scanny/checks/backticks_check_spec.rb +22 -0
  73. data/spec/scanny/checks/before_filters_check_spec.rb +45 -0
  74. data/spec/scanny/checks/csrf_check_spec.rb +16 -0
  75. data/spec/scanny/checks/denial_of_service_check_spec.rb +28 -0
  76. data/spec/scanny/checks/file_open_check_spec.rb +22 -0
  77. data/spec/scanny/checks/frameworks_check_spec.rb +16 -0
  78. data/spec/scanny/checks/http_basic_auth_check_spec.rb +20 -0
  79. data/spec/scanny/checks/http_header/header_injection_check_spec.rb +21 -0
  80. data/spec/scanny/checks/http_redirect_check_spec.rb +15 -0
  81. data/spec/scanny/checks/http_request_check_spec.rb +37 -0
  82. data/spec/scanny/checks/http_usage_check_spec.rb +20 -0
  83. data/spec/scanny/checks/information_leak_check_spec.rb +32 -0
  84. data/spec/scanny/checks/input_filtering_check_spec.rb +19 -0
  85. data/spec/scanny/checks/insecure_config/set_rails_env_check_spec.rb +17 -0
  86. data/spec/scanny/checks/insecure_config/set_secret_check_spec.rb +22 -0
  87. data/spec/scanny/checks/insecure_config/set_session_key_check_spec.rb +21 -0
  88. data/spec/scanny/checks/insecure_method/eval_method_check_spec.rb +22 -0
  89. data/spec/scanny/checks/insecure_method/marshal_check_spec.rb +26 -0
  90. data/spec/scanny/checks/insecure_method/system_method_check_spec.rb +33 -0
  91. data/spec/scanny/checks/mass_assignment_check_spec.rb +30 -0
  92. data/spec/scanny/checks/random_numbers_check_spec.rb +41 -0
  93. data/spec/scanny/checks/redirect_with_params_check_spec.rb +24 -0
  94. data/spec/scanny/checks/regexp_check_spec.rb +22 -0
  95. data/spec/scanny/checks/reset_session_check_spec.rb +15 -0
  96. data/spec/scanny/checks/session/access_to_session_check_spec.rb +29 -0
  97. data/spec/scanny/checks/session/session_secure_check_spec.rb +22 -0
  98. data/spec/scanny/checks/shell_expanding_methods_check_spec.rb +67 -0
  99. data/spec/scanny/checks/skip_before_filters_check_spec.rb +81 -0
  100. data/spec/scanny/checks/sql_injection/find_method_check_spec.rb +62 -0
  101. data/spec/scanny/checks/sql_injection/find_method_with_dynamic_string_check_spec.rb +27 -0
  102. data/spec/scanny/checks/sql_injection/find_method_with_params_check_spec.rb +93 -0
  103. data/spec/scanny/checks/sql_injection/sanitize_sql_check_spec.rb +16 -0
  104. data/spec/scanny/checks/sql_injection/string_interpolation_with_params_check_spec.rb +18 -0
  105. data/spec/scanny/checks/ssl/verify_check_spec.rb +25 -0
  106. data/spec/scanny/checks/ssl/verify_peer_check_spec.rb +17 -0
  107. data/spec/scanny/checks/system_tools/gpg_usage_check_spec.rb +43 -0
  108. data/spec/scanny/checks/system_tools/sudo_check_spec.rb +24 -0
  109. data/spec/scanny/checks/system_tools/tar_check_spec.rb +20 -0
  110. data/spec/scanny/checks/system_tools/tar_commands_check_spec.rb +41 -0
  111. data/spec/scanny/checks/system_tools/unizp_check_spec.rb +29 -0
  112. data/spec/scanny/checks/temp_file_open_check_spec.rb +22 -0
  113. data/spec/scanny/checks/user_find_check_spec.rb +22 -0
  114. data/spec/scanny/checks/validates_check_spec.rb +19 -0
  115. data/spec/scanny/checks/verify_check_spec.rb +27 -0
  116. data/spec/scanny/checks/xss/xss_flash_check_spec.rb +22 -0
  117. data/spec/scanny/checks/xss/xss_logger_check_spec.rb +24 -0
  118. data/spec/scanny/checks/xss/xss_mark_check_spec.rb +31 -0
  119. data/spec/scanny/checks/xss/xss_send_check_spec.rb +34 -0
  120. data/spec/scanny/cli_spec.rb +167 -0
  121. data/spec/scanny/issue_spec.rb +82 -0
  122. data/spec/scanny/rake_taks_spec.rb +82 -0
  123. data/spec/scanny/reporters/reporter_spec.rb +24 -0
  124. data/spec/scanny/reporters/simple_reporter_spec.rb +48 -0
  125. data/spec/scanny/reporters/xml_reporter_spec.rb +52 -0
  126. data/spec/scanny/ruby_version_check_spec.rb +24 -0
  127. data/spec/scanny/runner_spec.rb +128 -0
  128. data/spec/spec_helper.rb +10 -0
  129. data/spec/support/aruba.rb +4 -0
  130. data/spec/support/check_spec_helpers.rb +5 -0
  131. data/spec/support/checks/extend_test_check.rb +11 -0
  132. data/spec/support/checks/test_check.rb +15 -0
  133. data/spec/support/checks/test_strict_check.rb +17 -0
  134. data/spec/support/const_spec_helpers.rb +36 -0
  135. data/spec/support/matchers/check_matcher.rb +43 -0
  136. data/spec/support/matchers/xpath_matcher.rb +30 -0
  137. data/spec/support/mock_task.rb +43 -0
  138. metadata +242 -0
@@ -0,0 +1,78 @@
1
+ module Scanny
2
+ module Checks
3
+ # Check for logger methods that are called with request params or
4
+ # a dynamic string. This allows us to avoid executing dangerous code.
5
+ class XssLoggerCheck < Check
6
+ def pattern
7
+ [
8
+ pattern_logger_with_params,
9
+ pattern_dynamic_string,
10
+ ].join("|")
11
+ end
12
+
13
+ def check(node)
14
+ issue :low, warning_message, :cwe => [20, 79]
15
+ end
16
+
17
+ private
18
+
19
+ def warning_message
20
+ "Assigning request parameters into logger can lead to XSS issues."
21
+ end
22
+
23
+ # logger(params[:password])
24
+ # logger("User password: #{params[:password]} is...")
25
+ def pattern_logger_with_params
26
+ <<-EOT
27
+ SendWithArguments<
28
+ arguments = ActualArguments<
29
+ array = [
30
+ any*,
31
+ SendWithArguments<
32
+ name = :[],
33
+ receiver = Send<name = :params>
34
+ >
35
+ |
36
+ DynamicString<
37
+ array = [
38
+ any*,
39
+ ToString<
40
+ value = SendWithArguments<
41
+ name = :[],
42
+ receiver = Send<name = :params>
43
+ >
44
+ >,
45
+ any*
46
+ ]
47
+ >,
48
+ any*
49
+ ]
50
+
51
+ >,
52
+ name = :logger
53
+ >
54
+ EOT
55
+ end
56
+
57
+ # logger "#{secure_data}"
58
+ def pattern_dynamic_string
59
+ <<-EOT
60
+ SendWithArguments<
61
+ arguments = ActualArguments<
62
+ array = [
63
+ DynamicString<
64
+ array = [
65
+ any*,
66
+ ToString,
67
+ any*
68
+ ]
69
+ >
70
+ ]
71
+ >,
72
+ name = :logger
73
+ >
74
+ EOT
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,48 @@
1
+ module Scanny
2
+ module Checks
3
+ # Check for methods mark_as_xss_protected and mark_methods_as_xss_safe
4
+ # that are called and can mark dangerous string as safe for html.
5
+ class XssMarkCheck < Check
6
+ def pattern
7
+ [
8
+ pattern_mark_as_safe,
9
+ pattern_xss_safe,
10
+ pattern_mark_methods_as_xss_safe
11
+ ].join("|")
12
+ end
13
+
14
+ def check(node)
15
+ issue :info, warning_message
16
+ end
17
+
18
+ private
19
+
20
+ def warning_message
21
+ "Marking string as safe can lead to XSS issues."
22
+ end
23
+
24
+ # xss_safe()
25
+ def pattern_xss_safe
26
+ "Send<name = :xss_safe>"
27
+ end
28
+
29
+ # mark_as_xss_protected()
30
+ def pattern_mark_as_safe
31
+ <<-EOT
32
+ Send<name =
33
+ :mark_as_xss_protected |
34
+ :to_s_xss_protected
35
+ >
36
+ EOT
37
+ end
38
+
39
+ def pattern_mark_methods_as_xss_safe
40
+ <<-EOT
41
+ SendWithArguments<name = :mark_methods_as_xss_safe>
42
+ |
43
+ Send<name = :mark_methods_as_xss_safe>
44
+ EOT
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,70 @@
1
+ module Scanny
2
+ module Checks
3
+ # Checks for send_* methods that are called with :disposition => 'inline'.
4
+ # This can lead to download of private files from a server or to a XSS issue.
5
+ class XssSendCheck < Check
6
+ def pattern
7
+ [
8
+ pattern_send,
9
+ pattern_send_with_param
10
+ ].join("|")
11
+ end
12
+
13
+ def check(node)
14
+ if Machete.matches?(node, pattern_send)
15
+ issue :medium, warning_message, :cwe => [79, 115, 200]
16
+ elsif Machete.matches?(node, pattern_send_with_param)
17
+ issue :high, warning_message, :cwe => 201
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def warning_message
24
+ "Send file or data to client in \"inline\" " +
25
+ "mode or with param can lead to XSS issues."
26
+ end
27
+
28
+ # send_file "file.txt", :disposition => "inline"
29
+ # send_data "file.txt", :disposition => "inline"
30
+ def pattern_send
31
+ <<-EOT
32
+ SendWithArguments<
33
+ name = :send_file | :send_data,
34
+ arguments = ActualArguments<
35
+ array = [
36
+ any,
37
+ HashLiteral<
38
+ array = [
39
+ any{even},
40
+ SymbolLiteral<value = :disposition>,
41
+ StringLiteral<string = "inline">,
42
+ any{even}
43
+ ]
44
+ >
45
+ ]
46
+ >
47
+ >
48
+ EOT
49
+ end
50
+
51
+ def pattern_send_with_param
52
+ <<-EOT
53
+ SendWithArguments<
54
+ name = :send_file | :send_data,
55
+ arguments = ActualArguments<
56
+ array = [
57
+ any*,
58
+ SendWithArguments<
59
+ name = :[],
60
+ receiver = Send<name = :params>
61
+ >,
62
+ any*
63
+ ]
64
+ >
65
+ >
66
+ EOT
67
+ end
68
+ end
69
+ end
70
+ end
data/lib/scanny/cli.rb ADDED
@@ -0,0 +1,47 @@
1
+ module Scanny
2
+ module CLI
3
+ def build_paths
4
+ paths = ARGV.map do |path|
5
+ path += "/**/*.rb" if File.directory?(path)
6
+ path
7
+ end
8
+ paths << "./app/**/*.rb" if paths.size == 0
9
+
10
+ paths.map { |path| path.gsub('//', '/') }
11
+ end
12
+
13
+ def require_checks(checks)
14
+ checks = checks.to_s.split(",").map(&:strip)
15
+
16
+ checks.each do |directory|
17
+ Dir[directory + "/**/*.rb"].each do |file|
18
+ require File.expand_path(file, Dir.pwd)
19
+ end
20
+ end
21
+ end
22
+
23
+ def runner_with_custom_checks(runner, disabled_checks, strict = false)
24
+ disabled_checks = disabled_checks.to_s.split(",").map(&:strip)
25
+
26
+ runner.checks.reject! do |check|
27
+ disabled_checks.any? { |ch| check.class.name == ch } ||
28
+ (check.strict? && !strict)
29
+ end
30
+
31
+ runner
32
+ end
33
+
34
+ def use_parser(version)
35
+ return unless version
36
+ case version
37
+ when '18'
38
+ Rubinius::Melbourne
39
+ when '19'
40
+ Rubinius::Melbourne19
41
+ else
42
+ $stderr.puts "I can not recognize the version of the parser: #{version}"
43
+ exit 2
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,28 @@
1
+ module Scanny
2
+ class Issue
3
+ attr_reader :file, :line, :impact, :message, :cwe
4
+
5
+ def initialize(file, line, impact, message, cwe = nil)
6
+ @file, @line, @impact, @message, @cwe = file, line, impact, message, cwe
7
+ end
8
+
9
+ def ==(other)
10
+ other.instance_of?(self.class) &&
11
+ @file == other.file &&
12
+ @line == other.line &&
13
+ @impact == other.impact &&
14
+ @message == other.message &&
15
+ @cwe == other.cwe
16
+ end
17
+
18
+ def to_s
19
+ cwe_suffix = if @cwe
20
+ " (" + Array(@cwe).map { |cwe| "CWE-#{cwe}" }.join(", ") + ")"
21
+ else
22
+ ""
23
+ end
24
+
25
+ "[#{@impact}] #{@file}:#{@line}: #{@message}#{cwe_suffix}"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,56 @@
1
+ require 'rake'
2
+ require 'rake/tasklib'
3
+
4
+ module Scanny
5
+ class RakeTask < ::Rake::TaskLib
6
+ # name of rake task
7
+ attr_accessor :name
8
+ # paths to custom checks
9
+ attr_accessor :include
10
+ # list of disabled checks
11
+ attr_accessor :disable
12
+ # output format
13
+ attr_accessor :format
14
+ # strict mode
15
+ attr_accessor :strict
16
+ # custom path to scan
17
+ attr_accessor :path
18
+ # raise exception on error
19
+ attr_accessor :fail_on_error
20
+ # ruby mode
21
+ attr_accessor :ruby_mode
22
+
23
+ def initialize(name=:scanny)
24
+ @name = name
25
+ @include = []
26
+ @disable = []
27
+ @format = nil
28
+ @strict = nil
29
+ @path = nil
30
+ @fail_on_error = nil
31
+ @ruby_mode = nil
32
+
33
+ yield self if block_given?
34
+ define
35
+ end
36
+
37
+ def define
38
+ desc("Run scanny security scanner")
39
+
40
+ task name do
41
+ cmd = ["scanny"]
42
+ cmd << ["-i"] + [@include] unless @include.empty?
43
+ cmd << ["-d"] + [@disable] unless @disable.empty?
44
+ cmd << ["-f #{@format}"] if @format
45
+ cmd << ["-s"] if @strict
46
+ cmd << ["-m #{@ruby_mode}"] if @ruby_mode
47
+ cmd << [@path] if @path
48
+ cmd = cmd.flatten.join(" ")
49
+
50
+ unless system(cmd)
51
+ raise("Command #{cmd} failed") if fail_on_error
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,3 @@
1
+ require_relative "reporters/reporter"
2
+ require_relative "reporters/simple_reporter"
3
+ require_relative "reporters/xml_reporter"
@@ -0,0 +1,22 @@
1
+ module Scanny
2
+ module Reporters
3
+ class Reporter
4
+ attr_accessor :file, :checks_performed, :nodes_inspected, :issues
5
+
6
+ def initialize(arguments = {})
7
+ arguments.each do |key, value|
8
+ instance_variable_set("@#{key}", value) unless value.nil?
9
+ end
10
+ set_default_values!
11
+ end
12
+
13
+ private
14
+
15
+ def set_default_values!
16
+ @check_performed ||= 0
17
+ @nodes_inspected ||= 0
18
+ @issues ||= []
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ require_relative 'reporter'
2
+
3
+ module Scanny
4
+ module Reporters
5
+ class SimpleReporter < Reporter
6
+ def report
7
+ string = "#{file} [#{checks_performed} checks done | "
8
+ string += "#{nodes_inspected} nodes inspected | #{issues.size} issues]"
9
+
10
+ issues.each do |issue|
11
+ string += "\n - #{issue.to_s}"
12
+ end
13
+ puts string unless issues.empty?
14
+
15
+ string
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,64 @@
1
+ require_relative 'reporter'
2
+ require 'rexml/document'
3
+
4
+ module Scanny
5
+ module Reporters
6
+ class XMLReporter < Reporter
7
+
8
+ def initialize(*)
9
+ prepare_reports_directory
10
+ super
11
+ end
12
+
13
+ def report
14
+ out = ""
15
+ doc = REXML::Document.new
16
+
17
+ testsuite = REXML::Element.new("testsuite")
18
+ testsuite.add_attributes('assertions' => nodes_inspected.to_s,
19
+ 'errors' => issues.size.to_s,
20
+ 'skipped' => '0',
21
+ 'tests' => checks_performed.to_s,
22
+ 'failures' => '0',
23
+ 'name' => file)
24
+ #TODO: track time?
25
+
26
+ issues.each do |issue|
27
+ testcase = REXML::Element.new 'testcase'
28
+ testcase.add_attributes("name" => "#{issue.file}:#{issue.line}")
29
+
30
+ error = REXML::Element.new 'error'
31
+ error.add_attributes('type' => issue.impact.to_s,
32
+ 'message' => issue.message)
33
+ error.text = issue.to_s
34
+
35
+ testcase.add_element error
36
+ testsuite.add_element testcase
37
+ end
38
+ testsuite.add_element REXML::Element.new("system-out")
39
+ testsuite.add_element REXML::Element.new("system-err")
40
+
41
+ doc << testsuite
42
+ doc.write(out, 2)
43
+ File.open(output, "w") { |f| f.write(out) }
44
+
45
+ out
46
+ end
47
+
48
+ private
49
+
50
+ def output
51
+ file_name = file.gsub('/', '\\')
52
+ "reports/Test-#{file_name}.xml"
53
+ end
54
+
55
+ def prepare_reports_directory
56
+ if File.exists?('reports')
57
+ puts "Removing 'reports' directory"
58
+ FileUtils.rm_rf 'reports'
59
+ end
60
+ FileUtils.mkdir 'reports'
61
+ end
62
+ end
63
+ end
64
+ end