ai_root_shield 0.2.0 → 0.4.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 +4 -4
- data/CHANGELOG.md +52 -3
- data/Gemfile.lock +1 -1
- data/README.md +109 -3
- data/examples/policies/banking_policy.json +79 -0
- data/examples/policies/development_policy.json +64 -0
- data/examples/policies/enterprise_policy.json +89 -0
- data/exe/ai_root_shield +157 -14
- data/lib/ai_root_shield/advanced_proxy_detector.rb +406 -0
- data/lib/ai_root_shield/certificate_pinning_helper.rb +258 -0
- data/lib/ai_root_shield/enterprise_policy_manager.rb +431 -0
- data/lib/ai_root_shield/rasp_protection.rb +359 -0
- data/lib/ai_root_shield/version.rb +1 -1
- data/lib/ai_root_shield.rb +171 -4
- metadata +16 -5
data/exe/ai_root_shield
CHANGED
@@ -1,17 +1,30 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require_relative "../lib/ai_root_shield"
|
5
4
|
require "optparse"
|
6
5
|
require "json"
|
6
|
+
require_relative "../lib/ai_root_shield"
|
7
7
|
|
8
|
-
#
|
8
|
+
# Command line interface for AI Root Shield
|
9
9
|
class AiRootShieldCLI
|
10
10
|
def initialize
|
11
11
|
@options = {
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
format: "json",
|
13
|
+
verbose: false,
|
14
|
+
threshold: 50,
|
15
|
+
enable_root_detection: true,
|
16
|
+
enable_emulator_detection: true,
|
17
|
+
enable_hooking_detection: true,
|
18
|
+
enable_integrity_checks: true,
|
19
|
+
enable_network_analysis: true,
|
20
|
+
enable_ai_behavioral_analysis: true,
|
21
|
+
enable_rasp_protection: false,
|
22
|
+
rasp_monitoring_time: 5,
|
23
|
+
policy_file: nil,
|
24
|
+
enable_certificate_pinning: false,
|
25
|
+
enable_proxy_detection: false,
|
26
|
+
target_ip: nil,
|
27
|
+
target_url: nil
|
15
28
|
}
|
16
29
|
end
|
17
30
|
|
@@ -32,8 +45,58 @@ class AiRootShieldCLI
|
|
32
45
|
end
|
33
46
|
|
34
47
|
begin
|
35
|
-
|
48
|
+
# Configure enterprise policy if provided
|
49
|
+
if @options[:policy_file]
|
50
|
+
puts "Loading enterprise policy from #{@options[:policy_file]}..." if @options[:verbose]
|
51
|
+
AiRootShield.configure_policy(@options[:policy_file])
|
52
|
+
end
|
53
|
+
|
54
|
+
# Configure certificate pinning if enabled
|
55
|
+
if @options[:enable_certificate_pinning]
|
56
|
+
puts "Configuring certificate pinning..." if @options[:verbose]
|
57
|
+
AiRootShield.configure_certificate_pinning
|
58
|
+
end
|
59
|
+
|
60
|
+
# Configure proxy detection if enabled
|
61
|
+
if @options[:enable_proxy_detection]
|
62
|
+
puts "Configuring proxy detection..." if @options[:verbose]
|
63
|
+
AiRootShield.configure_proxy_detection
|
64
|
+
end
|
65
|
+
|
66
|
+
# Start RASP protection if enabled
|
67
|
+
if @options[:enable_rasp_protection]
|
68
|
+
puts "Starting RASP protection..." if @options[:verbose]
|
69
|
+
rasp = AiRootShield.start_rasp_protection(
|
70
|
+
enable_real_time_alerts: @options[:verbose],
|
71
|
+
protection_interval: 0.5
|
72
|
+
)
|
73
|
+
|
74
|
+
# Set up RASP event logging if verbose
|
75
|
+
if @options[:verbose]
|
76
|
+
rasp.on_rasp_event do |event|
|
77
|
+
puts "[RASP] #{event[:type]}: #{event[:message]}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Monitor for specified time
|
82
|
+
puts "Monitoring with RASP protection for #{@options[:rasp_monitoring_time]} seconds..." if @options[:verbose]
|
83
|
+
sleep(@options[:rasp_monitoring_time])
|
84
|
+
end
|
85
|
+
|
86
|
+
result = AiRootShield.scan_device_with_config(device_logs_path, @options)
|
87
|
+
|
88
|
+
# Add RASP status to result if enabled
|
89
|
+
if @options[:enable_rasp_protection] && AiRootShield.rasp_active?
|
90
|
+
result[:rasp_status] = AiRootShield.rasp_protection.protection_status
|
91
|
+
end
|
92
|
+
|
93
|
+
# Add security status if verbose
|
94
|
+
if @options[:verbose]
|
95
|
+
result[:security_status] = AiRootShield.security_status
|
96
|
+
end
|
97
|
+
|
36
98
|
output_result(result)
|
99
|
+
|
37
100
|
rescue AiRootShield::Error => e
|
38
101
|
puts "Error: #{e.message}"
|
39
102
|
exit 1
|
@@ -41,6 +104,9 @@ class AiRootShieldCLI
|
|
41
104
|
puts "Unexpected error: #{e.message}"
|
42
105
|
puts e.backtrace if @options[:verbose]
|
43
106
|
exit 1
|
107
|
+
ensure
|
108
|
+
# Stop RASP protection
|
109
|
+
AiRootShield.stop_rasp_protection if @options[:enable_rasp_protection]
|
44
110
|
end
|
45
111
|
end
|
46
112
|
|
@@ -54,7 +120,7 @@ class AiRootShieldCLI
|
|
54
120
|
|
55
121
|
opts.on("-f", "--format FORMAT", ["json", "text", "summary"],
|
56
122
|
"Output format (json, text, summary)") do |format|
|
57
|
-
@options[:
|
123
|
+
@options[:format] = format
|
58
124
|
end
|
59
125
|
|
60
126
|
opts.on("-v", "--verbose", "Enable verbose output") do
|
@@ -63,27 +129,59 @@ class AiRootShieldCLI
|
|
63
129
|
|
64
130
|
opts.on("-t", "--threshold SCORE", Integer,
|
65
131
|
"Risk threshold (0-100, default: 50)") do |threshold|
|
66
|
-
@options[:
|
132
|
+
@options[:threshold] = threshold
|
67
133
|
end
|
68
134
|
|
69
135
|
opts.on("--no-root", "Disable root detection") do
|
70
|
-
@options[:
|
136
|
+
@options[:enable_root_detection] = false
|
71
137
|
end
|
72
138
|
|
73
139
|
opts.on("--no-emulator", "Disable emulator detection") do
|
74
|
-
@options[:
|
140
|
+
@options[:enable_emulator_detection] = false
|
75
141
|
end
|
76
142
|
|
77
143
|
opts.on("--no-hooking", "Disable hooking detection") do
|
78
|
-
@options[:
|
144
|
+
@options[:enable_hooking_detection] = false
|
79
145
|
end
|
80
146
|
|
81
147
|
opts.on("--no-integrity", "Disable integrity checks") do
|
82
|
-
@options[:
|
148
|
+
@options[:enable_integrity_checks] = false
|
83
149
|
end
|
84
150
|
|
85
151
|
opts.on("--no-network", "Disable network analysis") do
|
86
|
-
@options[:
|
152
|
+
@options[:enable_network_analysis] = false
|
153
|
+
end
|
154
|
+
|
155
|
+
opts.on("--no-ai", "Disable AI behavioral analysis") do
|
156
|
+
@options[:enable_ai_behavioral_analysis] = false
|
157
|
+
end
|
158
|
+
|
159
|
+
opts.on("--enable-rasp", "Enable RASP protection during scan") do
|
160
|
+
@options[:enable_rasp_protection] = true
|
161
|
+
end
|
162
|
+
|
163
|
+
opts.on("--rasp-time SECONDS", Integer, "RASP monitoring time in seconds (default: 5)") do |time|
|
164
|
+
@options[:rasp_monitoring_time] = time
|
165
|
+
end
|
166
|
+
|
167
|
+
opts.on("--policy FILE", "Enterprise policy file path") do |file|
|
168
|
+
@options[:policy_file] = file
|
169
|
+
end
|
170
|
+
|
171
|
+
opts.on("--enable-cert-pinning", "Enable certificate pinning validation") do
|
172
|
+
@options[:enable_certificate_pinning] = true
|
173
|
+
end
|
174
|
+
|
175
|
+
opts.on("--enable-proxy-detection", "Enable advanced proxy detection") do
|
176
|
+
@options[:enable_proxy_detection] = true
|
177
|
+
end
|
178
|
+
|
179
|
+
opts.on("--target-ip IP", "Target IP address for network analysis") do |ip|
|
180
|
+
@options[:target_ip] = ip
|
181
|
+
end
|
182
|
+
|
183
|
+
opts.on("--target-url URL", "Target URL for certificate pinning validation") do |url|
|
184
|
+
@options[:target_url] = url
|
87
185
|
end
|
88
186
|
|
89
187
|
opts.on("-h", "--help", "Show this help message") do
|
@@ -99,7 +197,7 @@ class AiRootShieldCLI
|
|
99
197
|
end
|
100
198
|
|
101
199
|
def output_result(result)
|
102
|
-
case @options[:
|
200
|
+
case @options[:format]
|
103
201
|
when "json"
|
104
202
|
puts JSON.pretty_generate(result)
|
105
203
|
when "text"
|
@@ -117,6 +215,42 @@ class AiRootShieldCLI
|
|
117
215
|
puts "Version: #{result[:version]}"
|
118
216
|
puts ""
|
119
217
|
|
218
|
+
# Display compliance status if available
|
219
|
+
if result[:compliance]
|
220
|
+
puts "Policy Compliance:"
|
221
|
+
puts " Status: #{result[:compliance][:compliant] ? 'COMPLIANT' : 'NON-COMPLIANT'}"
|
222
|
+
puts " Policy Version: #{result[:compliance][:policy_version]}"
|
223
|
+
|
224
|
+
if result[:compliance][:violations].any?
|
225
|
+
puts " Violations:"
|
226
|
+
result[:compliance][:violations].each do |violation|
|
227
|
+
puts " • #{violation[:message]} (#{violation[:severity]})"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
puts ""
|
231
|
+
end
|
232
|
+
|
233
|
+
# Display network analysis if available
|
234
|
+
if result[:network_analysis]
|
235
|
+
puts "Network Security Analysis:"
|
236
|
+
|
237
|
+
if result[:network_analysis][:proxy_detection]
|
238
|
+
proxy = result[:network_analysis][:proxy_detection]
|
239
|
+
puts " Proxy Detection: #{proxy[:proxy_detected] ? 'DETECTED' : 'Clean'}"
|
240
|
+
if proxy[:proxy_detected]
|
241
|
+
puts " Types: #{proxy[:proxy_types].join(', ')}"
|
242
|
+
puts " Confidence: #{(proxy[:confidence_score] * 100).round}%"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
if result[:network_analysis][:certificate_pinning]
|
247
|
+
pinning = result[:network_analysis][:certificate_pinning]
|
248
|
+
puts " Certificate Pinning: #{pinning[:valid] ? 'VALID' : 'FAILED'}"
|
249
|
+
puts " Reason: #{pinning[:reason]}" unless pinning[:valid]
|
250
|
+
end
|
251
|
+
puts ""
|
252
|
+
end
|
253
|
+
|
120
254
|
if result[:factors].any?
|
121
255
|
puts "Detected Security Factors:"
|
122
256
|
result[:factors].each do |factor|
|
@@ -134,6 +268,15 @@ class AiRootShieldCLI
|
|
134
268
|
else
|
135
269
|
puts "No security threats detected."
|
136
270
|
end
|
271
|
+
|
272
|
+
# Display RASP status if available
|
273
|
+
if result[:rasp_status]
|
274
|
+
puts ""
|
275
|
+
puts "RASP Protection Status:"
|
276
|
+
puts " Active: #{result[:rasp_status][:active] ? 'YES' : 'NO'}"
|
277
|
+
puts " Events Detected: #{result[:rasp_status][:events_detected] || 0}"
|
278
|
+
puts " Protection Level: #{result[:rasp_status][:protection_level] || 'Standard'}"
|
279
|
+
end
|
137
280
|
end
|
138
281
|
|
139
282
|
def output_summary_format(result)
|
@@ -0,0 +1,406 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "resolv"
|
4
|
+
require "net/http"
|
5
|
+
require "ipaddr"
|
6
|
+
|
7
|
+
module AiRootShield
|
8
|
+
# Advanced proxy detection for VPN, Tor, custom DNS, and MITM appliances
|
9
|
+
class AdvancedProxyDetector
|
10
|
+
# Known Tor exit node IP ranges and identifiers
|
11
|
+
TOR_INDICATORS = {
|
12
|
+
dns_names: [
|
13
|
+
"tor-exit", "torservers", "artikel5ev", "privacyfoundation",
|
14
|
+
"torproject", "exitnode", "tor-relay"
|
15
|
+
],
|
16
|
+
asn_patterns: [
|
17
|
+
/tor/i, /privacy/i, /anonymous/i, /vpn/i
|
18
|
+
]
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
# Common VPN provider indicators
|
22
|
+
VPN_INDICATORS = {
|
23
|
+
dns_patterns: [
|
24
|
+
/vpn/i, /proxy/i, /tunnel/i, /shield/i, /secure/i,
|
25
|
+
/private/i, /anonymous/i, /hide/i, /mask/i
|
26
|
+
],
|
27
|
+
asn_patterns: [
|
28
|
+
/vpn/i, /virtual private/i, /proxy/i, /datacenter/i,
|
29
|
+
/hosting/i, /cloud/i, /server/i
|
30
|
+
],
|
31
|
+
known_providers: [
|
32
|
+
"nordvpn", "expressvpn", "surfshark", "cyberghost", "pia",
|
33
|
+
"mullvad", "protonvpn", "windscribe", "tunnelbear", "hotspotshield"
|
34
|
+
]
|
35
|
+
}.freeze
|
36
|
+
|
37
|
+
# MITM appliance indicators
|
38
|
+
MITM_INDICATORS = {
|
39
|
+
certificate_issuers: [
|
40
|
+
/blue coat/i, /websense/i, /forcepoint/i, /zscaler/i,
|
41
|
+
/checkpoint/i, /palo alto/i, /fortinet/i, /sophos/i,
|
42
|
+
/mcafee/i, /symantec/i, /broadcom/i
|
43
|
+
],
|
44
|
+
proxy_headers: [
|
45
|
+
"x-bluecoat-via", "x-forwarded-for", "x-real-ip",
|
46
|
+
"x-proxy-id", "x-cache", "via", "x-forwarded-proto"
|
47
|
+
]
|
48
|
+
}.freeze
|
49
|
+
|
50
|
+
# Custom DNS server indicators
|
51
|
+
CUSTOM_DNS_INDICATORS = {
|
52
|
+
public_dns: [
|
53
|
+
"8.8.8.8", "8.8.4.4", # Google
|
54
|
+
"1.1.1.1", "1.0.0.1", # Cloudflare
|
55
|
+
"208.67.222.222", "208.67.220.220", # OpenDNS
|
56
|
+
"9.9.9.9", "149.112.112.112" # Quad9
|
57
|
+
],
|
58
|
+
privacy_dns: [
|
59
|
+
"94.140.14.14", "94.140.15.15", # AdGuard
|
60
|
+
"76.76.19.19", "76.223.100.101", # Alternate DNS
|
61
|
+
"185.228.168.9", "185.228.169.9" # CleanBrowsing
|
62
|
+
]
|
63
|
+
}.freeze
|
64
|
+
|
65
|
+
def initialize(config = {})
|
66
|
+
@config = {
|
67
|
+
enable_tor_detection: true,
|
68
|
+
enable_vpn_detection: true,
|
69
|
+
enable_mitm_detection: true,
|
70
|
+
enable_dns_detection: true,
|
71
|
+
timeout: 5,
|
72
|
+
max_retries: 2,
|
73
|
+
use_external_apis: false
|
74
|
+
}.merge(config)
|
75
|
+
|
76
|
+
@detection_cache = {}
|
77
|
+
@last_detection_time = {}
|
78
|
+
end
|
79
|
+
|
80
|
+
# Perform comprehensive proxy detection
|
81
|
+
# @param target_ip [String] IP address to analyze
|
82
|
+
# @param additional_data [Hash] Additional network data
|
83
|
+
# @return [Hash] Detection results
|
84
|
+
def detect_proxy(target_ip, additional_data = {})
|
85
|
+
return cached_result(target_ip) if cached_and_fresh?(target_ip)
|
86
|
+
|
87
|
+
results = {
|
88
|
+
ip_address: target_ip,
|
89
|
+
timestamp: Time.now.to_f,
|
90
|
+
proxy_detected: false,
|
91
|
+
proxy_types: [],
|
92
|
+
confidence_score: 0.0,
|
93
|
+
indicators: [],
|
94
|
+
details: {}
|
95
|
+
}
|
96
|
+
|
97
|
+
# Perform different detection methods
|
98
|
+
results = detect_tor_exit_node(target_ip, results) if @config[:enable_tor_detection]
|
99
|
+
results = detect_vpn_service(target_ip, results) if @config[:enable_vpn_detection]
|
100
|
+
results = detect_mitm_appliance(target_ip, additional_data, results) if @config[:enable_mitm_detection]
|
101
|
+
results = detect_custom_dns(additional_data, results) if @config[:enable_dns_detection]
|
102
|
+
|
103
|
+
# Calculate overall confidence
|
104
|
+
results[:confidence_score] = calculate_confidence_score(results)
|
105
|
+
results[:proxy_detected] = results[:confidence_score] > 0.5
|
106
|
+
|
107
|
+
# Cache results
|
108
|
+
cache_result(target_ip, results)
|
109
|
+
|
110
|
+
results
|
111
|
+
end
|
112
|
+
|
113
|
+
# Detect Tor exit nodes
|
114
|
+
# @param ip [String] IP address to check
|
115
|
+
# @param results [Hash] Current results hash
|
116
|
+
# @return [Hash] Updated results
|
117
|
+
def detect_tor_exit_node(ip, results)
|
118
|
+
tor_indicators = []
|
119
|
+
|
120
|
+
# Check reverse DNS for Tor indicators
|
121
|
+
begin
|
122
|
+
hostname = Resolv.getname(ip)
|
123
|
+
TOR_INDICATORS[:dns_names].each do |indicator|
|
124
|
+
if hostname.downcase.include?(indicator)
|
125
|
+
tor_indicators << "tor_dns_pattern: #{indicator}"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
rescue Resolv::ResolvError
|
129
|
+
# No reverse DNS available
|
130
|
+
end
|
131
|
+
|
132
|
+
# Check if IP is in known Tor exit node lists (if external APIs enabled)
|
133
|
+
if @config[:use_external_apis]
|
134
|
+
tor_indicators.concat(check_tor_exit_lists(ip))
|
135
|
+
end
|
136
|
+
|
137
|
+
# Check for Tor-specific network characteristics
|
138
|
+
tor_indicators.concat(analyze_tor_network_characteristics(ip))
|
139
|
+
|
140
|
+
unless tor_indicators.empty?
|
141
|
+
results[:proxy_types] << "tor_exit_node"
|
142
|
+
results[:indicators].concat(tor_indicators)
|
143
|
+
results[:details][:tor] = {
|
144
|
+
detected: true,
|
145
|
+
indicators: tor_indicators,
|
146
|
+
risk_level: "high"
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
results
|
151
|
+
end
|
152
|
+
|
153
|
+
# Detect VPN services
|
154
|
+
# @param ip [String] IP address to check
|
155
|
+
# @param results [Hash] Current results hash
|
156
|
+
# @return [Hash] Updated results
|
157
|
+
def detect_vpn_service(ip, results)
|
158
|
+
vpn_indicators = []
|
159
|
+
|
160
|
+
# Check reverse DNS for VPN patterns
|
161
|
+
begin
|
162
|
+
hostname = Resolv.getname(ip)
|
163
|
+
VPN_INDICATORS[:dns_patterns].each do |pattern|
|
164
|
+
if hostname.match?(pattern)
|
165
|
+
vpn_indicators << "vpn_dns_pattern: #{pattern}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Check for known VPN providers
|
170
|
+
VPN_INDICATORS[:known_providers].each do |provider|
|
171
|
+
if hostname.downcase.include?(provider)
|
172
|
+
vpn_indicators << "known_vpn_provider: #{provider}"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
rescue Resolv::ResolvError
|
176
|
+
# No reverse DNS available
|
177
|
+
end
|
178
|
+
|
179
|
+
# Check ASN information for VPN characteristics
|
180
|
+
vpn_indicators.concat(analyze_asn_for_vpn(ip))
|
181
|
+
|
182
|
+
# Check for datacenter/hosting characteristics
|
183
|
+
vpn_indicators.concat(analyze_hosting_characteristics(ip))
|
184
|
+
|
185
|
+
unless vpn_indicators.empty?
|
186
|
+
results[:proxy_types] << "vpn_service"
|
187
|
+
results[:indicators].concat(vpn_indicators)
|
188
|
+
results[:details][:vpn] = {
|
189
|
+
detected: true,
|
190
|
+
indicators: vpn_indicators,
|
191
|
+
risk_level: "medium"
|
192
|
+
}
|
193
|
+
end
|
194
|
+
|
195
|
+
results
|
196
|
+
end
|
197
|
+
|
198
|
+
# Detect MITM appliances
|
199
|
+
# @param ip [String] IP address to check
|
200
|
+
# @param additional_data [Hash] Additional network data
|
201
|
+
# @param results [Hash] Current results hash
|
202
|
+
# @return [Hash] Updated results
|
203
|
+
def detect_mitm_appliance(ip, additional_data, results)
|
204
|
+
mitm_indicators = []
|
205
|
+
|
206
|
+
# Check for MITM certificate issuers
|
207
|
+
if additional_data[:certificates]
|
208
|
+
additional_data[:certificates].each do |cert_info|
|
209
|
+
issuer = cert_info[:issuer] || ""
|
210
|
+
MITM_INDICATORS[:certificate_issuers].each do |pattern|
|
211
|
+
if issuer.match?(pattern)
|
212
|
+
mitm_indicators << "mitm_certificate_issuer: #{pattern}"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Check for proxy headers
|
219
|
+
if additional_data[:http_headers]
|
220
|
+
MITM_INDICATORS[:proxy_headers].each do |header|
|
221
|
+
if additional_data[:http_headers].key?(header.downcase)
|
222
|
+
mitm_indicators << "proxy_header_detected: #{header}"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# Check for transparent proxy characteristics
|
228
|
+
mitm_indicators.concat(detect_transparent_proxy(ip, additional_data))
|
229
|
+
|
230
|
+
unless mitm_indicators.empty?
|
231
|
+
results[:proxy_types] << "mitm_appliance"
|
232
|
+
results[:indicators].concat(mitm_indicators)
|
233
|
+
results[:details][:mitm] = {
|
234
|
+
detected: true,
|
235
|
+
indicators: mitm_indicators,
|
236
|
+
risk_level: "high"
|
237
|
+
}
|
238
|
+
end
|
239
|
+
|
240
|
+
results
|
241
|
+
end
|
242
|
+
|
243
|
+
# Detect custom DNS configuration
|
244
|
+
# @param additional_data [Hash] Additional network data
|
245
|
+
# @param results [Hash] Current results hash
|
246
|
+
# @return [Hash] Updated results
|
247
|
+
def detect_custom_dns(additional_data, results)
|
248
|
+
dns_indicators = []
|
249
|
+
|
250
|
+
# Check configured DNS servers
|
251
|
+
if additional_data[:dns_servers]
|
252
|
+
dns_servers = Array(additional_data[:dns_servers])
|
253
|
+
|
254
|
+
dns_servers.each do |dns_server|
|
255
|
+
# Check for public DNS services
|
256
|
+
if CUSTOM_DNS_INDICATORS[:public_dns].include?(dns_server)
|
257
|
+
dns_indicators << "public_dns_detected: #{dns_server}"
|
258
|
+
elsif CUSTOM_DNS_INDICATORS[:privacy_dns].include?(dns_server)
|
259
|
+
dns_indicators << "privacy_dns_detected: #{dns_server}"
|
260
|
+
elsif !is_isp_dns?(dns_server)
|
261
|
+
dns_indicators << "custom_dns_detected: #{dns_server}"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Check for DNS over HTTPS/TLS usage
|
267
|
+
if additional_data[:doh_enabled] || additional_data[:dot_enabled]
|
268
|
+
dns_indicators << "encrypted_dns_detected"
|
269
|
+
end
|
270
|
+
|
271
|
+
unless dns_indicators.empty?
|
272
|
+
results[:proxy_types] << "custom_dns"
|
273
|
+
results[:indicators].concat(dns_indicators)
|
274
|
+
results[:details][:dns] = {
|
275
|
+
detected: true,
|
276
|
+
indicators: dns_indicators,
|
277
|
+
risk_level: "low"
|
278
|
+
}
|
279
|
+
end
|
280
|
+
|
281
|
+
results
|
282
|
+
end
|
283
|
+
|
284
|
+
# Get detection statistics
|
285
|
+
# @return [Hash] Detection statistics
|
286
|
+
def detection_statistics
|
287
|
+
{
|
288
|
+
total_detections: @detection_cache.size,
|
289
|
+
cache_hits: @detection_cache.values.count { |v| v[:cached] },
|
290
|
+
detection_types: @detection_cache.values.flat_map { |v| v[:proxy_types] }.tally,
|
291
|
+
average_confidence: calculate_average_confidence,
|
292
|
+
last_detection: @last_detection_time.values.max
|
293
|
+
}
|
294
|
+
end
|
295
|
+
|
296
|
+
# Clear detection cache
|
297
|
+
def clear_cache
|
298
|
+
@detection_cache.clear
|
299
|
+
@last_detection_time.clear
|
300
|
+
end
|
301
|
+
|
302
|
+
private
|
303
|
+
|
304
|
+
def cached_and_fresh?(ip, ttl = 300)
|
305
|
+
return false unless @detection_cache[ip]
|
306
|
+
return false unless @last_detection_time[ip]
|
307
|
+
|
308
|
+
Time.now.to_f - @last_detection_time[ip] < ttl
|
309
|
+
end
|
310
|
+
|
311
|
+
def cached_result(ip)
|
312
|
+
result = @detection_cache[ip].dup
|
313
|
+
result[:cached] = true
|
314
|
+
result
|
315
|
+
end
|
316
|
+
|
317
|
+
def cache_result(ip, results)
|
318
|
+
@detection_cache[ip] = results
|
319
|
+
@last_detection_time[ip] = Time.now.to_f
|
320
|
+
end
|
321
|
+
|
322
|
+
def calculate_confidence_score(results)
|
323
|
+
base_score = 0.0
|
324
|
+
|
325
|
+
# Weight different proxy types
|
326
|
+
type_weights = {
|
327
|
+
"tor_exit_node" => 0.9,
|
328
|
+
"mitm_appliance" => 0.8,
|
329
|
+
"vpn_service" => 0.6,
|
330
|
+
"custom_dns" => 0.3
|
331
|
+
}
|
332
|
+
|
333
|
+
results[:proxy_types].each do |type|
|
334
|
+
base_score += type_weights[type] || 0.5
|
335
|
+
end
|
336
|
+
|
337
|
+
# Factor in number of indicators
|
338
|
+
indicator_bonus = [results[:indicators].size * 0.1, 0.3].min
|
339
|
+
|
340
|
+
# Cap at 1.0
|
341
|
+
[base_score + indicator_bonus, 1.0].min
|
342
|
+
end
|
343
|
+
|
344
|
+
def check_tor_exit_lists(ip)
|
345
|
+
# Placeholder for external Tor exit node list checking
|
346
|
+
# In production, this would query Tor directory authorities
|
347
|
+
[]
|
348
|
+
end
|
349
|
+
|
350
|
+
def analyze_tor_network_characteristics(ip)
|
351
|
+
indicators = []
|
352
|
+
|
353
|
+
# Check for common Tor exit node ports
|
354
|
+
common_tor_ports = [80, 443, 993, 995]
|
355
|
+
# This would require actual port scanning in production
|
356
|
+
|
357
|
+
indicators
|
358
|
+
end
|
359
|
+
|
360
|
+
def analyze_asn_for_vpn(ip)
|
361
|
+
indicators = []
|
362
|
+
|
363
|
+
# This would require ASN lookup in production
|
364
|
+
# For now, return empty array
|
365
|
+
|
366
|
+
indicators
|
367
|
+
end
|
368
|
+
|
369
|
+
def analyze_hosting_characteristics(ip)
|
370
|
+
indicators = []
|
371
|
+
|
372
|
+
# Check if IP is in known datacenter ranges
|
373
|
+
# This would require GeoIP database in production
|
374
|
+
|
375
|
+
indicators
|
376
|
+
end
|
377
|
+
|
378
|
+
def detect_transparent_proxy(ip, additional_data)
|
379
|
+
indicators = []
|
380
|
+
|
381
|
+
# Check for transparent proxy indicators
|
382
|
+
if additional_data[:tcp_timestamp_anomaly]
|
383
|
+
indicators << "tcp_timestamp_anomaly"
|
384
|
+
end
|
385
|
+
|
386
|
+
if additional_data[:ttl_anomaly]
|
387
|
+
indicators << "ttl_hop_anomaly"
|
388
|
+
end
|
389
|
+
|
390
|
+
indicators
|
391
|
+
end
|
392
|
+
|
393
|
+
def is_isp_dns?(dns_server)
|
394
|
+
# Check if DNS server belongs to common ISP ranges
|
395
|
+
# This would require ISP database lookup in production
|
396
|
+
false
|
397
|
+
end
|
398
|
+
|
399
|
+
def calculate_average_confidence
|
400
|
+
return 0.0 if @detection_cache.empty?
|
401
|
+
|
402
|
+
total_confidence = @detection_cache.values.sum { |v| v[:confidence_score] }
|
403
|
+
total_confidence / @detection_cache.size
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|