"All that is required for evil to prevail is for good men to do nothing." -- Edmund Burke
| 1 macro (bad) dummy dummy %
ERROR: Old-style macro name ( must contain only letters, digits, and underscores. |
In this next example I have got the syntax correct and you can see what
the old-style macro is doing.
| 2 macro naughty DUMMY DUMMY %
3 %put >>>>>> You are a naughty hacker >>>>>>>; >>>>>> You are a DUMMY DUMMY hacker >>>>>>> |
Can you see that it has found the word "naughty", which is the macro
name, and has substituted that word with what follows "naughty" in the
macro definition. It's substitution powers are limited. This is really
important to know. See what happens when I put "naughty" in quotes.
No substitution is made.
| 1 macro naughty DUMMY DUMMY %
2 %put >>>>>> You are a naughty "naughty" hacker >>>>>>>; >>>>>> You are a DUMMY DUMMY "naughty" hacker >>>>>>> |
We will use that bit of good news (i.e. the substitution is not made inside quotes) later.
Old-style macros are limited to 8 characters in length. You will see
that the following attempt at a substitution does not work. Note that sas
did not produce any diagnostics to say that the name was too long.
| 4 macro mendacious DUMMY DUMMY %
5 %put >>>>>> You are a mendacious hacker >>>>>>>; >>>>>> You are a mendacious hacker >>>>>>> |
Substitutions are made for words and not part of a string. Here is the
"naughty" example again but with "naughty" inside "z"s. No substitution
is made.
| 6 macro naughty DUMMY DUMMY %
7 %put >>>>>> You are znaughtyz hacker >>>>>>>; >>>>>> You are znaughtyz hacker >>>>>>> |
Now we come to the part where we substitute a name with another one
with a different meaning. Firstly, here is the output with no old-style
macro substitution being made. The result is as expected.
| 5 %put >>>>>> %sysfunc(trim(hacker)) >>>>>>>;
>>>>>> hacker >>>>>>> 6 %put >>>>>> %trim(hacker) >>>>>>>; >>>>>> hacker >>>>>>> 7 run; |
Now an old-style macro makes a substitution. Look at the output carefully.
Do you see that the first "trim" has been substituted but the send "trim"
not. Old-style macros will not substitute for macro names. This
is important.
| 8 macro trim reverse %
9 %put >>>>>> %sysfunc(trim(hacker)) >>>>>>>; >>>>>> rekcah >>>>>>> 10 %put >>>>>> %trim(hacker) >>>>>>>; >>>>>> hacker >>>>>>> 11 run; |
In the above case the %trim macro had already been complied. Now what
I will do is save the code, quit the sas session, restart it and rerun
it. This is what I got.
| 1 macro trim reverse %
2 %put >>>>>> %sysfunc(trim(hacker)) >>>>>>>; >>>>>> rekcah >>>>>>> 3 %put >>>>>> %trim(hacker) >>>>>>>; MACRO CALLED: reverse v1.0 NOTE: Autocall member, TRIM, has not been compiled by the macro processor. It may contain a macro syntax error or not define a macro with the same name as the member. To autocall this member again, set OPTION MRECALL. >>>>>> (hacker) >>>>>>> 4 run; |
The difference in the above case is that as the %trim macro is being compiled, the old-style macro is acting on it before the compilation stage and has replaces the name of the macro "trim" with "reverse" and so the macro name is not the same as the autocall program name and so a compilation error occurs.
You are not encouraged to use old-style macros. You should never use them, in fact, for any new work you do but you may have to maintain some old code where they are used and you might be confused as to how they work. If you want to know more about these old-style macros and their syntax then follow this link on the sas support site.
Hopefully you now know that there exists a different style of macro than the ones you are used to, called "old-style macros" and you can see how they work and their limitations. Next you will see how they can be used for hacking.
| options nomprint nosymbolgen nomlogic nomacrogen nospool nosource nosource2 nonotes; |
Note that some of the option names above are 8 characters or less. They
are ripe for being substituted by old-style macros. Suppose the hacker
uses this code before calling this precious macro.
| macro macrogen nomacrogen %
macro nomprint mprint % macro nomlogic mlogic % macro nospool spool % macro nosource source % macro nonotes notes % |
Let's try this out and look at the MPRINT option setting. Do you see that MPRINT is in effect even though, within the macro, NOMPRINT was specified. I checked in sashelp.voption and indeed, MPRINT is in effect and not NOMPRINT. Macro hacked! Do you see how easy it is?
And look at the hacker turning macrogen into nomacrogen.
Why are they doing that? The answer is that "macrogen" is for old-style
macros and shows where these old-style macros are being used. The hacker
does not want the user to see this. If they want some real macro code then
they will want the MPRINT option setting.
|
1 macro nomprint mprint % 2 macro nomlogic mlogic % 3 macro nospool spool % 4 macro nosource source % 5 macro nonotes notes % 6 7 options nomprint nosymbolgen nomlogic nomacrogen nospool nosource nosource2 nonotes; 8 9 %put >>> %sysfunc(getoption(mprint)) >>>>>; >>> MPRINT >>>>> |
The possibilities for using old-style macros to substitute words like
that are endless. You may think that a "compiled" macro is something that
can not be looked at. Well, unless you are using sas v9.2 or later with
the "secure" option for the macro compilation then all a person has to
do is to open the compiled macro in a text editor. Most of the code can
be seen. The hacker can decide exactly what substitutions will be of benefit
to them and make those substitutions if they so wish, so long as the words
are 8 characters or less. There is not much you can do about it. However,
remember that these old-style macros will not substitute macro names. They
will not make substitutions in other old-style macros either. So if you
have this in your macro code at the top of the code then these hacking
attempts are thwarted and will never give honest sas code a problem.
| macro nomprint NOMPRINT %
macro nomlogic NOMLOGIC % macro nospool NOSPOOL % macro nosource NOSOURCE % macro nonotes NONOTES % |
| 26 options macrogen;
27 28 macro nomprint mprint % 29 30 %let var1=nom; 31 %let var2=print; 32 33 options &var1.&var2; NOTE: The old-style macro NOMPRINT is beginning resolution. 34 + mprint NOTE: The old-style macro NOMPRINT is ending resolution. 35 36 run; 37 38 %put >>>>> %sysfunc(getoption(mprint)) >>>> ; >>>>> MPRINT >>>> |
Up until now you have very likely never heard of %listm so I
will start up a fresh sas session and see what I get when I call the macro.
| 1 %listm;
NOTE: Old-style macro directory is empty. |
Now I will set up an old-style macro that I hope will do a bit of good
hacking for me and call %listm again. I'll quit sas and start a new session.
| 1 macro nomprint mprint %
2 %listm; Old-style macro directory: NOMPRINT |
You can see that if the old-style macro directory is empty then we get
a "NOTE" statement of the above form with the word "empty" in it and if
it has members then it is not a "NOTE". The messages are written to the
log and we can redirect the log if we want to. We want to in this case.
Note that I have set up the macro variable "osmempty" (old-style macros
empty) to determine or not whether the macro library is empty.
| 3 macro nomprint mprint %
4 proc printto log="C:\spectre\osmacros.txt" new; 5 run; NOTE: PROCEDURE PRINTTO used (Total process time):
9
NOTE: The infile "C:\spectre\osmacros.txt" is:
Old-style macro directory:
18
|
The old-style macro library is not empty in the above case due to the
presence of the "nomprint" macro. I will now delete this macro, using the
%delete
macro, and rerun. This time the library is empty and this is reflected
in the value of the "osmempty" macro variable.
| 20 %delete nomprint;
21 proc printto log="C:\spectre\osmacros.txt" new; 22 run; NOTE: PROCEDURE PRINTTO used (Total process time):
26
NOTE: The infile "C:\spectre\osmacros.txt" is:
NOTE: Old-style macro directory is empty.
35
|
So it looks as though we have a way of thwarting the hacker. But then,
any hacker worth his salt will suspect you have such a check in place and
will attempt to negate both the %listm and the %mlist macros. I'll start
a fresh session, define the old-style macro, and attempt to cancel out
the %listm and %mlist macro.
| 1 macro nomprint mprint %
2 3 %macro listm; ERROR: Macro LISTM has been given a reserved name. ERROR: A dummy macro will be compiled. 4 %put NOTE: Old-style macro directory is empty.; 5 %mend listm; 6 %macro mlist; ERROR: Macro MLIST has been given a reserved name. ERROR: A dummy macro will be compiled. 7 %put NOTE: Old-style macro directory is empty.; 8 %mend mlist; 9 10 proc printto log="C:\spectre\osmacros.txt" new; 11 run; NOTE: PROCEDURE PRINTTO used (Total process time):
15
NOTE: The infile "C:\spectre\osmacros.txt" is:
Old-style macro directory:
24
|
The hacker has been foiled in the above case. They can not replace the macros %listm or %mlist with their own versions. It looks as though we have beaten the hacker so long as we test for old-style macros and abort the sas job if any are detected. If osmempty=1 we carry on, if osmempty=0 we abort, running %listm again to give us a list of the old-style macros used in attempting the hack.
This looks good! It looks like we have beaten the hacker. Have we
won? Have we beaten the hacker? Have we, have we? The answer is "maybe".
There is an attack point in your solution that the hacker can try to exploit.
That is the macro variable name you use to do the check -- "osmempty"
to be specific. Even if your macro is compiled, the hacker can find the
name of the macro variable you are using by opening up your compiled macro
in a text editor. And you have used the unique name "osmempty" that
is a perfect target for attack, especially because your macro variable
name is 8 characters or less and so is available to be substituted
using an old-style macro. They can try to substitute that name with another
macro variable name that they have set to "1" before calling your macro.
The hacker stands a chance here but will the hacker succeed? Let's see.
Here is the code followed by the log message.
| macro osmempty haha %
macro nomprint mprint % %let haha = 1; options mprint; %macro test;
%let osmempty = 0;
%put >>> osmempty = &osmempty >>>>; %if &osmempty = 1 %then %put It is safe to carry on;
>>> osmempty = 0 >>>>
|
The hack against the counter-hack should have worked in the above case but it didn't. But to be on the safe side, if you are going to use a macro variable in this way to detect the presence of old-style macros then you should take precautions and make it more than 8 characters long so that there is no way it can be substituted. "osmempty" was a stupid macro variable name to use due to it being 8 characters long or less. When you counter-hack, you have got to put yourself in the position of the hacker who is going to attempt to hack your counter-hack. Leave no vulnerabilities. Thoroughly attempt to hack your solution yourself in any way you can. Better still, get another competent programmer to attempt to hack your solution as it is human nature to be unaware of our own weaknesses and failings.
The answer is "no". There are data sets..................................... and then there are views.
| 18 options nonotes;
19 data class / view = class; 20 if _n_ eq 1 then call execute('options mprint;'); 21 set sashelp.class; 22 run; 23 24 options mprint; 25 26 %macro test(data=); 27 options nomprint; 28 %put >>>>>> Before data step: %sysfunc(getoption(mprint)) >>>>>>>>>>>; 29 data _null_; 30 set class; 31 run; 32 %put >>>>>> After data step: %sysfunc(getoption(mprint)) >>>>>>>>>; 33 %mend test; 34 35 %test(data=class); MPRINT(TEST): options nomprint; >>>>>> Before data step: NOMPRINT >>>>>>>>>>> 1 + options mprint; >>>>>> After data step: MPRINT >>>>>>>>> |
To negate this hack attempt, then if we allow Views, we must reset system
options to what they should be directly after the data step. I have added
"options nomprint" after the data step and this is what we get.
| 36 options nonotes;
37 data class / view = class; 38 if _n_ eq 1 then call execute('options mprint;'); 39 set sashelp.class; 40 run; 41 42 options mprint; 43 44 %macro test(data=); 45 options nomprint; 46 %put >>>>>> Before data step: %sysfunc(getoption(mprint)) >>>>>>>>>>>; 47 data _null_; 48 set class; 49 run; 50 options nomprint; 51 %put >>>>>> After data step: %sysfunc(getoption(mprint)) >>>>>>>>>; 52 %mend test; 53 54 %test(data=class); MPRINT(TEST): options nomprint; >>>>>> Before data step: NOMPRINT >>>>>>>>>>> 1 + options mprint; MPRINT(TEST): options nomprint; >>>>>> After data step: NOMPRINT >>>>>>>> |
In the above case we seemed to have solved the problem. The answer is
to reset system options after each data step where the foreign data comes
in first time. But what if there is a double hack attempt and the
View
contains an old-style macro definition? Here it is again but with an
old-style macro defined in the View. MPRINT is back again!!
| 55 options nonotes;
56 data class / view = class; 57 if _n_ eq 1 then call execute('options mprint; macro nomprint mprint %'); 58 set sashelp.class; 59 run; 60 61 options mprint; 62 63 %macro test(data=); 64 options nomprint; 65 %put >>>>>> Before data step: %sysfunc(getoption(mprint)) >>>>>>>>>>>; 66 data _null_; 67 set class; 68 run; 69 options nomprint; 70 %put >>>>>> After data step: %sysfunc(getoption(mprint)) >>>>>>>>>; 71 %mend test; 72 73 %test(data=class); MPRINT(TEST): options nomprint; >>>>>> Before data step: NOMPRINT >>>>>>>>>>> 1 + options mprint; macro nomprint mprint % MPRINT(TEST): macro nomprint MPRINT(TEST): mprint % MPRINT(TEST): options mprint ; >>>>>> After data step: MPRINT >>>>>>>>> |
Is there a lesson to be learned from this? Yes there is. That is, if you are allowing Views, to have data steps at the top of your macro where they are read in and then follow that with your old-style macros that thwart hack attempts followed by your desired system options or used %listm, as shown above, to abort your job if any old-style macros are detected.
What about banning the use of Views? It's up to you. I have macro
named %mtype which will test a
dataset and return VIEW or DATA - but be careful when you test for the
word VIEW. Maybe your hacker has added this to his armoury:
| macro view DATA % |
Maybe this one extra, old-style macro inside your own macro would be
a good idea or, instead, use %listm in the way described above.
| macro view VIEW % |
| %if "%mtype(dataset)" EQ "VIEW" %then %do; |
But the following is not safe:
| %if %mtype(dataset) EQ VIEW %then %do; |
And now for the anti-hack macro, which time alone will tell whether
I have made it hack-proof or not. It is simple enough to use. You should
use it right at the start of your macro code and then test the setting
of osMacrosExist and if it is set to 1 then exit the macro. But you have
got to think.The code below shows a very big mistake.
| %if "%mtype(dataset)" NE "VIEW" %then %goto allok; |
There is a huge exposure to hacking in the above code. The hacker will find out that you have a label named allok that you jump to if everything is OK and they maybe have the chance to jump to that point directly using a "call execute" hidden inside their view, if the view gets the chance to run. You have got to be cleverer than that. Test tight and jump right to the macro exit, if you think anything is wrong.
So here comes the anti-hacking macro. It should be the first line of code inside your production macro and you should very carefully code the aborting of the macro if old-style macros are determined to be in existence. Remember, the reason there are no comments in the macro is because comment lines can also be the target of hacking attempts.
If anyone manages to hack this macro then please send me details.
| %macro osMacrosExist;
%listm; macro filename filename % macro capturel capturel % macro temp TEMP % macro log log % macro printto printto % filename capturel TEMP; proc printto log=capturel; run; %delete filename capturel temp log printto; %listm; proc printto log=log; run; macro capturel capturel % macro infile infile % macro _infile_ _infile_ % macro index index % macro call call % macro symput symput % macro then then % macro do do % macro input input % macro put put % macro end end % macro data data % macro warning WARNING % macro clear clear % %global osMacrosExist ; %let osMacrosExist=0; data _null_; infile capturel; input; if index(_infile_,"Old-style") then do; if index(_infile_,"Old-style")=1 then do; call symput("osMacrosExist","1"); end; end; run; filename capturel clear; %delete capturel infile _infile_ index call symput then do input put end data warning clear; %mend osMacrosExist; |
Here is how I expect the %osMacrosExist macro to be used.
| %macro secretmacro;
%osMacrosExist; %if &osMacrosExist=1 %then %do; %put ERROR: Disallowed old-style macros have been detected and the list written to the log.; %let error=1; %goto error; %end; ...... etc. |
But don't forget the views. Check for views and abort if you find any. And don't forget to use double quotes around the word in the form "VIEW" to thwart the hacker's attempt to hack your solution.
I am very disappointed that the SAS Institute has chosen to cover up this weakness with SAS software. On the theme of the nuclear industry again, supposing a SAS system were in place to track the storage, location and movements of dangerous nuclear materials. Supposing this were controlled by read-only sas code. It would be the easiest thing in the world for a hacker to substitute the code, using old-style macros and views, to disguise the disappearance and theft of nuclear materials. And all done right under the noses of those who control the code, due to their being unaware of this weakness and the ease with which this weakness can be exploited.
Covering up a weakness in software does nobody any favours. Instead, it betrays an ignorance of the way hackers work and plays into their hands. Hackers have networks that share information about application or code weakness. Once one hacker knows then any interested hackers will know about it within days or hours. Not all hackers are up to mischief, however. If you look up "hacking" on Wiki you will learn that there are "white hat", "grey hat" and "black hat" hackers. A "white hat" hacker will attempt to find weaknesses in software and inform the software vendor about it and give them sufficient time to remedy the situation or to document the weakness so that IT staff can protect against it. If the software vendor refuses to do anything then the "white hat" hacker will document the weakness and explain how to combat hacking attempts. Hopefully, this document has achieved that.
Go back to the home page.
E-mail the macro and web site author.