Files
dc/sas/sasjs/services/auditors/getdiffs.test.sas
T
dc 64383571b2
Build / Build-and-ng-test (pull_request) Successful in 5m5s
Lighthouse Checks / lighthouse (pull_request) Successful in 17m46s
Build / Build-and-test-development (pull_request) Successful in 30m28s
refactor(mpe_assert_abort): read the abort message from the status row
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.
2026-10-31 14:34:56 +00:00

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;