$ZF(-100,..) issues
Hi, I found some issues with $ZF(-100) whilst replacing our old $ZF(-1) calls following the security alert. They're easy enough to work around, just figured it might be useful to someone :)
There seems to be some inconsistency with how $ZF(-100) is functioning between Unix and Win, contrary to the documentation. For example, for a simple ‘output directory listing to file’ operation:
Windows:
set dev="dir.txt"
set com="dir"
set options(1)=""
set options(2)="e:\nbupg\webserver\"
w $ZF(-100,"/SHELL /STDERR=""NUL"" /STDOUT="_dev,com,.options)
Expected result (output of dir e:\nbupg\webserver\):
Directory of e:\nbupg\webserver
21/06/2018 17:21 <DIR> .
21/06/2018 17:21 <DIR> ..
15/04/2015 21:53 13,740 ABOUT_APACHE.txt
08/12/2017 22:10 <DIR> bin
08/12/2017 22:09 <DIR> cgi-bin
16/10/2017 10:54 248,177 CHANGES.txt
21/06/2018 17:21 <DIR> conf
08/12/2017 22:09 <DIR> error
21/06/2018 17:21 <DIR> htdocs
08/12/2017 22:10 <DIR> icons
08/12/2017 22:10 <DIR> include
17/05/2016 18:59 3,869 INSTALL.txt
08/12/2017 22:10 <DIR> lib
08/12/2017 09:33 40,401 LICENSE.txt
30/06/2018 23:17 <DIR> logs
28/12/2017 15:25 <DIR> modules
08/12/2017 09:33 2,311 NOTICE.txt
08/12/2017 09:33 35,810 OPENSSL-NEWS.txt
08/12/2017 09:33 4,668 OPENSSL-README.txt
23/01/2014 17:33 4,752 README.txt
8 File(s) 353,728 bytes
12 Dir(s) 24,989,622,272 bytes free
Actual result (within dir.txt):
Volume in drive E is Data
Volume Serial Number is 94F7-0AB1
Directory of e:\nbupg\work
03/07/2018 15:43 <DIR> .
03/07/2018 15:43 <DIR> ..
28/06/2018 16:25 0 16092_dir.dat
03/07/2018 15:36 1,927 7760_dir.dat
28/06/2018 17:03 0 chris.txt
03/07/2018 15:43 0 dir.txt
28/06/2018 17:00 0 myfile.txt
29/06/2018 14:27 <DIR> newly_created_directory2
29/06/2018 14:29 <DIR> newly_created_directory3
03/07/2018 14:38 0 NULL
29/06/2018 14:29 0 stderr_telnet.txt
28/06/2018 17:57 <DIR> temp
28/06/2018 17:29 7 temp.log
28/06/2018 17:38 0 temp.txt
28/06/2018 17:32 <DIR> test folder
30/06/2017 15:28 356 userlist.txt
10 File(s) 2,290 bytes
Directory of e:\nbupg\webserver
21/06/2018 17:21 <DIR> .
21/06/2018 17:21 <DIR> ..
15/04/2015 21:53 13,740 ABOUT_APACHE.txt
08/12/2017 22:10 <DIR> bin
08/12/2017 22:09 <DIR> cgi-bin
16/10/2017 10:54 248,177 CHANGES.txt
21/06/2018 17:21 <DIR> conf
08/12/2017 22:09 <DIR> error
21/06/2018 17:21 <DIR> htdocs
08/12/2017 22:10 <DIR> icons
08/12/2017 22:10 <DIR> include
17/05/2016 18:59 3,869 INSTALL.txt
08/12/2017 22:10 <DIR> lib
08/12/2017 09:33 40,401 LICENSE.txt
30/06/2018 23:17 <DIR> logs
28/12/2017 15:25 <DIR> modules
08/12/2017 09:33 2,311 NOTICE.txt
08/12/2017 09:33 35,810 OPENSSL-NEWS.txt
08/12/2017 09:33 4,668 OPENSSL-README.txt
23/01/2014 17:33 4,752 README.txt
8 File(s) 353,728 bytes
12 Dir(s) 24,989,622,272 bytes free
For some reason it is printing out the contents of the current directory as well as the specified one. If you set options(1) to a non-null, valid argument (eg options(1)="/n"), it works, but according to the docs: “You can specify a null argument as "" “, so null should work.
Using a comma-delimited list of arguments works fine, even with a null arg. Eg w $ZF(-100,"/SHELL /STDERR=""NUL"" /STDOUT="_dev,com,",e:\nbupg\webserver\")
Unix:
set dev="dir.txt"
set com="ls"
set options(1)="-l"
set options(2)="/rbts/webserver"
w $ZF(-100,"/SHELL /STDERR=""NUL"" /STDOUT="_dev,com,.options)
Expected result (output of ls -l /rbts/webserver)
total 84
-r--r--r-- 1 bin bin 11404 Oct 12 2016 GETTING_STARTED
drwxr-xr-x 2 bin bin 1024 Mar 23 08:47 LICENSES
drwxr-xr-x 2 bin bin 1024 Mar 23 08:48 bin
drwxr-xr-x 3 bin bin 1024 Mar 23 08:47 build
drwxr-xr-x 2 bin bin 1024 Mar 23 08:47 cgi-bin
drwxr-xr-x 6 bin bin 1024 Mar 23 08:48 conf
drwxr-xr-x 3 bin bin 1024 Mar 23 08:47 error
drwxr-xr-x 4 bin bin 1024 Mar 23 08:48 hpws_docs
drwxr-xr-x 5 www other 2048 Mar 23 08:47 htdocs
drwxr-xr-x 3 bin bin 4096 Mar 23 08:47 icons
drwxr-xr-x 2 bin bin 5120 Mar 23 08:47 include
drwxr-xr-x 5 bin bin 1024 Mar 23 08:47 lib
drwxr-xr-x 2 www other 1024 Jul 3 05:46 logs
drwxr-xr-x 4 bin bin 96 Mar 23 08:47 man
drwxr-xr-x 14 bin bin 6144 Mar 23 08:47 manual
drwxr-xr-x 2 bin bin 4096 Mar 23 08:47 modules
dr-xr-xr-x 4 bin bin 96 Mar 23 08:47 newconfig
dr-xr-xr-x 3 bin bin 96 Mar 23 08:47 old
drwxr-xr-x 2 bin bin 96 Mar 23 08:47 util
drwxr-xr-x 6 bin bin 96 Mar 23 08:48 webproxy
This is correct, but strangely for Unix the comma-delimited list doesn’t work. Eg:
RBTS>w $ZF(-100,"/SHELL /STDERR=""NUL"" /STDOUT="_dev,com,"-l,/rbts/webserver")
2
(dir.txt is empty)
Also, for some reason Studio is giving me an error message in the log when using .options:
ERROR: Interface.cls(1189) :Illegal number literal options
Not sure why – my use of it seems identical to that in the documentation example. My studio version is Ensemble 2017.1.1.111.0
Null arg is not the same as an empty string arg (""), as usual in COS. Therefore the settings of
set options(1)=""
made your first argument an empty string, and the whole command behaved as
dir "" e:\nbupg\webserver\
Didn't check your *nix version, just noticed that "NUL" should be spelled as "/dev/null".
P.S. May I ask you in turn :):
Why did you undertake this task, changing of $zf(-1) to $zf(-100), at all? Do you clearly understand the kind of treat you try to eliminate?
yes, but the docs for $ZF(-100) say
https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls...
so I would expect the function to pass this to the current shell as a viable null argument (or maybe not at all)
we have been specifically instructed by intersystems to do this. $ZF(-1) is deprecated also. I can think of a few ways you could exploit the existing function but I think it's probably not good for me to say them in a public forum :) it's used so infrequently in our code (and mostly legacy stuff) that's easier to just replace them all than work out if we have any specific vulnerabilities.
It seems that the docs is ambiguous here as it's not clear when one can use "" as a <null> value: in comma separated options list only, or in options array as well.
As to possible ways to exploit $zf(-1), there is some clue in ISC's announcement. It can be compromised if its arguments come from user input. Similar vulnerabilities are usually associated with dynamic SQL, not only in Caché. Other (Caché specific) samples: Xecute, $Xecute, argument indirection. This stuff is well-known, is it a secret for anybody?
It seems that if we never use such coding style, we are safe enough. As to our company's code base, we rarely use $zf(-1), and all its usage is encapsulated in a couple of class methods.
We'll follow ISC's security recommendations, as we always do, while I don't feel myself comfortable when I don't understand the reasons of doing something. "Don't repair, if it works", as it was said by some wise man. Does it need any comment?
to be clear i understood the risk perfectly, but it's trivial to replace $ZF(-1) calls with the equivalent $ZF(-100) command. so i elected to just do it rather than spend longer doing analysis into whether a specific instance poses a security risk :) our code base has a lot of MUMPS in it, predating cache.
Agree, it's trivial in most cases, except that one, when there is a series of commands depending on previous ones, e.g. (not from the production code))):
set rc=$zf(-1,"[ -f /etc/environment ] && . /etc/environment && export TZ")
$zf(-1) allows to execute such series at the whole, while $zf(-100) needs to split it into parts, moving checkup logic to COS code. It's trivial as well, but ruins the idea of (semi-)formal substitution of $zf(-1) calls with $zf(-100) ones.