Skip to main content
SAP Pentest Playbook
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage
Edit page

ABAP Custom Code Vulnerabilities (Z/Y Objects)

Description

Customer-developed Z/Y ABAP (reports, function modules, classes, BSP/Web Dynpro applications) frequently reintroduces vulnerability classes the SAP kernel and Open SQL already protect against - because the developer opted into a dynamic or native construct. A white-box code review with developer/read authorizations (S_DEVELOP etc.) hunts these sinks via SE38/SA38/SE80 source display, RS_ABAP_SOURCE_SCAN pattern search, a REPOSRC table dump (Base64-encoded DATA column - diffable against transport history to catch code that bypassed CTS entirely), SCI/ATC (SAP Code Vulnerability Analyzer, per SAP Note 1921820), or an offline SAST tool such as the OWASP ABAP Code Scanner. Every finding below is customer-reproducible: the review targets the customer’s own custom code, not an SAP-shipped 0day.

Risk

Depending on the sink, impact ranges from information disclosure to full remote code execution at the ABAP-VM layer (GENERATE SUBROUTINE POOL, dynamic dispatch), the OS layer (CALL 'SYSTEM', SXPG_*), or the database layer (EXEC SQL/ADBC, which bypasses all SAP-layer protections - Open SQL buffering, table-change logging, and ABAP authorization checks). Missing or broken AUTHORITY-CHECK - the single most common finding class - lets any user who can reach the transaction, report, or RFC-enabled function module perform the action regardless of SAP role. Hard-coded secrets and sy-uname-gated backdoors enable lateral movement and persistent, review-resistant access. Because custom code runs with the full privileges of the calling work process and its DB connection, these findings routinely compromise confidentiality, integrity, and availability well beyond the object where they were found.

Options

Discovery workflow (lead with the tool, not manual reading):

  • RS_ABAP_SOURCE_SCAN (via SE38) - pattern-based source scan across custom Z/Y programs/includes/function groups; the primary discovery tool for every sink below.
  • REPOSRC - raw table dump of all ABAP source (Base64 DATA column); diff against transport history to catch code written directly to the table, bypassing CTS.
  • SCI / ATC (Code Inspector / ABAP Test Cockpit) - automates many of the same pattern checks via the SAP Code Vulnerability Analyzer (SAP Note 1921820).
  • OWASP ABAP Code Scanner (RedRays) - free, offline SAST for exported ABAP source; published check categories: Injection (XSS/SQLi/directory traversal), Secrets (hard-coded credentials/keys), Cryptography (weak/outdated algorithms), Best practices (insecure dynamic statements).
  • #EC CI* pseudo-comment density - marks a suppressed CVA/ATC finding; treat clustering around injection-class checks as requiring manual re-verification.

Sinks and their secure alternative:

