Question
· Jul 4, 2018

$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

Discussion (5)2
Log in or sign up to continue

Using a comma-delimited list of arguments works fine, even with a null arg

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?

Null arg is not the same as an empty string arg (""), as usual in COS.

yes, but the docs for $ZF(-100) say

You can specify a null argument as ""

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)

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?

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.