interactsh 0.9.7 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ebd689434fbf52ffc8c0f50807110ae00d90449f846d0702242c6901a48998b
4
- data.tar.gz: 0e0a96410f931f7ca710b663cf21c8cf6846db483550477a2db11f315ae69a0d
3
+ metadata.gz: 9be22c7721be0a8a30588674e97e2cee74c5b3decfc58ebd2aeb673679396ccf
4
+ data.tar.gz: 74b359400aa589f489abbc3564d853065d6988a602a5a5c36f635c5173a6824e
5
5
  SHA512:
6
- metadata.gz: 441e2baef05e29ac9a3928e42a0da8e28cfc61e9603d4568bd7a0b03e4976e49842ee539984cdb868862417a23bd1a853144e43dc2186fa7f89f61916c505649
7
- data.tar.gz: 3a61e6ebe450236a0804e3eddc096387f887818eb0331466db6bdee75db295af80771e5c0e71e77fd17bd064e23e27a3db5b038b45c840df65ad3b1ecc9efa38
6
+ metadata.gz: d09e484e4874db56e46ba2dcffa1bcfcbefd2c95167e64f21aa2fe016c63b26320d81a57edf69a7bc6bff95c3aeed3541b67a041bed312423518adcfe18585b6
7
+ data.tar.gz: f30a046769f55ec741169ca4f9a9003e93c6c6b5ca7beb4d714215eb707421dbdd1f55c398b20a817a6d924f3c9d85b4977f9561fcda01b79d54289498628484
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interactsh
4
+ # Main client class for Interactsh interaction
5
+ class Client
6
+ attr_reader :public_key_encoded, :secret, :server, :random_data, :rsa, :token
7
+
8
+ # Creates a new Interactsh client
9
+ #
10
+ # @param server [String] The Interactsh server to use (default: 'oast.me')
11
+ # @param token [String, nil] Optional authentication token
12
+ def initialize(server = 'oast.me', token = nil)
13
+ @rsa = OpenSSL::PKey::RSA.new(2048)
14
+ @public_key = @rsa.public_key.to_pem
15
+ @public_key_encoded = Base64.strict_encode64(@public_key)
16
+
17
+ @secret = Utils.generate_uuid
18
+ @random_data = Utils.generate_random_string(13)
19
+
20
+ @server = server
21
+ @token = token
22
+ @http_client = HttpClient.new(server, token)
23
+ @crypto = Crypto.new(@rsa)
24
+ end
25
+
26
+ # Generates a new domain for interaction testing
27
+ #
28
+ # @return [String] The generated domain name for interaction testing
29
+ def new_domain
30
+ correlation_id = Xid.new.to_s
31
+ register(correlation_id)
32
+
33
+ "#{correlation_id}#{random_data}.#{server}"
34
+ end
35
+
36
+ # Polls the server for interaction data for a given host
37
+ #
38
+ # @param host [String] The host to poll data for
39
+ # @return [Array] Array of interaction data or empty array if polling failed
40
+ def poll(host)
41
+ correlation_id = host[0..19]
42
+ response = @http_client.make_poll_request(correlation_id, secret)
43
+
44
+ return [] unless @http_client.response_successful?(response)
45
+
46
+ data = JSON.parse(response.body)
47
+ parse_poll_data(data)
48
+ end
49
+
50
+ private
51
+
52
+ # Parses and decrypts poll data
53
+ #
54
+ # @param data [Hash] The poll data to parse
55
+ # @return [Array] The decoded interaction data
56
+ def parse_poll_data(data)
57
+ decoded_data = []
58
+
59
+ return decoded_data if data.empty? || !data['data'] || data['data'].empty?
60
+
61
+ data['data'].each do |enc_data|
62
+ decoded_data << @crypto.decrypt_data(data['aes_key'], enc_data)
63
+ end
64
+
65
+ decoded_data
66
+ end
67
+
68
+ # Registers a correlation ID with the Interactsh server
69
+ #
70
+ # @param correlation_id [String] The correlation ID to register
71
+ # @return [void]
72
+ def register(correlation_id)
73
+ data = {
74
+ 'public-key': public_key_encoded,
75
+ 'secret-key': secret,
76
+ 'correlation-id': correlation_id
77
+ }
78
+
79
+ response = @http_client.make_register_request(data)
80
+
81
+ return if response && response.code.to_i == 200
82
+
83
+ puts '[!] Interactsh - Problem with domain registration'
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interactsh
4
+ # Cryptographic operations for Interactsh
5
+ class Crypto
6
+ def initialize(rsa)
7
+ @rsa = rsa
8
+ end
9
+
10
+ # Decrypts interaction data using the provided AES key
11
+ #
12
+ # @param aes_key [String] The encrypted AES key
13
+ # @param enc_data [String] The encrypted data
14
+ # @return [Hash] The decrypted interaction data
15
+ def decrypt_data(aes_key, enc_data)
16
+ decrypted_aes_key = decrypt_aes_key(aes_key)
17
+ decrypt_payload(decrypted_aes_key, enc_data)
18
+ rescue StandardError => e
19
+ puts "[!] Interactsh - Error decrypting data: #{e.message}"
20
+ {}
21
+ end
22
+
23
+ private
24
+
25
+ # Decrypts the AES key using RSA
26
+ #
27
+ # @param aes_key [String] The encrypted AES key
28
+ # @return [String] The decrypted AES key
29
+ def decrypt_aes_key(aes_key)
30
+ pkey = OpenSSL::PKey::RSA.new(@rsa)
31
+ encrypted_aes_key = Base64.urlsafe_decode64(aes_key)
32
+ JOSE::JWA::PKCS1.rsaes_oaep_decrypt(
33
+ OpenSSL::Digest::SHA256,
34
+ encrypted_aes_key,
35
+ pkey
36
+ )
37
+ end
38
+
39
+ # Decrypts the payload using the decrypted AES key
40
+ #
41
+ # @param decrypted_aes_key [String] The decrypted AES key
42
+ # @param enc_data [String] The encrypted data
43
+ # @return [Hash] The decrypted payload as a hash
44
+ def decrypt_payload(decrypted_aes_key, enc_data)
45
+ secretdata = Base64.decode64(enc_data)
46
+ decipher = OpenSSL::Cipher.new('aes-256-cfb')
47
+ decipher.decrypt
48
+ decipher.key = decrypted_aes_key
49
+
50
+ # The data minus the size of the IV (first 16 bytes)
51
+ JSON.parse((decipher.update(secretdata) + decipher.final)[16..])
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interactsh
4
+ # HTTP client for Interactsh API
5
+ class HttpClient
6
+ def initialize(server, token)
7
+ @server = server
8
+ @token = token
9
+ end
10
+
11
+ # Makes the HTTP request to poll for interaction data
12
+ #
13
+ # @param correlation_id [String] The correlation ID to poll for
14
+ # @param secret [String] The secret key
15
+ # @return [Net::HTTPResponse] The HTTP response
16
+ def make_poll_request(correlation_id, secret)
17
+ uri = URI.parse("https://#{@server}/poll?id=#{correlation_id}&secret=#{secret}")
18
+ http = setup_http_client(uri)
19
+
20
+ request = Net::HTTP::Get.new(uri.request_uri)
21
+ apply_headers(request)
22
+
23
+ http.request(request)
24
+ rescue StandardError => e
25
+ puts "[!] Interactsh - HTTP request error: #{e.message}"
26
+ nil
27
+ end
28
+
29
+ # Makes the HTTP request to register a correlation ID
30
+ #
31
+ # @param data [Hash] The data to send
32
+ # @return [Net::HTTPResponse] The HTTP response
33
+ def make_register_request(data)
34
+ uri = URI.parse("https://#{@server}/register")
35
+ http = setup_http_client(uri)
36
+
37
+ request = Net::HTTP::Post.new(uri.request_uri)
38
+ apply_headers(request, 'Content-Type' => 'application/json')
39
+ request.body = data.to_json
40
+
41
+ http.request(request)
42
+ rescue StandardError => e
43
+ puts "[!] Interactsh - HTTP request error: #{e.message}"
44
+ nil
45
+ end
46
+
47
+ # Checks if the HTTP response was successful
48
+ #
49
+ # @param response [Net::HTTPResponse, nil] The HTTP response
50
+ # @return [Boolean] True if response is successful, false otherwise
51
+ def response_successful?(response)
52
+ if !response || response.code.to_i != 200
53
+ puts '[!] Interactsh - Problem with data recovery'
54
+ return false
55
+ end
56
+ true
57
+ end
58
+
59
+ private
60
+
61
+ # Sets up an HTTP client
62
+ #
63
+ # @param uri [URI] The URI to connect to
64
+ # @return [Net::HTTP] The configured HTTP client
65
+ def setup_http_client(uri)
66
+ http = Net::HTTP.new(uri.host, uri.port)
67
+ http.use_ssl = (uri.scheme == 'https')
68
+ http
69
+ end
70
+
71
+ # Applies headers to an HTTP request
72
+ #
73
+ # @param request [Net::HTTPRequest] The request to apply headers to
74
+ # @param additional_headers [Hash] Additional headers to apply
75
+ # @return [void]
76
+ def apply_headers(request, additional_headers = {})
77
+ headers = additional_headers.dup
78
+ headers['Authorization'] = @token if @token
79
+
80
+ headers.each { |key, value| request[key] = value }
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interactsh
4
+ # Utility methods for Interactsh
5
+ module Utils
6
+ module_function
7
+
8
+ # Generates a random string of specified length
9
+ #
10
+ # @param length [Integer] Length of the random string
11
+ # @return [String] Random alphanumeric string
12
+ def generate_random_string(length)
13
+ charset = Array('a'..'z') + Array(0..9)
14
+ Array.new(length) { charset.sample }.join
15
+ end
16
+
17
+ # Generates a RFC4122 version 4 UUID
18
+ #
19
+ # @return [String] The generated UUID
20
+ def generate_uuid
21
+ random_bytes = create_uuid_bytes
22
+ hex = random_bytes.map { |b| b.to_s(16).rjust(2, '0') }.join
23
+ format_uuid(hex)
24
+ end
25
+
26
+ # Creates the byte array for UUID with proper version and variant
27
+ #
28
+ # @return [Array] Array of bytes for UUID
29
+ def create_uuid_bytes
30
+ bytes = Array.new(16) { rand(0..255) }
31
+ # Set version (4) and variant (RFC4122)
32
+ bytes[6] = (bytes[6] & 0x0F) | 0x40 # version 4
33
+ bytes[8] = (bytes[8] & 0x3F) | 0x80 # variant RFC4122
34
+ bytes
35
+ end
36
+
37
+ # Formats a hex string as a UUID with hyphens
38
+ #
39
+ # @param hex [String] The hex string to format
40
+ # @return [String] The formatted UUID string
41
+ def format_uuid(hex)
42
+ [hex[0..7], hex[8..11], hex[12..15], hex[16..19], hex[20..31]].join('-')
43
+ end
44
+ end
45
+ end
data/lib/interactsh.rb CHANGED
@@ -1,89 +1,14 @@
1
- # frozen_string_literal: true
2
-
3
- require 'openssl'
4
- require 'stringio'
5
- require 'jose'
6
- require 'securerandom'
7
- require 'base64'
8
- require 'json'
9
- require 'ruby_xid'
10
- require 'typhoeus'
11
-
12
- # InteractSH Ruby Library
13
- class Interactsh
14
- attr_reader :public_key_encoded, :secret, :server, :random_data, :rsa, :token
15
-
16
- def initialize(server = 'oast.me', token = nil)
17
- @rsa = OpenSSL::PKey::RSA.new(2048)
18
- @public_key = @rsa.public_key.to_pem
19
- @public_key_encoded = Base64.encode64(@public_key)
20
-
21
- @secret = SecureRandom.uuid
22
- @random_data = Array.new(13) { (Array('a'..'z') + Array(0..9)).sample }.join
23
-
24
- @server = server
25
- @token = token
26
- end
27
-
28
- def new_domain
29
- correlation_id = Xid.new.to_s
30
- register(correlation_id)
31
-
32
- "#{correlation_id}#{random_data}.#{server}"
33
- end
34
-
35
- def poll(host)
36
- correlation_id = host[0..19]
37
- headers = {}
38
- headers['Authorization'] = token if token
39
-
40
- response = Typhoeus.get(File.join(server, "/poll?id=#{correlation_id}&secret=#{secret}"), headers: headers)
41
- unless response&.code == 200
42
- puts '[!] Interactsh - Problem with data recovery'
43
- return
44
- end
45
-
46
- datas = JSON.parse(response.body)
47
- parse_poll_datas(datas)
48
- end
49
-
50
- private
51
-
52
- def parse_poll_datas(datas)
53
- decoded_datas = []
54
-
55
- unless datas.empty?
56
- datas['data'].each do |enc_data|
57
- decoded_datas << decrypt_data(datas['aes_key'], enc_data)
58
- end
59
- end
60
-
61
- decoded_datas
62
- end
63
-
64
- def register(correlation_id)
65
- data = { "public-key": public_key_encoded, "secret-key": secret, "correlation-id": correlation_id }.to_json
66
-
67
- headers = { 'Content-Type' => 'application/json' }
68
- headers['Authorization'] = token if token
69
-
70
- response = Typhoeus.post(File.join(server, '/register'), body: data, headers: headers)
71
- return if response.code == 200
72
-
73
- puts '[!] Interactsh - Problem with domain registration'
74
- end
75
-
76
- def decrypt_data(aes_key, enc_data)
77
- pkey = OpenSSL::PKey::RSA.new(rsa)
78
- encrypted_aes_key = Base64.urlsafe_decode64(aes_key)
79
- decrypted_aes_key = JOSE::JWA::PKCS1.rsaes_oaep_decrypt(OpenSSL::Digest::SHA256, encrypted_aes_key, pkey)
80
-
81
- secretdata = Base64.decode64(enc_data)
82
- decipher = OpenSSL::Cipher.new('aes-256-cfb')
83
- decipher.decrypt
84
- decipher.key = decrypted_aes_key
85
-
86
- # The data minus the size of the IV
87
- JSON.parse((decipher.update(secretdata) + decipher.final)[16..])
88
- end
89
- end
1
+ # frozen_string_literal: true
2
+
3
+ # Gem dependencies
4
+ require 'openssl'
5
+ require 'jose'
6
+ require 'stringio'
7
+ require 'base64'
8
+ require 'json'
9
+ require 'ruby_xid'
10
+ require 'net/http'
11
+ require 'uri'
12
+
13
+ # Internal dependencies
14
+ Dir[File.join(__dir__, 'interactsh/*.rb')].each { |file| require file }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: interactsh
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.7
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua MARTINELLE
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-12 00:00:00.000000000 Z
11
+ date: 2025-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jose
@@ -50,41 +50,8 @@ dependencies:
50
50
  - - ">="
