Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
Former Member

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.

Relevant Notes:

https://service.sap.com/sap/support/notes/1336776

https://service.sap.com/sap/support/notes/1530983

24 Comments