While researching IPFS, I discovered that its mounting feature assumes safe user input, which opens up an excellent privilege escalation vector wherever IPFS mounts are used.
I disclosed this attack vector to the IPFS Security team on Feb 4th 2021. However, the team was already aware of the risks that symlinks pose for fuse mounts since 2018 https://github.com/ipfs/go-ipfs/issues/5161. At the moment, the IPFS team is not planning on releasing mitigation.
Mounting
Go-ipfs allows you to mount IPFS and IPNS on your file system,, which allows you to do neat things such as printing the contents of a file on ipfs:
$ cat /ipfs/QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG/readme
You can also use this to make existing software (such as a reverse proxy) compatible with IPFS storage without needing any custom code. All in all, it’s a great compatibility feature.
They use FUSE (File system in USEr space) and brazil (a go library for creating FUSE mounts). Unfortunately, brazil does not provide secure by default settings and assumes that it’s provided with safe input from the code calling into it. This assumption is not valid in IPFS’s case for two reasons:
- Any IPFS file node can be queried through the ipfs fuse mount.
- An attacker can create a IPFS file node with a symlink pointing to any point in the filesystem.
So if you cat
an IPFS node, which is a symlink to /etc/passwd
, through the mount then you’ll end up catting the contents of /etc/passwd
.
Proof of Concept
The following commands allow you to see how the bug works:
$ vagrant init ubuntu/groovy64
$ vagrant up
$ vagrant ssh
$ wget https://dist.ipfs.io/go-ipfs/v0.7.0/go-ipfs_v0.7.0_linux-amd64.tar.gz
$ tar -xvzf go-ipfs_v0.7.0_linux-amd64.tar.gz
$ cd go-ipfs
$ sudo bash install.sh
$ sudo mkdir /ipfs /ipns
$ sudo chown vagrant: /ipfs /ipns
$ echo "secret" > secret_file
$ mkdir poc
$ ln -s /home/vagrant/secret_file ./poc/not_so_secret
$ ipfs init
$ ipfs daemon --mount &
$ ipfs add -r ./poc
$ ipfs pin add <hash of poc node>
$ cat /ipfs/<hash_of_poc_node>/not_so_secret
secret
Kill Chain
The example above might not seem that useful to an attacker. The user executing cat
already has access to /etc/passwd
after all. You’d be right! In isolation, this bug won’t help you pop a shell!
It’s when there is other software that this bug becomes interesting. Let’s explore reverse proxies, as they’re a very straightforward example of how this bug can be used to leak critical information.
Let’s assume you’re running NGINX and that you’re using let’s encrypt + certbot for automatic ssl certificate maintenance. You have an awesome website which lets people access IPFS file nodes. For example:
youripfsportal.com/QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG/readme
Now an attacker might create a node with a cleverly crafted symlink that points directly where certbot would normally put your SSL private key.
certificate -> /etc/letsencrypt/live/youripfsportal.com/privkey.pem
Now all the attacker has to do is put that node on IPFS and query it through youripfsportal.com
and NGINX will diligently get it for them!
Recommendation
This vulnerability doesn’t just impact reverse proxies! It will arise in virtually every scenario where you have:
- An application that provides access to IPFS to users
- A user who (1) can influence file requests to the ipfs mount by the application and (2) has with fewer file system permissions than the application.
Don’t use ipfs file mounts unless you’re absolutely certain that you’re not meeting these conditions!
I’ve attached the original report below for documentation purposes:
Ipfs Fuse mount allows for symlinks outside the mount directory
Affected Versions: [ go-ipfs ⇐ 0.8.x]
Vulnerability Class: CWE-22
Severity: Medium - “CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N”
Summary
Go-ipfs uses fuse for user space mounting of ipfs nodes. It uses the Brazil library for this [2]. Brazil does not provide secure by default settings and assumes that it’s provided with safe input from the code calling into it. This assumption is not valid in IPFS’s case, where the nodes that can be queried are not sanitised. This allows an attacker to create symlinks on an IPFS mount that reference other potentially sensitive files.
This weakness affects any user that leverages the mount functionality to expose access to potentially unsafe IPFS nodes.
Details
Description
IPFS allows users to distribute nodes (representing files, directories and symlinks) on its network. The path name parameters for these nodes are never sanitised and can therefore contain malicious payloads.
go-ipfs allows users to mount ipfs in the filesystem. Once mounted, users can get the content of ipfs nodes by accessing them at /<mountpoint>/<node_hash>
.
When a user accesses the contents such a file, go-ipfs will fetch the content of the corresponding node and provide them to the user.
If the node being accessed is a symlink node, then it will try to resolve the symlink target and fetch it’s data.
Unfortunately, there is no limitation on what the symlink node can point to, and attackers can freely exploit this aspect since there is no other sanitisation.
Attackers can leverage such capabilities to leak infromation from nodes using go-ipfs’ fuse mount capabilities.
Proof of Concept
To reproduce follow the following steps:
vagrant init ubuntu/groovy64
vagrant up
vagrant ssh
$ wget https://dist.ipfs.io/go-ipfs/v0.7.0/go-ipfs_v0.7.0_linux-amd64.tar.gz
$ tar -xvzf go-ipfs_v0.7.0_linux-amd64.tar.gz
$ cd go-ipfs
$ sudo bash install.sh
$ sudo mkdir /ipfs /ipns
$ sudo chown vagrant: /ipfs /ipns
$ echo "secret" > secret_file
$ mkdir poc
$ ln -s /home/vagrant/secret_file ./poc/not_so_secret
$ ipfs init
$ ipfs daemon --mount &
$ ipfs add -r ./poc
$ ipfs pin add <hash of poc node>
$ cat /ipfs/<hash_of_poc_node>/not_so_secret
secret
Recommendation
The weakness that’s affecting go-ipfs is not unique and potentially affects other projects too. Unfortunately Brazil doesn’t provide an option to jail symlinks by default.
However fuse implementations such as osxfuse do implement this behaviour (Mount options · osxfuse/osxfuse Wiki · GitHub).
I’m planning to communicate with the maintainers of Brazil to see if they are amenable to adding symlink jailing functionality to the Brazil library.
If acceptable, I’d like to share this report with the maintainers of brazil to demonstrate the merit in adding this security control.
Otherwise, it should be sufficient to sanitise node data so that only symlinks pointing within the mount are accepted.
Timeline
Feb/04/2021 initial disclosure vendor
Mar/25/2021 vendor finishes triage and assigns low priority
Aug/4/2021 vendor reassesses priority as won't fix and shares the bug was previously known https://github.com/ipfs/go-ipfs/issues/5161
Aug/24/2021 full disclosure of attack vector and report