fushin 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.
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fushin
4
+ class NoMachingPostsError < StandardError; end
5
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fushin
4
+ class Item
5
+ attr_reader :title, :link
6
+
7
+ def initialize(title:, link:)
8
+ @title = title
9
+ @link = link
10
+ end
11
+
12
+ def post
13
+ @post ||= [].tap do |out|
14
+ case link
15
+ when /jugem\.jp/
16
+ out << Posts::Jugem.new(link)
17
+ when /kikey\.net/
18
+ out << Posts::Kikey.new(link)
19
+ when /seesaa\.net/
20
+ otu << Posts::Seesaa.new(link)
21
+ when /shinobi\.jp/
22
+ out << Posts::Shinobi.new(link)
23
+ when /teacup\.com/
24
+ out << Posts::Teacup.new(link)
25
+ else
26
+ raise NoMachingPostsError
27
+ end
28
+ end.first
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fushin
4
+ module Models
5
+ class BTC < Model
6
+ attr_reader :address
7
+ def initialize(address)
8
+ @address = address
9
+ end
10
+
11
+ def title
12
+ "BTC: #{address}"
13
+ end
14
+
15
+ def blockchain_link
16
+ "https://www.blockchain.com/btc/address/#{address}"
17
+ end
18
+
19
+ def to_attachements
20
+ [
21
+ {
22
+ fallback: "blockchain.com link",
23
+ title: title,
24
+ title_link: blockchain_link,
25
+ footer: "blockchain.com",
26
+ footer_icon: "http://www.google.com/s2/favicons?domain=blockchain.com"
27
+ }
28
+ ]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fushin
4
+ module Models
5
+ class Model
6
+ def title
7
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
8
+ end
9
+
10
+ def to_attachements
11
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha2"
4
+ require "uri"
5
+
6
+ module Fushin
7
+ module Models
8
+ class Website < Model
9
+ attr_reader :url
10
+ def initialize(url)
11
+ @url = url
12
+ end
13
+
14
+ def uri
15
+ @uri ||= URI(url)
16
+ end
17
+
18
+ def domain
19
+ uri.hostname
20
+ end
21
+
22
+ def urlscan_link
23
+ "https://urlscan.io/domain/#{domain}"
24
+ end
25
+
26
+ def url_for_vt
27
+ uri.path.empty? ? "#{url}/" : url
28
+ end
29
+
30
+ def vt_link
31
+ "https://www.virustotal.com/#/url/#{Digest::SHA256.hexdigest(url_for_vt)}"
32
+ end
33
+
34
+ def to_attachements
35
+ [
36
+ {
37
+ fallback: "virustotal.com link",
38
+ title: "VT: #{url}",
39
+ title_link: vt_link,
40
+ footer: "virustotal.com",
41
+ footer_icon: "http://www.google.com/s2/favicons?domain=virustotal.com"
42
+ },
43
+ {
44
+ fallback: "urlscan.io link",
45
+ title: "urlscan.io: #{domain}",
46
+ title_link: urlscan_link,
47
+ footer: "urlscan.io",
48
+ footer_icon: "http://www.google.com/s2/favicons?domain=urlscan.io"
49
+ },
50
+ ]
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fushin
4
+ class Monitor
5
+ attr_reader :rss
6
+ def initialize
7
+ @rss = RSS.new
8
+ end
9
+
10
+ def check
11
+ rss.items.each do |item|
12
+ next if Cache.cached?(item.link)
13
+
14
+ attachements = [].tap do |out|
15
+ out << item.post.btcs.map(&:to_attachements)
16
+ out << item.post.urls.map(&:to_attachements)
17
+ out << item.post.links.map(&:to_attachements)
18
+ end.flatten
19
+ attachements << { text: "IoC is not found." } if attachements.empty?
20
+ Notifier.notify("#{item.title} (#{item.link})", attachements)
21
+
22
+ Cache.save(item.link, true)
23
+ rescue StandardError => e
24
+ puts "#{e.class} (#{e}) is happened during processing #{item.link}"
25
+ end
26
+ end
27
+
28
+ def self.check
29
+ new.check
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "slack/incoming/webhooks"
4
+
5
+ module Fushin
6
+ class Notifier
7
+ def notify(title, attachments = [])
8
+ if slack_webhook_url?
9
+ slack = Slack::Incoming::Webhooks.new(slack_webhook_url, channel: slack_channel)
10
+ slack.post title, attachments: attachments
11
+ else
12
+ puts title
13
+ attachments.each do |attachment|
14
+ puts "#{attachment.dig(:title)} (#{attachment.dig(:title_link)})"
15
+ end
16
+ end
17
+ end
18
+
19
+ def slack_webhook_url
20
+ ENV.fetch "SLACK_WEBHOOK_URL"
21
+ end
22
+
23
+ def slack_channel
24
+ ENV.fetch "SLACK_CHANNEL", "#general"
25
+ end
26
+
27
+ def slack_webhook_url?
28
+ ENV.key? "SLACK_WEBHOOK_URL"
29
+ end
30
+
31
+ def self.notify(title, text)
32
+ new.notify(title, text)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fushin
4
+ module Posts
5
+ class Jugem < Post
6
+ def main_selector
7
+ "div.entry_body"
8
+ end
9
+
10
+ def main_cleanup_selectors
11
+ %w(script iframe .service_button)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fushin
4
+ module Posts
5
+ class Kikey < Post
6
+ def main_selector
7
+ "#categoryBlog > div.entryBody"
8
+ end
9
+
10
+ def main_cleanup_selectors
11
+ []
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "charlock_holmes"
4
+ require "http"
5
+ require "oga"
6
+ require "uri"
7
+ require "yaml"
8
+ require "url_regex"
9
+
10
+ module Fushin
11
+ module Posts
12
+ class Post
13
+ attr_reader :url
14
+
15
+ def initialize(url)
16
+ @url = url
17
+ end
18
+
19
+ def main
20
+ @main ||= [].tap do |out|
21
+ body = doc.at_css(main_selector)
22
+
23
+ main_cleanup_selectors.each do |selector|
24
+ body.css(selector).each(&:remove)
25
+ end
26
+
27
+ out << body
28
+ end.first
29
+ end
30
+
31
+ def main_text
32
+ @main_text ||= [].tap do |out|
33
+ detection = CharlockHolmes::EncodingDetector.detect(main.text)
34
+ out << CharlockHolmes::Converter.convert(main.text, detection[:encoding], "UTF-8")
35
+ end.first
36
+ end
37
+
38
+ def btcs
39
+ @btcs ||= main_text.scan(/\b[13][a-km-zA-HJ-NP-Z0-9]{26,33}\b/).uniq.map do |address|
40
+ Models::BTC.new(address)
41
+ end
42
+ end
43
+
44
+ def urls
45
+ @urls ||= main_text.scan(UrlRegex.get(scheme_required: true, mode: :parsing)).uniq.map do |url|
46
+ next if whitelisted_domain?(url)
47
+
48
+ Models::Website.new(url)
49
+ end.compact
50
+ end
51
+
52
+ def links
53
+ @links ||= main.css("a").map { |a| a.get("href") }.compact.uniq.map do |url|
54
+ next if whitelisted_domain?(url)
55
+
56
+ Models::Website.new(url)
57
+ end.compact
58
+ end
59
+
60
+ private
61
+
62
+ def main_selector
63
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
64
+ end
65
+
66
+ def main_cleanup_selectors
67
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
68
+ end
69
+
70
+ def whitelisted_domain?(url)
71
+ uri = URI(url)
72
+ whitelisted_domains.any? { |domain| uri.hostname.end_with? domain }
73
+ end
74
+
75
+ def whitelisted_domains
76
+ @whitelisted_domains ||= YAML.safe_load(
77
+ File.read(File.expand_path("./../config/whitelisted_domains.yml", __dir__))
78
+ )
79
+ end
80
+
81
+ def doc
82
+ @doc ||= Oga.parse_html(body)
83
+ end
84
+
85
+ def body
86
+ res = HTTP.get(url)
87
+ return nil unless res.code == 200
88
+
89
+ res.body.to_s
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fushin
4
+ module Posts
5
+ class Seesaa < Post
6
+ def main_selector
7
+ "#content > div.blog > div > div.text"
8
+ end
9
+
10
+ def main_cleanup_selectors
11
+ %w(script iframe .listCategoryArticle)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fushin
4
+ module Posts
5
+ class Shinobi < Post
6
+ def main_selector
7
+ "#primary > div.inner > div > div:nth-child(3)"
8
+ end
9
+
10
+ def main_cleanup_selectors
11
+ %w(script)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fushin
4
+ module Posts
5
+ class Teacup < Post
6
+ def main_selector
7
+ "#tcmainlay > div.postdate > div > div.postbody"
8
+ end
9
+
10
+ def main_cleanup_selectors
11
+ %w(script .tagword)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "http"
4
+ require "rss"
5
+ require "uri"
6
+
7
+ module Fushin
8
+ class RSS
9
+ BASE_URL = "https://www.inoreader.com/stream/user/1006141524/tag/%E4%B8%8D%E5%AF%A9%E3%83%A1%E3%83%BC%E3%83%AB"
10
+
11
+ def feed
12
+ @feed ||= ::RSS::Parser.parse(body)
13
+ end
14
+
15
+ def items
16
+ feed.items.map do |item|
17
+ Item.new(title: item.title, link: item.link)
18
+ end
19
+ end
20
+
21
+ def body
22
+ res = HTTP.get(BASE_URL)
23
+ return nil unless res.code == 200
24
+
25
+ res.body.to_s
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fushin
4
+ VERSION = "0.1.0"
5
+ end
metadata ADDED
@@ -0,0 +1,256 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fushin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Manabu Niseki
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-01-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: coveralls
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.8'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.8'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '12.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '12.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.8'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.8'
69
+ - !ruby/object:Gem::Dependency
70
+ name: vcr
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '4.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '4.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.5'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.5'
97
+ - !ruby/object:Gem::Dependency
98
+ name: charlock_holmes
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.7'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.7'
111
+ - !ruby/object:Gem::Dependency
112
+ name: http
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '4.0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '4.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: lightly
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.3'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.3'
139
+ - !ruby/object:Gem::Dependency
140
+ name: oga
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '2.15'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '2.15'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rss
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0.2'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0.2'
167
+ - !ruby/object:Gem::Dependency
168
+ name: slack-incoming-webhooks
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '0.2'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '0.2'
181
+ - !ruby/object:Gem::Dependency
182
+ name: url_regex
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '0.0'
188
+ type: :runtime
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '0.0'
195
+ description: A malicious blog posts checker
196
+ email:
197
+ - manabu.niseki@gmail.com
198
+ executables:
199
+ - fushin
200
+ extensions: []
201
+ extra_rdoc_files: []
202
+ files:
203
+ - ".gitignore"
204
+ - ".rspec"
205
+ - ".travis.yml"
206
+ - Gemfile
207
+ - LICENSE
208
+ - LICENSE.txt
209
+ - README.md
210
+ - Rakefile
211
+ - bin/console
212
+ - bin/setup
213
+ - exe/fushin
214
+ - fushin.gemspec
215
+ - lib/fushin.rb
216
+ - lib/fushin/cache.rb
217
+ - lib/fushin/config/whitelisted_domains.yml
218
+ - lib/fushin/erros.rb
219
+ - lib/fushin/item.rb
220
+ - lib/fushin/models/btc.rb
221
+ - lib/fushin/models/model.rb
222
+ - lib/fushin/models/website.rb
223
+ - lib/fushin/monitor.rb
224
+ - lib/fushin/notifier.rb
225
+ - lib/fushin/posts/jugem.rb
226
+ - lib/fushin/posts/kikey.rb
227
+ - lib/fushin/posts/post.rb
228
+ - lib/fushin/posts/seesaa.rb
229
+ - lib/fushin/posts/shinobi.rb
230
+ - lib/fushin/posts/teacup.rb
231
+ - lib/fushin/rss.rb
232
+ - lib/fushin/version.rb
233
+ homepage: https://github.com/ninoseki/fushin
234
+ licenses:
235
+ - MIT
236
+ metadata: {}
237
+ post_install_message:
238
+ rdoc_options: []
239
+ require_paths:
240
+ - lib
241
+ required_ruby_version: !ruby/object:Gem::Requirement
242
+ requirements:
243
+ - - ">="
244
+ - !ruby/object:Gem::Version
245
+ version: '0'
246
+ required_rubygems_version: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - ">="
249
+ - !ruby/object:Gem::Version
250
+ version: '0'
251
+ requirements: []
252
+ rubygems_version: 3.0.2
253
+ signing_key:
254
+ specification_version: 4
255
+ summary: A malicious blog posts checker
256
+ test_files: []