51
51
  - !ruby/object:Gem::Version
52
52
  version: 1.0.7
53
- - !ruby/object:Gem::Dependency
54
- name: securerandom
55
- requirement: !ruby/object:Gem::Requirement
56
- requirements:
57
- - - "~>"
58
- - !ruby/object:Gem::Version
59
- version: 0.2.0
60
- type: :runtime
61
- prerelease: false
62
- version_requirements: !ruby/object:Gem::Requirement
63
- requirements:
64
- - - "~>"
65
- - !ruby/object:Gem::Version
66
- version: 0.2.0
67
- - !ruby/object:Gem::Dependency
68
- name: typhoeus
69
- requirement: !ruby/object:Gem::Requirement
70
- requirements:
71
- - - "~>"
72
- - !ruby/object:Gem::Version
73
- version: '1.4'
74
- - - ">="
75
- - !ruby/object:Gem::Version
76
- version: 1.4.0
77
- type: :runtime
78
- prerelease: false
79
- version_requirements: !ruby/object:Gem::Requirement
80
- requirements:
81
- - - "~>"
82
- - !ruby/object:Gem::Version
83
- version: '1.4'
84
- - - ">="
85
- - !ruby/object:Gem::Version
86
- version: 1.4.0
87
- description:
53
+ description: Ruby client library for Interactsh - a tool for detecting out-of-band
54
+ interactions
88
55
  email:
