A clean SAP audit is a real achievement and it tells you less than you think. The standard checklist covers user master data, role design, segregation of duties, patch levels, password parameters, and a short list of profile parameters. All of that is worth doing. But the SAP security risks that actually get used in an attack tend to live in places the checklist never opens: code your own developers wrote, connections your basis team configured years ago, HTTP services somebody switched on for a project that ended, and a cloud layer that most SAP audit programs have not caught up with yet.
This post is about that gap. Not how to test for it, just what is sitting there.
At a glance
Custom ABAP is the largest unread codebase you own
Every SAP customer has a Z namespace, and almost nobody knows what is in it. The code was written under go-live pressure, patched during a rollout, extended by three different partners, and it has been running ever since. Nobody reviews it because it works.
The problems are not exotic. A report builds a WHERE clause by concatenating a selection screen field into a string and passes it to SELECT ... WHERE (lv_where). A utility takes a file name from a parameter and hands it to OPEN DATASET with no normalization, so ../../ walks out of the intended directory. A helper wraps an OS call and pipes user input into it. A background job carries a hardcoded service user and password in a constant, because it had to run unattended and nobody came back to fix it. Dynamic ABAP shows up too: GENERATE SUBROUTINE POOL and INSERT REPORT built from runtime values, which is arbitrary code execution with extra steps.
Two constructs from older code are worth naming, because both are legal ABAP and both read as harmless. CALL TRANSACTION 'SE38' WITHOUT AUTHORITY-CHECK skips S_TCODE by design. And SUBMIT on a report name assembled at runtime leans on S_PROGRAM, which depends on an authorization group in TRDIR that nobody has maintained since the object was created.
I will argue this one with anybody. A single missing AUTHORITY-CHECK in a Z transaction that reads PA0008 is worth more to an attacker than most of the segregation of duties conflicts your GRC tool reports every month. At a lot of companies the SoD tool has become the SAP security program, and it answers a compliance question rather than a security one. It tells you which two roles conflict. It cannot tell you that ZHR_SALARY_LIST never checks P_ORGIN at all, which makes the role design beside the point.
RFC destinations turn one compromised system into three
Open SM59 on production and read the destinations properly. Not the count, the content.
Type 3 destinations with a stored user and password are the standard way SAP systems talk to each other, and they are also a lateral movement map drawn by your own basis team. If the stored user in the PRD to BW destination has SAP_ALL, and someone reaches PRD, they reach BW as that user without needing a single credential. The direction that matters most is the one people get backwards: a destination stored in your development system pointing at production, with a service user that has more rights than the developers who can edit the destination. Development systems have loose transport controls, debug authorization and often shared accounts. They are the softest system in the chain and they frequently hold a key to the hardest one.
Trusted RFC removes the stored password and replaces it with a trust relationship maintained in SMT1 and SMT2. That is better, and it moves the question to S_RFCACL, an authorization object that is easy to grant too widely and absent from most role review reports. Watch for wildcards in the RFC user field.
The call side has its own gap. auth/rfc_authority_check controls whether the S_RFC check runs on inbound RFC calls, and in most systems S_RFC is still granted on the function group rather than the individual function module. A role that grants S_RFC for one convenient function group grants every remote-enabled module inside it, including the ones nobody meant to expose. When you inventory RFC-enabled function modules in custom code, the release state and the RFC scope on each module matter more than the module name.
ICF services that outlived the project that needed them
The ICF tree in SICF is where SAP systems accumulate attack surface without anyone noticing. Services get activated for a Fiori pilot, a Web Dynpro application, a SOAP interface, an integration test. The project finishes. The service stays active.
Pull the active list and ask a different question about each node than the usual one. Not whether it is dangerous, but which project switched it on and whether that project ended. The answers cluster. /sap/bc/soap/rfc and /sap/bc/gui/sap/its/webgui trace back to a remote access requirement from a decade ago. /sap/bc/adt traces to a developer tooling rollout. Anything under /sap/bc/bsp/sap/ usually traces to a partner who has since left. Each is defensible in the right context. Almost nobody can explain, service by service, why the active ones are active.
The same pattern repeats one layer out. Cloud Connector access control lists exist to expose specific paths to specific BTP subaccounts. When someone configures a resource with path / and the “path and all sub paths” option, the entire ICF tree of that backend becomes reachable from the cloud side. That single setting undoes the segmentation the rest of your architecture assumes.
Authorization checks that run at the wrong moment
A missing check is easy to explain to management. A check that runs in the wrong place is harder, and it is the reason a role review and a code review cannot stand in for each other.
An AUTHORITY-CHECK means something only if it runs before the data is used and if the result is evaluated. Three failure shapes turn up constantly in custom ABAP:
- The check runs after the
SELECT. The developer reads the table, then checks authorization, then displays. Ifsy-subrchandling is sloppy, the data is already loaded and often already on the screen or in the spool. sy-subrcis never tested. The check statement is present, the return code is ignored, and the code continues either way. It passes a visual review because the keyword is there.- The check runs on the wrong values. The program checks activity
03for display and then performs an update, or checks a company code the user selected rather than the one in the record it changes. The check exists, it sits in the right place, and it authorizes something other than what happens next.
S_TCODE is checked when a transaction starts and not again. Anything the program does afterward, including calling other programs, reading tables directly with no S_TABU_NAM check, or writing files, is governed only by checks the developer chose to write. This is why “the role is correct” is not an answer. The role is an input to a check that may not exist.
Nobody owns the SAP security risks in BTP
Ask for the last SAP security assessment report at most enterprises and you will find no mention of Cloud Foundry, approuter configuration, integration flows, or API proxies. The SAP security risks on that side are ordinary application security problems, owned by neither the ABAP team nor the platform team, because each assumes the other has them.
Everything the approuter lets through is decided in a file, xs-app.json, that no SAP audit program asks for. A single route carrying "authenticationType": "none" is a public endpoint on a system the business describes as behind SSO, and nothing on the SAP GUI side will ever surface it. That is the ownership problem in one artifact: the people who could read that file are not the people who get asked for evidence.
Integration Suite is worse, because iFlows contain real code. A Groovy script step can run OS commands, open sockets, parse XML with external entities enabled by default, and write whatever it likes into the message log, including full payloads with PII or tokens. Credentials pasted into a script step instead of the Security Material store survive for years. On the API Management side, the pattern I see most is an authentication policy that is present in the bundle but attached under a condition, or a security policy set to continue on error. That is a control which reports success while letting the request through.
Reading artifacts is where the remaining SAP security risks live
What ties these together is that they live in artifacts, not in configuration tables. Source code, destination definitions, service nodes, route files, script steps. Audits are good at reading tables. They are bad at reading artifacts, because reading artifacts means reading thousands of them.
So automate the reading. RedRays pulls custom ABAP objects from the system over ADT and analyzes the source itself, which is what turns the artifacts in this post from unread into listed: the report with the concatenated WHERE clause, the utility that trusts a file name, the constant holding a service password, the function module released for RFC that checks nothing. A separate product covers the BTP side, where the artifacts are Cloud Foundry application configuration, integration flows including their Groovy scripts, and API Management proxies with their policies. Findings carry a severity, a CVSS score where the engine can compute one, and reports export to PDF and Excel.
Here is where the approach stops. Static analysis can tell you an AUTHORITY-CHECK is missing, and it can tell you one is present. It cannot reliably tell you the check authorizes the right thing. Whether ACTVT 03 is correct where the code goes on to update, whether the organizational field checked governs the record being changed, whether a check that looks redundant is load bearing: those are judgment calls that need somebody who knows the business process. The scan gets you to a short list. A person still has to work it, which is why we also do this by hand as an assessment or penetration test.
Start with the systems that hold a stored password to production. Then read your Z code.
This is the class of finding our scanner reads code to find
Missing and misplaced authorization checks, injection into dynamic ABAP, and hardcoded credentials are what the RedRays ABAP Code Scanner looks for in your own code, object by object.
Explore the ABAP Code ScannerRedRays is an independent SAP security vendor and is not affiliated with SAP SE. SAP and the product names referenced here are trademarks of SAP SE. Transaction codes, authorization objects and parameters named in this article are given for orientation; verify them against your own release before acting on them.




