Daniel Berlin on Security SAP Security, SAP Authorizations, IT Audit… and all the rest

27May 12

Find step users in SAP batch jobs

Hello all,
using personal users to execute batch job steps is not a good idea!
Of course it's convenient, but those users might vanish or their authorizations might change – but how to clean this mess up? Since SE37 does not allow you to select jobs by step user, one might try to use SE16 (→ table TBTCP) to find the affected jobs… which will drive you crazy, believe me!


This time, I'll provide you with another nice report to clean up your system and prevent any batch-related headache!


  • Create a new report in SE38 and paste this source code (don't forget to set a program authorization group).
  • In the selection texts:
    • Tick "dictionary reference" for R_JOBNAM, R_SDLUSR and R_USRNAM
    • Leave all other texts empty ("?...")

  • Activate & execute.


On the selection screen you can choose the:

  • job name, -scheduler and -status (like in SE37) and
  • step user name (initially set to your user name, but try DDIC or SAP*).

Submit your selection and you'll get a result similar to this:

The columns at your disposal are:

  • Job name — … self-explanatory.
  • Job no. — the internal ID of the job.
  • Scheduler — the user, who scheduled the job.
  • Job status — … self-explanatory.
  • Step no. — the step ID that matches your selection (the job might consist of more steps).
  • Rept. name — the report or command.
  • Name — the step user (green, if the user still exists; red otherwise)

As always in my reports, you can double-click on almost everything:

  • Job name — goto SE37 and show all matching jobs by name.
  • Job no. — show only the particular job in SE37.
  • Scheduler — open SU01 for the user, who scheduled the job.
  • Step no. — display the step list of the selected job.
  • Rept. name — jump to SE38.
  • Name — show the step user in SU01.


Red is dead, green is clean … unless it's a personal user ! 😎

28Apr 12

Find SAP tables without table authorization group

Hi people,
another common finding of audits is, that not all (of your customer-) tables are assigned to a table authorization group — allowing everyone with S_TABU_DIS and DICBERCLS = "&NC&" to access them. Therefore, you might want to take care of this…

(Certainly, this is not always a sensible check 🙄 – it depends on the data contained in the respective tables! … but anyway many auditors check for it.)

The challenge

The assignment of tables to authorization groups is defined in table TDDAT; but it only contains registered assignments. If a developer never assigned his/her table to an authorization group, it won't appear in it. So the problem is to find all tables without an entry in TDDAT plus all tables with an empty entry there. Furthermore, some "tables" are uninteresting, e.g. structures, help views and temporary or local tables, as they don't contain any sensible data.

Please, do not start Excel at this point… there is a better way!

The idea

Obviously we need a way to list all unprotected tables (no matter whether they're in TDDAT or not) and automatically exclude all table- (and view-) types containing uncritical or no data.

Therefore, we need to find DDIC objects, which…

  • have no entry in TDDAT or
  • are assigned to the authorization group "&NC&" (which has the same effect as no entry) or
  • are assigned to an empty authorization group (same effect) and
  • aren't internal tables or append structures (as they cannot contain data) and
  • aren't help views, structure views or append views (which do not contain real data as well) and
  • aren't tables for temporary data and
  • aren't local (i.e. not transportable) ← YMMV.

The solution

Fortunately, I was bored some time ago and programmed a report for this…

  • Create a new program in SE38:

  • Fill in the attributes:

… and choose a suitable authorization group (P_GROUP, marked red)!

  • Maintain the selection texts:

… and leave P_COUNT and P_EMPTY empty, tick "Dictionary reference" for the other ones.

  • Activate and execute it.


The report shows the following selection screen on execution:

On the first tab you can:

  • Select the range of Table names to inspect, e.g. "Z*".
  • Choose whether or not to Check tables for data (i.e. determine whether the number of rows is > 0)
    … which might heavily increase the runtime!
  • Decide whether to Show empty tables (if you've selected the previous option).

The second tab is pre-filled with sensible values (YMMV):

  • The Authorization group field is set to "&NC&" and empty values (only for tables that exist in TDDAT).
  • The Table category is set to exclude structures and append structures.
  • The View type field excludes help-, structure- and append-views as well as views without any type,
  • The Delivery class option excludes tables for temporary data.
  • The Package field is dynamically ❗ filled with all local packages on the system, the report runs on.

Now execute it and you'll get a result like this:

In the ALV header, you can see the number of tables matching your selection and the number without an authorization group.


  • Table name — … self-explanatory.
  • Short description — table description from the data dictionary.
  • AuGr — assigned authorization group (usually "&NC&" or empty).
  • Description — the authorization group's description from TBRGT.
  • Data — whether or not the table contains any data (only available if you ticked "Check table for data").
    If it contains a flash symbol, the check failed.
  • Cl.-spec. — whether or not the table is client specific.
  • Log — whether or not the table has "Log data changes" enabled in its technical settings.
  • Del. class — the delivery class, which might give you an indication to classify the table contents.
  • Package — the package the table belongs to (might point you to the "area" the table belongs to).
  • Tab. cat. — the type of the table (e.g. transparent -, cluster -, pool table or view).

Furthermore, you can double-click on the following fields:

  • Table name — jump to SE11.
  • Short description — inspect the selected table via SE16.
  • AuGr — open SE54.
  • Data — show the number of entries in the table.
  • Cl.-spec. — show description.
  • Log — show technical table settings.
  • Del. class — show all available delivery classes.
  • Package — open the package explorer.
  • Tab. cat. — show a list of table types.


Have fun, play around with the options and clean up your system! 😀

14Apr 12

Remove developer keys from productive systems

Dear reader,
a check, many auditors perform on your productive SAP systems is whether any developer keys exist (in table DEVACCESS). If there are any, this might become a finding you can easily avoid (… although your system is properly protected against changes in SCC4 and SE03 :roll:).

To delete all entries from DEVACCESS on production without touching the development system (and make your developers as well as the auditors happy)…

  • Launch SE14
  • Enter object name "DEVACCESS"

  • Select "Delete data", then click "Activate and adjust database"

  • Confirm

  • Mission accomplished

Note: This changes the activation log (go to SE11, choose DEVACCESS and click Utilities » Activation Log).

5Feb 12

SAP Web AS protection with an application firewall

Hello there,
this article is intended to demonstrate the potential of securing SAP webservices on the application level.
Concretely, we'll protect the SAP GUI for HTML (aka. WebGUI) from malicious login attempts via the internet using a web application firewall.

The challenge

Let's assume that we want to make a SAP webservice available for direct access from the internet – the SAP WebGUI in this example.

Of course, we are not interested in bad guys trying to break into our system; but usually they don't care about what we want.
The easiest way of getting access to a SAP system without knowing anything about it, is to try the standard users with a list of (well-known) passwords.

The users of choice for this are SAP*, DDIC and friends, so protecting them inside the system is a good idea – but no matter, how secure their passwords are, break-in attempts are always dangerous and should be prevented at the root!
Therefore, the protection method discussed in this article blocks all login attempts for the two users above before they reach the SAP system.

So our goal is to reject all logon requests for DDIC and SAP* from the internet.

Background — What's a web application firewall (WAF) ?

A WAF is a protection measure on the application layer, which applies a customizable set of rules to HTTP conversations and reacts to matches. It is usually operated behind a standard firewall (packet filter) as an additional security layer.

Below, we will use ModSecurity as our WAF, as it's a mature and flexible solution used in many installations around the world. It operates as a module inside the Apache web server and is able to inspect and process input from clients before passing it to the destination (i.e. a SAP webservice). It's even capable of intercepting the output before its transmitted back to clients (this way you could close information leaks due to buggy server-side applications). Btw.: it works for encrypted traffic too, because it has access to transaction data before SSL encryption and after SSL decryption. With these features, ModSecurity can mitigate many threats – if you supply it with appropriate rules.

This is what you need:

In the final setup, inbound requests to the SAP system will pass through two firewall instances (unless you go without a packet filter *cough*):

Setup (1) — Preparation

Before we can proceed to the interesting part, the required framework needs to be prepared.


  1. Set up an Apache HTTP server
  2. Secure it
  3. Install ModSecurity – as a precompiled package or by compiling it yourself

The first step is out of the scope of this article, but there's plenty of information on the internet (start here).
Since Apache serves as the "runtime environment" for ModSecurity, it's strategic to protect it. You can find tips this way.
And finally, if you are seeking assistance for the ModSecurity installation: the official reference manual is a good starting point.

Setup (2) — Reverse proxy configuration

Now, we're ready to direct all requests from the internet to the SAP Web AS through our Apache (» reverse proxying).


  1. Configure mod_proxy to point to your SAP server's ICM
  2. Adjust your firewall NAT rules to point to the Apache instead of the SAP server directly
  3. Test the new setup

A simple example of a working mod_proxy configuration looks like this:

ProxyPass        / http://sap-host:8000/
ProxyPassReverse / http://sap-host:8000/

Background — ICM logon procedures

Before we continue to the WAF setup, we have to look at the methods available for user authentication in SAP ICM, as this determines what aspect of the HTTP communication we have to examine.

The ICM offers several authentication methods, which SAP calls "Logon Procedures". The one used for a specific webservice can be customized in the "Logon Data" tab in SICF.
We'll deal with the two most basic ones – "Fields Authentication" and "Basic Authentication" – since they allow us to extract the username very easily.

Setup (3) — Web application firewall

Finally, we're ready for the interesting part!
First, ModSecurity needs to be provided with a reasonable base configuration (» official recommendation); after that, we can create our own rules using the "SecRule" directive.
Each SecRule has three parameters, the variable to inspect, the operator to use for the inspection and one or more actions. Long lines may be wrapped with a backslash.

Depending on the logon procedure, one of the following sets of rules is suited for the job…

Rules for the "Fields Authentication" logon procedure:

SecRule  REQUEST_FILENAME   "^/sap/bc/gui/sap/its/webgui" \
 SecRule ARGS_POST:sap-user "^(ddic|sap\*)$" "t:trim,t:lowercase,capture"

The first SecRule line inspects the REQUEST_FILENAME variable, which contains the relative request URL; in our case it is matched against the path of the "SAP GUI for HTML" webservice. The second line contains the associated actions and consists of several parts:

  • "phase:2" means that the rule is processed in the second communication phase (so the request body is available)
  • "t:normalizePath" is a function, which is applied to the variable before it is matched (» description)
  • "chain" means that this rule is chained to the next one – i.e. if it matches, the next rule is evaluated
  • "deny" makes ModSecurity intercept the request, if this rule (and the rest of the chain) matches
  • "log" activates logging and "logdata:'%{TX.1}'" adds more detail to the log entry (» explanation)

The second SecRule line tests the POST parameter "sap-user" against the regular expression specified in the operator and thus matches either "ddic" or "sap*".
The actions at the end have the following meaning:

  • "t:trim" removes leading and trailing whitespace from the "sap-user" parameter
  • "t:lowercase" converts the username to lowercase
  • "capture" saves the matched string (either "ddic" or "sap*") to the variable "TX.1"… which is then logged

If the second rule also matches, the final action from the first SecRule is executed: the connection is denied and a log entry is generated.

Rules for the "Basic Authentication" method:

SecRule    REQUEST_FILENAME              "^/sap/bc/gui/sap/its/webgui" \
 SecRule   REQUEST_HEADERS:Authorization "^Basic +([a-zA-Z0-9]+=*)$" "t:trim,capture,chain"
  SecRule  TX:1                          "^([^:]+):"                 "t:base64Decode,capture,chain"
   SecRule TX:1                          "^(ddic|sap\*)$"            "t:trim,t:lowercase"

The first SecRule is almost identical to the one used for "Fields Authentication" above. The only difference is the phase, the rule is executed in: "phase:1" means that the rule is processed in the first communication phase when the request headers are available (but the body isn't yet). This is sufficient, since the whole rule chain works with header data only – no need to wait for the body.

After the first rule matched (i.e. the request URL points to the WebGUI service), the second rule is processed. It inspects the "Authorization" HTTP header and matches it's value against a regular expression (» background information). The actual login data (which is a base64-encoded string containing the username and password) is braced and thus saved to the variable "TX.1" by the "capture" action. The "t:trim" function trims the header value before processing (just to be sure) and the "chain" action makes ModSecurity evaluate the next rule.
At this point, the request URL matched and the "Authentication" header's value has been saved in the variable "TX.1".

Rule #3 does not do any filtering, it just base64-decodes the authentication data in variable "TX.1" (via the "t:base64Decode" function), captures the result in the same variable and chains the next rule.

The last rule finally checks the extracted username against the list of forbidden ones. If it matches, the current request is denied immediately.

Mission accomplished 😎

Next steps

You might want to:

  • protect further standard users, such as SAPCPIC, TMSADM and friends
  • alternatively, you could adjust the rules to filter all users except for those in a specific namespace
  • create more rules to prevent bad things from happening to your other webservices
  • check the ModSecurity Core Rule Set, which provides a wealth of generic and useful rules
21Jan 12

SAP table authorizations

Hi folks,
in this post, we'll dive into the deep waters of SAP table authorizations.
Due to the extent of this topic, we need an…


  1. Standard S_TABU_* authorization checks
    1. Controlling access by authorization group
    2. Controlling access by table name
    3. Cross-client table maintenance
    4. Overview
  2. A deeper look at SE16, SM30 and friends
    1. Direct program invocation
    2. Redundant transactions
    3. Weak parameter transactions
  3. Other ways to get hold of table data
    1. Customer programs
    2. Function modules
    3. Debug with replace
    4. Transport requests
  4. Conclusion

1. Standard S_TABU_* authorization checks

Auditors often criticize extensive table authorizations and (shortly afterwards) security consultants are asked to check those table rights... So, let's see how SAP allows us to limit direct table access. "Direct", as we are dealing with the SAP standard tools for displaying table data on-screen in this article… not to be confused with business transactions that work with (the same) table data, too.

Therefore, the scope of this article is:

  • Standard table browsing and maintenance transactions: SE16, SE16N, SE17, SM30, SM31 etc.
  • Proxy-transactions like SPRO (which call the aforementioned ones internally)
  • SAP Query (SQVI, SQ01, …)

(btw.: the standard table authorizations checked in the above transactions are potentially checked in other tcodes as well, but that's another topic)

1.1. Controlling access by authorization group

The authorization object S_TABU_DIS controls access to tables via an Activity (ACTVT) in combination with a Table Authorization Group (DICBERCLS ≈ dictionary Berechtigungs-class… nice denglish, SAP :smile:).
Table Auth. Groups combine an arbitrary number of tables with a similar "purpose" or contents; e.g. the group "SC" ("RS: User control") includes hundreds of tables containing user master-, address-, audit- and similar data. Each table can only be assigned to a single Table Auth. Group.

You can check a table's group via SE54 or directly via SE16 (table TTDAT, field CCLASS):

The purpose of a Table Authorization Group (or at least its humble description) can be found in table TBRGT
(go for BROBJ = "S_TABU_DIS", BRGRU = <group> and check the BEZEI field):

The assignment of tables to Table Auth. Groups can be maintained in SE54 (or directly via SM30 for view V_DDAT_54).

So with S_TABU_DIS we're able to grant access "by purpose"… which sounds good and is in fact rather useful… but there are some downsides:

  • less than 20% of all tables are assigned to a Table Auth. Group on a recent IDES system… and the rest ? – all other tables automatically belong to the fallback group "&NC&" (= "not classified").
    If you want to check this yourself, just compare the number of entries in table DD02L (with AS4LOCAL = "A") to the number of entries in TDDAT (with CCLASS ≠ "&NC&" and ≠ "").
  • you always have to grant access to all tables linked to a group, which often feels like taking a sledgehammer to crack a nut. This is a major lack of granularity!

1.2. Controlling access by table name

In recent releases, SAP introduced a new authorization object, which finally offers fine-grained control: S_TABU_NAM ("Table Access via Generic Standard Tools"):

It allows granting access by individual tables names (wildcards are allowed, but only at the end of the TABLE value). S_TABU_NAM and is only considered, if a previous check for S_TABU_DIS failed (it's an additional check after S_TABU_DIS, not a replacement). Thus, it's now possible to grant display access to a complete Table Auth. Group while limiting maintenance rights to selected tables in that group.

Check out SAP Note 1481950, Note 1500054 and the related ones mentioned therein — strongly recommended.

1.3. Cross-client table maintenance

Client-independent tables are shared among all clients of a SAP system; modifications to their contents have an impact on the whole system. Hence, changes to them require special care and must only be allowed for users who know about the possible side-effects. SAP mitigated this problem by providing a supplementary authorization object, which is only checked when trying to maintain client-independent tables in addition to S_TABU_DIS and _NAM: S_TABU_CLI ("Cross-Client Table Maintenance"). The allowed values for its only field CLIIDMAINT are "X" (allowed) and " " (not allowed). Most client-independent tables are customizing tables, so the main audience for this right is administrators and supporters.

1.4. Overview

At this point, we can summarize the complete flow of authorization checks for table access:

  • first S_TABU_DIS is checked using the table's authorization group (or the dummy group "&NC&" if none is set)
  • only if this fails, S_TABU_NAM is checked for the table name
  • on success and in case the user wants to modify (not display) a client-independent table, an additional object – S_TABU_CLI – is checked

Update: in release 750, S_TABU_NAM is actually checked first – even though the check logic ("OR") is not affected by this; see this comment.

2. A deeper look at SE16, SM30 and friends

At this point, we took a look at the basic measures for table protection; it is time for the pitfalls one might want to avoid!
A common mistake, (overworked) authorization admins often make when they're asked to clean up table authorizations is to just remove SE16 etc. from S_TCODE but leave S_TABU_* as it is.

So, what is the problem with table authorizations without the corresponding tcodes ? Simple answer: many roads lead to Rome! — read on…

2.1. Direct program invocation

Transactions are nothing but a convenient way to execute programs, optionally passing parameters along.

Let's check via SE93, what exactly is behind SE16 and SM30 (check the other ones yourself):

  • SE16 calls SAPLSETB, which is a function pool (» function pools are not directly executable)
  • SE16N executes the report RK_SE16N (» executable)
  • SM30 and SM31 call SAPMSVMA – a module pool (» not directly executable; almost * )

This means, removing SE16N from S_TCODE, but allowing for instance SA38 or START_REPORT does not necessarily prevent direct table access.
It's not a big deal, since table authorizations are checked as usual… but keep in mind: removing the tcodes from S_TCODE has a limited effect unless you also take care of S_TABU_*, S_PROGRAM and S_DEVELOP (check SAP Note 1012066 for the last one)!

(*) Direct module pool execution is possible through a strange speciality in SE38: if a module pool name is entered in the first screen and then executed, SAP internally searches for tcodes using the same program ("SELECT * FROM tstc WHERE pgmna = <NAME>") and arbitrarily executes the first one! This is done in class CL_WB_PGEDITOR, method EXECUTE.

2.2. Redundant transactions

In the last chapter, we've identified the programs behind the most common table transactions. Unfortunately, they're not the only ones, which point to the underlying programs. A user might hit on the idea to search for a tcode, which executes a program he/she isn't allowed to execute directly – SE16 or SM30 in our case.

To assess this risk, we have to search for other tcodes (in table TSTC), which use the same programs (in the field PGMNA). Check your system!

2.3. Weak parameter transactions

Parameter transactions execute an existing transaction delivering pre-defined screen input.

For example, the transaction code SM30_PRGN_CUST is a shortcut to SM30 for the maintenance view PRGN_CUST. When you call it, SM30 is executed with "PRGN_CUST" in maintenance mode. Since the option "Skip initial screen" is selected, this tcode jumps directly into the table maintenance view itself – if it wasn't selected, one would be able to override the given screen options, including the table name! That's the point where the weakness starts.

Have a look at these parameter transactions from table TSTCP:

The first one – VYCM – is safe, because the "/*" indicates that the first screen is skipped and thus the view name cannot be overridden. The second one – VYCQ – just calls SM30 and fills in the given view name "TKKVBLERM"… leaving the choice of the actual view name up to the user (due to "/N")!

So, to determine all unsafe parameter transactions for SE16, SM30… you need to search for PARAMs matching "/N<TCD>" (e.g. "/NSM30*").

3. Other ways to get hold of table data

Now we're ready to face the truth: there are ways to avoid S_TABU_* checks!

3.1. Customer programs

All customer programs are as secure as the developers made them!
To scare your boss, just show him/her something like this:

TABLES: pa0008.      " HR Master Record: Infotype 0008 (Basic Pay)
  " SAP Client, User Name, Personnel Number, Annual Salary
  WRITE: / pa0008-mandt, pa0008-uname, pa0008-pernr, pa0008-ansal.

Note: UNAME is actually the user, who last changed the record; to get the SAP user related to the employee, check this comment.

Ways to mitigate this risk include:

  • organizational: add a "code check" step to your company's transport process (i.e. 4 eyes principle)
  • internal control: perform a regular control of customer programs (e.g. via report RS_ABAP_SOURCE_SCAN) – but be aware, that this is not safe due to code obfuscation

3.2. Function modules

Many function modules do not perform (sensible) authorization checks, as they are intended to be called from within reports – which are assumed to do those checks instead. If users are allowed to "test" (i.e. execute) FMs, most protection measures become pointless! Concerning table access, this means that in the worst case, the standard checks from the first chapter are just ignored.

Try SE37 with "TRINT_DISPLAY_TABLE_CONTENTS" to view any table you want!
This is what you need:


😯 … ouch – no S_TABU_* check!

Update: meanwhile SAP fixed this and added a proper check to the function module - check Note 1716236.

3.3. Debug with replace

Allowing users to debug with replace (i.e. S_DEVELOP with object type DEBUG and ACTVT 02 & 03) is extremely dangerous, since it allows stepping over parts of the code (e.g. AUTHORITY-CHECKs) and change field values (e.g. SY-SUBRC thereafter)! This means that almost all authorization checks are useless, as they can be bypassed or the return code can be changed. The only checks, which are unavoidable, are kernel-based authorization checks – like S_DATASET.

Example: to display the contents of any table, you only need to set three breakpoints in the includes LSUSEU11, LSETBU03 and LSVIXU16. This can easily be accomplished with the following authorizations (attn.: this is neither the smallest nor the only possible set of authorizations):


Watch out: replacing variables creates a system log message; check SM21!

3.4. Transport requests

Table contents contained in transport requests can be viewed without a single S_TABU_* check
Being authorized to create a new transport request and include a table’s contents is equal to SE16. Btw, there is no need to release the request; it can even be deleted later – without leaving significant traces.

Try this at home (while running an authorization trace):

… and then …

… et voilà.

See also SAP Note 1237762 → Solution → Item 4.

4. Conclusion

The main takeaways of this post are:

  • S_TABU_DIS is suitable for users, who need access to many tables, i.e. admins and most supporters
  • explicitly assigning a table to group "&NC&" via SE54 has absolutely no effect – it the same as no entry in TDDAT at all
  • S_TABU_NAM is good for users, who only need access to a small number of well-known tables, i.e. some supporters and few business (key-) users
  • S_TABU_CLI is strictly for SAP basis admins, especially for (but not limited to) multi-client systems
  • table data access is not limited to the standard tcodes SE16(N), SM30 etc. – many other ways exist
  • direct report execution is sometimes necessary, but might be evil – be careful
  • search your system for alternative tcodes and weak parameter transactions (especially Y… and Z… ones)
  • being able to test function modules on production is russian roulette for your system security!
  • don't let your programmers transport without (ex-ante or ex-post) inspection
  • debug with replace on any productive system is evil! really!
  • being able to create transport requests on production opens insecurities as big as a barn door (but might be necessary during support package installations)