Sink (search pattern)ClassSecure alternativeCWE
SELECT (dyn_cols) FROM (dyn_table) / dynamic WHERE (dyn_cond) / INSERT INTO (dyn_table) / DELETE FROM (dyn_table)SQL injection (dynamic Open SQL)Allow-list via CL_ABAP_DYN_PRG=>CHECK_TABLE_NAME_STR / CHECK_WHITELIST_TAB; never build table/field/WHERE text from unrestricted inputCWE-89
EXEC SQL … ENDEXEC / dynamic CL_SQL_STATEMENT/CL_SQL_CONNECTION (ADBC)Native SQL / ADBC injection - bypasses all Open SQL protections, authorization checks, and change loggingAvoid Native SQL for application logic; bind variables (:host_var) only, never dynamic statement textCWE-89
CALL 'SYSTEM' (undocumented kernel call)OS command injection - RCE as <sid>admNever use in custom code; disable via rdisp/call_systemCWE-78
SXPG_COMMAND_EXECUTE / SXPG_STEP_XPG_START / SXPG_CALL_SYSTEM with a dynamically built command/parameterOS command injectionSM69-defined fixed commands only, no free-text parameter passthrough; enforce S_LOG_COM/S_C_FUNCTCWE-78
CALL 'ThWpInfo' ID 'OPCODE' … (Linux-only kernel call)OS command injection, backdoor-gradeNever legitimate in custom code; treat any occurrence as a backdoor findingCWE-78
GENERATE SUBROUTINE POOL <itab> NAME <subpool> where <itab> is populated from user/RFC inputABAP code injection - arbitrary source executed with no further authorization checkNever generate/execute code from external input; sandbox and strictly whitelist syntax if unavoidableCWE-94
INSERT REPORT <name> FROM <itab> (after READ REPORT/EDITOR-CALL)Persistent backdoor - writes executable code directly to REPOSRC, bypassing the transport systemRestrict S_DEVELOP OBJTYPE=PROG ACTVT=01/02; never reachable from a user-controlled program-name/source parameterCWE-94
Dynamic PERFORM … IN PROGRAM (…) / CALL METHOD (…) / CALL FUNCTION (…) with an unsanitized target nameABAP code injection - arbitrary subroutine/method/FM dispatchStatic dispatch, or validate the target name against a hard-coded allow-listCWE-94
Commented-out source reassembled at runtime via READ REPORT + SHIFT/REPLACE + INSERT REPORT/GENERATE SUBROUTINE POOLSteganographic backdoor - passes casual review since comments are skippedBan self-modifying-code patterns via secure-coding guideline; ATC custom check for READ REPORT + INSERT REPORT co-occurrenceCWE-94 / CWE-912
OPEN DATASET <dyn_path> FOR OUTPUT/INPUT / TRANSFER … TO <dyn_path> / READ DATASET <dyn_path>Directory traversal - arbitrary application-server file read/writeFILE_VALIDATE_NAME (SAP Note 1497003) plus logical file names (FILE/SFILENAME) instead of raw physical paths; AUTHORITY-CHECK on S_DATASET/S_PATH before every file operationCWE-22
Sensitive logic with no AUTHORITY-CHECK at allMissing authorization checkAdd AUTHORITY-CHECK OBJECT for every sensitive business object and branch on sy-subrcCWE-862
AUTHORITY-CHECK present but sy-subrc never evaluatedBroken authorization check - silent no-opAlways branch on sy-subrc <> 0 immediately after the checkCWE-862
AUTHORITY-CHECK … FOR USER <var> with attacker-influenceable <var>Authorization decision made for the wrong userNever parameterize FOR USER from external inputCWE-863
IF sy-uname = '<hardcoded_user>' gating a security-relevant branch (e.g. skipping AUTHORITY-CHECK, enabling EDITOR-CALL/INSERT REPORT)sy-uname backdoor - a named account bypasses controls normal users faceNever branch security-relevant logic on sy-uname; a critical finding regardless of stated intentCWE-912
Dynamic CALL TRANSACTION <dyn_tcode> without the WITH AUTHORITY-CHECK additionInsecure CALL TRANSACTIONAlways use CALL TRANSACTION … WITH AUTHORITY-CHECKCWE-862
Remote-enabled custom Z/Y function module with no AUTHORITY-CHECK for the business objects it touchesMissing authorization in an RFC-enabled FM - reachable by any caller with generic S_RFC (see Critical RFC-Enabled Function Modules)Mandate AUTHORITY-CHECK inside every remote-enabled custom FM; scope S_RFC/S_RFCACL tightly rather than relying on an over-privileged interface userCWE-862
Custom report executable via SA38/SE38 with no P_GROUP authorization group / no dedicated transactionAccess-control bypass of the transaction layerAssign an S_PROGRAM authorization group, wrap in a dedicated tcode (SE93), and restrict direct SA38/SE38 execute access to developersCWE-284
Hard-coded password/API-key/username literal (CALL FUNCTION 'RFC_...', HTTP client setup, CONNECT TO)Hard-coded secrets - readable via S_DEVELOP source display or, via REPOSRC, even without ABAP authorizationsRetrieve from Secure Storage (CL_SECSTORE_CONFIG/SSF) or a secrets manager instead - see Decrypting SAP Secure Storage for how attackers extract what’s stored there; never hard-codeCWE-798
User-controlled value (e.g. cl_wd_runtime_services=>get_url_parameter) written to BSP/Web Dynpro/ITS output without encodingReflected/stored XSSEncode all dynamic output with ABAP’s ESCAPE() function (or the BSP/Web Dynpro output-encoding API); treat every URL/form parameter as untrustedCWE-79
Hard-coded BREAK-POINT left in production code, or broad S_DEVELOP OBJTYPE=DEBUG ACTVT=02 (“debug with replace”) on non-emergency rolesDebugger exposure - debug-with-replace can directly overwrite variable contents mid-transaction, including sy-subrc right after a failed AUTHORITY-CHECKRemove all BREAK-POINT statements before transport; restrict debug-with-replace to a monitored firefighter/emergency-user process; NW 7.57+ offers the finer-grained S_DBG objectCWE-489

Mitigation

  • Adopt a secure ABAP coding guideline that explicitly covers every sink class above, and enforce it via mandatory SCI/ATC (SAP Code Vulnerability Analyzer) checks before transport release.
  • Treat #EC CI* suppression clusters - especially around injection-class checks - as requiring documented, reviewed justification, not a rubber stamp.
  • Run the OWASP ABAP Code Scanner (or equivalent offline SAST) over exported source as a CI gate, complementing SCI/ATC.
  • Never build dynamic Open SQL, Native SQL/ADBC statement text, OS commands, or generated ABAP source from unsanitized input; use the allow-list/bind-variable alternatives in the table above.
  • Mandate AUTHORITY-CHECK with explicit sy-subrc branching everywhere, including inside remote-enabled custom function modules; scope S_RFC/S_RFCACL tightly instead of relying on broad interface-user grants.
  • Store secrets in Secure Storage or a secrets manager, never hard-coded; rotate any credential found during a review.
  • Restrict S_DEVELOP OBJTYPE=DEBUG ACTVT=02 to a monitored, change-controlled process; strip all BREAK-POINT statements before go-live.
  • See ABAP Code Review Process for the review workflow this catalog feeds into, and Code Verification for confirming whether a found backdoor is live - this page is about finding vulnerable/backdoored code, not verifying an implant.

References