mihari 4.1.2 → 4.2.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +1 -1
  3. data/README.md +1 -1
  4. data/lib/mihari/analyzers/base.rb +18 -10
  5. data/lib/mihari/analyzers/rule.rb +1 -1
  6. data/lib/mihari/cli/base.rb +0 -4
  7. data/lib/mihari/commands/init.rb +1 -1
  8. data/lib/mihari/commands/search.rb +11 -58
  9. data/lib/mihari/commands/validator.rb +1 -2
  10. data/lib/mihari/emitters/base.rb +5 -2
  11. data/lib/mihari/emitters/slack.rb +40 -4
  12. data/lib/mihari/enrichers/base.rb +5 -2
  13. data/lib/mihari/enrichers/ipinfo.rb +4 -3
  14. data/lib/mihari/{web/entities → entities}/alert.rb +0 -0
  15. data/lib/mihari/{web/entities → entities}/artifact.rb +0 -0
  16. data/lib/mihari/{web/entities → entities}/autonomous_system.rb +0 -0
  17. data/lib/mihari/{web/entities → entities}/command.rb +0 -0
  18. data/lib/mihari/{web/entities → entities}/config.rb +0 -0
  19. data/lib/mihari/{web/entities → entities}/dns.rb +0 -0
  20. data/lib/mihari/{web/entities → entities}/geolocation.rb +0 -0
  21. data/lib/mihari/{web/entities → entities}/ip_address.rb +0 -0
  22. data/lib/mihari/{web/entities → entities}/message.rb +0 -0
  23. data/lib/mihari/{web/entities → entities}/reverse_dns.rb +0 -0
  24. data/lib/mihari/{web/entities → entities}/rule.rb +0 -0
  25. data/lib/mihari/{web/entities → entities}/source.rb +0 -0
  26. data/lib/mihari/{web/entities → entities}/tag.rb +0 -0
  27. data/lib/mihari/{web/entities → entities}/whois.rb +0 -0
  28. data/lib/mihari/errors.rb +2 -0
  29. data/lib/mihari/feed/reader.rb +11 -55
  30. data/lib/mihari/http.rb +94 -0
  31. data/lib/mihari/mixins/error_notification.rb +20 -0
  32. data/lib/mihari/mixins/retriable.rb +12 -2
  33. data/lib/mihari/mixins/rule.rb +1 -2
  34. data/lib/mihari/structs/ipinfo.rb +2 -3
  35. data/lib/mihari/structs/rule.rb +30 -0
  36. data/lib/mihari/structs/shodan.rb +9 -1
  37. data/lib/mihari/version.rb +1 -1
  38. data/lib/mihari/web/api.rb +0 -20
  39. data/lib/mihari/web/app.rb +2 -2
  40. data/lib/mihari/web/endpoints/rules.rb +3 -1
  41. data/lib/mihari/web/middleware/error_notification_adapter.rb +19 -0
  42. data/lib/mihari/web/public/index.html +1 -1
  43. data/lib/mihari/web/public/redoc-static.html +1881 -165
  44. data/lib/mihari/web/public/static/css/app.43138058.css +1 -0
  45. data/lib/mihari/web/public/static/css/chunk-vendors.3ed9b08e.css +7 -0
  46. data/lib/mihari/web/public/static/fonts/fa-brands-400.1fd0b4d7.ttf +0 -0
  47. data/lib/mihari/web/public/static/fonts/fa-brands-400.5d5236fb.woff2 +0 -0
  48. data/lib/mihari/web/public/static/fonts/fa-regular-400.64b3730e.woff2 +0 -0
  49. data/lib/mihari/web/public/static/fonts/fa-regular-400.95a8a8af.ttf +0 -0
  50. data/lib/mihari/web/public/static/fonts/fa-solid-900.6115ad71.woff2 +0 -0
  51. data/lib/mihari/web/public/static/fonts/fa-solid-900.f0203cfc.ttf +0 -0
  52. data/lib/mihari/web/public/static/fonts/fa-v4compatibility.e1023515.ttf +0 -0
  53. data/lib/mihari/web/public/static/js/app-legacy.46b666f0.js +2 -0
  54. data/lib/mihari/web/public/static/js/app-legacy.46b666f0.js.map +1 -0
  55. data/lib/mihari/web/public/static/js/app.4818aedd.js +2 -0
  56. data/lib/mihari/web/public/static/js/app.4818aedd.js.map +1 -0
  57. data/lib/mihari/web/public/static/js/chunk-vendors-legacy.c99e452e.js +17 -0
  58. data/lib/mihari/web/public/static/js/chunk-vendors-legacy.c99e452e.js.map +1 -0
  59. data/lib/mihari/web/public/static/js/chunk-vendors.15e84e22.js +23 -0
  60. data/lib/mihari/web/public/static/js/chunk-vendors.15e84e22.js.map +1 -0
  61. data/lib/mihari.rb +63 -15
  62. data/mihari.gemspec +3 -3
  63. data/sig/lib/mihari/emitters/slack.rbs +29 -1
  64. data/sig/lib/mihari/feed/reader.rbs +2 -2
  65. data/sig/lib/mihari/http.rbs +65 -0
  66. data/sig/lib/mihari/mixins/error_notification.rbs +12 -0
  67. data/sig/lib/mihari/structs/rule.rbs +6 -0
  68. data/sig/lib/mihari.rbs +4 -8
  69. metadata +68 -55
  70. data/lib/mihari/cli/mixins/utils.rb +0 -72
  71. data/lib/mihari/emitters/stdout.rb +0 -22
  72. data/lib/mihari/notifiers/base.rb +0 -24
  73. data/lib/mihari/notifiers/exception_notifier.rb +0 -126
  74. data/lib/mihari/notifiers/slack.rb +0 -63
  75. data/sig/lib/mihari/cli/mixins/utils.rbs +0 -50
  76. data/sig/lib/mihari/notifiers/base.rbs +0 -18
  77. data/sig/lib/mihari/notifiers/exception_notifier.rbs +0 -75
  78. data/sig/lib/mihari/notifiers/slack.rbs +0 -50
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mihari
4
- module CLI
5
- module Mixins
6
- module Utils
7
- #
8
- # Send an exception notification if there is any error in a block
9
- #
10
- # @return [Nil]
11
- #
12
- def with_error_handling
13
- yield
14
- rescue StandardError => e
15
- notifier = Notifiers::ExceptionNotifier.new
16
- notifier.notify e
17
- end
18
-
19
- #
20
- # Check required keys in JSON
21
- #
22
- # @param [Hash] json
23
- #
24
- # @return [Boolean]
25
- #
26
- def required_alert_keys?(json)
27
- %w[title description artifacts].all? { |key| json.key? key }
28
- end
29
-
30
- #
31
- # Run analyzer
32
- #
33
- # @param [Class<Mihari::Analyzers::Base>] analyzer_class
34
- # @param [String] query
35
- # @param [Hash] options
36
- #
37
- # @return [nil]
38
- #
39
- def run_analyzer(analyzer_class, query:, options:)
40
- # options = Thor::CoreExt::HashWithIndifferentAccess
41
- # ref. https://www.rubydoc.info/github/wycats/thor/Thor/CoreExt/HashWithIndifferentAccess
42
- # so need to covert it to a plain hash
43
- hash_options = options.to_hash
44
-
45
- hash_options = hash_options.symbolize_keys
46
- hash_options = normalize_options(hash_options)
47
-
48
- analyzer = analyzer_class.new(query, **hash_options)
49
-
50
- analyzer.ignore_old_artifacts = options[:ignore_old_artifacts] || false
51
- analyzer.ignore_threshold = options[:ignore_threshold] || 0
52
-
53
- analyzer.run
54
- end
55
-
56
- #
57
- # Normalize options (reject keys not for analyzers)
58
- #
59
- # @param [Hash] options
60
- #
61
- # @return [Hash]
62
- #
63
- def normalize_options(options)
64
- [:ignore_old_artifacts, :ignore_threshold].each do |ignore_key|
65
- options.delete(ignore_key)
66
- end
67
- options
68
- end
69
- end
70
- end
71
- end
72
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mihari
4
- module Emitters
5
- class StandardOutput < Base
6
- def valid?
7
- true
8
- end
9
-
10
- def emit(title:, description:, artifacts:, source:, tags:)
11
- h = {
12
- title: title,
13
- description: description,
14
- artifacts: artifacts.map(&:data),
15
- source: source,
16
- tags: tags
17
- }
18
- puts JSON.pretty_generate(h)
19
- end
20
- end
21
- end
22
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mihari
4
- module Notifiers
5
- class Base
6
- # Validate notifier availability
7
- #
8
- # @return [Boolean]
9
- #
10
- def valid?
11
- raise NotImplementedError, "You must implement #{self.class}##{__method__}"
12
- end
13
-
14
- #
15
- # Send a notification
16
- #
17
- # @return [nil]
18
- #
19
- def notify
20
- raise NotImplementedError, "You must implement #{self.class}##{__method__}"
21
- end
22
- end
23
- end
24
- end
@@ -1,126 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mihari
4
- module Notifiers
5
- class ExceptionNotifier
6
- def initialize
7
- @backtrace_lines = 10
8
- @color = "danger"
9
-
10
- @slack = Notifiers::Slack.new
11
- end
12
-
13
- def valid?
14
- @slack.valid?
15
- end
16
-
17
- def notify(exception)
18
- notify_to_stdout exception
19
-
20
- clean_message = exception.message.tr("`", "'")
21
- attachments = to_attachments(exception, clean_message)
22
- notify_to_slack(text: clean_message, attachments: attachments) if @slack.valid?
23
- end
24
-
25
- #
26
- # Send notification to Slack
27
- #
28
- # @param [String] text
29
- # @param [Array<Hash>] attachments
30
- #
31
- # @return [nil]
32
- #
33
- def notify_to_slack(text:, attachments:)
34
- @slack.notify(text: text, attachments: attachments)
35
- end
36
-
37
- #
38
- # Send notification to STDOUT
39
- #
40
- # @param [Exception] exception
41
- #
42
- # @return [nil]
43
- #
44
- def notify_to_stdout(exception)
45
- text = to_text(exception.class).chomp
46
- message = "#{text}: #{exception.message}"
47
- puts message
48
- puts format_backtrace(exception.backtrace) if exception.backtrace
49
- end
50
-
51
- #
52
- # Convert exception to attachments (for Slack)
53
- #
54
- # @param [Exception] exception
55
- # @param [String] clean_message
56
- #
57
- # @return [Array<Hash>]
58
- #
59
- def to_attachments(exception, clean_message)
60
- text = to_text(exception.class)
61
- backtrace = exception.backtrace
62
- fields = to_fields(clean_message, backtrace)
63
-
64
- [color: @color, text: text, fields: fields, mrkdwn_in: %w[text fields]]
65
- end
66
-
67
- #
68
- # Convert exception class to text
69
- #
70
- # @param [Class<Exception>] exception_class
71
- #
72
- # @return [String]
73
- #
74
- def to_text(exception_class)
75
- measure_word = /^[aeiou]/i.match?(exception_class.to_s) ? "An" : "A"
76
- exception_name = "*#{measure_word}* `#{exception_class}`"
77
- "#{exception_name} *occured in background*\n"
78
- end
79
-
80
- #
81
- # Convert clean_message and backtrace into fields (for Slack)
82
- #
83
- # @param [String] clean_message
84
- # @param [Array] backtrace
85
- #
86
- # @return [Array<Hash>]
87
- #
88
- def to_fields(clean_message, backtrace)
89
- fields = [
90
- { title: "Exception", value: clean_message },
91
- { title: "Hostname", value: hostname }
92
- ]
93
-
94
- if backtrace
95
- formatted_backtrace = format_backtrace(backtrace)
96
- fields << { title: "Backtrace", value: formatted_backtrace }
97
- end
98
- fields
99
- end
100
-
101
- #
102
- # Hostname of runnning instance
103
- #
104
- # @return [String]
105
- #
106
- def hostname
107
- Socket.gethostname
108
- rescue StandardError => _e
109
- "N/A"
110
- end
111
-
112
- #
113
- # Format backtrace in string
114
- #
115
- # @param [Array] backtrace
116
- #
117
- # @return [String]
118
- #
119
- def format_backtrace(backtrace)
120
- return nil unless backtrace
121
-
122
- "```#{backtrace.first(@backtrace_lines).join("\n")}```"
123
- end
124
- end
125
- end
126
- end
@@ -1,63 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "slack-notifier"
4
-
5
- module Mihari
6
- module Notifiers
7
- class Slack < Base
8
- SLACK_WEBHOOK_URL_KEY = "SLACK_WEBHOOK_URL"
9
- SLACK_CHANNEL_KEY = "SLACK_CHANNEL"
10
- DEFAULT_USERNAME = "mihari"
11
-
12
- #
13
- # Slack channel to post
14
- #
15
- # @return [String]
16
- #
17
- def slack_channel
18
- Mihari.config.slack_channel || "#general"
19
- end
20
-
21
- #
22
- # Slack webhook URL
23
- #
24
- # @return [String]
25
- #
26
- def slack_webhook_url
27
- Mihari.config.slack_webhook_url
28
- end
29
-
30
- #
31
- # Check Slack webhook URL is set
32
- #
33
- # @return [Boolean]
34
- #
35
- def slack_webhook_url?
36
- !Mihari.config.slack_webhook_url.nil?
37
- end
38
-
39
- #
40
- # Check Slack webhook URL is set. Alias of #slack_webhook_url?.
41
- #
42
- # @return [Boolean]
43
- #
44
- def valid?
45
- slack_webhook_url?
46
- end
47
-
48
- #
49
- # Send notification to Slack
50
- #
51
- # @param [String] text
52
- # @param [Array<Hash>] attachments
53
- # @param [Boolean] mrkdwn
54
- #
55
- # @return [nil]
56
- #
57
- def notify(text:, attachments: [], mrkdwn: true)
58
- notifier = ::Slack::Notifier.new(slack_webhook_url, channel: slack_channel, username: DEFAULT_USERNAME)
59
- notifier.post(text: text, attachments: attachments, mrkdwn: mrkdwn)
60
- end
61
- end
62
- end
63
- end
@@ -1,50 +0,0 @@
1
- module Mihari
2
- module CLI
3
- module Mixins
4
- module Utils
5
- #
6
- # Send an exception notification if there is any error in a block
7
- #
8
- # @return [Nil]
9
- #
10
- def with_error_handling: () { () -> untyped } -> void
11
-
12
- #
13
- # Check required keys in JSON
14
- #
15
- # @param [Hash] json
16
- #
17
- # @return [Boolean]
18
- #
19
- def required_alert_keys?: (Hash[(String | Symbol), untyped] json) -> bool
20
-
21
- #
22
- # Load configuration and establish DB connection
23
- #
24
- # @return [Hash]
25
- #
26
- def load_configuration: () -> Hash[(String | Symbol), untyped]
27
-
28
- #
29
- # Run analyzer
30
- #
31
- # @param [Class<Mihari::Analyzers::Base>] analyzer_class
32
- # @param [String] query
33
- # @param [Hash] options
34
- #
35
- # @return [nil]
36
- #
37
- def run_analyzer: (untyped analyzer_class, query: String query, options: untyped options) -> void
38
-
39
- #
40
- # Normalize options (reject keys not for analyzers)
41
- #
42
- # @param [Hash] options
43
- #
44
- # @return [Hash]
45
- #
46
- def normalize_options: (Hash[(String | Symbol), untyped] options) -> Hash[(String | Symbol), untyped]
47
- end
48
- end
49
- end
50
- end
@@ -1,18 +0,0 @@
1
- module Mihari
2
- module Notifiers
3
- class Base
4
- # Validate notifier availability
5
- #
6
- # @return [Boolean]
7
- #
8
- def valid?: () -> bool
9
-
10
- #
11
- # Send a notification
12
- #
13
- # @return [nil]
14
- #
15
- def notify: () -> void
16
- end
17
- end
18
- end
@@ -1,75 +0,0 @@
1
- module Mihari
2
- module Notifiers
3
- class ExceptionNotifier
4
- def initialize: () -> void
5
-
6
- def valid?: () -> bool
7
-
8
- def notify: (Exception exception) -> void
9
-
10
- #
11
- # Send notification to Slack
12
- #
13
- # @param [String] text
14
- # @param [Array<Hash>] attachments
15
- #
16
- # @return [nil]
17
- #
18
- def notify_to_slack: (text: String text, attachments: Array[Hash[(String | Symbol), untyped]] attachments) -> void
19
-
20
- #
21
- # Send notification to STDOUT
22
- #
23
- # @param [Exception] exception
24
- #
25
- # @return [nil]
26
- #
27
- def notify_to_stdout: (Exception exception) -> void
28
-
29
- #
30
- # Convert exception to attachments (for Slack)
31
- #
32
- # @param [Exception] exception
33
- # @param [String] clean_message
34
- #
35
- # @return [Array<Hash>]
36
- #
37
- def to_attachments: (Exception exception, String clean_message) -> ::Array[{ color: untyped, text: untyped, fields: untyped, :mrkdwn_in => ::Array["text" | "fields"] }]
38
-
39
- #
40
- # Convert exception class to text
41
- #
42
- # @param [Class<Exception>] exception_class
43
- #
44
- # @return [String]
45
- #
46
- def to_text: (singleton(Exception) exception_class) -> ::String
47
-
48
- #
49
- # Convert clean_message and backtrace into fields (for Slack)
50
- #
51
- # @param [String] clean_message
52
- # @param [Array] backtrace
53
- #
54
- # @return [Array<Hash>]
55
- #
56
- def to_fields: (String clean_message, untyped backtrace) -> Array[Hash[(String | Symbol), untyped]]
57
-
58
- #
59
- # Hostname of runnning instance
60
- #
61
- # @return [String]
62
- #
63
- def hostname: () -> String
64
-
65
- #
66
- # Format backtrace in string
67
- #
68
- # @param [Array] backtrace
69
- #
70
- # @return [String]
71
- #
72
- def format_backtrace: (untyped backtrace) -> (nil | ::String)
73
- end
74
- end
75
- end
@@ -1,50 +0,0 @@
1
- module Mihari
2
- module Notifiers
3
- class Slack < Base
4
- SLACK_WEBHOOK_URL_KEY: ::String
5
-
6
- SLACK_CHANNEL_KEY: ::String
7
-
8
- DEFAULT_USERNAME: ::String
9
-
10
- #
11
- # Slack channel to post
12
- #
13
- # @return [String]
14
- #
15
- def slack_channel: () -> String
16
-
17
- #
18
- # Slack webhook URL
19
- #
20
- # @return [String]
21
- #
22
- def slack_webhook_url: () -> String
23
-
24
- #
25
- # Check Slack webhook URL is set
26
- #
27
- # @return [Boolean]
28
- #
29
- def slack_webhook_url?: () -> bool
30
-
31
- #
32
- # Check Slack webhook URL is set. Alias of #slack_webhook_url?.
33
- #
34
- # @return [Boolean]
35
- #
36
- def valid?: () -> bool
37
-
38
- #
39
- # Send notification to Slack
40
- #
41
- # @param [String] text
42
- # @param [Array<Hash>] attachments
43
- # @param [Boolean] mrkdwn
44
- #
45
- # @return [nil]
46
- #
47
- def notify: (text: String text, ?attachments: Array[Hash[(String | Symbol), untyped]] attachments, ?mrkdwn: bool mrkdwn) -> untyped
48
- end
49
- end
50
- end