mihari 3.8.1 → 3.10.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +6 -7
  3. data/config.ru +1 -1
  4. data/lib/mihari/analyzers/greynoise.rb +65 -0
  5. data/lib/mihari/analyzers/rule.rb +1 -0
  6. data/lib/mihari/analyzers/shodan.rb +3 -1
  7. data/lib/mihari/cli/analyzer.rb +2 -0
  8. data/lib/mihari/commands/greynoise.rb +21 -0
  9. data/lib/mihari/commands/search.rb +3 -2
  10. data/lib/mihari/commands/web.rb +9 -5
  11. data/lib/mihari/database.rb +1 -1
  12. data/lib/mihari/errors.rb +2 -0
  13. data/lib/mihari/mixins/configuration.rb +12 -2
  14. data/lib/mihari/models/alert.rb +29 -54
  15. data/lib/mihari/models/artifact.rb +3 -0
  16. data/lib/mihari/schemas/configuration.rb +3 -2
  17. data/lib/mihari/structs/alert.rb +45 -0
  18. data/lib/mihari/structs/greynoise.rb +55 -0
  19. data/lib/mihari/structs/ipinfo.rb +3 -4
  20. data/lib/mihari/structs/shodan.rb +2 -2
  21. data/lib/mihari/types.rb +2 -0
  22. data/lib/mihari/version.rb +1 -1
  23. data/lib/mihari/web/api.rb +43 -0
  24. data/lib/mihari/web/app.rb +48 -30
  25. data/lib/mihari/web/endpoints/alerts.rb +74 -0
  26. data/lib/mihari/web/endpoints/artifacts.rb +92 -0
  27. data/lib/mihari/web/endpoints/command.rb +32 -0
  28. data/lib/mihari/web/endpoints/configs.rb +22 -0
  29. data/lib/mihari/web/endpoints/ip_addresses.rb +27 -0
  30. data/lib/mihari/web/endpoints/sources.rb +18 -0
  31. data/lib/mihari/web/endpoints/tags.rb +38 -0
  32. data/lib/mihari/web/entities/alert.rb +23 -0
  33. data/lib/mihari/web/entities/artifact.rb +24 -0
  34. data/lib/mihari/web/entities/autonomous_system.rb +9 -0
  35. data/lib/mihari/web/entities/command.rb +14 -0
  36. data/lib/mihari/web/entities/config.rb +16 -0
  37. data/lib/mihari/web/entities/dns.rb +10 -0
  38. data/lib/mihari/web/entities/geolocation.rb +10 -0
  39. data/lib/mihari/web/entities/ip_address.rb +13 -0
  40. data/lib/mihari/web/entities/message.rb +9 -0
  41. data/lib/mihari/web/entities/reverse_dns.rb +9 -0
  42. data/lib/mihari/web/entities/source.rb +9 -0
  43. data/lib/mihari/web/entities/tag.rb +13 -0
  44. data/lib/mihari/web/entities/whois.rb +16 -0
  45. data/lib/mihari/web/public/grape.rb +73 -0
  46. data/lib/mihari/web/public/index.html +1 -1
  47. data/lib/mihari/web/public/redoc-static.html +53 -27
  48. data/lib/mihari/web/public/static/js/app.0a0cc502.js +21 -0
  49. data/lib/mihari/web/public/static/js/app.0a0cc502.js.map +1 -0
  50. data/lib/mihari/web/public/static/js/app.14008741.js +21 -0
  51. data/lib/mihari/web/public/static/js/app.14008741.js.map +1 -0
  52. data/lib/mihari/web/public/static/js/app.378da3dc.js +50 -0
  53. data/lib/mihari/web/public/static/js/app.378da3dc.js.map +1 -0
  54. data/lib/mihari/web/public/static/js/app.6b636b62.js +50 -0
  55. data/lib/mihari/web/public/static/js/app.6b636b62.js.map +1 -0
  56. data/lib/mihari.rb +8 -14
  57. data/mihari.gemspec +10 -6
  58. data/sig/lib/mihari/analyzers/rule.rbs +1 -1
  59. data/sig/lib/mihari/models/alert.rbs +3 -31
  60. data/sig/lib/mihari/structs/alert.rbs +27 -0
  61. data/sig/lib/mihari/structs/greynoise.rbs +30 -0
  62. data/sig/lib/mihari/structs/shodan.rbs +1 -1
  63. data/sig/lib/mihari/web/app.rbs +2 -2
  64. metadata +150 -76
  65. data/lib/mihari/serializers/alert.rb +0 -14
  66. data/lib/mihari/serializers/artifact.rb +0 -18
  67. data/lib/mihari/serializers/autonomous_system.rb +0 -9
  68. data/lib/mihari/serializers/dns.rb +0 -11
  69. data/lib/mihari/serializers/geolocation.rb +0 -11
  70. data/lib/mihari/serializers/reverse_dns.rb +0 -11
  71. data/lib/mihari/serializers/tag.rb +0 -11
  72. data/lib/mihari/serializers/whois.rb +0 -11
  73. data/lib/mihari/web/controllers/alerts_controller.rb +0 -74
  74. data/lib/mihari/web/controllers/analyzers_controller.rb +0 -38
  75. data/lib/mihari/web/controllers/artifacts_controller.rb +0 -94
  76. data/lib/mihari/web/controllers/base_controller.rb +0 -22
  77. data/lib/mihari/web/controllers/command_controller.rb +0 -26
  78. data/lib/mihari/web/controllers/config_controller.rb +0 -13
  79. data/lib/mihari/web/controllers/ip_address_controller.rb +0 -21
  80. data/lib/mihari/web/controllers/sources_controller.rb +0 -12
  81. data/lib/mihari/web/controllers/tags_controller.rb +0 -30
  82. data/lib/mihari/web/helpers/json.rb +0 -53
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_model_serializers"
4
-
5
- module Mihari
6
- module Serializers
7
- class DnsRecordSerializer < ActiveModel::Serializer
8
- attributes :resource, :value
9
- end
10
- end
11
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_model_serializers"
4
-
5
- module Mihari
6
- module Serializers
7
- class GeolocationSerializer < ActiveModel::Serializer
8
- attributes :country, :country_code
9
- end
10
- end
11
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_model_serializers"
4
-
5
- module Mihari
6
- module Serializers
7
- class ReverseDnsNameSerializer < ActiveModel::Serializer
8
- attributes :name
9
- end
10
- end
11
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_model_serializers"
4
-
5
- module Mihari
6
- module Serializers
7
- class TagSerializer < ActiveModel::Serializer
8
- attributes :id, :name
9
- end
10
- end
11
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_model_serializers"
4
-
5
- module Mihari
6
- module Serializers
7
- class WhoisRecordSerializer < ActiveModel::Serializer
8
- attributes :domain, :created_on, :updated_on, :expires_on, :registrar, :contacts
9
- end
10
- end
11
- end
@@ -1,74 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mihari
4
- module Controllers
5
- class AlertsController < BaseController
6
- get "/api/alerts" do
7
- param :page, Integer
8
- param :artifact, String
9
- param :description, String
10
- param :source, String
11
- param :tag, String
12
-
13
- param :from_at, DateTime
14
- param :fromAt, DateTime
15
- param :to_at, DateTime
16
- param :toAt, DateTime
17
-
18
- page = params["page"] || 1
19
- page = page.to_i
20
- limit = 10
21
-
22
- artifact_data = params["artifact"]
23
- description = params["description"]
24
- source = params["source"]
25
- tag_name = params["tag"]
26
- title = params["title"]
27
-
28
- from_at = params["from_at"] || params["fromAt"]
29
- to_at = params["to_at"] || params["toAt"]
30
-
31
- alerts = Mihari::Alert.search(
32
- artifact_data: artifact_data,
33
- description: description,
34
- from_at: from_at,
35
- limit: limit,
36
- page: page,
37
- source: source,
38
- tag_name: tag_name,
39
- title: title,
40
- to_at: to_at
41
- )
42
- total = Mihari::Alert.count(
43
- artifact_data: artifact_data,
44
- description: description,
45
- from_at: from_at,
46
- source: source,
47
- tag_name: tag_name,
48
- title: title,
49
- to_at: to_at
50
- )
51
-
52
- json({ alerts: alerts, total: total, current_page: page, page_size: limit })
53
- end
54
-
55
- delete "/api/alerts/:id" do
56
- param :id, Integer, required: true
57
-
58
- id = params["id"].to_i
59
-
60
- begin
61
- alert = Mihari::Alert.find(id)
62
- alert.destroy
63
-
64
- status 204
65
- body ""
66
- rescue ActiveRecord::RecordNotFound
67
- status 404
68
-
69
- json({ message: "ID:#{id} is not found" })
70
- end
71
- end
72
- end
73
- end
74
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mihari
4
- module Controllers
5
- class AnalyzersController < BaseController
6
- post "/api/analyzer" do
7
- contract = Mihari::Schemas::AnalyzerRunContract.new
8
- result = contract.call(params)
9
-
10
- unless result.errors.empty?
11
- status 400
12
-
13
- return json(result.errors.to_h)
14
- end
15
-
16
- args = result.to_h
17
-
18
- ignore_old_artifacts = args[:ignoreOldArtifacts]
19
- ignore_threshold = args[:ignoreThreshold]
20
-
21
- analyzer = Mihari::Analyzers::Basic.new(
22
- title: args[:title],
23
- description: args[:description],
24
- source: args[:source],
25
- artifacts: args[:artifacts],
26
- tags: args[:tags]
27
- )
28
- analyzer.ignore_old_artifacts = ignore_old_artifacts
29
- analyzer.ignore_threshold = ignore_threshold
30
-
31
- analyzer.run
32
-
33
- status 201
34
- body ""
35
- end
36
- end
37
- end
38
- end
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mihari
4
- module Controllers
5
- class ArtifactsController < BaseController
6
- get "/api/artifacts/:id" do
7
- param :id, Integer, required: true
8
-
9
- id = params["id"].to_i
10
-
11
- begin
12
- artifact = Mihari::Artifact.includes(
13
- :autonomous_system,
14
- :geolocation,
15
- :whois_record,
16
- :dns_records,
17
- :reverse_dns_names
18
- ).find(id)
19
- rescue ActiveRecord::RecordNotFound
20
- status 404
21
-
22
- return json({ message: "ID:#{id} is not found" })
23
- end
24
-
25
- # TODO: improve queries
26
- alert_ids = Mihari::Artifact.where(data: artifact.data).pluck(:alert_id)
27
- tag_ids = Mihari::Tagging.where(alert_id: alert_ids).pluck(:tag_id)
28
- tag_names = Mihari::Tag.where(id: tag_ids).distinct.pluck(:name)
29
-
30
- artifact_json = Serializers::ArtifactSerializer.new(artifact).as_json
31
-
32
- # convert reverse DNS names into an array of string
33
- # also change it as nil if it is empty
34
- reverse_dns_names = (artifact_json[:reverse_dns_names] || []).filter_map { |v| v[:name] }
35
- reverse_dns_names = nil if reverse_dns_names.empty?
36
- artifact_json[:reverse_dns_names] = reverse_dns_names
37
-
38
- # change DNS records as nil if it is empty
39
- dns_records = artifact_json[:dns_records] || []
40
- dns_records = nil if dns_records.empty?
41
- artifact_json[:dns_records] = dns_records
42
-
43
- # set tags
44
- artifact_json[:tags] = tag_names
45
-
46
- json artifact_json
47
- end
48
-
49
- get "/api/artifacts/:id/enrich" do
50
- param :id, Integer, required: true
51
-
52
- id = params["id"].to_i
53
-
54
- begin
55
- artifact = Mihari::Artifact.includes(
56
- :autonomous_system,
57
- :geolocation,
58
- :whois_record,
59
- :dns_records,
60
- :reverse_dns_names
61
- ).find(id)
62
- rescue ActiveRecord::RecordNotFound
63
- status 404
64
-
65
- return json({ message: "ID:#{id} is not found" })
66
- end
67
-
68
- artifact.enrich_all
69
- artifact.save
70
-
71
- status 201
72
- body ""
73
- end
74
-
75
- delete "/api/artifacts/:id" do
76
- param :id, Integer, required: true
77
-
78
- id = params["id"].to_i
79
-
80
- begin
81
- alert = Mihari::Artifact.find(id)
82
- alert.destroy
83
-
84
- status 204
85
- body ""
86
- rescue ActiveRecord::RecordNotFound
87
- status 404
88
-
89
- json({ message: "ID:#{id} is not found" })
90
- end
91
- end
92
- end
93
- end
94
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "rack/contrib/json_body_parser"
4
- require "sinatra"
5
- require "sinatra/param"
6
-
7
- module Mihari
8
- module Controllers
9
- class BaseController < Sinatra::Base
10
- helpers Sinatra::Param
11
-
12
- use Rack::JSONBodyParser
13
-
14
- set :show_exceptions, false
15
- set :raise_sinatra_param_exceptions, true
16
-
17
- error Sinatra::Param::InvalidParameterError do
18
- json({ error: "#{env["sinatra.error"].param} is invalid" })
19
- end
20
- end
21
- end
22
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "safe_shell"
4
-
5
- module Mihari
6
- module Controllers
7
- class CommandController < BaseController
8
- post "/api/command" do
9
- param :command, String, required: true
10
-
11
- command = params["command"]
12
- if command.nil?
13
- status 400
14
- return json({ message: "command is required" })
15
- end
16
-
17
- command = command.split
18
-
19
- output = SafeShell.execute("mihari", *command)
20
- success = $?.success?
21
-
22
- json({ output: output, success: success })
23
- end
24
- end
25
- end
26
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mihari
4
- module Controllers
5
- class ConfigController < BaseController
6
- get "/api/config" do
7
- report = Status.check
8
-
9
- json report.to_camelback_keys
10
- end
11
- end
12
- end
13
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mihari
4
- module Controllers
5
- class IPAddressController < BaseController
6
- get "/api/ip_addresses/:ip" do
7
- param :ip, String, required: true
8
-
9
- ip = params["ip"].to_s
10
-
11
- data = Enrichers::IPInfo.query(ip)
12
- if data.nil?
13
- status 404
14
- json({ message: "IP:#{ip} is not found" })
15
- else
16
- json data.to_hash
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mihari
4
- module Controllers
5
- class SourcesController < BaseController
6
- get "/api/sources" do
7
- sources = Mihari::Alert.distinct.pluck(:source)
8
- json sources
9
- end
10
- end
11
- end
12
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mihari
4
- module Controllers
5
- class TagsController < BaseController
6
- get "/api/tags" do
7
- tags = Mihari::Tag.distinct.pluck(:name)
8
- json tags
9
- end
10
-
11
- delete "/api/tags/:name" do
12
- param :name, String, required: true
13
-
14
- name = params["name"].to_s
15
-
16
- begin
17
- Mihari::Tag.where(name: name).destroy_all
18
-
19
- status 204
20
- body ""
21
- rescue ActiveRecord::RecordNotFound
22
- status 404
23
-
24
- message = { message: "Name:#{name} is not found" }
25
- json message
26
- end
27
- end
28
- end
29
- end
30
- end
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "awrence"
4
- require "multi_json"
5
- require "sinatra/base"
6
-
7
- module Sinatra
8
- module JSON
9
- class << self
10
- def encode(object)
11
- ::MultiJson.dump(object)
12
- end
13
- end
14
-
15
- def json(object, options = {})
16
- object = object.to_camelback_keys
17
-
18
- content_type resolve_content_type(options)
19
- resolve_encoder_action object, resolve_encoder(options)
20
- end
21
-
22
- private
23
-
24
- def resolve_content_type(options = {})
25
- options[:content_type] || settings.json_content_type
26
- end
27
-
28
- def resolve_encoder(options = {})
29
- options[:json_encoder] || settings.json_encoder
30
- end
31
-
32
- def resolve_encoder_action(object, encoder)
33
- [:encode, :generate].each do |method|
34
- return encoder.send(method, object) if encoder.respond_to? method
35
- end
36
-
37
- if encoder.is_a? Symbol
38
- object.__send__(encoder)
39
- else
40
- fail "#{encoder} does not respond to #generate nor #encode"
41
- end
42
- end
43
- end
44
-
45
- Base.set :json_encoder do
46
- ::MultiJson
47
- end
48
-
49
- Base.set :json_content_type, :json
50
-
51
- # Load the JSON helpers in modular style automatically
52
- Base.helpers JSON
53
- end