Summary
When updating Ports from a malicious or compromised MacPorts mirror, arbitrary commands can be executed even if signatures are validated.
A malicious or compromised MacPorts mirror can execute arbitrary commands as root
on the machine of a client running port selfupdate
against the mirror.
MacPorts is a package manager for MacOS. Similar to other package managers like APT, users will once in a while update the list of available packages, their versions, checksums etc. To do so, they will either run port sync
or preferably port selfupdate
. Under the hood, the client then uses rsync
to download a ports.tar.gz
file and an according signature file ports.tar.gz.rmd160
. The MacPorts client then verifies the signatures or discards the archive.
If the signature is valid, the ports.tar.gz
file is extracted into the same directory which was the target directory of rsync
. This creates a directory structure such as:
The ports
directory is essentially an up-to-date version of the macports/macports-ports GitHub repository. Each Port contains a Portfile within this directory structure. These files are written in Tcl and inform the MacPorts client about the name of the Port, dependencies, how to build the Port and more.
Once the archive is extracted, the MacPorts client attempts to fetch an index of all the Ports from the same Rsync server (which is also signed) or if it’s not served or outdated, creates its own index using the portindex binary. This helper finds all the Portfile
files within ports
and evaluates them.
When evaluated, a Portfile
can technically instruct the client to execute arbitrary system commands, for example:
set x [exec "bash" "-c" "id > /tmp/poc"]
We can verify that this works if we create a file called ports/foo/bar/Portfile
with the content above and then run the portindex
binary in the ports
directory:
uid=0(root) gid=0(wheel) groups=0(wheel), snip
The question now becomes, how can an attacker place a controlled Portfile on the client's machine, when the archive is signed? We will answer this question in the next section.
Creating Arbitrary Portfiles on the Client's machine
We mentioned that the client uses Rsync to fetch the ports.tar.gz
file from its configured mirror(s). The important detail here is that the target directory of rsync
and the target in which portindex
are run are the same. In theory, a malicious server could serve a valid, signed archive and additional Portfiles.
However, the client uses a few flags that prevent such an attack. The following CLI invocation shows the second Rsync CLI invocation that is used to fetch the PortIndex
file and its signature PortIndex.rmd16
:
/usr/bin/rsync -rtzvl --delete-after --include=/PortIndex.rmd160 --include=/PortIndex --exclude=* rsync://localhost:12000/macports/PortIndex_linux_5_i386/
The important flags here are:
--include=PortIndex.rmd160
--include=PortIndex
--exclude=*
This instructs the Rsync client to only fetch PortIndex
and PortIndex.rmd160
and reject everything else.
The problem here is that in some Rsync implementations and versions these filters are only enforced on the server-side. We compiled rsync.samba.org’s server version with a single change that ignores all filters sent by the client:
diff --git a/exclude.c b/exclude.c
index 87edbcf7..05028469 100644
--- a/exclude.c
+++ b/exclude.c
@@ -1436,7 +1436,7 @@ void parse_filter_str(filter_rule_list *listp, const char *rulestr,
}
}
- add_rule(listp, pat, pat_len, rule, xflags);
+// add_rule(listp, pat, pat_len, rule, xflags);
if (new_rflags & FILTRULE_CVS_IGNORE
&& !(new_rflags & FILTRULE_MERGE_FILE))
As a result, the server can simply create a ports/foo/bar/Portfile
file on the client's machine. We confirmed that this worked with openrsync 2.6.9, the default rsync binary for MacOS at the time of writing.
It should also work for rsync.samba.org Rsync versions before commit b7231c7, which was first released with 3.2.5.
Severity
Moderate - When updating Ports from a malicious or compromised MacPorts mirror, arbitrary commands can be executed even if signatures are validated.
Proof of Concept
- Apply the gitdiff above to the rsync.samba.org Rsync version and compile
- Setup a daemon to be run as a macports.org mirror by placing a valid
ports.tar.gz
and signature file in its serving directories. Do not include a PortIndex
file to cause the client to run portindex
- Add a file called
PortIndex_darwin_24_arm/ports/A/bar/Portfile
. You may have to change the PortIndex path to reflect your client’s machine. Add the following contents:
set x [exec "bash" "-c" "id > /tmp/poc"]
- Run
port -v selfupdate
- The file
/tmp/poc
should have been created.
Further Analysis
We recommend hardening MacPorts to ensure that no Portfiles are sneaked in by a malicious mirror. This could be done by downloading the archive and index files to a temporary directory first and then copying them later. While it can be argued that this is an issue with openrsync
, future bypasses of the filter lists and / or if the clients use other Rsync implementations may be subject to the same behavior.
Timeline
Date reported: 11/18/2024
Date fixed: 11/22/2024
Date disclosed: 12/22/2024
Summary
When updating Ports from a malicious or compromised MacPorts mirror, arbitrary commands can be executed even if signatures are validated.
A malicious or compromised MacPorts mirror can execute arbitrary commands as
root
on the machine of a client runningport selfupdate
against the mirror.MacPorts is a package manager for MacOS. Similar to other package managers like APT, users will once in a while update the list of available packages, their versions, checksums etc. To do so, they will either run
port sync
or preferablyport selfupdate
. Under the hood, the client then usesrsync
to download aports.tar.gz
file and an according signature fileports.tar.gz.rmd160
. The MacPorts client then verifies the signatures or discards the archive.If the signature is valid, the
ports.tar.gz
file is extracted into the same directory which was the target directory ofrsync
. This creates a directory structure such as:The
ports
directory is essentially an up-to-date version of the macports/macports-ports GitHub repository. Each Port contains a Portfile within this directory structure. These files are written in Tcl and inform the MacPorts client about the name of the Port, dependencies, how to build the Port and more.Once the archive is extracted, the MacPorts client attempts to fetch an index of all the Ports from the same Rsync server (which is also signed) or if it’s not served or outdated, creates its own index using the portindex binary. This helper finds all the
Portfile
files withinports
and evaluates them.When evaluated, a
Portfile
can technically instruct the client to execute arbitrary system commands, for example:set x [exec "bash" "-c" "id > /tmp/poc"]
We can verify that this works if we create a file called
ports/foo/bar/Portfile
with the content above and then run theportindex
binary in theports
directory:uid=0(root) gid=0(wheel) groups=0(wheel), snip
The question now becomes, how can an attacker place a controlled Portfile on the client's machine, when the archive is signed? We will answer this question in the next section.
Creating Arbitrary Portfiles on the Client's machine
We mentioned that the client uses Rsync to fetch the
ports.tar.gz
file from its configured mirror(s). The important detail here is that the target directory ofrsync
and the target in whichportindex
are run are the same. In theory, a malicious server could serve a valid, signed archive and additional Portfiles.However, the client uses a few flags that prevent such an attack. The following CLI invocation shows the second Rsync CLI invocation that is used to fetch the
PortIndex
file and its signaturePortIndex.rmd16
:The important flags here are:
--include=PortIndex.rmd160
--include=PortIndex
--exclude=*
This instructs the Rsync client to only fetch
PortIndex
andPortIndex.rmd160
and reject everything else.The problem here is that in some Rsync implementations and versions these filters are only enforced on the server-side. We compiled rsync.samba.org’s server version with a single change that ignores all filters sent by the client:
As a result, the server can simply create a
ports/foo/bar/Portfile
file on the client's machine. We confirmed that this worked with openrsync 2.6.9, the default rsync binary for MacOS at the time of writing.It should also work for rsync.samba.org Rsync versions before commit b7231c7, which was first released with 3.2.5.
Severity
Moderate - When updating Ports from a malicious or compromised MacPorts mirror, arbitrary commands can be executed even if signatures are validated.
Proof of Concept
ports.tar.gz
and signature file in its serving directories. Do not include aPortIndex
file to cause the client to runportindex
PortIndex_darwin_24_arm/ports/A/bar/Portfile
. You may have to change the PortIndex path to reflect your client’s machine. Add the following contents:set x [exec "bash" "-c" "id > /tmp/poc"]
port -v selfupdate
/tmp/poc
should have been created.Further Analysis
We recommend hardening MacPorts to ensure that no Portfiles are sneaked in by a malicious mirror. This could be done by downloading the archive and index files to a temporary directory first and then copying them later. While it can be argued that this is an issue with
openrsync
, future bypasses of the filter lists and / or if the clients use other Rsync implementations may be subject to the same behavior.Timeline
Date reported: 11/18/2024
Date fixed: 11/22/2024
Date disclosed: 12/22/2024