89
56
  - contact@jomar.fr
90
57
  executables: []
@@ -92,6 +59,10 @@ extensions: []
92
59
  extra_rdoc_files: []
93
60
  files:
94
61
  - lib/interactsh.rb
62
+ - lib/interactsh/client.rb
63
+ - lib/interactsh/crypto.rb
64
+ - lib/interactsh/http_client.rb
65
+ - lib/interactsh/utils.rb
95
66
  homepage: https://github.com/JoshuaMart/Interactsh-Library
96
67
  licenses:
97
68
  - MIT
@@ -99,6 +70,7 @@ metadata:
99
70
  source_code_uri: https://github.com/JoshuaMart/Interactsh-Library
100
71
  homepage_uri: https://github.com/JoshuaMart/Interactsh-Library
101
72
  github_repo: https://github.com/JoshuaMart/Interactsh-Library
73
+ rubygems_mfa_required: 'true'
102
74
  post_install_message:
103
75
  rdoc_options: []
104
76
  require_paths:
@@ -114,7 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
86
  - !ruby/object:Gem::Version
115
87
  version: '0'
116
88
  requirements: []
117
- rubygems_version: 3.4.19
89
+ rubygems_version: 3.5.22
118
90
  signing_key:
119
91
  specification_version: 4
120
92
  summary: Interactsh Ruby Library