The macro decided an aborted call by scanning the child's response text for a sasjsAbort object and then recovering the message from that text - anchors for the separator, gating on whether the payload was escaped, cuts for the log extract, and a capture width that had to be widened more than once. Every one of those steps existed to undo the escaping in the response. Core now publishes the abort reason in outstatus.errmsg for both the SASJS and Viya branches, with the whole message and no truncation, so the macro reads the row instead: STATE says whether the child was canceled, ERRMSG is the reason. The cut at the appended log extract is the only thing left, because that text is part of the message the child raised; it is stripped as characters, as an escaped pair or as a doubled pair, whichever the producer wrote. The macro therefore takes outstatus= rather than outref=, and the call sites in the service tests now pass the status dataset they already asked %mpe_execute for. stagedata.test.4's two aborting calls had no status row at all and now have one. A platform that reports the state but no message - SAS Viya until core fills errmsg from the job result - is judged on the state alone, so the same tests run on both platforms. @mpe_assert_abort.test covers the twelve cases directly against status rows instead of building response files, including the doubled separator the estate actually writes, a message past the old 256 character width, quotes and backslashes. Requires @sasjs/core 7.0.2, which carries the SASJS half of this.
185 lines
4.6 KiB
SAS
185 lines
4.6 KiB
SAS
/**
|
|
@file
|
|
@brief testing getdiffs service - input validation (security)
|
|
@details The libds, table and stp_diffs_csv request params must be
|
|
well-formed before they reach the access check and the staging file
|
|
path. An invalid value aborts the service, which shows up as a
|
|
canceled child job (an aborted service registers no webout).
|
|
|
|
A real load is staged first (stagedata) and a diffs csv written into
|
|
the staging directory, so every payload below resolves to that REAL
|
|
file when executed - on a vulnerable service the job completes, and
|
|
only the validating service cancels it. The assertions cannot pass
|
|
against a service that does not validate.
|
|
|
|
<h4> SAS Macros </h4>
|
|
@li mpe_assert_abort.sas
|
|
@li mp_assert.sas
|
|
@li mpe_execute.sas
|
|
@li mx_execute.sas
|
|
@li mf_getuniquefileref.sas
|
|
|
|
**/
|
|
|
|
%let _program=&appLoc/services/auditors/getdiffs;
|
|
|
|
/**
|
|
* Stage a real load so a real staging directory exists
|
|
*/
|
|
data work.sascontroltable;
|
|
action='LOAD';
|
|
message="getdiffs test prep";
|
|
libds="&dclib..MPE_X_TEST";
|
|
output;
|
|
stop;
|
|
run;
|
|
|
|
proc sql noprint;
|
|
select max(primary_key_field) into: maxpk
|
|
from &dclib..mpe_x_test;
|
|
quit;
|
|
|
|
data work.jsdata;
|
|
set &dclib..mpe_x_test(rename=(
|
|
some_date=dt2 SOME_DATETIME=dttm2 some_time=tm2)
|
|
);
|
|
some_date=put(dt2,date9.);
|
|
SOME_DATETIME=put(dttm2,datetime19.);
|
|
some_time=put(tm2,time.);
|
|
drop dt2 dttm2 tm2;
|
|
if _n_=1 then do;
|
|
_____DELETE__THIS__RECORD_____='No';
|
|
some_char='getdiffs security test';
|
|
some_num=&maxpk+1;
|
|
end;
|
|
else stop;
|
|
run;
|
|
|
|
%mx_execute(&appLoc/services/editors/stagedata,
|
|
viyacontext=&defaultcontext,
|
|
inputdatasets=work.jsdata work.sascontroltable,
|
|
outlib=webstage,
|
|
mdebug=&sasjs_mdebug
|
|
)
|
|
|
|
%let stagetest=0;
|
|
data _null_;
|
|
set webstage.sasparams;
|
|
putlog (_all_)(=);
|
|
if status='SUCCESS' then call symputx('stagetest',1);
|
|
call symputx('loadref',dsid);
|
|
run;
|
|
|
|
%mp_assert(
|
|
iftrue=(&stagetest=1 and &syscc=0),
|
|
desc=stagedata succeeded in getdiffs prep,
|
|
outds=work.test_results
|
|
)
|
|
|
|
/**
|
|
* Write the diffs csv into the real staging directory
|
|
*/
|
|
%let diffscsv=tempDiffs_secrev.csv;
|
|
data _null_;
|
|
file "&dc_staging_area/&loadref./&diffscsv";
|
|
put 'SOME_CHAR,_____STATUS_____';
|
|
put 'getdiffs security test,UPDATED';
|
|
run;
|
|
|
|
/**
|
|
* Test 1 - the mpe_accesscheck SQL injection payload in libds must
|
|
* abort the service (validation fires before the authz query, so
|
|
* the authz bypass cannot happen). The payload is sent through the
|
|
* sasjs table channel (BrowserParams) like the frontend does - the
|
|
* raw-quote form is masked in plain URL params on this platform.
|
|
*/
|
|
%let fb1=%mf_getuniquefileref();
|
|
data _null_;
|
|
file &fb1 termstr=crlf;
|
|
length _row $400.;
|
|
put 'TABLE:$41. STP_DIFFS_CSV:$100. libds:$41.';
|
|
_row=cats(symget('loadref'),',',symget('diffscsv'),',',
|
|
'SOMELIB.SOMEDS',"'22'x"," or ","'22'x",'1',"'22'x",' ne ',"'22'x",'2');
|
|
put _row;
|
|
run;
|
|
|
|
%mpe_execute(&_program,
|
|
viyacontext=&defaultcontext,
|
|
inputfiles=&fb1:BrowserParams,
|
|
outref=web1,
|
|
outstatus=work.ab1,
|
|
failfast=0,
|
|
viyaresult=WEBOUT_TXT
|
|
)
|
|
|
|
%mpe_assert_abort(
|
|
desc=SQL injection payload in libds aborts the service,
|
|
outstatus=work.ab1,
|
|
msg=Invalid libds
|
|
)
|
|
|
|
/**
|
|
* Test 2 - path traversal in table must abort the service (the
|
|
* payload resolves to the real staged file through a .. detour)
|
|
*/
|
|
data _null_;
|
|
length _dir $512;
|
|
_dir=scan(symget('dc_staging_area'),-1,'/');
|
|
call symputx('travtable',cats('../',_dir,'/','&loadref'));
|
|
run;
|
|
|
|
%let fb2=%mf_getuniquefileref();
|
|
data _null_;
|
|
file &fb2 termstr=crlf;
|
|
put 'TABLE:$41. STP_DIFFS_CSV:$100. libds:$41.';
|
|
put "&travtable.,&diffscsv.,&dclib..MPE_X_TEST";
|
|
run;
|
|
|
|
%mpe_execute(&_program,
|
|
viyacontext=&defaultcontext,
|
|
inputfiles=&fb2:BrowserParams,
|
|
outref=web2,
|
|
outstatus=work.ab2,
|
|
failfast=0,
|
|
viyaresult=WEBOUT_TXT
|
|
)
|
|
|
|
%mpe_assert_abort(
|
|
desc=Path traversal in table aborts the service,
|
|
outstatus=work.ab2,
|
|
msg=Invalid table
|
|
)
|
|
|
|
/**
|
|
* Test 3 - path traversal in stp_diffs_csv must abort the service
|
|
*/
|
|
%let fb3=%mf_getuniquefileref();
|
|
data _null_;
|
|
file &fb3 termstr=crlf;
|
|
put 'TABLE:$41. STP_DIFFS_CSV:$100. libds:$41.';
|
|
put "&loadref.,../&loadref./&diffscsv.,&dclib..MPE_X_TEST";
|
|
run;
|
|
|
|
%mpe_execute(&_program,
|
|
viyacontext=&defaultcontext,
|
|
inputfiles=&fb3:BrowserParams,
|
|
outref=web3,
|
|
outstatus=work.ab3,
|
|
failfast=0,
|
|
viyaresult=WEBOUT_TXT
|
|
)
|
|
|
|
%mpe_assert_abort(
|
|
desc=Path traversal in stp_diffs_csv aborts the service,
|
|
outstatus=work.ab3,
|
|
msg=Invalid stp_diffs_csv
|
|
)
|
|
|
|
/**
|
|
* dump results to the log for offline inspection
|
|
*/
|
|
data _null_;
|
|
set work.test_results;
|
|
putlog 'TEST_RESULT_LINE: ' test_result ' - ' test_description;
|
|
run;
|