Unpacker improperly validates symlinks, allowing gems writes to arbitrary locations
Medium
R
RubyGems
Submitted None
Actions:
Reported by
nmalkin
Vulnerability Details
Technical details and impact analysis
# Unpacker improperly validates symlinks, allowing gems writes to arbitrary locations
The RubyGems installer attempts to prevent a gem from writing any files outside the install directory; however it is possible to bypass the check with a symbolic link in a crafted gem.
## Example structure of malicious gem
```
$ tar -xvf symlink.gem
metadata.gz
data.tar.gz
$ tar -tvf data.tar.gz
-rw-r--r-- 0/0 12 1969-12-31 16:00 README
lrw-r--r-- 0/0 0 1969-12-31 16:00 link -> /tmp
-rw-r--r-- 0/0 6 1969-12-31 16:00 link/HACKED
```
## Proof of concept
Using the attached `symlink.gem`:
```
gem install symlink.gem
# or
gem unpack symlink.gem
```
This will create a file /tmp/HACKED.
## Impact
The name and contents of the written file, as well as the file permissions, are arbitrary.
Using this technique, an attacker could easily get code execution, for example by overwriting a system binary or writing into a user's `.profile`.
Note that the exploit will even work with `gem unpack`, which is supposed to be safe of system-level side-effects.
For comparison, this exploit is more powerful than #243156 (and #270068) as the target directory doesn't need to contain a dash.
## Root cause
The code in [install_location](https://github.com/rubygems/rubygems/blob/v2.6.13/lib/rubygems/package.rb#L415) is supposed to check if the target filename is outside the destination directory. It does this by fully resolving (using `File.realpath`) the destination directory and then seeing if the target filename `.start_with?` that directory.
This test succeeds for a symlink that points outside the gem's install directory, because its "destination directory" is the directory where it's located (not where it points), which is local.
The test also succeeds for a file that uses the symlink to "escape" the local directory, because the symlink really is its prefix.
However, in combination, these files can allow for arbitrary writes, as shown.
The root cause vulnerability is the ability of symlinks to point outside the gem. This was actually forbidden in a [commit from 2015](https://github.com/rubygems/rubygems/commit/3a02b6379e62eb7a5eb359cc87473a65a355cfe6), but was made more permissive [in a later commit](https://github.com/rubygems/rubygems/commit/14b1eec7bd0f9fca5cedeae781fbef6f36dc466a), creating this vulnerability.
## Suggested remediation
The course of action we recommend is to (again) disallow symlinks that point outside the gem directory.
## Attached files
- `symlink.gem`, an example of a vulnerable gem. **Note**: extract this using `tar` instead of `gem` to avoid triggering the vulnerability (e.g., `tar -Oxf symlink.gem data.tar.gz | tar -tzvf -`)
- `make-symlink-gem.py`, sample code that generates the proof of concept (to run: `./make-symlink-gem.py > symlink.gem`)
- `0001-Add-a-test-test_extract_symlink_parent.patch`, test code that can be added to RubyGems to test for this vulnerability
Report Details
Additional information and metadata
State
Closed
Substate
Resolved
Submitted
Weakness
Path Traversal