Secure Execution of OS Commands by ABAP programs
Often it is required to execute commands on the operating system level from within an ABAP programs. Reasons may be eventing mechanisms, for instance start/stop of a printer, the necessity to (de-)compress/encrypt files or others. Sometimes these commands may even require user input. Although this should be avoided where possible, there are cases where you need to even supply user input to OS commands. Once this is the case, you are in trouble. You can see this by this rather simple example below. This code will just allow the user to ping a host and enables him to specify the target and the number of packets to send. The task seems to be pretty easy at a first glance however a solution like the one shown below can be pretty dangerous.
Doing it wrong
Let’s start with some example application, allowing us to ping some remote host.
REPORT Z_TEST_PING.
TYPES char255 TYPE c LENGTH 255.
DATA lt_result TYPE TABLE OF char255 WITH EMPTY KEY.DATA lv_line TYPE char255.DATA lv_cmd TYPE char255.
PARAMETERS: pv_count TYPE i DEFAULT 5,
pv_targt TYPE c LENGTH 80 LOWER CASE.
INITIALIZATION.
CALL ‘C_SAPGPARAM’ ID ‘NAME’ FIELD ‘SAPDBHOST’
ID ‘VALUE’ FIELD lv_cmd.
pv_targt = lv_cmd. ” get the db server as default target
AT SELECTION-SCREEN.
if pv_count < 1 OR
pv_count > 10.
MESSAGE ‘pv_count: The number of packets to send must be between 1 and 10.’ TYPE ‘E’.
ENDIF.
if pv_targt = ”.
MESSAGE ‘pv_target: You need to specify a destination to ping.’ TYPE ‘E’.
ENDIF.START-OF-SELECTION.
lv_cmd = |ping –c{ pv_count } { pv_targt }|. ” this only works on UNIX like ping this wayWrite :/ ‘now executing: ‘ && lv_cmd.Write :/ .CALL ‘SYSTEM’ ID ‘COMMAND’ field lv_cmd
ID ‘TAB’ field lt_result.IF SY–SUBRC = 0.
LOOP at lt_result into lv_line.
Write :/ lv_line.
ENDLOOP.ELSE.
MESSAGE ‘Error, return code ‘ && sy–subrc TYPE ‘E’.ENDIF.
If you just enter some data for the hostname like ve74Hmst in my example, the output should look similar to the screen shown here:
However let’s see what happens if we try to enter ve74Hmst; ls –l /etc
We still get the output of the ping command, but in addition, we also get listed the contents of the directory /etc. As you may have detected, the system I used was linux based. For this OS, the command separator is the semicolon. Thus what actually happened, was that the OS first executed the ping command and afterwards the ‘ls’ command, which actually is an OS command injection.
To counter this risk, you can now try to validate the input, which for the given example here would even be feasible. However how to do this for all OS, taking in consideration Unicode encodings and other fancy stuff? Well, there is no need to do this yourself. SAP has created a framework to be used for execution of commands which already does most of the work for you. Especially it takes care of command separators, pipe symbols and other stuff, which may be used for command injections. It also allows the specification of commands in a way, allowing programs to be less dependent on the OS.
The SXPT function group
If you have read my blog on countering directory traversal attacks, you have already learned about SFIL and its functions. The SXPT framework is somewhat similar, as it also consists of a transaction to administrate the command definitions (SM69) and function modules to make use of these definitions. However in addition, there is also transaction SM49 to directly execute commands defined in SM69.
If you open SM69, you will find a lot of definitions from SAP already being listed there. You can identify them by the type, which is SAP. Entries in the list will always in addition contain a logical command name, the operating system, for which they were defined and the command itself. For further information on SM69, please check the documentation.
Just allow me one remark which is more targeted at administrators here. For sure, you can define programs like ping as shown in the above figure for Z_PING by just specifying the command name. However from a security point of view, it may be better to specify the complete path like for Z_MYPING. The reason here is, that an attacker might not be able to replace the command (like /bin/ping on a linux system) but might be able to place another command named ping in a directory listed in the PATH environment variable ahead of the real ping command. From a programmer’s point of view, this is not relevant, as you can have one definition of a logical program name per OS. A programmer will still be able to just use one and the same logical name, although it will point to different places, depending on the operating system.
To make use of the commands defined in SM69, there is number of function modules available in function group SXPT. Besides modules to check if the user is permitted to execute the command, get a list of available commands and other utility functions, there are three modules available to execute commands:
· SXPG_CALL_SYSTEM: runs the command on the locally
· SXPG_COMMAND_EXECUTE: runs the command on a target host
· SXPG_COMMAND_EXECUTE_LONG: like SXPG_COMMAND_EXECUTE but allows to specify a longer list of parameters
For a more detailed explanation, please check the documentation.
Doing it right
For the purpose of this example, I will use SXPG_CALL_SYSTEM, as it is best matching to the CALL ‘SYSTEM’ command. Using this function module, the code could look similar to the one below.
REPORT Z_TEST_PING_XPG.
TYPES char255 TYPE c LENGTH 255.
PARAMETERS: pv_count TYPE i DEFAULT 5,
pv_targt TYPE char255 LOWER CASE.
DATA lt_result TYPE TABLE OF btcxpm WITH EMPTY KEY.DATA lv_line TYPE btcxpm.DATA lv_param TYPE char255.
INITIALIZATION.
CALL ‘C_SAPGPARAM’ ID ‘NAME’ FIELD ‘SAPDBHOST’
ID ‘VALUE’ FIELD lv_param.
pv_targt = lv_param. ” get the db server as default target
AT SELECTION-SCREEN.
if pv_count < 1 OR
pv_count > 10.
MESSAGE ‘Please enter a number between 1 and 10.’ TYPE ‘E’.
ENDIF.
if pv_targt = ”.
MESSAGE ‘pv_target: You need to specify a destination to ping.’ TYPE ‘E’.
ENDIF.
START-OF-SELECTION.
lv_param = |–c{ pv_count } { pv_targt }|. ” this only works on UNIX like ping this wayWrite :/ ‘now executing: Z_MyPing with parameters: ‘ && lv_param.Write :/ .CALL FUNCTION ‘SXPG_CALL_SYSTEM’
EXPORTING
commandname = ‘z_MyPING’
additional_parameters = lv_param
TABLES
exec_protocol = lt_result
EXCEPTIONS
no_permission = 1
command_not_found = 2
security_risk = 3
OTHERS = 4.
IF SY–SUBRC = 0.
LOOP at lt_result into lv_line.
Write :/ lv_line–message.
ENDLOOP.
ELSE.
MESSAGE ‘Error, return code ‘ && sy–subrc TYPE ‘E’.
ENDIF.
If I now enter the hostname as in the very first example ‘ve74Hmst’, the output will look similar to the result of the first example:
However if I now try to enter ve74Hmst; ls –l /etc again, the output is very different:
Authorizations checks when using SAPXPG
There are some not so obvious differences when switching from CALL ‘SYSTEM’ to the SAPXPG framework. The most notable is the different authorization checks done by both tools. In case CALL ‘system’ is being used, the authorization object S_C_FUNCT will be checked. Using this check you can however only allow or disallow the usage of CALL ‘SYSTEM’ as a whole not restrict the usage to certain programs. For more information, check the documentation of S_C_FUNCT in SU21.
In case of using the SAPXPG framework, the checked authorization object is S_LOG_COM.
S_LOG_COM contains the fields
· ‘COMMAND’ where you specify a logical command name as defined in SM69
· ‘OPSYSTEM’ to specify permitted operating systems for the user, and
· ‘HOST’ to even limit execution rights to single hosts
Last Remarks
It is not only a good idea to use the SXPT framework because of the improved security but also because the admin may be able to disable the CALL ‘SYSTEM’ command setting the profile parameter ‘rdisp/call_system’ to ‘0’. However some SAP programs still make use of CALL ‘SYSTEM’. For this reason you need to check first, whether it is feasible in your environment.
Finding such issues in ABAP coding is made easy using static code check tools. Most of them will report such code as dangerous.
If you are interested in additional details, maybe have a look at the blogpost from Horst Keller for some more info.
Hi,
very interesting blog. Do i understand this rigtht: setting the parameter
‘rdisp/call_system’ to ‘0’ still enable you to execute system commands via the sxpg framewaork?
//Rainer
correct, rdisp/call_system will only disable the CALL 'SYSTEM' but not affect the execution of commands via SAPXPG.
Regards,
Patrick
Hi Patrick,
Thank you for confirming that!
Cheers,
Julius
My 2 cents ...
It's common sense not to use the Kernel calls directly; but i have seen people suggesting using them in the ABAP forums so many times.
We had strict QA procedures in place & so i have never used Kernel calls ever. It has been one or the other FMs of the FuGr SXPT.
Thanks for publishing this blog & i wish that members reading it find some sense not to use the C-calls.
BR,
Suhas
How many SAP Reports are using this? Are there any experiences setting the parameter to '0'?
Depends on what you refer to by 'SAP reports'.
If this is reports provided by SAP in the standard, I'm pretty sure the number is 0, unless you are on a very old release / service pack. SAP uses it's own code scanner for development of standard SAP coding, CALL 'SYSTEM' is forbidden there since years already.
If you are refer to customer SAP programs, this is the reason, why I created this blog.
Regards,
Patrick
Of course i'm not referring to Z-Reports 🙂 I know that SAP is using the Code Profiler which can detect the usage of this (if enabled). Good to know that there are other possibilities to close this backdoor. At least i need to do a complete check where this is used in our environment. We are using system commands pretty sure but i dont have an overview how they are used.
Hi Rainer,
SAP development is not using the codeprofiler but the SAP NetWeaver AS, add-on for code vulnerability analysis. As OS commands are different on the different OS'es, SAP uses the SAPXPG framework to achieve portablity on the different platforms supported. You can trace the execution of all external commands on a particular host system by setting the environment variable sapxpg_trace in the host system in which the external job is to be started. Format and values:
sapxpg_trace=<Trace-Level>. The trace level can be 1, 2, or 3 where 3 is the most detailed, and 0deactivates the trace.
For more info, please have a look at the docs.
Regards,
Patrick
well, seams I have to correct myself on the number of usages of CALL 'SYSTEM' in SAP code, there are some still, although the functions may be used only rarely. So please check first before you enable rdisp/call_system. I will update the blog accordingly.
Regards,
Patrick
Hi Patrick, I reported some but was told when setting rdisp/call_system = 0 initially that I will have to forfeit functionality if I do not want to leave it open... I guess all the developers need to do is to pass through SAP's code scans and then if their code is legitimate and for some reason should not use the APIs of SXPT, then their calling program must be added to the protected call stack list. Sounds simple, but in reality it was an uphill battle (back in 2005 or so) which we at the time gave up on it as the CALL 'SYSTEM' was not officially remote enabled itself. We were told to move on to more important things so started investigating remote calls to kernel functions. Nice to see this topic being raised again by SAP this time! ps: In a related way, it would be nice if SAP removed the activity values A6 and A7 of object S_DATASET from the example role SAP_BC_ENDUSER(Non critical end user common functions). I would class that as a more likely security risk to occur than aprogram making pings. Would it be possible to use some internal pings for that as well while following up on CALL ' SYSTEM'? It seems the role does not have an owner so attempts to have it changed got no where but you have some attention there. S_PROGRAM as well while you are about it. Sorry for asking a lot but you hit a nail here! 🙂 Cheers, Julius
The examples you listed at the top, file compression & spool control, can be controlled by APIs which do not require the developer to interact with the OS. I'm not familiar with eventing.
Is there ever a situation where it is justified for a customer to call a kernel method? I have never done it, but if it is sometimes permissible, I am interested to hear your thoughts.
One of them with thousands of scan hits is 'C_SAPGPARAM' which Patrick also uses even in his code WITH the API call to the 'SYSTEM' function.
There is no API to read the instance and system parameters. You can only call TH_CHANGE_PARMETER to change them dynamically, but you don't need to change it. You could read table PAHI but if the param was changed on the OS instead of from the RZ10 at application layer then there are no change docs.
You are left with little choise other than calling the Kernel function. But with so many source code locations, you can feel cumfy with the fact that changing the IDs or a call stack validation is unlikely to happen in our life time anymore... 😉
But those tolerable calls can be counted on one hand IMO and 'SYSTEM' does not need to belong to them.
Cheers,
Julius
Hi Eric,
in the past we used to trigger external programs to do something because of a state change in an ABAP application. This could be to have the app process some files, start some actions, you name it. Often it was a trigger to upload/download data from an external system, which was only capable to operate on files and that needs a trigger to look for the files. These are some examples for eventing. With regards to spool control, this was about starting/stopping printers that required the execution of external commands. This still occurs sometimes today for very big printers but you are right, that this is less common today. For the compression, ABAP only supports some few algorythms, if you have other algorythms or even crypto involved, like PGP or similiar, you will be required to start an external command even today.
Regards,
Patrick
It is also popular amongst file conversion softwares to monitor the batch processes, spools and progress of registered external programs via such external programs which are called by software on external hosts.
Perhaps at the time they did not have APIs to do it or did not want to have to create / maintain their own ABAP APIs for it. Even today with APIs there is still resistence, as they need to adher to API interface definitions (they need someone who understands ABAP) and they need an authenticated user and provide roles for it. Some who at least use APIs since gw/monitor is defaulted = 1 now) then hardcode the logon credentials and in the documentation recommend to give the user SAP_ALL. Some docs I have seen even claim that this is a problem caused by SAP.
Which of those two are worse is a matter of preference.
That is why this blog is very useful - you can use the APIs and object S_LOG_COM without any functionality restrictions and do it in a safe way. It must just be designed correctly to enable a safe way.
Cheers and thanks again for this blog! Great refrence for how to do it correctly!
Cheers,
Julius
OS Commands in ABAP - Postscriptum by a security researcher
Hi Patrick,
nice introduction to the topic. I am happy to see that the key risks described in Virtual Forge's 2009 book on ABAP security and earlier publications are now made available to a broader audience by SAP, too.
Having read the above, I conclude that there are some additional things worth mentioning.
First of all, when asked by Rainer Hübenthal about the number of SAP reports that use CALL 'SYSTEM', you initially stated "I'm pretty sure the number is 0". You later correct this to "there are some still". In order to answer the question, I did a simple scan on a current SAP ECC 6 stack and found more than 110 occurrences of CALL 'SYSTEM' in the SAP standard. Some have SAP Security Notes related to them, some are related to current security advisories by Virtual Forge.
Second, there is definitely more than one "evil" way to execute OS commands. This is relevant for testing. I have taken the liberty of sharing my knowledge and research in a blog post. It gives an overview over the different ways to execute OS Commands and also discusses some risks related to the SXPG_* family of function modules.
Finally, you state that "SAP development is not using the codeprofiler". I believe it would be fair to put this statement in the right light. SAP (development) has been using CodeProfiler for a couple of years. SAP has its own code scanning tool now as you say, but the role CodeProfiler and Virtual Forge play(ed) are worth mentioning IMHO.
Hi Andreas,
I have been using code scans for many years (the old RSEC scanner, the new SCI with it's hidden coding blocks..., your tools, other tools, the new tools from SAP) but when confronted with a customer system you still need to look at the code and think about it and understand which options the system configuration and ABAP constructs offer for misuse.
That means you need to know what you are looking for anyway and with report RS_ABAP_SOURCE_SCAN I have had the best results (bar constructor methods and other things you can only see at runtime).
On our consulting projects we scan the coding for various things but also have to live with some of them (at least for a while). What customers need is content information about how to correct it any migrate from CALL 'SYSTEM' to SM69 or better even is SAP triggers the processing.
That needs content and understanding for the design. Just hacking SAP based on assumptions is not enough. That is why I was very happy with Patrick's statement about 'SYSTEM'.. -> I assume that he is using a tool which considers 'SYSTEM's own call stack validation. SXPG also calls 'SYSTEM'. If the input validation on SAP side is OK and call stack list is ok (110 is a bit long for an API..) then I don't see a problem with a few hits...
Important is APIs. Then there is consistency but customers still have freedom to implement without dark horses in the stable.
Cheers,
Julius
Hi Julius,
I am not entirely sure where you're getting at with your reply to my comment.
First, RS_ABAP_SOURCE_SCAN is a nice helper. For people like you that know what they are looking for. But such helpers are of limited use, when you need to analyze a couple of million lines of code. Our business application benchmark shows that customers have 1.8 Million lines of custom ABAPs per system on average. There are simply not enough ABAP security experts to cover the vast amount of ABAP code out there. Therefore, you need static code analyzers that pre-process the coding, so the experts can focus their efforts on relevant parts of the code.
The content for migrating from risky OS command execution to SM49/SM69 was published by SAP Press back in 2009. Unfortunately only in German 😉
But what do you mean with "I assume that he is using a tool which considers 'SYSTEM's own call stack validation"?
What is your understanding of a call stack validation? That the kernel checks, if 'SYSTEM' is called from a defined list of ABAP programs?
Actually, there is no such mechanism for 'SYSTEM'.
You further state that "SXPG also calls 'SYSTEM'". Well, that's not the case. When Virtual Forge researched the various ways to execute OS commands from ABAP back in 2010, we investigated which of those are used by the SXPG function modules (SXPG_CALL_SYSTEM and SXPG_COMMAND_EXECUTE). This is how we found the 0-day in SXPG's mechanism. It turns out, SXPG is using its own executable on the gateway.
Regarding the 110 occurrences of CALL 'SYSTEM'. One can argue if this a high or low number of results.
But two things should be considered:
- Even a single flaw related to the usage of CALL 'SYSTEM' can result in total compromise of the SAP server.
- Customers can't disable execution of CALL 'SYSTEM' in their Z code (by means of profile parameter 'rdisp/call_system') as long as the SAP standard uses CALL 'SYSTEM'
Cheers,
Andreas
Hi Andreas,
100% agree with you that if SAP uses CALL 'SYSTEM' then customers cannot turn it off without forfeiting functionality. That SXPG functions and some others themselves use 'SYSTEM' in some cases lead to my conclusion that there is a list of whitelisted programs (= call stack validation) exempted from rdisp/call_system. I don't see any other way how they could be there. Are you sure there is no validation within 'SYSTEM'? How could we ever know except by trial and error? 🙂
Regarding use of analyzers: if the company has 1.8 million lines of ABAP then if it fair to assume that there is going to be at least one ABAP expert around who can hit F8 on the key board and understand a customizing variant for SCI and a screen variant for RS_ABAP_SOURCE_SCAN and be given some consulting content to help fix it.
Making it faster than that to find problems still does not help the ABAP expert to maintain code of an ABAPer who long ago also left the system though. That (responsibility and bravery to touch old code) is often the biggest problem in the wild... 🙂
Cheers,
Julius
Hi Andreas,
thanks for your valuable comments. Maybe allow me some additional words from my side.
I limited this blog also to cover CALL 'SYSTEM', as this is by far the most common used way to execute OS commands in ABAP. It was not meant to discuss other possiblities, like the usage of ThPwInfo (which is fixed as you also stated in your blog) or the OPEN DATASET ... FILTER ... approach (which still can be abused for this). However the FILTER addtion is also mentioned in the ABAP docs covering the topic.
I also explicitly used SXPG_CALL_SYSTEM as it allows to execute the commands only locally and does therfor not have the disadvantages of SXPG_COMMAND_EXECUTE.
You are also correct, that SAP did use the SAP Codeprofiler and SAP IT even does it until today for some projects. However the SAP development organization started to migrate to SAP's own solution already in 2011 and also SAP IT has a first project making use of our own tool. However I'm not sure, why this would be relevant here. Rainer said:
and I was referring to this statement, when I mentioned that SAP is using the SAP NetWeaver AS, add-on for code vulnerability analysis.
I'm fully aware, that an active program from SAP in the system doing a 'CALL SYSTEM' will make it impossible to the admin to set rdisp/call_system. However it is somewhat hard to convince ppl to change their coding if it just contains a hard-coded command, as this not only affects the code but also would be an incompatible change to the customer, as they need to adapt their roles accordingly as well. Sometimes compatibilty is considered higher than other attributes, especailly for SAP code and sometimes it is even customer pressure. One example for instance being the SXPT framework, where we had added a validation function to avoid OS injections in case the command referenced was a shell. We had to remove the function again based on customer feedback, as it broke the customers programs.
With regards to not mention prior discussions of this topic, I have to admit, for me this is just some old topic, which sometimes was not taken serious enough. SAP talked about the risks of call system much before 2009. The oldest ppt I could find on my system was a presentation from 2004. Actually somehow there is still some odd version of this ppt available on erpdb. Call System is mentioned on slide 6, although the notes are somewhat incomplete in this version. The SXPT function group is even way older. The info on CALL SYSTEM for quite some time was not mentioned in the docs, as the CALL keyword was flagged as SAP internal. So this blog was more intended to be a heads-up than to talk about something new.
Sorry if the intention of this blog or my comments were not clear enough. Hopefully this explanation helps to clarify this.
Kind regards,
Patrick
Hi Patrick,
thank you for all the clarifications (like!).
From the title of your blog I was indeed under the impression that it was about OS commands in general.
That in mind, I wanted to provide some missing details and answer open questions.
Cheers,
Andreas
Hi Andreas,
Yes, you are correct. I could find 85 function modules with CALL 'SYSTEM' in them but not in the SXPG functions. So that either changed or I confused it with one of the others.
Thanks for pointing that correction out!
Cheers,
Julius
Hi Patrick,
I have a question. We have automated tools for code remediation and the customer does not want to create a new commandname in sm69 for using it in the function module "CALL FUNCTION 'SXPG_CALL_SYSTEM'
EXPORTING
commandname = newcommandname
.... "
And we are not supposed to use CALL 'SYSTEM' for security reasons.
Please guide if it is possible to execute a command using this function module without creating any new command name.
Is there any other way to achieve this.
Thanks,
Sri
Hi Sri,
the SAP standard provides the SXPG* function modules in order to execute OS commands in a way that can be controlled by an admin and limited by roles & authorizations.
If you bypass this mechanism, you have a compliance issue.
On top of the significant security risk.
The technical alternatives to SXPG* are described in my blob post (see my first comment above), but they all leads to the dark side...
Cheers,
Andreas
Hi Sri,
there is nothing to add from my side to what Andreas already said. If the customer does not want to create these commands, then he might not want to allow the execution of these commands. More generic abililties just would lead to an increased risk for system operation.
Kind regards,
Patrick